--- /dev/null
+#!/usr/bin/perl -w
+
+use v5.36;
+use strict;
+
+use Y::AoC qw(grey);
+use Y::AoC::UA;
+
+use Mojo::UserAgent -signatures;
+use Mojo::Util qw(getopt);
+use Time::ParseDate;
+
+my $start = '6:00:02';
+
+getopt
+ 'd|day=i' => \my $day,
+ 'y|year=i' => \my $year;
+
+$year //= Y::AoC::year;
+my $sleep;
+
+if (!$day) {
+ my $now = time;
+ my @now = localtime($now);
+ if ($now[4] == 11 && $now[2] >= 5 && $now[2] < 7) {
+ $day = $now[3];
+ if ($now[2] == 5 && $now[1] >= 50) {
+ my $then = parsedate($start);
+ $sleep = $then - $now;
+ }
+ }
+}
+
+die "Use -d day command-line switch.\n"
+ if !$day;
+
+my $task = 2*$day-1;
+my $url = "https://adventofcode.com/$year/day/$day/input";
+my $dest = $task.'in.txt';
+
+if ($sleep) {
+ say "Sleeping for $sleep s till $start before downloading\n",
+ "$url to $dest";
+ sleep $sleep;
+}
+say "Downloading $url to $dest";
+
+my $data = Y::AoC::UA::request($url, { body => 1 });
+Mojo::File->new($dest)->spurt($data);
+say grey('==================================================');
+print $data;
+say grey('==================================================');
+
+say "lines words chars";
+system 'wc', $dest;
+
+$url =~ s/\/input\z//;
+$dest = $task.'test.txt';
+
+my $tst = Y::AoC::UA::request($url, { cache_to => "task-$year-$day.html" })
+ ->find('main > article > pre > code')->first->all_text;
+Mojo::File->new($dest)->spurt($tst);
+say "\n", grey("also downloaded $dest"), "\n";
--- /dev/null
+#!/usr/bin/perl
+
+use v5.36;
+use warnings;
+use strict;
+
+use Time::HiRes qw(sleep);
+use Linux::Inotify2;
+use POSIX qw(strftime);
+use Mojo::File;
+
+use Y::AoC qw(grey red);
+
+my $cmd = shift;
+my $backup = "backup/$cmd";
+$cmd = "./$cmd" if $cmd !~ /\//;
+
+my $last_backup;
+while (1) {
+ my $b = "$backup-".strftime("%H-%M-%S", localtime(time));
+ system 'cp', $cmd, $b;
+ say grey("\nrunning $cmd @ARGV... ============================");
+ system $cmd, @ARGV;
+ if ($?) {
+ say grey("FAILED: $?");
+ } else {
+ say grey("finished OK");
+ }
+
+ my $inotify = Linux::Inotify2->new;
+ $inotify->watch($cmd, IN_MODIFY);
+ say grey("\nWaiting for modification of $cmd ...");
+ $inotify->read;
+ sleep 0.1;
+}
--- /dev/null
+export PERL5LIB="/home/kas/aoc/lib:$PERL5LIB"
+export PS1="AoC\$ "
+ulimit -v 35500000
--- /dev/null
+package Y::AoC;
+
+use v5.36;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(red grey white yellow day year);
+
+use FindBin qw($Bin);
+use Term::ANSIColor;
+
+sub red { colored(['bright_red on_black'], @_); }
+sub white { colored(['bright_white on_black'], @_); }
+sub grey { colored(['white on_black'], @_); }
+sub yellow { colored(['bright_yellow on_black'], @_); }
+
+sub day {
+ my ($num) = $0 =~ /.*\D(\d+)/;
+ int ((1+$num)/2);
+}
+
+sub year {
+ my ($num) = $Bin =~ /.*(\d{4})/;
+ $num;
+}
+
+1;
--- /dev/null
+package Y::AoC::Task;
+
+use v5.36;
+
+use Exporter ('import');
+our @EXPORT = qw(t asay bsay);
+use Y::AoC qw(red white grey yellow day year);
+
+our $printed_err;
+$SIG{__DIE__} = sub($msg) {
+ $msg =~ s/\A(.*?)( at \S+ )(line \d+)/red($1).$2.white($3)/e
+ if -t STDERR && !$printed_err++;
+ say STDERR $msg;
+ die "\n";
+};
+
+$SIG{__WARN__} = sub($msg) {
+ $msg =~ s/\A(.*?)( at \S+ )(line \d+)/red($1).$2.white($3)/e
+ if -t STDERR && !$printed_err++;
+ say STDERR $msg;
+};
+
+our $in_test;
+sub t($subtest = ()) {
+ $subtest //= '';
+ $ARGV[0] =~ s/in\.txt/test$subtest.txt/;
+ $in_test = 1;
+}
+
+sub asay(@msg) {
+ try_submit(1, @msg);
+}
+
+sub bsay(@msg) {
+ try_submit(2, @msg);
+}
+
+sub try_submit($part, @msg) {
+ my $msg = join($, // '', @msg);
+ my $ans;
+ $msg =~ s/(\w+)\z/white($ans = $1)/e;
+
+ say $msg;
+
+ return if $in_test;
+
+ my $day = day;
+ my $year = year;
+
+ my $url = "https://adventofcode.com/$year/day/$day/answer";
+ my $cachefile = "ans-$year-$day-$part-$ans.html";
+ local $| = 1;
+ print "\nSubmit $url\nlevel=", white($part), ' answer=',
+ white($ans), ' ? [Enter]/[Ctrl-C]: ';
+
+ scalar <STDIN>;
+ eval '{
+ local $SIG{__DIE__};
+ require Y::AoC::UA;
+ }';
+ my $res = Y::AoC::UA::request($url, {
+ form => {
+ answer => $ans,
+ level => $part,
+ },
+ cache_to => $cachefile,
+ });
+
+ my $art = $res->find('main > article > p')->join("\n");
+ if (!$art) {
+ say $res->to_string;
+ return;
+ }
+
+ $art =~ s/'/'/g;
+ $art =~ s/(?<=That's )not(?= the right answer)/red($&)/e;
+ my $ok = $art =~ s/(?<=That's the )(right answer)/yellow($&)/e;
+ $art =~ s/(gold stars?)/yellow($&)/e;
+ $art =~ s/(silver star)/grey($&)/e;
+ Y::AoC::UA::cache_del($cachefile)
+ if $msg =~ s/(?<=Please wait ).*?(?= before trying again)/white($&)/e;
+ $art =~ s/(?<=your answer is )([^\.;]+)/red($&)/e;
+ $art =~ s/<code>([^<]+)<\/code>/white($1)/e;
+ $art =~ s/<[^>]+>//g;
+
+ if ($ok) {
+ system "cp $0 backup/$0-ok-$ans-$part";
+ }
+
+ say $art;
+}
+
+1;
+__END__
+package Y::AoC::UA;
+
+sub new($class, $year, $day) {
+ eval 'require
+}
+
+sub get($ua) {
+1;
--- /dev/null
+package Y::AoC::UA;
+
+use v5.36;
+
+use Mojo::Base -signatures;
+use Mojo::UserAgent;
+use Mojo::DOM;
+use Y::AoC qw(white);
+
+our $user_agent = 'kas@yenya.net https://www.fi.muni.cz/~kas/git/aoc.git';
+our $cache_dir = '/home/kas/aoc/cache';
+our $cookie;
+
+sub request($url, $args) {
+ chomp($cookie //= Mojo::File->new("$cache_dir/cookie")->slurp);
+
+ my ($cache, $cachefile);
+ if ($args->{cache_to}) {
+ $cachefile = $cache_dir . '/' . $args->{cache_to};
+ $cache = Mojo::File->new("$cachefile");
+
+ if ($args->{max_age}) {
+ $cache->remove
+ if $cache->stat
+ && time - $cache->stat->mtime
+ > $args->{max_age};
+ }
+
+ if ($cache->stat) {
+ say "\n", white('cached'),
+ " response from $cachefile";
+ return Mojo::DOM->new($cache->slurp);
+ }
+ }
+
+ my $res;
+ my %hdrs = (
+ Cookie => $cookie,
+ 'User-Agent' => $user_agent,
+ );
+ my $ua = Mojo::UserAgent->new;
+ if ($args->{form}) {
+ $res = $ua->post($url => \%hdrs => form => $args->{form})
+ ->result;
+ } else {
+ $res = $ua->get($url => \%hdrs)->result;
+ }
+
+ if (!$res->is_success) {
+ say $res->message;
+ say "body:\n", $res->body;
+ die red("http request failed");
+ }
+
+ if ($cache) {
+ $cache->spurt($res->body);
+ # say "Stored response to $cachefile";
+ }
+ return $args->{body} ? $res->body : $res->dom;
+}
+
+sub cache_del($file) {
+ my $f = Mojo::File->new("$cache_dir/$file");
+ return if !$f->stat;
+ $f->move_to("$cache_dir/old-$file");
+}
+
+sub is_cached($file) { Mojo::File->new("$cache_dir/$file")->stat; }
+
+1;
+__END__
+package Y::AoC::UA;
+
+sub new($class, $year, $day) {
+ eval 'require
+}
+
+sub get($ua) {
+1;