1package Plagger::Feed; 2use strict; 3 4use base qw( Plagger::Thing ); 5__PACKAGE__->mk_accessors(qw( link url image language tags meta type source_xml aggregator )); 6__PACKAGE__->mk_text_accessors(qw( description author title )); 7__PACKAGE__->mk_date_accessors(qw( updated )); 8 9use Digest::MD5 qw(md5_hex); 10use URI; 11use Plagger::Util; 12use Scalar::Util qw(blessed); 13 14sub new { 15 my $class = shift; 16 bless { 17 meta => {}, 18 tags => [], 19 entries => [], 20 type => 'feed', 21 }, $class; 22} 23 24sub add_entry { 25 my($self, $entry) = @_; 26 push @{ $self->{entries} }, $entry; 27} 28 29sub delete_entry { 30 my($self, $entry) = @_; 31 my @entries = grep { $_ ne $entry } $self->entries; 32 $self->{entries} = \@entries; 33} 34 35sub entries { 36 my $self = shift; 37 wantarray ? @{ $self->{entries} } : $self->{entries}; 38} 39 40sub count { 41 my $self = shift; 42 scalar @{ $self->{entries} }; 43} 44 45sub id { 46 my $self = shift; 47 $self->{id} = shift if @_; 48 $self->{id} || $self->url || $self->link; 49} 50 51sub id_safe { 52 my $self = shift; 53 Plagger::Util::safe_id($self->id); 54} 55 56sub title_text { 57 my $self = shift; 58 $self->title ? $self->title->plaintext : undef; 59} 60 61sub sort_entries { 62 my $self = shift; 63 64 # xxx reverse chron only, using Schwartzian transform 65 my @entries = map { $_->[1] } 66 sort { $b->[0] <=> $a->[0] } 67 map { [ $_->date || DateTime->from_epoch(epoch => 0), $_ ] } $self->entries; 68 69 $self->{entries} = \@entries; 70} 71 72sub clear_entries { 73 my $self = shift; 74 $self->{entries} = []; 75} 76 77sub dedupe_entries { 78 my $self = shift; 79 80 # this logic breaks ordering of entries, to be sorted using sort_entries 81 82 my(%seen, @entries); 83 for my $entry ($self->entries) { 84 push @{ $seen{$entry->permalink} }, $entry; 85 } 86 87 for my $permalink (keys %seen) { 88 my @sorted = _sort_prioritize($permalink, @{ $seen{$permalink} }); 89 push @entries, $sorted[0]; 90 } 91 92 $self->{entries} = \@entries; 93} 94 95sub _sort_prioritize { 96 my($permalink, @entries) = @_; 97 98 # use domain match, date and full-content-ness to prioritize source entry 99 # TODO: Date vs Full-content check should be user configurable 100 101 my $now = time; 102 return 103 map { $_->[0] } 104 sort { $b->[1] <=> $a->[1] || $b->[2] <=> $a->[2] || $b->[3] <=> $a->[3] || $b->[4] <=> $a->[4] } 105 map { [ 106 $_, # Plagger::Entry for Schwartzian 107 _is_same_domain($permalink, $_->source->url), # permalink and $feed->url is the same domain 108 _is_same_domain($permalink, $_->source->link), # permalink and $feed->link is the same domain 109 ($_->date ? ($now - $_->date->epoch) : 0), # Older entry date is prioritized 110 length($_->body || ''), # Prioritize full content feed 111 ] } @entries; 112} 113 114sub _is_same_domain { 115 my $u1 = URI->new($_[0]); 116 my $u2 = URI->new($_[1]); 117 118 return 0 unless $u1->can('host') && $u2->can('host'); 119 return lc($u1->host) eq lc($u2->host); 120} 121 122sub primary_author { 123 my $self = shift; 124 $self->author || do { 125 # if all entries are authored by the same person, use him/her as primary 126 my %authors = map { defined $_->author ? ($_->author => 1) : () } $self->entries; 127 my @authors = keys %authors; 128 @authors == 1 ? $authors[0] : undef; 129 }; 130} 131 1321; 133