summaryrefslogtreecommitdiff
path: root/lib/VNWeb/Images/Lib.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNWeb/Images/Lib.pm')
-rw-r--r--lib/VNWeb/Images/Lib.pm166
1 files changed, 166 insertions, 0 deletions
diff --git a/lib/VNWeb/Images/Lib.pm b/lib/VNWeb/Images/Lib.pm
new file mode 100644
index 00000000..0170d37e
--- /dev/null
+++ b/lib/VNWeb/Images/Lib.pm
@@ -0,0 +1,166 @@
+package VNWeb::Images::Lib;
+
+use VNWeb::Prelude;
+use Exporter 'import';
+
+our @EXPORT = qw/enrich_image validate_token image_flagging_display image_hidden image_ enrich_image_obj/;
+
+
+my @SEX = qw/Safe Suggestive Explicit/;
+my @VIO = qw/Tame Violent Brutal /;
+
+# Enrich images so that they match the format expected by the 'ImageResult' Elm
+# API response.
+#
+# Also adds signed tokens to the image list - indicating that the current user
+# is permitted to vote on these images. These tokens ensure that non-moderators
+# can only vote on images that they have been randomly assigned, thus
+# preventing possible abuse when a single person uses multiple accounts to
+# influence the rating of a single image.
+sub enrich_image {
+ my($canvote, $l) = @_;
+ enrich_merge id => sub { sql q{
+ SELECT i.id, i.width, i.height, i.c_votecount AS votecount
+ , i.c_sexual_avg::real/100 AS sexual_avg, i.c_sexual_stddev::real/100 AS sexual_stddev
+ , i.c_violence_avg::real/100 AS violence_avg, i.c_violence_stddev::real/100 AS violence_stddev
+ , iv.sexual AS my_sexual, iv.violence AS my_violence
+ , COALESCE(EXISTS(SELECT 1 FROM image_votes iv0 WHERE iv0.id = i.id AND iv0.ignore) AND NOT iv.ignore, FALSE) AS my_overrule
+ , COALESCE(v.id, c.id, vsv.id) AS entry_id
+ , COALESCE(v.title[1+1], c.title[1+1], vsv.title[1+1]) AS entry_title
+ FROM images i
+ LEFT JOIN image_votes iv ON iv.id = i.id AND iv.uid =}, \auth->uid, q{
+ LEFT JOIN}, vnt, q{v ON i.id BETWEEN 'cv1' AND vndbid_max('cv') AND v.image = i.id
+ LEFT JOIN}, charst, q{c ON i.id BETWEEN 'ch1' AND vndbid_max('ch') AND c.image = i.id
+ LEFT JOIN vn_screenshots vs ON i.id BETWEEN 'sf1' AND vndbid_max('sf') AND vs.scr = i.id
+ LEFT JOIN}, vnt, q{vsv ON i.id BETWEEN 'sf1' AND vndbid_max('sf') AND vsv.id = vs.id
+ WHERE i.id IN}, $_
+ }, $l;
+
+ enrich votes => id => id => sub { sql '
+ SELECT iv.id, iv.uid, iv.sexual, iv.violence, iv.ignore OR (u.id IS NOT NULL AND NOT u.perm_imgvote) AS ignore, ', sql_user(), '
+ FROM image_votes iv
+ LEFT JOIN users u ON u.id = iv.uid
+ WHERE iv.id IN', $_,
+ auth ? ('AND (iv.uid IS NULL OR iv.uid <> ', \auth->uid, ')') : (), '
+ ORDER BY u.username'
+ }, $l;
+
+ for(grep defined $_->{width}, @$l) {
+ $_->{entry} = $_->{entry_id} ? { id => $_->{entry_id}, title => $_->{entry_title} } : undef;
+ delete $_->{entry_id};
+ delete $_->{entry_title};
+ for my $v ($_->{votes}->@*) {
+ $v->{user} = xml_string sub { user_ $v }; # Easier than duplicating user_() in Elm
+ delete $v->{$_} for grep /^user_/, keys %$v;
+ }
+ $_->{token} = ($_->{votecount} == 0 && auth->permImgvote) || (ref $canvote eq 'CODE' ? $canvote->($_) : $canvote) ? auth->csrftoken(0, "imgvote-$_->{id}") : undef;
+ }
+}
+
+# Validates the token generated by enrich_image;
+sub validate_token {
+ my($l) = @_;
+ my $ok = 1;
+ $ok &&= $_->{token} && auth->csrfcheck($_->{token}, "imgvote-$_->{id}") for @$l;
+ $ok;
+}
+
+
+# Returns a string like 'Not flagged' or 'Safe / Tame (5)'
+sub image_flagging_display {
+ my($img, $small) = @_;
+ !$img->{votecount} ? 'Not flagged' :
+ $small ? sprintf '%s / %s', $SEX[$img->{sexual}], $VIO[$img->{violence}]
+ : sprintf '%s / %s (%d)', $SEX[$img->{sexual}], $VIO[$img->{violence}], $img->{votecount}
+}
+
+
+# Returns whether the image is hidden according to the user's preferences.
+# Return values:
+# 0 -> visible
+# 4 -> hidden for some reason
+# 5 -> hidden because of sexual flag
+# 6 -> hidden because of violence flag
+# 7 -> hidden because both
+sub image_hidden {
+ my($img) = @_;
+ my($sex,$vio) = $img->@{'sexual', 'violence'};
+ my $sexp = auth->pref('max_sexual')||0;
+ my $viop = auth->pref('max_violence')||0;
+ my $sexh = $sex > $sexp && $sexp >= 0 if $img->{votecount};
+ my $vioh = $vio > $viop if $img->{votecount};
+ my $hidden = $sexp < 0 || $sexh || $vioh || (!$img->{votecount} && ($sexp < 2 || $viop < 2));
+ $hidden ? 4 + ($sexh?1:0)+($vioh?2:0) : 0;
+}
+
+
+# Display (or not) an image with preference toggle and hover-information.
+# Given $img is assumed to be an object generated by enrich_image_obj().
+# %opt:
+# alt -> alt text
+# width -> if different from original image
+# height -> if different from original image
+# url -> link the image to a page (if not hidden by settings)
+# overlay -> CODE ref, html to replace the overlay with.
+# XXX: Not all of these options are used, could clean up a few.
+sub image_ {
+ my($img, %opt) = @_;
+ return p_ 'No image' if !$img;
+
+ my($sex,$vio) = $img->@{'sexual', 'violence'};
+ my($w,$h) = $opt{width} ? @opt{'width','height'} : @{$img}{'width', 'height'};
+ my $hidden = image_hidden $img;
+ my $hide_on_click = $opt{url} ? $hidden : $sex || $vio || !$img->{votecount} || (auth->pref('max_sexual')||0) < 0;
+ my $small = $w*$h < 20000;
+
+ label_ class => 'imghover', style => "width: ${w}px; height: ${h}px", sub {
+ input_ type => 'checkbox', class => 'hidden', $hidden ? () : (checked => 'checked') if $hide_on_click;
+ div_ class => 'imghover--visible', sub {
+ a_ href => $opt{url} if $opt{url};
+ img_ src => imgurl($img->{id}), width => $w, height => $h, $opt{alt} ? (alt => $opt{alt}) : ();
+ end_ if $opt{url};
+ if(!exists $opt{overlay}) {
+ a_ class => 'imghover--overlay', href => "/$img->{id}?view=".viewset(show_nsfw=>1), image_flagging_display $img, $small if auth;
+ span_ class => 'imghover--overlay', image_flagging_display $img, $small if !auth;
+ } elsif(ref $opt{overlay} eq 'CODE') {
+ $opt{overlay}->();
+ }
+ };
+ div_ class => 'imghover--warning', sub {
+ if($img->{votecount}) {
+ if(!$small) {
+ txt_ 'This image has been flagged as:';
+ br_; br_;
+ }
+ txt_ 'Sexual: '; $hidden & 1 ? b_ $SEX[$sex] : txt_ $SEX[$sex];
+ br_;
+ txt_ 'Violence: '; $hidden & 2 ? b_ $VIO[$vio] : txt_ $VIO[$vio];
+ } else {
+ txt_ 'This image has not yet been flagged';
+ }
+ if(!$small) {
+ br_; br_;
+ span_ class => 'fake_link', 'Show me anyway';
+ br_; br_;
+ small_ 'This warning can be disabled in your account';
+ }
+ } if $hide_on_click;
+ }
+}
+
+
+sub enrich_image_obj {
+ my $field = shift;
+ enrich_obj $field => id => 'SELECT id, width, height, c_votecount AS votecount, c_sexual_avg::real/100 AS sexual_avg, c_violence_avg::real/100 AS violence_avg FROM images WHERE id IN', @_;
+
+ # Also add our final verdict. Still no clue why I chose these thresholds, but they seem to work.
+ for (map +(ref $_ eq 'ARRAY' ? @$_ : $_), @_) {
+ local $_ = $_->{$field};
+ if(ref $_) {
+ $_->{sexual} = !$_->{votecount} ? 2 : $_->{sexual_avg} > 1.3 ? 2 : $_->{sexual_avg} > 0.4 ? 1 : 0;
+ $_->{violence} = !$_->{votecount} ? 2 : $_->{violence_avg} > 1.3 ? 2 : $_->{violence_avg} > 0.4 ? 1 : 0;
+ }
+ }
+}
+
+1;