summaryrefslogtreecommitdiff
path: root/foostats.pl
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-30 01:22:17 +0300
committerPaul Buetow <paul@buetow.org>2025-06-30 01:22:17 +0300
commit0d7aff848958701865f05b6badac28b2a3e06492 (patch)
tree4e8abc508bf8b9b43904f67c9c3cf6f9c5aea306 /foostats.pl
parent5a0c0ef55bc9e848aa212321387710f7247d4e53 (diff)
feat: Move all .gmi reports to gemtext subfolder
- Modified daily report generation to write to stats/gemtext/ - Modified 30-day report generation to write to stats/gemtext/ - Added mkdir calls to create gemtext directory if it doesn't exist - All .gmi files are now organized in a dedicated subfolder 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'foostats.pl')
-rw-r--r--foostats.pl410
1 files changed, 122 insertions, 288 deletions
diff --git a/foostats.pl b/foostats.pl
index a78ef23..b83d0db 100644
--- a/foostats.pl
+++ b/foostats.pl
@@ -828,16 +828,17 @@ package Foostats::Reporter {
my $current_month = $today->strftime('%Y%m%d');
$report_content .= "=> ./30day_summary_$current_month.gmi 30-Day Summary Report\n\n";
- my $report_path = "$stats_dir/$date.gmi";
+ # Ensure gemtext directory exists
+ my $gemtext_dir = "$stats_dir/gemtext";
+ mkdir $gemtext_dir unless -d $gemtext_dir;
+
+ my $report_path = "$gemtext_dir/$date.gmi";
say "Writing report to $report_path";
FileHelper::write( $report_path, $report_content );
}
# Generate 30-day summary report
generate_30day_report($stats_dir, %merged);
-
- # Generate yearly report
- generate_yearly_report($stats_dir, %merged);
}
sub generate_30day_report {
@@ -850,295 +851,121 @@ package Foostats::Reporter {
my $today = localtime;
my $report_date = $today->strftime('%Y%m%d');
- my $report_content = "# 30-Day Summary Report
-";
- $report_content .= "## Generated on " . $today->strftime('%Y-%m-%d') . "
-
-";
-
- # Summary section - day-to-day evolution
- $report_content .= "## Daily Summary Evolution (Last 30 Days)
-
-";
- $report_content .= "### Total Requests by Day
-
-```
-";
+ # Build report content
+ my $report_content = build_report_header($today);
+ $report_content .= build_daily_summary_section(\@dates, \%merged);
+ $report_content .= build_feed_statistics_section(\@dates, \%merged);
- my @summary_rows;
- for my $date (reverse @dates) {
- my $stats = $merged{$date};
- next unless $stats->{count};
-
- my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
- my $formatted_date = "$year-$month-$day";
-
- my $total_requests = ($stats->{count}{gemini} // 0) + ($stats->{count}{web} // 0);
- my $filtered = $stats->{count}{filtered} // 0;
- my $gemini = $stats->{count}{gemini} // 0;
- my $web = $stats->{count}{web} // 0;
- my $ipv4 = $stats->{count}{IPv4} // 0;
- my $ipv6 = $stats->{count}{IPv6} // 0;
-
- push @summary_rows, [ $formatted_date, $total_requests, $filtered, $gemini, $web, $ipv4, $ipv6 ];
- }
+ # Aggregate and add top lists
+ my ($all_hosts, $all_urls) = aggregate_hosts_and_urls(\@dates, \%merged);
+ $report_content .= build_top_hosts_section($all_hosts);
+ $report_content .= build_top_urls_section($all_urls);
- $report_content .= format_table(
- [ 'Date', 'Total', 'Filtered', 'Gemini', 'Web', 'IPv4', 'IPv6' ],
- \@summary_rows
- );
- $report_content .= "
-```
-
-";
+ # Add daily report links
+ $report_content .= build_daily_reports_links(\@dates, \%merged);
- # Feed statistics evolution
- $report_content .= "### Feed Statistics Evolution
-
-```
-";
+ # Ensure gemtext directory exists and write the 30-day report
+ my $gemtext_dir = "$stats_dir/gemtext";
+ mkdir $gemtext_dir unless -d $gemtext_dir;
- my @feed_rows;
- for my $date (reverse @dates) {
- my $stats = $merged{$date};
- next unless $stats->{feed_ips};
-
- my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
- my $formatted_date = "$year-$month-$day";
-
- push @feed_rows, [
- $formatted_date,
- $stats->{feed_ips}{'Total'} // 0,
- $stats->{feed_ips}{'Gemini Gemfeed'} // 0,
- $stats->{feed_ips}{'Gemini Atom'} // 0,
- $stats->{feed_ips}{'Web Gemfeed'} // 0,
- $stats->{feed_ips}{'Web Atom'} // 0
- ];
- }
+ my $report_path = "$gemtext_dir/30day_summary_$report_date.gmi";
+ say "Writing 30-day summary report to $report_path";
+ FileHelper::write( $report_path, $report_content );
+ }
+
+ sub build_report_header {
+ my ($today) = @_;
- $report_content .= format_table(
- [ 'Date', 'Total', 'Gem Feed', 'Gem Atom', 'Web Feed', 'Web Atom' ],
- \@feed_rows
- );
- $report_content .= "
-```
-
-";
+ my $content = "# 30-Day Summary Report\n";
+ $content .= "## Generated on " . $today->strftime('%Y-%m-%d') . "\n\n";
+ return $content;
+ }
+
+ sub build_daily_summary_section {
+ my ($dates, $merged) = @_;
- # Aggregate hosts and URLs for 30-day period
- my %all_hosts;
- my %all_urls;
+ my $content = "## Daily Summary Evolution (Last 30 Days)\n\n";
+ $content .= "### Total Requests by Day\n\n```\n";
- for my $date (@dates) {
- my $stats = $merged{$date};
- next unless $stats->{page_ips};
-
- # Aggregate hosts
- while (my ($host, $count) = each %{$stats->{page_ips}{hosts}}) {
- $all_hosts{$host} //= 0;
- $all_hosts{$host} += $count;
- }
+ my @summary_rows;
+ for my $date (reverse @$dates) {
+ my $stats = $merged->{$date};
+ next unless $stats->{count};
- # Aggregate URLs
- while (my ($url, $count) = each %{$stats->{page_ips}{urls}}) {
- $all_urls{$url} //= 0;
- $all_urls{$url} += $count;
- }
+ push @summary_rows, build_daily_summary_row($date, $stats);
}
- # Top 50 hosts
- $report_content .= "## Top 50 Hosts (30-Day Total)
-
-```
-";
-
- my @host_rows;
- my @sorted_hosts = sort { $all_hosts{$b} <=> $all_hosts{$a} } keys %all_hosts;
- @sorted_hosts = @sorted_hosts[0..49] if @sorted_hosts > 50;
-
- for my $host (@sorted_hosts) {
- push @host_rows, [ $host, $all_hosts{$host} ];
- }
-
- $report_content .= format_table(
- [ 'Host', 'Total Unique Visitors' ],
- \@host_rows
+ $content .= format_table(
+ [ 'Date', 'Total', 'Filtered', 'Gemini', 'Web', 'IPv4', 'IPv6' ],
+ \@summary_rows
);
- $report_content .= "
-```
-
-";
-
- # Top 50 URLs
- $report_content .= "## Top 50 URLs (30-Day Total)
-
-```
-";
-
- my @url_rows;
- my @sorted_urls = sort { $all_urls{$b} <=> $all_urls{$a} } keys %all_urls;
- @sorted_urls = @sorted_urls[0..49] if @sorted_urls > 50;
-
- for my $url (@sorted_urls) {
- push @url_rows, [ $url, $all_urls{$url} ];
- }
+ $content .= "\n```\n\n";
- $report_content .= format_table(
- [ 'URL', 'Total Unique Visitors' ],
- \@url_rows
- );
- $report_content .= "
-```
-
-";
-
- # Links to daily reports
- $report_content .= "## Daily Reports
-
-";
-
- for my $date (@dates) {
- next unless exists $merged{$date} && $merged{$date}->{count};
-
- my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
- my $formatted_date = "$year-$month-$day";
-
- $report_content .= "=> ./$date.gmi $formatted_date Daily Report
-";
- }
-
- # Add link to yearly report
- $report_content .= "\n## Related Reports\n\n";
- my $year = $today->strftime('%Y');
- $report_content .= "=> ./yearly_summary_$year.gmi Yearly Summary Report\n";
-
- # Write the 30-day report
- my $report_path = "$stats_dir/30day_summary_$report_date.gmi";
- say "Writing 30-day summary report to $report_path";
- FileHelper::write( $report_path, $report_content );
+ return $content;
}
- sub generate_yearly_report {
- my ( $stats_dir, %merged ) = @_;
-
- # Get all available dates
- my @all_dates = sort { $a cmp $b } keys %merged;
-
- # Filter to only dates that have data
- @all_dates = grep { exists $merged{$_} && $merged{$_}->{count} } @all_dates;
-
- return unless @all_dates; # No data available
+ sub build_daily_summary_row {
+ my ($date, $stats) = @_;
- my $today = localtime;
- my $current_year = $today->strftime('%Y');
+ my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
+ my $formatted_date = "$year-$month-$day";
- # Count total days with data
- my $total_days = scalar @all_dates;
+ my $total_requests = ($stats->{count}{gemini} // 0) + ($stats->{count}{web} // 0);
+ my $filtered = $stats->{count}{filtered} // 0;
+ my $gemini = $stats->{count}{gemini} // 0;
+ my $web = $stats->{count}{web} // 0;
+ my $ipv4 = $stats->{count}{IPv4} // 0;
+ my $ipv6 = $stats->{count}{IPv6} // 0;
- my $report_content = "# Yearly Summary Report\n";
- $report_content .= "## Generated on " . $today->strftime('%Y-%m-%d') . "\n\n";
- $report_content .= "## Report covers $total_days days of data\n\n";
+ return [ $formatted_date, $total_requests, $filtered, $gemini, $web, $ipv4, $ipv6 ];
+ }
+
+ sub build_feed_statistics_section {
+ my ($dates, $merged) = @_;
- # Monthly aggregation
- my %monthly_stats;
+ my $content = "### Feed Statistics Evolution\n\n```\n";
- for my $date (@all_dates) {
- my $stats = $merged{$date};
- next unless $stats->{count};
-
- my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
- my $month_key = "$year-$month";
-
- # Initialize monthly stats
- $monthly_stats{$month_key} //= {
- total_requests => 0,
- filtered => 0,
- gemini => 0,
- web => 0,
- ipv4 => 0,
- ipv6 => 0,
- feed_total => 0,
- gem_feed => 0,
- gem_atom => 0,
- web_feed => 0,
- web_atom => 0,
- days => 0
- };
-
- my $ms = $monthly_stats{$month_key};
- $ms->{days}++;
-
- # Aggregate counts
- $ms->{total_requests} += ($stats->{count}{gemini} // 0) + ($stats->{count}{web} // 0);
- $ms->{filtered} += $stats->{count}{filtered} // 0;
- $ms->{gemini} += $stats->{count}{gemini} // 0;
- $ms->{web} += $stats->{count}{web} // 0;
- $ms->{ipv4} += $stats->{count}{IPv4} // 0;
- $ms->{ipv6} += $stats->{count}{IPv6} // 0;
+ my @feed_rows;
+ for my $date (reverse @$dates) {
+ my $stats = $merged->{$date};
+ next unless $stats->{feed_ips};
- # Aggregate feed stats
- if ($stats->{feed_ips}) {
- $ms->{feed_total} += $stats->{feed_ips}{'Total'} // 0;
- $ms->{gem_feed} += $stats->{feed_ips}{'Gemini Gemfeed'} // 0;
- $ms->{gem_atom} += $stats->{feed_ips}{'Gemini Atom'} // 0;
- $ms->{web_feed} += $stats->{feed_ips}{'Web Gemfeed'} // 0;
- $ms->{web_atom} += $stats->{feed_ips}{'Web Atom'} // 0;
- }
- }
-
- # Summary section - monthly evolution
- $report_content .= "## Monthly Summary Evolution\n\n";
- $report_content .= "### Total Requests by Month\n\n```\n";
-
- my @summary_rows;
- for my $month (sort keys %monthly_stats) {
- my $ms = $monthly_stats{$month};
- push @summary_rows, [
- $month,
- $ms->{days},
- $ms->{total_requests},
- $ms->{filtered},
- $ms->{gemini},
- $ms->{web},
- $ms->{ipv4},
- $ms->{ipv6}
- ];
+ push @feed_rows, build_feed_statistics_row($date, $stats);
}
- $report_content .= format_table(
- [ 'Month', 'Days', 'Total', 'Filtered', 'Gemini', 'Web', 'IPv4', 'IPv6' ],
- \@summary_rows
+ $content .= format_table(
+ [ 'Date', 'Total', 'Gem Feed', 'Gem Atom', 'Web Feed', 'Web Atom' ],
+ \@feed_rows
);
- $report_content .= "\n```\n\n";
+ $content .= "\n```\n\n";
- # Feed statistics by month
- $report_content .= "### Feed Statistics by Month\n\n```\n";
+ return $content;
+ }
+
+ sub build_feed_statistics_row {
+ my ($date, $stats) = @_;
- my @feed_rows;
- for my $month (sort keys %monthly_stats) {
- my $ms = $monthly_stats{$month};
- push @feed_rows, [
- $month,
- $ms->{feed_total},
- $ms->{gem_feed},
- $ms->{gem_atom},
- $ms->{web_feed},
- $ms->{web_atom}
- ];
- }
+ my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
+ my $formatted_date = "$year-$month-$day";
- $report_content .= format_table(
- [ 'Month', 'Total', 'Gem Feed', 'Gem Atom', 'Web Feed', 'Web Atom' ],
- \@feed_rows
- );
- $report_content .= "\n```\n\n";
+ return [
+ $formatted_date,
+ $stats->{feed_ips}{'Total'} // 0,
+ $stats->{feed_ips}{'Gemini Gemfeed'} // 0,
+ $stats->{feed_ips}{'Gemini Atom'} // 0,
+ $stats->{feed_ips}{'Web Gemfeed'} // 0,
+ $stats->{feed_ips}{'Web Atom'} // 0
+ ];
+ }
+
+ sub aggregate_hosts_and_urls {
+ my ($dates, $merged) = @_;
- # Aggregate hosts and URLs for entire period
my %all_hosts;
my %all_urls;
- for my $date (@all_dates) {
- my $stats = $merged{$date};
+ for my $date (@$dates) {
+ my $stats = $merged->{$date};
next unless $stats->{page_ips};
# Aggregate hosts
@@ -1154,61 +981,68 @@ package Foostats::Reporter {
}
}
- # Top 50 hosts
- $report_content .= "## Top 50 Hosts (Yearly Total)\n\n```\n";
+ return (\%all_hosts, \%all_urls);
+ }
+
+ sub build_top_hosts_section {
+ my ($all_hosts) = @_;
+
+ my $content = "## Top 50 Hosts (30-Day Total)\n\n```\n";
my @host_rows;
- my @sorted_hosts = sort { $all_hosts{$b} <=> $all_hosts{$a} } keys %all_hosts;
+ my @sorted_hosts = sort { $all_hosts->{$b} <=> $all_hosts->{$a} } keys %$all_hosts;
@sorted_hosts = @sorted_hosts[0..49] if @sorted_hosts > 50;
for my $host (@sorted_hosts) {
- push @host_rows, [ $host, $all_hosts{$host} ];
+ push @host_rows, [ $host, $all_hosts->{$host} ];
}
- $report_content .= format_table(
+ $content .= format_table(
[ 'Host', 'Total Unique Visitors' ],
\@host_rows
);
- $report_content .= "\n```\n\n";
+ $content .= "\n```\n\n";
- # Top 50 URLs
- $report_content .= "## Top 50 URLs (Yearly Total)\n\n```\n";
+ return $content;
+ }
+
+ sub build_top_urls_section {
+ my ($all_urls) = @_;
+
+ my $content = "## Top 50 URLs (30-Day Total)\n\n```\n";
my @url_rows;
- my @sorted_urls = sort { $all_urls{$b} <=> $all_urls{$a} } keys %all_urls;
+ my @sorted_urls = sort { $all_urls->{$b} <=> $all_urls->{$a} } keys %$all_urls;
@sorted_urls = @sorted_urls[0..49] if @sorted_urls > 50;
for my $url (@sorted_urls) {
- push @url_rows, [ $url, $all_urls{$url} ];
+ push @url_rows, [ $url, $all_urls->{$url} ];
}
- $report_content .= format_table(
+ $content .= format_table(
[ 'URL', 'Total Unique Visitors' ],
\@url_rows
);
- $report_content .= "\n```\n\n";
+ $content .= "\n```\n\n";
- # Links to monthly reports
- $report_content .= "## Monthly Reports\n\n";
+ return $content;
+ }
+
+ sub build_daily_reports_links {
+ my ($dates, $merged) = @_;
- # Get list of all 30-day summary reports
- my @monthly_reports = glob "$stats_dir/30day_summary_*.gmi";
- @monthly_reports = sort { $b cmp $a } @monthly_reports;
+ my $content = "## Daily Reports\n\n";
- for my $report_path (@monthly_reports) {
- my ($date) = $report_path =~ /30day_summary_(\d{8})\.gmi$/;
- next unless $date;
+ for my $date (@$dates) {
+ next unless exists $merged->{$date} && $merged->{$date}->{count};
my ( $year, $month, $day ) = $date =~ /(\d{4})(\d{2})(\d{2})/;
my $formatted_date = "$year-$month-$day";
- $report_content .= "=> ./30day_summary_$date.gmi 30-Day Summary for $formatted_date\n";
+ $content .= "=> ./$date.gmi $formatted_date Daily Report\n";
}
- # Write the yearly report
- my $report_path = "$stats_dir/yearly_summary_$current_year.gmi";
- say "Writing yearly summary report to $report_path";
- FileHelper::write( $report_path, $report_content );
+ return $content;
}
}