2022-05-04 23:09:46 +00:00
import re
2022-11-15 09:19:08 +00:00
import time
from calendar import timegm
2022-05-04 23:09:46 +00:00
from sqlalchemy import *
2023-05-05 21:44:24 +00:00
from sqlalchemy . orm import load_only
2022-11-15 09:19:08 +00:00
2022-06-24 14:30:59 +00:00
from files . helpers . regex import *
2022-07-09 10:32:49 +00:00
from files . helpers . sorting_and_time import *
2022-11-15 09:19:08 +00:00
from files . routes . wrappers import *
from files . __main__ import app
2022-05-04 23:09:46 +00:00
2022-06-23 09:02:49 +00:00
search_operator_hole = HOLE_NAME
2022-06-22 06:35:50 +00:00
valid_params = [
2022-05-04 23:09:46 +00:00
' author ' ,
' domain ' ,
2022-06-22 06:35:50 +00:00
' over18 ' ,
2022-10-02 08:55:39 +00:00
' post ' ,
' before ' ,
' after ' ,
' title ' ,
2023-03-03 02:04:09 +00:00
' sentto ' ,
2022-06-23 09:02:49 +00:00
search_operator_hole ,
2022-05-04 23:09:46 +00:00
]
def searchparse ( text ) :
2022-06-21 05:31:31 +00:00
text = text . lower ( )
2022-05-04 23:09:46 +00:00
criteria = { x [ 0 ] : x [ 1 ] for x in query_regex . findall ( text ) }
for x in criteria :
if x in valid_params :
text = text . replace ( f " { x } : { criteria [ x ] } " , " " )
2022-07-04 08:19:41 +00:00
text = text . strip ( )
2022-05-04 23:09:46 +00:00
if text :
2022-08-24 02:54:27 +00:00
criteria [ ' full_text ' ] = text
2022-07-04 08:19:41 +00:00
criteria [ ' q ' ] = [ ]
2022-07-06 11:49:13 +00:00
for m in search_token_regex . finditer ( text ) :
2022-07-04 08:19:41 +00:00
token = m [ 1 ] if m [ 1 ] else m [ 2 ]
2023-02-17 14:17:05 +00:00
if not token : token = ' '
2022-07-04 08:19:41 +00:00
# Escape SQL pattern matching special characters
token = token . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' \ % ' )
criteria [ ' q ' ] . append ( token )
2022-05-04 23:09:46 +00:00
return criteria
@app.get ( " /search/posts " )
2023-02-26 08:41:04 +00:00
@limiter.limit ( DEFAULT_RATELIMIT )
2023-01-21 04:39:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def searchposts ( v : User ) :
2022-05-04 23:09:46 +00:00
query = request . values . get ( " q " , ' ' ) . strip ( )
2023-01-22 23:30:22 +00:00
if not query :
abort ( 403 , " Empty searches aren ' t allowed! " )
2022-05-04 23:09:46 +00:00
2023-05-05 05:23:59 +00:00
page = get_page ( )
2022-05-04 23:09:46 +00:00
sort = request . values . get ( " sort " , " new " ) . lower ( )
t = request . values . get ( ' t ' , ' all ' ) . lower ( )
criteria = searchparse ( query )
2023-05-05 21:44:24 +00:00
posts = g . db . query ( Submission ) . options ( load_only ( Submission . id ) ) \
2022-08-08 22:21:59 +00:00
. join ( Submission . author ) \
. filter ( Submission . author_id . notin_ ( v . userblocks ) )
2023-01-01 11:36:20 +00:00
2022-10-06 05:37:50 +00:00
if v . admin_level < PERMS [ ' POST_COMMENT_MODERATION ' ] :
2022-08-08 22:21:59 +00:00
posts = posts . filter (
Submission . deleted_utc == 0 ,
Submission . is_banned == False ,
2022-10-06 05:37:50 +00:00
Submission . private == False )
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
if ' author ' in criteria :
posts = posts . filter ( Submission . ghost == False )
2023-02-27 15:38:12 +00:00
author = get_user ( criteria [ ' author ' ] , v = v )
2022-10-30 07:31:21 +00:00
if not author . is_visible_to ( v ) :
2022-10-15 09:11:36 +00:00
if v . client :
2022-10-11 13:01:39 +00:00
abort ( 403 , f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them " )
2022-05-04 23:09:46 +00:00
return render_template ( " search.html " ,
v = v ,
query = query ,
total = 0 ,
page = page ,
listing = [ ] ,
sort = sort ,
t = t ,
domain = None ,
domain_obj = None ,
error = f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them. "
2022-10-30 07:33:42 +00:00
) , 403
2023-05-07 19:35:31 +00:00
posts = posts . filter ( Submission . author_id == author . id )
2022-05-04 23:09:46 +00:00
2022-10-02 08:55:39 +00:00
if ' q ' in criteria :
2022-08-24 02:54:27 +00:00
if ( ' title ' in criteria ) :
words = [ or_ ( Submission . title . ilike ( ' % ' + x + ' % ' ) ) \
for x in criteria [ ' q ' ] ]
else :
2023-02-21 18:18:42 +00:00
words = [ or_ (
Submission . title . ilike ( ' % ' + x + ' % ' ) ,
Submission . body . ilike ( ' % ' + x + ' % ' ) ,
Submission . url . ilike ( ' % ' + x + ' % ' ) ,
) for x in criteria [ ' q ' ] ]
2022-06-13 17:33:20 +00:00
posts = posts . filter ( * words )
2023-01-01 11:36:20 +00:00
2022-05-04 23:09:46 +00:00
if ' over18 ' in criteria : posts = posts . filter ( Submission . over_18 == True )
if ' domain ' in criteria :
domain = criteria [ ' domain ' ]
domain = domain . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
posts = posts . filter (
or_ (
Submission . url . ilike ( " https:// " + domain + ' / % ' ) ,
Submission . url . ilike ( " https:// " + domain + ' / % ' ) ,
Submission . url . ilike ( " https:// " + domain ) ,
Submission . url . ilike ( " https:// " + domain ) ,
Submission . url . ilike ( " https://www. " + domain + ' / % ' ) ,
Submission . url . ilike ( " https://www. " + domain + ' / % ' ) ,
Submission . url . ilike ( " https://www. " + domain ) ,
Submission . url . ilike ( " https://www. " + domain ) ,
Submission . url . ilike ( " https://old. " + domain + ' / % ' ) ,
Submission . url . ilike ( " https://old. " + domain + ' / % ' ) ,
Submission . url . ilike ( " https://old. " + domain ) ,
Submission . url . ilike ( " https://old. " + domain )
)
)
2022-06-23 09:02:49 +00:00
if search_operator_hole in criteria :
posts = posts . filter ( Submission . sub == criteria [ search_operator_hole ] )
2022-05-04 23:09:46 +00:00
2022-08-15 19:02:23 +00:00
if ' after ' in criteria :
2022-08-30 01:15:54 +00:00
after = criteria [ ' after ' ]
try : after = int ( after )
2022-08-31 01:48:20 +00:00
except :
try : after = timegm ( time . strptime ( after , " % Y- % m- %d " ) )
except : abort ( 400 )
2022-08-15 19:02:23 +00:00
posts = posts . filter ( Submission . created_utc > after )
if ' before ' in criteria :
2022-08-30 01:15:54 +00:00
before = criteria [ ' before ' ]
try : before = int ( before )
2022-08-31 01:48:20 +00:00
except :
try : before = timegm ( time . strptime ( before , " % Y- % m- %d " ) )
except : abort ( 400 )
2022-08-15 19:02:23 +00:00
posts = posts . filter ( Submission . created_utc < before )
2022-07-09 10:32:49 +00:00
posts = apply_time_filter ( t , posts , Submission )
2022-05-04 23:09:46 +00:00
total = posts . count ( )
2023-05-05 21:44:24 +00:00
posts = sort_objects ( sort , posts , Submission )
2022-05-04 23:09:46 +00:00
2023-05-05 21:44:24 +00:00
posts = posts . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE ) . all ( )
2022-05-04 23:09:46 +00:00
2023-05-05 21:44:24 +00:00
ids = [ x . id for x in posts ]
2022-05-04 23:09:46 +00:00
2022-11-09 14:16:22 +00:00
posts = get_posts ( ids , v = v , eager = True )
2022-05-04 23:09:46 +00:00
2023-03-16 06:27:58 +00:00
if v . client : return { " total " : total , " data " : [ x . json ( g . db ) for x in posts ] }
2022-05-04 23:09:46 +00:00
return render_template ( " search.html " ,
2022-09-04 23:15:37 +00:00
v = v ,
query = query ,
page = page ,
listing = posts ,
sort = sort ,
t = t ,
2023-05-05 21:44:24 +00:00
total = total
2022-09-04 23:15:37 +00:00
)
2022-05-04 23:09:46 +00:00
@app.get ( " /search/comments " )
2023-02-26 08:41:04 +00:00
@limiter.limit ( DEFAULT_RATELIMIT )
2023-01-21 04:39:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def searchcomments ( v : User ) :
2022-05-04 23:09:46 +00:00
query = request . values . get ( " q " , ' ' ) . strip ( )
2023-01-22 23:30:22 +00:00
if not query :
abort ( 403 , " Empty searches aren ' t allowed! " )
2022-05-04 23:09:46 +00:00
2023-05-05 05:23:59 +00:00
page = get_page ( )
2022-05-04 23:09:46 +00:00
sort = request . values . get ( " sort " , " new " ) . lower ( )
t = request . values . get ( ' t ' , ' all ' ) . lower ( )
criteria = searchparse ( query )
2023-05-05 21:44:24 +00:00
comments = g . db . query ( Comment ) . options ( load_only ( Comment . id ) ) . outerjoin ( Comment . post ) \
2022-12-07 16:51:51 +00:00
. filter (
or_ ( Comment . parent_submission != None , Comment . wall_user_id != None ) ,
Comment . author_id . notin_ ( v . userblocks ) ,
)
2022-05-04 23:09:46 +00:00
2023-01-01 11:36:20 +00:00
2022-07-01 11:11:23 +00:00
if ' post ' in criteria :
try : post = int ( criteria [ ' post ' ] )
2022-10-15 10:30:13 +00:00
except : abort ( 404 )
2022-07-01 11:11:23 +00:00
comments = comments . filter ( Comment . parent_submission == post )
2022-05-04 23:09:46 +00:00
if ' author ' in criteria :
comments = comments . filter ( Comment . ghost == False )
2023-02-27 15:38:12 +00:00
author = get_user ( criteria [ ' author ' ] , v = v )
2022-10-30 07:31:21 +00:00
if not author . is_visible_to ( v ) :
2022-10-15 09:11:36 +00:00
if v . client :
2022-10-11 13:01:39 +00:00
abort ( 403 , f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them " )
2022-05-04 23:09:46 +00:00
2023-05-05 21:44:24 +00:00
return render_template ( " search_comments.html " , v = v , query = query , total = 0 , page = page , comments = [ ] , sort = sort , t = t , error = f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them! " ) , 403
2022-05-04 23:09:46 +00:00
else : comments = comments . filter ( Comment . author_id == author . id )
2022-10-02 08:55:39 +00:00
if ' q ' in criteria :
2022-10-04 05:31:27 +00:00
tokens = map ( lambda x : re . sub ( r ' [ \ 0():|&*!<>] ' , ' ' , x ) , criteria [ ' q ' ] )
2022-10-27 22:24:12 +00:00
tokens = filter ( lambda x : len ( x ) > 0 , tokens )
2022-11-18 21:55:15 +00:00
tokens = map ( lambda x : re . sub ( r " ' " , " \\ ' " , x ) , tokens )
2022-11-21 00:31:27 +00:00
tokens = map ( lambda x : x . strip ( ) , tokens )
2022-10-02 19:50:05 +00:00
tokens = map ( lambda x : re . sub ( r ' \ s+ ' , ' <-> ' , x ) , tokens )
2022-10-02 10:24:03 +00:00
comments = comments . filter ( Comment . body_ts . match (
2022-10-02 11:42:42 +00:00
' & ' . join ( tokens ) ,
2022-10-02 10:24:03 +00:00
postgresql_regconfig = ' english ' ) )
2022-05-04 23:09:46 +00:00
if ' over18 ' in criteria : comments = comments . filter ( Comment . over_18 == True )
2022-06-23 09:02:49 +00:00
if search_operator_hole in criteria :
comments = comments . filter ( Submission . sub == criteria [ search_operator_hole ] )
2022-06-23 06:11:03 +00:00
2022-07-09 10:32:49 +00:00
comments = apply_time_filter ( t , comments , Comment )
2022-05-04 23:09:46 +00:00
2022-10-06 06:45:27 +00:00
if v . admin_level < PERMS [ ' POST_COMMENT_MODERATION ' ] :
2023-03-16 06:27:58 +00:00
private = [ x [ 0 ] for x in g . db . query ( Submission . id ) . filter ( Submission . private == True ) . all ( ) ]
2022-05-04 23:09:46 +00:00
2022-12-11 16:30:40 +00:00
comments = comments . filter (
Comment . is_banned == False ,
Comment . deleted_utc == 0 ,
or_ (
Comment . parent_submission . notin_ ( private ) ,
Comment . wall_user_id != None
)
)
2022-05-04 23:09:46 +00:00
2022-08-15 19:02:23 +00:00
if ' after ' in criteria :
2022-08-30 01:15:54 +00:00
after = criteria [ ' after ' ]
try : after = int ( after )
2022-08-31 01:48:20 +00:00
except :
try : after = timegm ( time . strptime ( after , " % Y- % m- %d " ) )
except : abort ( 400 )
2022-08-15 19:02:23 +00:00
comments = comments . filter ( Comment . created_utc > after )
if ' before ' in criteria :
2022-08-30 01:15:54 +00:00
before = criteria [ ' before ' ]
try : before = int ( before )
2022-08-31 01:48:20 +00:00
except :
try : before = timegm ( time . strptime ( before , " % Y- % m- %d " ) )
except : abort ( 400 )
2022-08-15 19:02:23 +00:00
comments = comments . filter ( Comment . created_utc < before )
2022-05-04 23:09:46 +00:00
total = comments . count ( )
2023-05-05 21:44:24 +00:00
comments = sort_objects ( sort , comments , Comment )
2022-05-04 23:09:46 +00:00
2023-05-05 21:44:24 +00:00
comments = comments . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE ) . all ( )
2022-05-04 23:09:46 +00:00
2023-05-05 21:44:24 +00:00
ids = [ x . id for x in comments ]
2022-05-04 23:09:46 +00:00
comments = get_comments ( ids , v = v )
2023-03-16 06:27:58 +00:00
if v . client : return { " total " : total , " data " : [ x . json ( db = g . db ) for x in comments ] }
2023-05-05 21:44:24 +00:00
return render_template ( " search_comments.html " , v = v , query = query , page = page , comments = comments , sort = sort , t = t , total = total , standalone = True )
2022-05-04 23:09:46 +00:00
2023-02-19 19:56:52 +00:00
@app.get ( " /search/messages " )
2023-02-26 08:41:04 +00:00
@limiter.limit ( DEFAULT_RATELIMIT )
2023-02-19 19:56:52 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , key_func = get_ID )
@auth_required
def searchmessages ( v : User ) :
query = request . values . get ( " q " , ' ' ) . strip ( )
if not query :
abort ( 403 , " Empty searches aren ' t allowed! " )
2023-05-05 05:23:59 +00:00
page = get_page ( )
2023-02-19 19:56:52 +00:00
sort = request . values . get ( " sort " , " new " ) . lower ( )
t = request . values . get ( ' t ' , ' all ' ) . lower ( )
criteria = searchparse ( query )
2023-02-26 09:22:42 +00:00
dm_conditions = [ Comment . author_id == v . id , Comment . sentto == v . id ]
if v . admin_level > = PERMS [ ' VIEW_MODMAIL ' ] :
dm_conditions . append ( Comment . sentto == MODMAIL_ID ) ,
2023-05-05 21:44:24 +00:00
comments = g . db . query ( Comment ) . options ( load_only ( Comment . id ) ) \
2023-02-19 19:56:52 +00:00
. filter (
Comment . sentto != None ,
Comment . parent_submission == None ,
2023-02-26 09:22:42 +00:00
or_ ( * dm_conditions ) ,
2023-02-19 19:56:52 +00:00
)
if ' author ' in criteria :
comments = comments . filter ( Comment . ghost == False )
2023-02-27 15:38:12 +00:00
author = get_user ( criteria [ ' author ' ] , v = v )
2023-02-19 19:56:52 +00:00
if not author . is_visible_to ( v ) :
if v . client :
abort ( 403 , f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them " )
2023-05-05 21:44:24 +00:00
return render_template ( " search_comments.html " , v = v , query = query , total = 0 , page = page , comments = [ ] , sort = sort , t = t , error = f " @ { author . username } ' s profile is private; You can ' t use the ' author ' syntax on them! " ) , 403
2023-02-19 19:56:52 +00:00
else : comments = comments . filter ( Comment . author_id == author . id )
if ' q ' in criteria :
tokens = map ( lambda x : re . sub ( r ' [ \ 0():|&*!<>] ' , ' ' , x ) , criteria [ ' q ' ] )
tokens = filter ( lambda x : len ( x ) > 0 , tokens )
tokens = map ( lambda x : re . sub ( r " ' " , " \\ ' " , x ) , tokens )
tokens = map ( lambda x : x . strip ( ) , tokens )
tokens = map ( lambda x : re . sub ( r ' \ s+ ' , ' <-> ' , x ) , tokens )
comments = comments . filter ( Comment . body_ts . match (
' & ' . join ( tokens ) ,
postgresql_regconfig = ' english ' ) )
comments = apply_time_filter ( t , comments , Comment )
if ' after ' in criteria :
after = criteria [ ' after ' ]
try : after = int ( after )
except :
try : after = timegm ( time . strptime ( after , " % Y- % m- %d " ) )
except : abort ( 400 )
comments = comments . filter ( Comment . created_utc > after )
if ' before ' in criteria :
before = criteria [ ' before ' ]
try : before = int ( before )
except :
try : before = timegm ( time . strptime ( before , " % Y- % m- %d " ) )
except : abort ( 400 )
comments = comments . filter ( Comment . created_utc < before )
2023-03-03 02:04:09 +00:00
if ' sentto ' in criteria :
sentto = criteria [ ' sentto ' ]
2023-05-09 18:39:56 +00:00
sentto = get_user ( sentto , graceful = True )
if not sentto :
2023-05-09 18:59:41 +00:00
abort ( 400 , " The `sentto` field must contain an existing user ' s username! " )
2023-05-09 18:39:56 +00:00
2023-03-03 02:04:09 +00:00
comments = comments . filter ( Comment . sentto == sentto . id )
2023-02-19 19:56:52 +00:00
total = comments . count ( )
2023-05-05 21:44:24 +00:00
comments = sort_objects ( sort , comments , Comment )
2023-02-19 19:56:52 +00:00
2023-05-05 21:44:24 +00:00
comments = comments . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE ) . all ( )
2023-02-19 19:56:52 +00:00
2023-03-25 18:25:38 +00:00
for x in comments : x . unread = True
2023-05-05 21:45:25 +00:00
2023-03-22 20:16:52 +00:00
comments = [ x . top_comment for x in comments ]
2023-02-19 19:56:52 +00:00
2023-03-16 06:27:58 +00:00
if v . client : return { " total " : total , " data " : [ x . json ( db = g . db ) for x in comments ] }
2023-05-05 21:44:24 +00:00
return render_template ( " search_comments.html " , v = v , query = query , page = page , comments = comments , sort = sort , t = t , total = total , standalone = True , render_replies = True )
2023-02-19 19:56:52 +00:00
2022-05-04 23:09:46 +00:00
@app.get ( " /search/users " )
2023-02-26 08:41:04 +00:00
@limiter.limit ( DEFAULT_RATELIMIT )
2023-01-21 04:39:46 +00:00
@limiter.limit ( DEFAULT_RATELIMIT , key_func = get_ID )
2022-05-04 23:09:46 +00:00
@auth_required
2022-11-26 21:00:03 +00:00
def searchusers ( v : User ) :
2022-05-04 23:09:46 +00:00
query = request . values . get ( " q " , ' ' ) . strip ( )
2023-03-03 02:04:09 +00:00
if not query :
abort ( 403 , " Empty searches aren ' t allowed! " )
2022-05-04 23:09:46 +00:00
2023-05-05 05:23:59 +00:00
page = get_page ( )
2022-11-07 21:34:40 +00:00
2023-03-16 06:27:58 +00:00
users = g . db . query ( User )
2023-01-01 11:36:20 +00:00
2023-03-03 02:04:09 +00:00
criteria = searchparse ( query )
if ' after ' in criteria :
after = criteria [ ' after ' ]
try : after = int ( after )
except :
try : after = timegm ( time . strptime ( after , " % Y- % m- %d " ) )
except : abort ( 400 )
users = users . filter ( User . created_utc > after )
if ' before ' in criteria :
before = criteria [ ' before ' ]
try : before = int ( before )
except :
try : before = timegm ( time . strptime ( before , " % Y- % m- %d " ) )
except : abort ( 400 )
users = users . filter ( User . created_utc < before )
2023-01-01 11:36:20 +00:00
2023-03-03 02:04:09 +00:00
if ' q ' in criteria :
term = criteria [ ' q ' ] [ 0 ]
term = term . lstrip ( ' @ ' )
term = term . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' )
2023-01-01 11:36:20 +00:00
2023-03-03 02:04:09 +00:00
users = users . filter (
or_ (
User . username . ilike ( f ' % { term } % ' ) ,
User . original_username . ilike ( f ' % { term } % ' )
)
) . order_by ( User . username . ilike ( term ) . desc ( ) , User . stored_subscriber_count . desc ( ) )
total = users . count ( )
2023-01-01 11:36:20 +00:00
2023-05-05 21:44:24 +00:00
users = users . offset ( PAGE_SIZE * ( page - 1 ) ) . limit ( PAGE_SIZE ) . all ( )
2022-05-04 23:09:46 +00:00
2022-10-15 09:11:36 +00:00
if v . client : return { " data " : [ x . json for x in users ] }
2023-05-05 21:44:24 +00:00
return render_template ( " search_users.html " , v = v , query = query , page = page , users = users , total = total )