#!/usr/bin/perl -w use LiveJournal; use Getopt::Std; use Date::Parse; use strict; sub DELAY() { 0 }; $|++; my %opts; my @queries; my $date_start; my $date_end; my $event_action; # -------------------------------------------------------------------- my $lj = LiveJournal::LiveJournalrc->new(); $lj->version(1); my $jo = LiveJournal::Journal->new(); my $sess = $lj->login(); die "couldn't log in" unless response_is_good($sess); # -------------------------------------------------------------------- getopts('i:d:f:t:e:m:Aps:o:a:F', \%opts); if ($opts{p}) { $event_action = sub { security_print($_[0]); }; } if ($opts{s}) { $event_action = sub { security_set($_[0], $opts{s}); sleep DELAY; }; } if ($opts{o}) { $event_action = sub { security_or($_[0], $opts{o}); sleep DELAY; }; } if ($opts{a}) { $event_action = sub { security_and($_[0], $opts{a}); sleep DELAY; }; } if ($opts{F}) { $event_action = sub { security_fix($_[0]); sleep DELAY; }; } if (defined $opts{i}) { @queries = ({ selecttype => 'one', itemid => uncook_itemid($opts{i}), }); } if (defined $opts{d} && $opts{d} =~ /(\d+)-(\d+)-(\d+)/) { @queries = ({ selecttype => 'day', year => $1, month => $2, day => $3, }); } if ($opts{f}) { $date_start = str2time($opts{f}); } if ($opts{t}) { $date_end = str2time($opts{t}); } if ($opts{f} || $opts{t} || $opts{A}) { my $day_counts = $jo->getdaycounts(); die "error getting daycounts" unless response_is_good($day_counts); for my $d (sort keys %$day_counts) { next unless $d =~ /(\d+)-(\d+)-(\d+)/; next if defined $date_start && str2time($d) < $date_start; next if defined $date_end && str2time($d) > $date_end; push @queries, { selecttype => 'day', year => $1, month => $2, day => $3, }; } } if ($opts{m} && $opts{m} =~ /^0x/i) { $opts{m} = hex($opts{m}); } die_usage() unless @queries && $event_action; # -------------------------------------------------------------------- for my $q (@queries) { my $items; print "requesting ", $q->{selecttype} eq 'one' ? "entry #$q->{itemid}" : $q->{selecttype} eq 'day' ? "date $q->{year}-$q->{month}-$q->{day}" : "something", ": "; # kludge this because it failed on me. ideally everything should # retry. my $tries = 5; while($tries--) { $items = $jo->get($q); last if response_is_good($items); print "retry($tries)... "; sleep DELAY; } die "gave up" if $tries == 0; my $events = parse_lj_events($items); print "got ", scalar keys %$events, "\n"; sleep DELAY; for my $event (values %$events) { next if $opts{e} && $event->{security} && $event->{security} ne $opts{e}; next if $opts{m} && $event->{allowmask} && $event->{allowmask} != $opts{m}; &$event_action($event); } } # -------------------------------------------------------------------- sub security_set { my ($event, $mask) = @_; if ($mask =~ /^0x/i) { $mask = hex($mask); } printf '%8d [%s]: mask %#08lx -> %#08lx...' => cook_itemid($event), $event->{eventtime}, $event->{allowmask} || 0, $mask; $event->{security} = 'usemask'; $event->{allowmask} = $mask; my $edit = $jo->edit($event); if (response_is_good($edit)) { print " ok.\n"; } else { print " failed.\n"; } } sub security_or { my ($event, $mask) = @_; if ($mask =~ /^0x/i) { $mask = hex($mask); } printf '%8d [%s]: mask %#08lx -> %#08lx...' => cook_itemid($event), $event->{eventtime}, $event->{allowmask} || 0, $event->{allowmask} | $mask; $event->{security} = 'usemask'; $event->{allowmask} |= $mask; my $edit = $jo->edit($event); if (response_is_good($edit)) { print " ok.\n"; } else { print " failed.\n"; } } sub security_and { my ($event, $mask) = @_; if ($mask =~ /^0x/i) { $mask = hex($mask); } printf '%8d [%s]: mask %#08lx -> %#08lx...' => cook_itemid($event), $event->{eventtime}, $event->{allowmask} || 0, $event->{allowmask} & $mask; $event->{security} = 'usemask'; $event->{allowmask} &= $mask; my $edit = $jo->edit($event); if (response_is_good($edit)) { print " ok.\n"; } else { print " failed.\n"; } } sub security_print { my ($event) = @_; printf '%8d [%s]: mask %#08lx' . "\n" => cook_itemid($event), $event->{eventtime}, $event->{allowmask} || 0; } sub security_fix { my ($event) = @_; my $newmask; if (!defined $event->{allowmask} || $event->{allowmask} == 0) { $newmask = 0x08000000; # was_public } elsif ($event->{allowmask} == 1) { $newmask = 0x10000000; # was_friends } elsif ($event->{allowmask} == 0x08000000 || $event->{allowmask} == 0x10000000 || $event->{allowmask} == 0x04000000) { $newmask = 0; # skip it } else { $newmask = 0x04000000; # was_custom } printf '%8d [%s]: mask %#08lx -> %#08lx...' => cook_itemid($event), $event->{eventtime}, $event->{allowmask} || 0, $newmask || $event->{allowmask}; if ($newmask == 0) { print " skipping.\n"; return; } $event->{security} = 'usemask'; $event->{allowmask} = $newmask; my $edit = $jo->edit($event); if (response_is_good($edit)) { print " ok.\n"; } else { print " failed.\n"; } } # in: ugly hashref returned by getitems mode # out: hashref keyed by itemid to hashrefs suitable for editevents sub parse_lj_events { my ($raw_items) = @_; my (%parsed_items, %events); for my $item (keys %$raw_items) { if ($item =~ /(\w+)_(\d+)_(.*)/) { $parsed_items{$1}[$2 - 1]{$3} = $raw_items->{$item}; } } # bloody hell, why can't the server parse the date? for my $item (@{$parsed_items{events}}) { for my $prop (keys %$item) { if ($prop eq 'eventtime' && $item->{$prop} =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/) { $events{$item->{itemid}}->{eventtime} = $item->{$prop}; $events{$item->{itemid}}->{year} = $1; $events{$item->{itemid}}->{mon} = $2; $events{$item->{itemid}}->{day} = $3; $events{$item->{itemid}}->{hour} = $4; $events{$item->{itemid}}->{min} = $5; $events{$item->{itemid}}->{sec} = $6; } else { # i'm not exactly sure what the value of sending back # only the event text cooked is. oh well, hooray for # special cases ... $events{$item->{itemid}}->{$prop} = $prop eq 'event' ? $item->{$prop} : $lj->escape($item->{$prop}); } } } # i never met a parsed item i didn't like. for my $meta (@{$parsed_items{prop}}) { $events{$meta->{itemid}}->{"prop_$meta->{name}"} = $lj->escape($meta->{value}); } return \%events; } sub uncook_itemid { my ($itemid) = @_; return int($itemid / 256); } sub cook_itemid { my ($event) = @_; return $event->{itemid} * 256 + $event->{anum}; } sub response_is_good { my ($res, $what) = @_; return defined $res->{success} && $res->{success} eq "OK"; } sub die_usage { print "securitytool.pl: must select entry/entries with one of:\n"; print " -i itemid (a single entry)\n"; print " -d date (entries on a particular date)\n"; print " -f startdate -t enddate (entries within a range of dates)\n"; print " -A (all entries in journal)\n"; print "optionally, narrow to entries with a certain mask:\n"; print " -e sec (existing security)\n"; print " -m mask (existing mask)\n"; print "and an action to perform with one of:\n"; print " -p (print existing security mask)\n"; print " -s mask (set security mask)\n"; print " -o mask (OR with existing security mask)\n"; print " -a mask (AND with existing security mask)\n"; print " -F (decklin's custom fix)\n"; exit 1; }