]> git.99rst.org Git - irssi-scripts.irssi.org.git/commitdiff
twitch_notify: init at 0.0.1
author= <redacted>
Sun, 30 Aug 2020 01:41:38 +0000 (21:41 -0400)
committer= <redacted>
Sun, 30 Aug 2020 17:22:24 +0000 (13:22 -0400)
scripts/twitch_notify.pl [new file with mode: 0644]

diff --git a/scripts/twitch_notify.pl b/scripts/twitch_notify.pl
new file mode 100644 (file)
index 0000000..381bfed
--- /dev/null
@@ -0,0 +1,241 @@
+#
+# Usage:
+# * /set twitch_channels "channel1 channel2 channel3"
+# * /twitch_online to see which channels are online
+#
+# On load:
+# * refreshes online channel list every minute
+# * prints changes to the (status) window
+#
+
+use strict;
+use warnings;
+
+use English qw( -no_match_vars );
+use HTTP::Tiny;
+use IO::Handle;
+use IPC::Open3;
+use Irssi;
+use JSON::PP;
+use POSIX ();
+
+our $VERSION = '0.0.1';
+our %IRSSI   = (
+    authors     => 'leocp1',
+    name        => 'twitch_notify',
+    description => 'Notify when a twitch channel comes online'
+        . 'Uses Twitch v5 API',
+    license => 'Public Domain',
+);
+
+## no critic (ProhibitConstantPragma) since Readonly is not in core perl
+
+# youtube-dl's Client ID
+use constant CLIENTID => 'kimne78kx3ncx6brgo4mv6wki5h1ko';
+
+# values for channel map
+use constant {
+    OFFLINE   => 0,
+    ONLINE    => 1,
+    WASONLINE => 2,
+};
+
+# How long between refreshing live channels in ms
+use constant UPDATETIMEOUT => 60_000;
+## use critic
+
+our %CHANNELS;
+our $READHANDLE;
+our $WRITEHANDLE;
+our $REFRESHTAG;
+our $UPDATETAG;
+
+## no critic (RequireArgUnpacking)
+sub uniq {
+    my %seen;
+    return grep { !$seen{$_}++ } @_;
+}
+## use critic
+
+sub get_chan_names {
+    return uniq( map {lc}
+            ( split q{ }, Irssi::settings_get_str('twitch_channels') ) );
+}
+
+sub call_api {
+    my ($resource)     = @_;
+    my $endpointprefix = 'https://api.twitch.tv/kraken/';
+    my $jsonresp       = undef;
+    use Symbol 'gensym';
+    if ( not HTTP::Tiny::can_ssl() ) {
+        my $pid = open3(
+            my $curl_in, my $curl_out, my $curl_err = gensym,
+            'curl',      '-f', '-L',
+            '--request', 'GET',
+            '-H', 'Accept:application/vnd.twitchtv.v5+json; charset=UTF-8',
+            '-H', 'Client-ID:' . CLIENTID,
+            $endpointprefix . $resource
+        );
+        waitpid $pid, 0;
+        if ( $CHILD_ERROR == 0 ) {
+            $jsonresp = do {
+                local $INPUT_RECORD_SEPARATOR = undef;
+                <$curl_out>;
+            };
+        }
+    }
+    else {
+        my $http = HTTP::Tiny->new;
+        my $resp = $http->get(
+            "$endpointprefix" . $resource,
+            {   headers => {
+                    'Client-ID' => CLIENTID,
+                    'Accept' =>
+                        'application/vnd.twitchtv.v5+json; charset=UTF-8'
+                }
+            }
+        );
+        if ( $resp->{success} ) {
+            $jsonresp = $resp->{content};
+        }
+    }
+    return ($jsonresp);
+}
+
+## no critic (ProhibitConstantPragma)
+sub live_channels {
+    my (@names) = @_;
+    my @channels = ();
+    use constant MAXUSERS   => 100;
+    use constant MAXSTREAMS => 25;
+    while ( my @name_slice = splice @names, 0, MAXUSERS ) {
+        my $users = join q{,}, @name_slice;
+        my @ids = eval {
+            my ($users_resp) = call_api( 'users?login=' . $users );
+            map { $_->{_id} } @{ decode_json($users_resp)->{users} };
+        };
+        while ( my @id_slice = splice @ids, 0, MAXSTREAMS ) {
+            my $streams = join q{,}, @id_slice;
+            my @live_chans = eval {
+                my ($streams_resp)
+                    = call_api( 'streams?channel=' . $streams );
+                map { $_->{channel}->{name} }
+                    @{ decode_json($streams_resp)->{streams} };
+            };
+            push @channels, @live_chans;
+        }
+    }
+    return @channels;
+}
+## use critic
+
+sub update {
+    my $pid = fork;
+    if ( not defined $pid ) {
+        return;
+    }
+    if ( $pid > 0 ) {
+        Irssi::pidwait_add($pid);
+        return;
+    }
+
+    my @live_chan_names = eval { live_channels( get_chan_names() ); };
+
+    foreach (@live_chan_names) {
+        say {$WRITEHANDLE} $_
+            or _msg_warn("Error writing to twitch_notify pipe: $ERRNO");
+    }
+
+    say {$WRITEHANDLE} q{+}
+        or _msg_warn("Error writing to twitch_notify pipe: $ERRNO");
+
+    close $READHANDLE
+        or _msg_warn("Error closing twitch_notify read pipe: $ERRNO");
+    close $WRITEHANDLE
+        or _msg_warn("Error closing twitch_notify write pipe: $ERRNO");
+    POSIX::_exit(1);
+    return;
+}
+
+sub notify {
+    for my $chan ( keys %CHANNELS ) {
+        if ( $CHANNELS{$chan} == ONLINE ) {
+            $CHANNELS{$chan} = WASONLINE;
+        }
+    }
+    while ( chomp( my $chan = <$READHANDLE> ) ) {
+        last if $chan eq q{+};
+        if ( not defined $CHANNELS{$chan} or $CHANNELS{$chan} != WASONLINE ) {
+            _msg_status("https://twitch.tv/$chan is now online.");
+        }
+        $CHANNELS{$chan} = ONLINE;
+    }
+
+    for my $chan ( keys %CHANNELS ) {
+        if ( $CHANNELS{$chan} == WASONLINE ) {
+            $CHANNELS{$chan} = OFFLINE;
+            _msg_status("https://twitch.tv/$chan is now offline.");
+        }
+    }
+    return;
+}
+
+sub online {
+    my @chans = get_chan_names();
+    _msg('The following channels are online:');
+    foreach my $chan (@chans) {
+        if ( defined $CHANNELS{$chan} and $CHANNELS{$chan} == ONLINE ) {
+            _msg("* https://twitch.tv/$chan");
+        }
+    }
+    return;
+}
+
+sub _msg {
+    my ($msg) = @_;
+    my $win = Irssi::active_win();
+    $win->print( $msg, Irssi::MSGLEVEL_CLIENTCRAP );
+    return;
+}
+
+sub _msg_status {
+    my ($msg) = @_;
+    my $win = Irssi::window_find_name('(status)');
+    $win->print( $msg, Irssi::MSGLEVEL_CLIENTCRAP );
+    return;
+}
+
+sub _msg_warn {
+    my ($msg) = @_;
+    my $win = Irssi::active_win();
+    $win->print( $msg, Irssi::MSGLEVEL_CLIENTERROR );
+    return;
+}
+
+sub load {
+    Irssi::settings_add_str( 'twitch_channel_notification',
+        'twitch_channels', q{} );
+    pipe $READHANDLE, $WRITEHANDLE;
+    $WRITEHANDLE->autoflush();
+    $REFRESHTAG
+        = Irssi::input_add( fileno($READHANDLE), Irssi::INPUT_READ, \&notify,
+        q{} );
+    update();
+    $UPDATETAG = Irssi::timeout_add( UPDATETIMEOUT, \&update, q{} );
+    Irssi::command_bind( 'twitch_online', \&online );
+    return;
+}
+
+sub UNLOAD {
+    Irssi::command_unbind( 'twitch_online', \&online );
+    close $READHANDLE
+        or _msg_warn("Error closing twitch_notify pipe: $ERRNO");
+    close $WRITEHANDLE
+        or _msg_warn("Error closing twitch_notify pipe: $ERRNO");
+    input_remove($REFRESHTAG);
+    timeout_remove($UPDATETAG);
+    return;
+}
+
+load();
+1;
git clone https://git.99rst.org/PROJECT