From 9eed830f296dab257759f5276d0963467007aa6b Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Thu, 26 Dec 2024 17:08:34 -0500 Subject: initial commit --- yttitle.pl | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 yttitle.pl (limited to 'yttitle.pl') diff --git a/yttitle.pl b/yttitle.pl new file mode 100644 index 0000000..a1aaf3a --- /dev/null +++ b/yttitle.pl @@ -0,0 +1,194 @@ +#!/usr/bin/perl + +no strict; + +use POSIX ":sys_wait_h"; + +use Irssi qw/ + signal_add_first + signal_continue + signal_register + command_bind + timeout_add_once + get_irssi_dir +/; + +our $VERSION = "0.1"; +our %IRSSI = ( + authors => 'Urchlay', + contact => 'Urchlay on Libera', + name => 'yttitle', + description => 'get titles for youtube videos using yt-dlp', + license => 'WTFPL', + url => 'none', +); + +# video ID => title +our %cache; + +our $tmpdir = get_irssi_dir . "/yttitle.tmp"; + +# pid => [server, target, video ID] +our %jobs; + +# video ID keys (values meaningless; only care about key presence) +our %job_videos; + +# attempts to spawn more 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; + +# command to execute, 1st %s replaced with video ID, 2nd with tmp filename. +# --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"; + +our $DEBUG = 1; + +sub debug { + Irssi::print(join "", @_) if $DEBUG; +} + +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"); + + if($jobcount > $maxjobs) { + debug("spawn_job(): jobcount $jobcount > maxjobs $maxjobs, ignoring request"); + return; + } + + if($job_videos{$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"); + + my $pid = fork(); + + 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)); + } else { + # child, debug() and Irssi::print don't work, here. + exec $cmd; + } +} + +sub job_done { + my $pid = shift; + + my ($server, $target, $video_id) = @{$jobs{$pid}}; + + debug("job_done() pid $pid, target $target, video_id $video_id"); + + my $file = get_tmp_filename($video_id); + my $title = read_tmp_file($file); + + unlink $file; + + $cache{$video_id} = $title; + delete $job_videos{$video_id}; + + if(!defined($title)) { + debug("job_done(): video_id $video_id failed to get title"); + return; + } + + 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)" : ""); + $server->command("msg $target $tag: $title"); +} + +sub on_public_msg { + my ($server, $msg, $nick, $address, $target) = @_; + my $mynick = $server->{nick}; + + unless(length $target) { + $target = $nick; + $nick = $mynick; + } + + if($target eq $mynick) { + # private message... send response to sender + $target = $nick; + } + + for my $video_id ($msg =~ /(?:youtube\.com\S+(?:embed\/|v=)|youtu.be\/)([-0-9a-zA-Z_]{11})/g) { + if($cache{$video_id}) { + debug("video_id $video_id found in cache"); + say_title($server, $target, $video_id, $cache{$video_id}); + } else { + debug("video_id $video_id NOT found in cache, queuing job"); + spawn_job($server, $target, $video_id); + } + } +} + +### main() +mkdir $tmpdir; +signal_add_first("message public", "on_public_msg"); -- cgit v1.2.3