package BurguillosInfo::Posts; use v5.34.1; use strict; use warnings; use feature 'signatures'; use Data::Dumper; use MIME::Base64; use BurguillosInfo::Categories; use Const::Fast; use Mojo::DOM; use Path::Tiny; use DateTime::Format::ISO8601; use DateTime; use BurguillosInfo::Preview; const my $CURRENT_FILE => __FILE__; const my $ROOT_PROJECT => path($CURRENT_FILE)->parent->parent->parent; const my $PUBLIC_DIR => $ROOT_PROJECT->child('public'); const my $POSTS_DIR => $ROOT_PROJECT->child('content/posts'); my $cached_posts_by_category; my $cached_posts_by_slug; sub new { return bless {}, shift; } sub _ReturnCacheFilter ( $self, $filters = 1 ) { my %posts_by_category_filtered; my %posts_by_slug_filtered; my $iso8601 = DateTime::Format::ISO8601->new; my $current_date = DateTime->now; for my $category ( keys %$cached_posts_by_category ) { for my $post ( @{ $cached_posts_by_category->{$category} } ) { if ($filters) { my $date_post; eval { $date_post = $iso8601->parse_datetime( $post->{date} ); }; if ($@) { print $@ . ': ' . Data::Dumper::Dumper $post; } if ( $date_post > $current_date ) { next; } } $posts_by_slug_filtered{ $post->{slug} } = $post; $posts_by_category_filtered{ $category } //= []; push @{ $posts_by_category_filtered{ $category } }, $post; } } return ( \%posts_by_category_filtered, \%posts_by_slug_filtered ); } sub _GeneratePostFromFile ( $self, $post_file ) { warn "Bad file $post_file, omiting...", return if !-f $post_file || $post_file !~ /\.xml$/; my $dom = Mojo::DOM->new( $post_file->slurp_utf8 ); my $title = $dom->at(':root > title')->text or die "Missing title at $post_file."; my $author = $dom->at(':root > author')->text or die "Missing author at $post_file."; my $date = $dom->at(':root > date')->text or die "Missing date at $post_file."; my $ogdesc = $dom->at(':root > ogdesc')->text or die "Missing ogdesc at $post_file"; my @categories = $dom->find(':root > category')->map('text')->each; my $slug = $dom->at(':root > slug')->text or die "Missing slug at $post_file."; my $content_tag = $dom->at(':root > content') or die "Missing content at $post_file."; for my $tag ( $content_tag->children->each ) { $tag->content( $tag->content =~ s/\n//gr ); } if ( !scalar @categories ) { die 'Missing category'; } my $content = $content_tag->content; my $pinned_node = $dom->at(':root > pinned'); my $image_element = $dom->at(':root > img'); my $image; my $image_bottom_preview; my $attributes = $self->_GetAttributes( $post_file, $dom ); my $pinned; if ( defined $pinned_node ) { $pinned = int( $pinned_node->text ); } if ( defined $image_element ) { $image = $image_element->attr->{src}; $image_bottom_preview = $image_element->attr->{'bottom-preview'}; } my $last_modification_date_element = $dom->at(':root > last_modification_date'); my $last_modification_date; if ( defined $last_modification_date_element ) { $last_modification_date = $last_modification_date_element->content; } return { title => $title, author => $author, date => $date, ogdesc => $ogdesc, categories => [@categories], category => $categories[0], slug => $slug, content => $content, attributes => $attributes, image_bottom_preview => $image_bottom_preview, ( ( defined $last_modification_date ) ? ( last_modification_date => $last_modification_date ) : () ), ( ( defined $image ) ? ( image => $image ) : () ), ( ( defined $pinned ) ? ( pinned => $pinned ) : () ) }; } sub _GetAttributes ( $self, $post_file, $dom ) { my $attributes_tag = $dom->at(':root > attributes'); my %attributes; if ( defined $attributes_tag ) { my @attribute_list = $attributes_tag->find('attributes > attribute')->map('text')->each; %attributes = map { my $identifier = $_; ( $identifier => 1 ); } @attribute_list; } return \%attributes; } sub _GeneratePostCache ($self) { $cached_posts_by_category = {}; $cached_posts_by_slug = {}; for my $post_file ( sort { $b cmp $a } $POSTS_DIR->children ) { my $post = $self->_GeneratePostFromFile($post_file); if ( !defined $post ) { next; } my $categories = $post->{categories}; for my $category ($post->{categories}->@*) { $cached_posts_by_category->{$category} //= []; my $slug = $post->{slug}; my $category_posts = $cached_posts_by_category->{$category}; $cached_posts_by_slug->{$slug} = $post; push @$category_posts, $post; } } } sub Retrieve ( $self, $filters = 1 ) { if ( defined $cached_posts_by_category && defined $cached_posts_by_slug ) { return $self->_ReturnCacheFilter($filters); } $self->_GeneratePostCache(); return $self->_ReturnCacheFilter($filters); } my $cache_all_post_categories = {}; sub RetrieveAllPostsForCategory ( $self, $category_name ) { my $categories = BurguillosInfo::Categories->new->Retrieve; my $category = $categories->{$category_name}; if ( defined $cache_all_post_categories->{$category_name} ) { my $posts = $cache_all_post_categories->{$category_name}; return $self->shufflePostsIfRequired( $category, $posts ); } my $posts = $self->RetrieveDirectPostsForCategory($category_name); for my $child_category ( $category->{children}->@* ) { my $child_category_name = $child_category->{slug}; push @$posts, @{ $self->RetrieveDirectPostsForCategory($child_category_name) }; } @$posts = sort { DateTime::Format::ISO8601->parse_datetime( $b->{date} ) <=> DateTime::Format::ISO8601->parse_datetime( $a->{date} ) } @$posts; $cache_all_post_categories->{$category_name} = $posts; return $self->shufflePostsIfRequired( $category, $posts ); } sub comparePinned ( $self, $a, $b ) { my $cmp = $b->{pinned} <=> $a->{pinned}; if ( $cmp != 0 ) { return $cmp; } return int( rand(3) ) - 1; } sub shufflePostsIfRequired ( $self, $category, $posts ) { my $pinned_posts = [ sort { $self->comparePinned( $a, $b ) } grep { defined $_->{pinned} } @$posts ]; $posts = [ grep { !exists $_->{pinned} } @$posts ]; if ( exists $category->{random} && $category->{random} ) { require List::AllUtils; $posts = [ List::AllUtils::shuffle @$posts ]; } return [ @$pinned_posts, @$posts ]; } sub RetrieveDirectPostsForCategory ( $self, $category_name ) { my ($post_by_category) = $self->Retrieve; my $categories = BurguillosInfo::Categories->new->Retrieve; my $category = $categories->{$category_name}; if ( !defined $category ) { die "$category_name category does not exists"; } my $posts = $post_by_category->{$category_name}; $posts //= []; return $self->shufflePostsIfRequired( $category, [@$posts] ); } sub PreviewOg ( $self, $post, $isWhatsApp ) { my $title = $post->{title}; my $content = $post->{content}; my $image_file = $post->{image}; my $image_bottom_preview = $post->{image_bottom_preview}; if ($isWhatsApp) { return BurguillosInfo::Preview->WhatsappAlternativeGenerate( $title, $content, $image_file, $image_bottom_preview ); } return BurguillosInfo::Preview->Generate( $title, $content, $image_file, $image_bottom_preview ); } 1;