diff options
Diffstat (limited to 'lib/VNWeb/Filters.pm')
-rw-r--r-- | lib/VNWeb/Filters.pm | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/lib/VNWeb/Filters.pm b/lib/VNWeb/Filters.pm new file mode 100644 index 00000000..b422ad8c --- /dev/null +++ b/lib/VNWeb/Filters.pm @@ -0,0 +1,246 @@ +package VNWeb::Filters; + +# This module implements validating old search filters and converting them to +# the new AdvSearch system. It only exists for compatibility with old URLs. + +use v5.26; +use TUWF; +use VNDB::Types; +use VNWeb::Auth; +use VNWeb::Validation; +use Exporter 'import'; + +our @EXPORT = qw/filter_parse filter_vn_adv filter_release_adv filter_char_adv filter_staff_adv/; + + +my $VN = form_compile any => { + date_before => { default => undef, uint => 1, range => [0, 99999999] }, # don't use 'rdate' validation here, the search form allows invalid dates + date_after => { default => undef, uint => 1, range => [0, 99999999] }, # ^ + released => { undefbool => 1 }, + length => { undefarray => { enum => \%VN_LENGTH } }, + hasani => { undefbool => 1 }, + hasshot => { undefbool => 1 }, + tag_inc => { undefarray => { id => 1 } }, + tag_exc => { undefarray => { id => 1 } }, + taginc => { undefarray => {} }, # [old] Tag search by name + tagexc => { undefarray => {} }, # [old] Tag search by name + tagspoil => { default => 0, uint => 1, range => [0,2] }, + lang => { undefarray => { enum => \%LANGUAGE } }, + olang => { undefarray => { enum => \%LANGUAGE } }, + plat => { undefarray => { enum => \%PLATFORM } }, + staff_inc => { undefarray => { id => 1 } }, + staff_exc => { undefarray => { id => 1 } }, + ul_notblack => { undefbool => 1 }, + ul_onwish => { undefbool => 1 }, + ul_voted => { undefbool => 1 }, + ul_onlist => { undefbool => 1 }, +}; + +my $RELEASE = form_compile any => { + type => { default => undef, enum => \%RELEASE_TYPE }, + patch => { undefbool => 1 }, + freeware => { undefbool => 1 }, + doujin => { undefbool => 1 }, + uncensored => { undefbool => 1 }, + date_before => { default => undef, range => [0, 99999999] }, # don't use 'rdate' validation here, the search form allows invalid dates + date_after => { default => undef, range => [0, 99999999] }, # ^ + released => { undefbool => 1 }, + minage => { undefarray => { enum => [-1, keys %AGE_RATING] } }, + lang => { undefarray => { enum => \%LANGUAGE } }, + olang => { undefarray => { enum => \%LANGUAGE } }, + resolution => { undefarray => {} }, + plat => { undefarray => { enum => [ 'unk', keys %PLATFORM ] } }, + prod_inc => { undefarray => { id => 1 } }, + prod_exc => { undefarray => { id => 1 } }, + med => { undefarray => { enum => [ 'unk', keys %MEDIUM ] } }, + voiced => { undefarray => { enum => \%VOICED } }, + ani_story => { undefarray => { enum => \%ANIMATED } }, + ani_ero => { undefarray => { enum => \%ANIMATED } }, + engine => { default => undef }, +}; + +my $CHAR = form_compile any => { + gender => { undefarray => { enum => \%GENDER } }, + bloodt => { undefarray => { enum => \%BLOOD_TYPE } }, + bust_min => { default => undef, uint => 1, range => [ 0, 32767 ] }, + bust_max => { default => undef, uint => 1, range => [ 0, 32767 ] }, + waist_min => { default => undef, uint => 1, range => [ 0, 32767 ] }, + waist_max => { default => undef, uint => 1, range => [ 0, 32767 ] }, + hip_min => { default => undef, uint => 1, range => [ 0, 32767 ] }, + hip_max => { default => undef, uint => 1, range => [ 0, 32767 ] }, + height_min => { default => undef, uint => 1, range => [ 0, 32767 ] }, + height_max => { default => undef, uint => 1, range => [ 0, 32767 ] }, + weight_min => { default => undef, uint => 1, range => [ 0, 32767 ] }, + weight_max => { default => undef, uint => 1, range => [ 0, 32767 ] }, + cup_min => { default => undef, enum => \%CUP_SIZE }, + cup_max => { default => undef, enum => \%CUP_SIZE }, + va_inc => { undefarray => { id => 1 } }, + va_exc => { undefarray => { id => 1 } }, + trait_inc => { undefarray => { id => 1 } }, + trait_exc => { undefarray => { id => 1 } }, + tagspoil => { default => 0, uint => 1, range => [0,2] }, + role => { undefarray => { enum => \%CHAR_ROLE } }, +}; + +my $STAFF = form_compile any => { + gender => { undefarray => { enum => [qw[unknown m f]] } }, + role => { undefarray => { enum => [ 'seiyuu', keys %CREDIT_TYPE ] } }, + truename => { undefbool => 1 }, + lang => { undefarray => { enum => \%LANGUAGE } }, +}; + + + +# Compatibility with old VN filters. Modifies the filter in-place and returns the number of changes made. +sub filter_vn_compat { + my($fil) = @_; #XXX: This function is called from old VNDB:: code and the filter data may not have been normalized as per the schema. + my $mod = 0; + + # older tag specification (by name rather than ID) + for ('taginc', 'tagexc') { + my $l = delete $fil->{$_}; + next if !$l; + $l = [ map lc($_), ref $l ? @$l : $l ]; + $fil->{ s/^tag/tag_/rg } ||= [ map $_->{id}, tuwf->dbAlli( + 'SELECT DISTINCT id FROM tags WHERE searchable AND lower(name) IN', $l + )->@* ]; + $mod++; + } + + $mod; +} + + +# Resolutions were passed as integers into an array index before 6bd0b0cd1f3892253d881f71533940f0cf07c13d. +# New resolutions have been added to this array in the past, so some older filters may reference the wrong resolution. +my @OLDRES = (qw/unknown nonstandard 640x480 800x600 1024x768 1280x960 1600x1200 640x400 960x600 1024x576 1024x600 1024x640 1280x720 1280x800 1366x768 1600x900 1920x1080/); + +sub filter_release_compat { + my($fil) = @_; + my $mod = 0; + $fil->{resolution} &&= [ map /^(?:0|[1-9][0-9]*)$/ && $_ <= $#OLDRES ? do { $mod++; $OLDRES[$_] } : $_, $fil->{resolution}->@* ]; + $mod; +} + + + +my @fil_escape = split //, '_ !"#$%&\'()*+,-./:;<=>?@[\]^`{}~'; + +sub _fil_parse { + my $str = shift; + my %r; + for (split /\./, $str) { + next if !/^([a-z0-9_]+)-([a-zA-Z0-9_~\x81-\x{ffffff}]+)$/; + my($f, $v) = ($1, $2); + my @v = split /~/, $v; + s/_([0-9]{2})/$1 > $#fil_escape ? '' : $fil_escape[$1]/eg for(@v); + $r{$f} = @v > 1 ? \@v : $v[0] + } + return \%r; +} + + +# Throws error on failure. +sub filter_parse { + my($type, $str) = @_; + return {} if !$str; + my $s = {v => $VN, r => $RELEASE, c => $CHAR, s => $STAFF}->{$type}; + my $data = ref $str ? $str : $str =~ /^{/ ? JSON::XS->new->decode($str) : _fil_parse $str; + die "Invalid filter data: $str\n" if !$data; + my $f = $s->validate($data)->data; + filter_vn_compat $f if $type eq 'v'; + filter_release_compat $f if $type eq 'r'; + $f +} + + +sub filter_vn_adv { + my($fil) = @_; + [ 'and', + defined $fil->{date_before} ? [ 'released', '<=', $fil->{date_before} ] : (), + defined $fil->{date_after} ? [ 'released', '>=', $fil->{date_after} ] : (), + defined $fil->{released} ? [ 'released', $fil->{released} ? '<=' : '>', 1 ] : (), + defined $fil->{length} ? [ 'or', map [ 'length', '=', $_ ], $fil->{length}->@* ] : (), + defined $fil->{hasani} ? [ 'has_anime', $fil->{hasani} ? '=' : '!=', 1 ] : (), + defined $fil->{hasshot} ? [ 'has_screenshot', $fil->{hasshot} ? '=' : '!=', 1 ] : (), + defined $fil->{tag_inc} ? [ 'and', map [ 'tag', '=', [ $_, $fil->{tagspoil}, 0 ] ], $fil->{tag_inc}->@* ] : (), + defined $fil->{tag_exc} ? [ 'and', map [ 'tag', '!=', [ $_, 2, 0 ] ], $fil->{tag_exc}->@* ] : (), + defined $fil->{lang} ? [ 'or', map [ 'lang', '=', $_ ], $fil->{lang}->@* ] : (), + defined $fil->{olang} ? [ 'or', map [ 'olang', '=', $_ ], $fil->{olang}->@* ] : (), + defined $fil->{plat} ? [ 'or', map [ 'platform', '=', $_ ], $fil->{plat}->@* ] : (), + defined $fil->{staff_inc} ? [ 'staff', '=', [ 'or', map [ 'id', '=', $_ ], $fil->{staff_inc}->@* ] ] : (), + defined $fil->{staff_exc} ? [ 'staff', '!=', [ 'or', map [ 'id', '=', $_ ], $fil->{staff_exc}->@* ] ] : (), + auth ? ( + defined $fil->{ul_notblack} ? [ 'label', '!=', [ auth->uid, 6 ] ] : (), + defined $fil->{ul_onwish} ? [ 'label', $fil->{ul_onwish} ? '=' : '!=', [ auth->uid, 5 ] ] : (), + defined $fil->{ul_voted} ? [ 'label', $fil->{ul_voted} ? '=' : '!=', [ auth->uid, 7 ] ] : (), + defined $fil->{ul_onlist} ? [ 'on-list', $fil->{ul_onlist} ? '=' : '!=', 1 ] : (), + ) : () + ] +} + + +sub filter_release_adv { + my($fil) = @_; + [ 'and', + defined $fil->{type} ? [ 'rtype', '=', $fil->{type} ] : (), + defined $fil->{patch} ? [ 'patch', $fil->{patch} ? '=' : '!=', 1 ] : (), + defined $fil->{freeware} ? [ 'freeware', $fil->{freeware} ? '=' : '!=', 1 ] : (), + defined $fil->{doujin} ? [ 'doujin', $fil->{doujin} ? '=' : '!=', 1 ] : (), + defined $fil->{uncensored} ? [ 'uncensored', $fil->{uncensored} ? '=' : '!=', 1 ] : (), + defined $fil->{date_before} ? [ 'released', '<=', $fil->{date_before} ] : (), + defined $fil->{date_after} ? [ 'released', '>=', $fil->{date_after} ] : (), + defined $fil->{released} ? [ 'released', $fil->{released} ? '<=' : '>', 1 ] : (), + defined $fil->{minage} ? [ 'or', map [ 'minage', '=', $_ == -1 ? undef : $_ ], $fil->{minage}->@* ] : (), + defined $fil->{lang} ? [ 'or', map [ 'lang', '=', $_ ], $fil->{lang}->@* ] : (), + defined $fil->{olang} ? [ 'vn', '=', [ 'or', map [ 'olang', '=', $_ ], $fil->{olang}->@* ] ] : (), + defined $fil->{resolution} ? [ 'or', map [ 'resolution', '=', $_ eq 'unknown' ? [0,0] : $_ eq 'nonstandard' ? [0,1] : [split /x/] ], $fil->{resolution}->@* ] : (), + defined $fil->{plat} ? [ 'or', map [ 'platform', '=', $_ eq 'unk' ? '' : $_ ], $fil->{plat}->@* ] : (), + defined $fil->{prod_inc} ? [ 'or', map [ 'producer-id', '=', $_ ], $fil->{prod_inc}->@* ] : (), + defined $fil->{prod_exc} ? [ 'and', map [ 'producer-id', '!=', $_ ], $fil->{prod_exc}->@* ] : (), + defined $fil->{med} ? [ 'or', map [ 'medium', '=', $_ eq 'unk' ? '' : $_ ], $fil->{med}->@* ] : (), + defined $fil->{voiced} ? [ 'or', map [ 'voiced', '=', $_ ], $fil->{voiced}->@* ] : (), + defined $fil->{ani_story} ? [ 'or', map [ 'animation-story', '=', $_ ], $fil->{ani_story}->@* ] : (), + defined $fil->{ani_ero} ? [ 'or', map [ 'animation-ero', '=', $_ ], $fil->{ani_ero}->@* ] : (), + defined $fil->{engine} ? [ 'engine', '=', $fil->{engine} ] : (), + ] +} + + +sub filter_char_adv { + my($fil) = @_; + [ 'and', + defined $fil->{gender} ? [ 'or', map [ 'sex', '=', $_ ], $fil->{gender}->@* ] : (), + defined $fil->{bloodt} ? [ 'or', map [ 'blood_type', '=', $_ ], $fil->{bloodt}->@* ] : (), + defined $fil->{bust_min} ? [ 'bust', '>=', $fil->{bust_min} ] : (), + defined $fil->{bust_max} ? [ 'bust', '<=', $fil->{bust_max} ] : (), + defined $fil->{waist_min} ? [ 'waist', '>=', $fil->{waist_min} ] : (), + defined $fil->{waist_max} ? [ 'waist', '<=', $fil->{waist_max} ] : (), + defined $fil->{hip_min} ? [ 'hips', '>=', $fil->{hip_min} ] : (), + defined $fil->{hip_max} ? [ 'hips', '<=', $fil->{hip_max} ] : (), + defined $fil->{height_min} ? [ 'height', '>=', $fil->{height_min} ] : (), + defined $fil->{height_max} ? [ 'height', '<=', $fil->{height_max} ] : (), + defined $fil->{weight_min} ? [ 'weight', '>=', $fil->{weight_min} ] : (), + defined $fil->{weight_max} ? [ 'weight', '<=', $fil->{weight_max} ] : (), + defined $fil->{cup_min} ? [ 'cup', '>=', $fil->{cup_min} ] : (), + defined $fil->{cup_max} ? [ 'cup', '<=', $fil->{cup_max} ] : (), + defined $fil->{va_inc} ? [ 'seiyuu', '=', [ 'or', map [ 'id', '=', $_ ], $fil->{va_inc}->@* ] ] : (), + defined $fil->{va_exc} ? [ 'seiyuu', '!=', [ 'or', map [ 'id', '=', $_ ], $fil->{va_exc}->@* ] ] : (), + defined $fil->{trait_inc} ? [ 'and', map [ 'trait', '=', [ $_, $fil->{tagspoil} ] ], $fil->{trait_inc}->@* ] : (), + defined $fil->{trait_exc} ? [ 'and', map [ 'trait', '!=', [ $_, 2 ] ], $fil->{trait_exc}->@* ] : (), + defined $fil->{role} ? [ 'or', map [ 'role', '=', $_ ], $fil->{role}->@* ] : (), + ] +} + + +# 'truename' filter is ignored, not part of the AdvSearch interface +sub filter_staff_adv { + my($fil) = @_; + [ 'and', + defined $fil->{gender} ? [ 'or', map [ 'gender', '=', $_ ], $fil->{gender}->@* ] : (), + defined $fil->{role} ? [ 'or', map [ 'role', '=', $_ ], $fil->{role}->@* ] : (), + defined $fil->{lang} ? [ 'or', map [ 'lang', '=', $_ ], $fil->{lang}->@* ] : (), + ] +} + +1; |