diff options
author | B. Watson <urchlay@slackware.uk> | 2024-12-29 00:58:47 -0500 |
---|---|---|
committer | B. Watson <urchlay@slackware.uk> | 2024-12-29 00:58:47 -0500 |
commit | 71a4fad6b779a76bf622cb3c0d77497e507c5487 (patch) | |
tree | 89a67c297b0559500065f88f8d0063e1de0bc111 | |
parent | 9eed830f296dab257759f5276d0963467007aa6b (diff) | |
download | stupid-irssi-tricks-71a4fad6b779a76bf622cb3c0d77497e507c5487.tar.gz |
yttitle.pl: rewrite, using irssi pidwait, input_add, etc, instead of timers and waitpid
-rw-r--r-- | yttitle.pl | 164 |
1 files changed, 82 insertions, 82 deletions
@@ -1,16 +1,28 @@ #!/usr/bin/perl +# yttitle.pl + +# When someone says something with a youtube video link in it, get the +# title and say it in the channel. + +# Uses yt-dlp to do the title lookups. Works much better than the +# usual HTML-scraping, but unfortunately is *slow* (like, average 3 +# sec per lookup). + +# Since lookups take so long, they're done asynchronously, via +# fork(). This script owes a great debt to the dns.pl script +# included with irssi, which shows how to use irssi's pidwait and +# input_(add|remove). + no strict; -use POSIX ":sys_wait_h"; +use POSIX '_exit'; use Irssi qw/ signal_add_first - signal_continue - signal_register - command_bind - timeout_add_once - get_irssi_dir + pidwait_add + input_add + input_remove /; our $VERSION = "0.1"; @@ -26,24 +38,29 @@ our %IRSSI = ( # video ID => title our %cache; -our $tmpdir = get_irssi_dir . "/yttitle.tmp"; +# keep track of video IDs of in-progress lookups, so we don't spawn +# a 2nd process for the same ID. +our %in_progress; -# pid => [server, target, video ID] -our %jobs; +# TODO: the rest of these variables should be irssi settings. -# video ID keys (values meaningless; only care about key presence) -our %job_videos; +# how many jobs are currently running? +our $jobcount = 0; -# attempts to spawn more jobs than this are just ignored. +# attempts to spawn more simultaneous jobs than this are just ignored. our $maxjobs = 10; -# milliseconds: how often to check %jobs to see if any jobs are done. -our $queue_time = 1000; +# yt-dlp processes that don't respond within this many seconds +# are terminated. +our $job_timeout = 15; + +# passed to yt-dlp itself. +our $socket_timeout = 10; -# command to execute, 1st %s replaced with video ID, 2nd with tmp filename. +# command to execute, %s replaced with video ID. # --socket-timeout arg is seconds, should be longer that $timeout. our $command_fmt = - "yt-dlp -q --print '%%(title)s' --socket-timeout 10 -- %s >%s 2>/dev/null"; + "yt-dlp -q --print '%%(title)s' --socket-timeout $socket_timeout -- %s 2>/dev/null"; our $DEBUG = 1; @@ -55,35 +72,8 @@ sub debugf { Irssi::print(sprintf(@_)) if $DEBUG; } -sub start_timer { - timeout_add_once($queue_time, "check_jobs", undef); -} - -sub get_tmp_filename { - my $id = shift; - return $tmpdir . "/" . $id; -} - -sub read_tmp_file { - my $file = shift; - - open my $fh, "<:encoding(UTF-8)", $file; - - if(!$fh) { - debug("read_tmp_file() failed to open $file: $!"); - return; - } - - my $result = <$fh>; - close $fh; - - chomp $result if defined $result; - return length($result) ? $result : undef; -} - sub spawn_job { my ($server, $target, $video_id) = @_; - my $jobcount = keys %jobs; debug("spawn_job() called, video_id $video_id"); @@ -92,45 +82,76 @@ sub spawn_job { return; } - if($job_videos{$video_id}) { + if($in_progress{$video_id}) { debug("spawn_job(): video_id $video_id already in queue"); return; } - my $cmd = sprintf($command_fmt, $video_id, get_tmp_filename($video_id)); - debug("spawn_job() command is: $cmd"); + $in_progress{$video_id} = 1; + + my $cmd = sprintf($command_fmt, $video_id); + debug("spawn_job() command is: $cmd"); + + my($read, $write); + pipe($read, $write); my $pid = fork(); + if(!defined($pid)) { + debug("spawn_job(): video_id $video_id: fork() failed!"); + close $read; + close $write; + return; + } if($pid) { # parent - debug("spawn_job() forked, kid pid is $pid"); - start_timer unless keys %jobs; - $jobs{$pid} = [ $server, $target, $video_id ]; - $job_videos{$video_id} = 1; - debug("spawn_job(): job pids: " . join(",", keys %jobs)); + $jobcount++; + debug("spawn_job() forked, kid pid is $pid, jobcount now $jobcount"); + close $write; + my $pipe_args = [ $pid, $server, $target, $video_id, $read ]; + pidwait_add($pid); + $pipe_tags{$video_id} = input_add(fileno($read), INPUT_READ, \&job_done, $pipe_args); + return; } else { # child, debug() and Irssi::print don't work, here. - exec $cmd; + eval { + local $SIG{ALRM} = sub { die "alarum\n" }; + alarm $job_timeout; + print $write `$cmd`; + alarm 0; + }; + # don't bother to die() on error here, we're about to exit anyway. + if($@ && ($@ eq "alarum\n")) { + # print *something* if $cmd timed out + print $write "\n"; + } + # not sure this needs to be wrapped in eval, but... + eval { + close $write; + }; + _exit(1); } } sub job_done { - my $pid = shift; + my $pipe_args = shift; + my ($pid, $server, $target, $video_id, $read) = @$pipe_args; - my ($server, $target, $video_id) = @{$jobs{$pid}}; + debug("job_done() pid $pid, target $target, video_id $video_id, jobcount was $jobcount"); - debug("job_done() pid $pid, target $target, video_id $video_id"); + my $title = <$read>; + chomp $title; + close $read; + input_remove($pipe_tags{$video_id}); - my $file = get_tmp_filename($video_id); - my $title = read_tmp_file($file); - - unlink $file; + $jobcount--; + delete $in_progress{$video_id}; $cache{$video_id} = $title; - delete $job_videos{$video_id}; - if(!defined($title)) { + if(defined($title)) { + debug("job_done(): video_id $video_id title is $title"); + } else { debug("job_done(): video_id $video_id failed to get title"); return; } @@ -138,26 +159,6 @@ sub job_done { say_title($server, $target, $video_id, $title); } -sub check_jobs { - my @k = keys %jobs; - - debug("check_jobs(): " . @k . " jobs"); - return unless @k; - - for my $jobpid (@k) { - debug("check_jobs(): about to waitpid($jobpid, WNOHANG)"); - $pid = waitpid($jobpid, WNOHANG); - - debug("check_jobs(): jobpid $jobpid, pid $pid"); - - next if $pid == 0; # still running, let it run - job_done($jobpid) if $pid > 0; # -1 is "no such pid" - delete $jobs{$jobpid}; - } - - start_timer if keys %jobs; -} - sub say_title { my ($server, $target, $video_id, $title) = @_; my $tag = "YouTube" . ($DEBUG ? "($video_id)" : ""); @@ -190,5 +191,4 @@ sub on_public_msg { } ### main() -mkdir $tmpdir; signal_add_first("message public", "on_public_msg"); |