2024-02-16 06:17:13 +13:00
from flask import request , g
2024-01-25 20:16:08 +13:00
from flask_login import current_user
2023-08-29 22:01:06 +12:00
from flask_wtf import FlaskForm
2024-03-15 14:24:45 +13:00
from wtforms import StringField , SubmitField , TextAreaField , BooleanField , HiddenField , SelectField , FileField , \
DateField
2024-05-08 21:07:22 +12:00
from wtforms . fields . choices import SelectMultipleField
2024-03-26 17:29:08 +01:00
from wtforms . validators import ValidationError , DataRequired , Email , EqualTo , Length , Regexp , Optional
2023-08-29 22:01:06 +12:00
from flask_babel import _ , lazy_gettext as _l
2024-01-25 20:16:08 +13:00
from app import db
2024-03-15 14:24:45 +13:00
from app . models import Community , utcnow
2023-12-13 21:04:11 +13:00
from app . utils import domain_from_url , MultiCheckboxField
2024-01-25 20:16:08 +13:00
from PIL import Image , ImageOps
from io import BytesIO
import pytesseract
2023-09-17 21:19:51 +12:00
2023-08-29 22:01:06 +12:00
2024-03-13 16:40:20 +13:00
class AddCommunityForm ( FlaskForm ) :
2023-09-03 16:30:20 +12:00
community_name = StringField ( _l ( ' Name ' ) , validators = [ DataRequired ( ) ] )
2023-12-08 17:13:38 +13:00
url = StringField ( _l ( ' Url ' ) )
2023-08-29 22:01:06 +12:00
description = TextAreaField ( _l ( ' Description ' ) )
2024-06-16 14:03:59 +08:00
icon_file = FileField ( _l ( ' Icon image ' ) , render_kw = { ' accept ' : ' image/* ' } )
banner_file = FileField ( _l ( ' Banner image ' ) , render_kw = { ' accept ' : ' image/* ' } )
2023-08-29 22:01:06 +12:00
rules = TextAreaField ( _l ( ' Rules ' ) )
2023-12-31 12:09:20 +13:00
nsfw = BooleanField ( ' NSFW ' )
2024-01-27 11:19:23 +13:00
local_only = BooleanField ( ' Local only ' )
2024-05-08 21:07:22 +12:00
languages = SelectMultipleField ( _l ( ' Languages ' ) , coerce = int , validators = [ Optional ( ) ] , render_kw = { ' class ' : ' form-select ' } )
2023-08-29 22:01:06 +12:00
submit = SubmitField ( _l ( ' Create ' ) )
2023-11-16 22:31:14 +13:00
def validate ( self , extra_validators = None ) :
if not super ( ) . validate ( ) :
return False
if self . url . data . strip ( ) == ' ' :
2024-04-30 21:29:49 +12:00
self . url . errors . append ( _l ( ' Url is required. ' ) )
2023-11-16 22:31:14 +13:00
return False
else :
if ' - ' in self . url . data . strip ( ) :
2024-04-30 21:29:49 +12:00
self . url . errors . append ( _l ( ' - cannot be in Url. Use _ instead? ' ) )
return False
community = Community . query . filter ( Community . name == self . url . data . strip ( ) . lower ( ) , Community . ap_id == None ) . first ( )
if community is not None :
self . url . errors . append ( _l ( ' A community with this url already exists. ' ) )
2023-11-16 22:31:14 +13:00
return False
return True
2023-08-29 22:01:06 +12:00
2024-03-13 16:40:20 +13:00
class EditCommunityForm ( FlaskForm ) :
title = StringField ( _l ( ' Title ' ) , validators = [ DataRequired ( ) ] )
description = TextAreaField ( _l ( ' Description ' ) )
2024-06-16 14:03:59 +08:00
icon_file = FileField ( _l ( ' Icon image ' ) , render_kw = { ' accept ' : ' image/* ' } )
banner_file = FileField ( _l ( ' Banner image ' ) , render_kw = { ' accept ' : ' image/* ' } )
2024-03-13 16:40:20 +13:00
rules = TextAreaField ( _l ( ' Rules ' ) )
nsfw = BooleanField ( _l ( ' Porn community ' ) )
local_only = BooleanField ( _l ( ' Only accept posts from current instance ' ) )
restricted_to_mods = BooleanField ( _l ( ' Only moderators can post ' ) )
new_mods_wanted = BooleanField ( _l ( ' New moderators wanted ' ) )
2024-05-17 21:27:45 +12:00
topic = SelectField ( _l ( ' Topic ' ) , coerce = int , validators = [ Optional ( ) ] , render_kw = { ' class ' : ' form-select ' } )
2024-05-08 21:07:22 +12:00
languages = SelectMultipleField ( _l ( ' Languages ' ) , coerce = int , validators = [ Optional ( ) ] , render_kw = { ' class ' : ' form-select ' } )
2024-03-13 16:40:20 +13:00
layouts = [ ( ' ' , _l ( ' List ' ) ) ,
( ' masonry ' , _l ( ' Masonry ' ) ) ,
( ' masonry_wide ' , _l ( ' Wide masonry ' ) ) ]
2024-05-17 21:27:45 +12:00
default_layout = SelectField ( _l ( ' Layout ' ) , coerce = str , choices = layouts , validators = [ Optional ( ) ] , render_kw = { ' class ' : ' form-select ' } )
2024-03-13 16:40:20 +13:00
submit = SubmitField ( _l ( ' Save ' ) )
2024-07-17 22:11:31 +08:00
class EditCommunityWikiPageForm ( FlaskForm ) :
title = StringField ( _l ( ' Title ' ) , validators = [ DataRequired ( ) ] )
slug = StringField ( _l ( ' Slug ' ) , validators = [ DataRequired ( ) ] )
body = TextAreaField ( _l ( ' Body ' ) , render_kw = { ' rows ' : ' 10 ' } )
edit_options = [ ( 0 , _l ( ' Mods and admins ' ) ) ,
( 1 , _l ( ' Trusted accounts ' ) ) ,
( 2 , _l ( ' Community members ' ) ) ,
( 3 , _l ( ' Any account ' ) )
]
who_can_edit = SelectField ( _l ( ' Who can edit ' ) , coerce = int , choices = edit_options , validators = [ Optional ( ) ] , render_kw = { ' class ' : ' form-select ' } )
submit = SubmitField ( _l ( ' Save ' ) )
2024-03-13 16:40:20 +13:00
class AddModeratorForm ( FlaskForm ) :
user_name = StringField ( _l ( ' User name ' ) , validators = [ DataRequired ( ) ] )
2024-07-14 14:01:22 +08:00
submit = SubmitField ( _l ( ' Find ' ) )
2024-03-13 16:40:20 +13:00
2024-03-27 20:20:08 +13:00
class EscalateReportForm ( FlaskForm ) :
reason = StringField ( _l ( ' Amend the report description if necessary ' ) , validators = [ DataRequired ( ) ] )
submit = SubmitField ( _l ( ' Escalate report ' ) )
class ResolveReportForm ( FlaskForm ) :
note = StringField ( _l ( ' Note for mod log ' ) , validators = [ Optional ( ) ] )
also_resolve_others = BooleanField ( _l ( ' Also resolve all other reports about the same thing. ' ) , default = True )
submit = SubmitField ( _l ( ' Resolve report ' ) )
2023-08-29 22:01:06 +12:00
class SearchRemoteCommunity ( FlaskForm ) :
2024-01-29 22:18:06 +13:00
address = StringField ( _l ( ' Community address ' ) , render_kw = { ' placeholder ' : ' e.g. !name@server ' , ' autofocus ' : True } , validators = [ DataRequired ( ) ] )
2023-09-17 21:19:51 +12:00
submit = SubmitField ( _l ( ' Search ' ) )
2024-03-15 14:24:45 +13:00
class BanUserCommunityForm ( FlaskForm ) :
reason = StringField ( _l ( ' Reason ' ) , render_kw = { ' autofocus ' : True } , validators = [ DataRequired ( ) ] )
2024-03-21 21:19:50 +13:00
ban_until = DateField ( _l ( ' Ban until ' ) , validators = [ Optional ( ) ] )
2024-03-15 14:24:45 +13:00
delete_posts = BooleanField ( _l ( ' Also delete all their posts ' ) )
delete_post_replies = BooleanField ( _l ( ' Also delete all their comments ' ) )
submit = SubmitField ( _l ( ' Ban ' ) )
2024-07-08 13:21:02 +02:00
class CreatePostForm ( FlaskForm ) :
2024-05-12 13:02:45 +12:00
communities = SelectField ( _l ( ' Community ' ) , validators = [ DataRequired ( ) ] , coerce = int , render_kw = { ' class ' : ' form-select ' } )
2024-07-08 13:21:02 +02:00
title = StringField ( _l ( ' Title ' ) , validators = [ DataRequired ( ) , Length ( min = 3 , max = 255 ) ] )
body = TextAreaField ( _l ( ' Body ' ) , validators = [ Optional ( ) , Length ( min = 3 , max = 5000 ) ] , render_kw = { ' rows ' : 5 } )
2024-05-12 13:02:45 +12:00
tags = StringField ( _l ( ' Tags ' ) , validators = [ Optional ( ) , Length ( min = 3 , max = 5000 ) ] )
2024-04-07 11:15:04 +12:00
sticky = BooleanField ( _l ( ' Sticky ' ) )
nsfw = BooleanField ( _l ( ' NSFW ' ) )
nsfl = BooleanField ( _l ( ' Gore/gross ' ) )
notify_author = BooleanField ( _l ( ' Notify about replies ' ) )
2024-05-09 17:54:30 +12:00
language_id = SelectField ( _l ( ' Language ' ) , validators = [ DataRequired ( ) ] , coerce = int , render_kw = { ' class ' : ' form-select ' } )
2024-04-07 11:15:04 +12:00
submit = SubmitField ( _l ( ' Save ' ) )
2024-07-08 13:21:02 +02:00
class CreateDiscussionForm ( CreatePostForm ) :
pass
class CreateLinkForm ( CreatePostForm ) :
2024-04-07 11:15:04 +12:00
link_url = StringField ( _l ( ' URL ' ) , validators = [ DataRequired ( ) , Regexp ( r ' ^https?:// ' , message = ' Submitted links need to start with " http:// " " or " https:// " ' ) ] ,
render_kw = { ' placeholder ' : ' https://... ' } )
2023-09-17 21:19:51 +12:00
def validate ( self , extra_validators = None ) - > bool :
2024-04-07 11:15:04 +12:00
domain = domain_from_url ( self . link_url . data , create = False )
if domain and domain . banned :
2024-04-30 21:29:49 +12:00
self . link_url . errors . append ( _l ( " Links to %(domain)s are not allowed. " , domain = domain . name ) )
2023-09-17 21:19:51 +12:00
return False
2024-04-07 11:15:04 +12:00
return True
2023-09-17 21:19:51 +12:00
2024-04-07 11:15:04 +12:00
2024-07-08 13:21:02 +02:00
class CreateVideoForm ( CreatePostForm ) :
2024-04-16 20:59:58 +12:00
video_url = StringField ( _l ( ' URL ' ) , validators = [ DataRequired ( ) , Regexp ( r ' ^https?:// ' , message = ' Submitted links need to start with " http:// " " or " https:// " ' ) ] ,
render_kw = { ' placeholder ' : ' https://... ' } )
def validate ( self , extra_validators = None ) - > bool :
domain = domain_from_url ( self . video_url . data , create = False )
if domain and domain . banned :
2024-04-30 21:29:49 +12:00
self . video_url . errors . append ( _l ( " Videos from %(domain)s are not allowed. " , domain = domain . name ) )
2024-04-16 20:59:58 +12:00
return False
return True
2024-07-08 13:21:02 +02:00
class CreateImageForm ( CreatePostForm ) :
2024-05-25 22:44:43 +12:00
image_alt_text = StringField ( _l ( ' Alt text ' ) , validators = [ Optional ( ) , Length ( min = 3 , max = 1500 ) ] )
2024-06-16 14:03:59 +08:00
image_file = FileField ( _l ( ' Image ' ) , validators = [ DataRequired ( ) ] , render_kw = { ' accept ' : ' image/* ' } )
2024-04-07 11:15:04 +12:00
def validate ( self , extra_validators = None ) - > bool :
uploaded_file = request . files [ ' image_file ' ]
if uploaded_file and uploaded_file . filename != ' ' :
Image . MAX_IMAGE_PIXELS = 89478485
# Do not allow fascist meme content
try :
image_text = pytesseract . image_to_string ( Image . open ( BytesIO ( uploaded_file . read ( ) ) ) . convert ( ' L ' ) )
except FileNotFoundError as e :
image_text = ' '
if ' Anonymous ' in image_text and (
' No. ' in image_text or ' N0 ' in image_text ) : # chan posts usually contain the text 'Anonymous' and ' No.12345'
self . image_file . errors . append (
f " This image is an invalid file type. " ) # deliberately misleading error message
current_user . reputation - = 1
db . session . commit ( )
2023-09-17 21:19:51 +12:00
return False
2024-04-07 11:15:04 +12:00
if self . communities :
community = Community . query . get ( self . communities . data )
if community . is_local ( ) and g . site . allow_local_image_posts is False :
2024-04-30 21:29:49 +12:00
self . communities . errors . append ( _l ( ' Images cannot be posted to local communities. ' ) )
2023-09-17 21:19:51 +12:00
return True
2023-10-02 22:16:44 +13:00
2023-12-13 21:04:11 +13:00
2024-07-08 13:21:02 +02:00
class CreatePollForm ( CreatePostForm ) :
2024-05-18 20:17:05 +12:00
mode = SelectField ( _ ( ' Mode ' ) , validators = [ DataRequired ( ) ] , choices = [ ( ' single ' , _l ( ' Voters choose one option ' ) ) , ( ' multiple ' , _l ( ' Voters choose many options ' ) ) ] , render_kw = { ' class ' : ' form-select ' } )
2024-05-16 21:53:38 +12:00
finish_choices = [
( ' 30m ' , _l ( ' 30 minutes ' ) ) ,
( ' 1h ' , _l ( ' 1 hour ' ) ) ,
( ' 6h ' , _l ( ' 6 hours ' ) ) ,
( ' 12h ' , _l ( ' 12 hours ' ) ) ,
( ' 1d ' , _l ( ' 1 day ' ) ) ,
( ' 3d ' , _l ( ' 3 days ' ) ) ,
( ' 7d ' , _l ( ' 7 days ' ) ) ,
]
2024-05-17 21:27:45 +12:00
finish_in = SelectField ( _ ( ' End voting in ' ) , validators = [ DataRequired ( ) ] , choices = finish_choices , render_kw = { ' class ' : ' form-select ' } )
2024-05-16 21:53:38 +12:00
local_only = BooleanField ( _l ( ' Accept votes from this instance only ' ) )
choice_1 = StringField ( ' Choice ' ) # intentionally left out of internationalization (no _l()) as this label is not used
choice_2 = StringField ( ' Choice ' )
choice_3 = StringField ( ' Choice ' )
choice_4 = StringField ( ' Choice ' )
choice_5 = StringField ( ' Choice ' )
choice_6 = StringField ( ' Choice ' )
choice_7 = StringField ( ' Choice ' )
choice_8 = StringField ( ' Choice ' )
choice_9 = StringField ( ' Choice ' )
choice_10 = StringField ( ' Choice ' )
2024-05-18 19:41:20 +12:00
def validate ( self , extra_validators = None ) - > bool :
choices_made = 0
for i in range ( 1 , 10 ) :
choice_data = getattr ( self , f " choice_ { i } " ) . data . strip ( )
if choice_data != ' ' :
choices_made + = 1
if choices_made == 0 :
self . choice_1 . errors . append ( _l ( ' Polls need options for people to choose from ' ) )
return False
elif choices_made < = 1 :
self . choice_2 . errors . append ( _l ( ' Provide at least two choices ' ) )
return False
return True
2024-05-16 21:53:38 +12:00
2023-12-13 21:04:11 +13:00
class ReportCommunityForm ( FlaskForm ) :
reason_choices = [ ( ' 1 ' , _l ( ' Breaks instance rules ' ) ) ,
( ' 2 ' , _l ( ' Abandoned by moderators ' ) ) ,
( ' 3 ' , _l ( ' Cult ' ) ) ,
( ' 4 ' , _l ( ' Scam ' ) ) ,
( ' 5 ' , _l ( ' Alt-right pipeline ' ) ) ,
( ' 6 ' , _l ( ' Hate / genocide ' ) ) ,
( ' 7 ' , _l ( ' Other ' ) ) ,
]
reasons = MultiCheckboxField ( _l ( ' Reason ' ) , choices = reason_choices )
description = StringField ( _l ( ' More info ' ) )
report_remote = BooleanField ( ' Also send report to originating instance ' )
submit = SubmitField ( _l ( ' Report ' ) )
2023-12-21 22:14:43 +13:00
2024-03-12 09:53:34 +13:00
def reasons_to_string ( self , reason_data ) - > str :
result = [ ]
for reason_id in reason_data :
for choice in self . reason_choices :
if choice [ 0 ] == reason_id :
result . append ( str ( choice [ 1 ] ) )
return ' , ' . join ( result )
2023-12-21 22:14:43 +13:00
class DeleteCommunityForm ( FlaskForm ) :
submit = SubmitField ( _l ( ' Delete community ' ) )
2024-05-27 23:51:14 +01:00
class RetrieveRemotePost ( FlaskForm ) :
address = StringField ( _l ( ' Full URL ' ) , render_kw = { ' placeholder ' : ' e.g. https://lemmy.world/post/123 ' , ' autofocus ' : True } , validators = [ DataRequired ( ) ] )
submit = SubmitField ( _l ( ' Retrieve ' ) )