#!/usr/bin/perl

=head1 NAME

lj-community-search.pl

=head1 AUTHOR

dimio (http://dimio.org)

=head1 DESCRIPTION

A simple tool to search for livejournal communities.
It allows you to find the posts of this author in these communities.

=head1 SYNOPSYS

See usage() subroutine for usage summary.

=head1 SEE ALSO

 http://dimio.org/?p=1448
 http://dimio.org/lj-tools
 https://github.com/dimio/libperl-lj-light

=cut

BEGIN {
    unshift @INC, '/home/dimiomnk/perl/usr/lib/perl5/libperl-lj-light/lib';
}

require 5.008_008;
use warnings;
use strict;
use utf8;
use FindBin qw ($Bin $Script);
use LWP::UserAgent;
use LJ::Light;

our $VERSION = '0.19';    # 2016-03-10

# set 'print' like a 'say'
$\ = $/;

usage() if !@ARGV;

my $options = {
    postername      => shift,
    show_posts_cnt  => shift,    # 50 max
    communities_cnt => shift,    # max or -1 for unlim
    communities     => shift,
};
my $re_ljusername = qr/^[a-z0-9][-\w]{1,15}[a-z0-9]$/i;
$options->{postername} =~ $re_ljusername or die "Bad username: \"$options->{postername}\"";
$options->{show_posts_cnt} =~ m#^\d{1,2}$#
    or warn "Bad posts count: \"$options->{show_posts_cnt}\"";
$options->{show_posts_cnt} = 50 if $options->{show_posts_cnt} > 50;
$options->{communities} =~ m#^[-,\w\s]{1,5000}$# or die 'Bad community name or communities list is too long!';
my @communities = split( /[,\s]{1,2}/, delete $options->{communities} );
if ( scalar @communities > $options->{communities_cnt} && $options->{communities_cnt} != '-1' ) {
    splice( @communities, $options->{communities_cnt} );
}

my $prefs = {
    flat_url     => 'http://www.livejournal.com/interface/flat',
    base_url     => 'http://www.livejournal.com/',
    userinfo_url => 'http://www.livejournal.com/userinfo.bml?user=',

    conf_file => "$Bin/lj-community-search.conf",
    login     => '',
    hpassword => '',                                # md5 hash
};
( $prefs->{login}, $prefs->{hpassword} ) = read_config( $prefs->{conf_file} );
if ( !$prefs->{login} or !$prefs->{hpassword} ) {
    print 'Login or password for LJ account not found. See usage:', $/;
    usage();
}

my $ua = LWP::UserAgent->new(
    agent   => "LJ Search Client v. $VERSION",
    timeout => 360,
);

my $lj_client = LJ::Light->new(
    login => $prefs->{login},
    hpass => $prefs->{hpassword},
    ua    => $ua,
);

my $user_info = $lj_client->get_userinfo( $options->{postername}, [ 'id', ] );

my $results
    = result_make_raw( \@communities, $lj_client, $options->{show_posts_cnt}, $user_info->{id}, );

my $results_formatted = result_make_formatted($results);

results_print_formatted($results_formatted);

exit;

=head1 SUBROUTINES

=over

=item usage()

Printing usage for this script.

=cut

sub usage {
    print <<EOF;
$Script ver. $VERSION, dimio (dimio.org, more on http://dimio.org/?p=1448)

Config - see (or create) "lj-community-search.conf" in the script "$Bin" directory.
Config ctructure:
lj_login
lj_password

Password for LJ account can be replaced by md5 hash of password.

USAGE:
perl $Script <postername> <show_posts_cnt> <communities_cnt> <communities>

All options are required!
postername      - username of LJ user for which to search posts
show_posts_cnt  - count of posts per community to get (50 max)
communities_cnt - max count of processed communities (-1 for unlim)
communities     - list of communities for search posts (separated by commas)

EXAMPLE:
perl $Script "dimio-blog" "3" "-1" "ru-auto,ru-perl,ru-kino"
EOF
    exit;
}

=item read_config( $conf_file_path )

Read the config file with login data.
File should contain two lines:
login
password (or hpassword - md5 hexdigest from password)

=cut

sub read_config {
    my $conf = shift;

    my ( $login, $pass );

    open( my $fh, '<', $conf )
        or die "Can't open config '$conf': $!";

    my @login_pass = <$fh>;

    close($fh)
        or warn "Can't close config '$conf': $!";

    chomp @login_pass;

    return @login_pass;
}

=item results_print_formatted( $results_formatted )

Print the results in format:
COMMUNITY_NAME_N1
No posts in community_name_N1

COMMUNITY_NAME_N2
dates of posts
links to posts found

etc...

=cut

sub results_print_formatted {
    my $results = shift;

    foreach my $community ( keys %$results ) {
        print uc($community);

        if ( !keys %{ $results->{$community} } ) {
            print 'No posts in ', $community;
            { local $\; print $/; }
            next;
        }

        foreach my $date ( sort { $b cmp $a } keys %{ $results->{$community} } ) {
            my @year_month_day = split( '-', $date );
            print join( '-', reverse @year_month_day );

            # print url's for specified date
            print join( $/, @{ $results->{$community}->{$date} } );
        }

        { local $\; print $/; }
    }
}

=item result_make_formatted( $results_raw )

 Get results raw, convert it to format:
 {
    community_name_N =>
                        {
                            date =>
                                    [
                                        post_url_1, .. , post_url_N,
                                    ]
                        }
 }

=cut

sub result_make_formatted {
    my $results_raw = shift;

    my $results_formatted = {};

    foreach my $community ( keys %$results_raw ) {

        # save empty hash with community name for 'no posts' server response
        $results_formatted->{$community} = {} if !$results_formatted->{$community};

        foreach my $event_num ( keys %{ $results_raw->{$community} } ) {

            foreach ( keys %{ $results_raw->{$community}->{$event_num} } ) {
                my $date = delete $results_raw->{$community}->{$event_num}
                    ->{ 'events_' . $event_num . '_eventtime' };
                next if !$date;

                # del posting time from date
                $date =~ s/\s[\w:]+$//;

                my $url = delete $results_raw->{$community}->{$event_num}
                    ->{ 'events_' . $event_num . '_url' };

                $results_formatted->{$community}->{$date} = []
                    if !$results_formatted->{$community}->{$date};

                push( @{ $results_formatted->{$community}->{$date} }, $url );

                # experimental push in array ref
                #push( $results_formatted->{$date}, $url );
            }
        }
    }
    return $results_formatted;
}

=item result_make_raw(
    \@communities, $lj_client, $lj_events, $show_posts_cnt, $poster_id,
)

 Make raw results in format:
 {
    community_name_N =>
                        {
                            event_number =>
                                            {
                                                post_date   => date time,
                                                post_url    => url,
                                            }
                        }
 }

=back
=cut

sub result_make_raw {
    my $communities    = shift;
    my $lj_client      = shift;
    my $show_posts_cnt = shift;
    my $poster_id      = shift;

    my $results = {};

    foreach my $community (@$communities) {
        next if exists $results->{$community};

        $community =~ s/\s//g;
        $community =~ $re_ljusername or die "Bad community name: \"$community\"";

        # save empty hash with community name for 'no posts' server response
        #$results->{$community} = {} if !$results->{$community};
        $results->{$community} = {};

        my $entries = $lj_client->events->get_entries_by_poster(
            show_posts_cnt   => $show_posts_cnt,
            target_community => $community,
            poster           => $poster_id,
            auth             => $lj_client->auth->challenge,
        );

        foreach my $entry ( keys %$entries ) {
            if ( $entry =~ m{^(events_(\d+)_eventtime)$} ) {
                $results->{$community}->{$2}->{$1} = $entries->{$entry};
            }
            if ( $entry =~ m{^(events_(\d+)_url)$} ) {
                $results->{$community}->{$2}->{$1} = $entries->{$entry};
            }
        }
        #undef $entries;
    }
    return $results;
}