]> git.99rst.org Git - irssi-scripts.irssi.org.git/commitdiff
Added script to lookup Spotify URIs
authorÖrjan Persson <redacted>
Mon, 28 Jul 2014 16:26:57 +0000 (18:26 +0200)
committerÖrjan Persson <redacted>
Mon, 28 Jul 2014 16:28:49 +0000 (18:28 +0200)
_data/scripts.yaml
scripts/spotify.pl [new file with mode: 0644]

index 037ce86d95b2df030b38997558ea552d595d5b33..83fd7b6da01907672293f9f45274e86b6c986e81 100644 (file)
   name: "splitlong"
   version: "0.20"
 
+- authors: "Örjan Persson"
+  contact: "o@42mm.org"
+  description: "Lookup spotify uris"
+  filename: "spotify.pl"
+  modified: "2014-07-28 18:26:57"
+  license: "GPLv2"
+  name: "spotify"
+  url: "https://github.com/op/irssi-spotify"
+  version: "1.0"
+
 - authors: "Marcus darix Rückert, tira, Stefan tommie Tomanek"
   contact: "darix@irssi.de, tira@isx.de, stefan@pico.ruhr.de"
   description: "prints the stats for german stocks"
diff --git a/scripts/spotify.pl b/scripts/spotify.pl
new file mode 100644 (file)
index 0000000..92f82e4
--- /dev/null
@@ -0,0 +1,682 @@
+# spotify.pl - lookup spotify resources
+#
+# Copyright (c) 2009-2014 Örjan Persson <o@42mm.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+use strict;
+use vars qw($VERSION %IRSSI);
+$VERSION = '1.0';
+%IRSSI = (
+       authors     => 'Örjan Persson',
+       contact     => 'o@42mm.org',
+       name        => 'spotify',
+       description => 'Lookup spotify uris',
+       license     => 'GPLv2',
+       url         => 'https://github.com/op/irssi-spotify'
+);
+
+use Irssi;
+
+use Encode;
+use HTTP::Request::Common;
+use HTTP::Request;
+use JSON;
+use LWP::UserAgent;
+use POSIX;
+use URI;
+
+sub cmd_spotify_help {
+       Irssi::print(<<'EOF', MSGLEVEL_CLIENTCRAP);
+SPOTIFY [-a | auto] [-l | lookup] [...]
+
+    -a, auto: see SPOTIFY AUTO
+    -l, lookup: see SPOTIFY LOOKUP
+
+Lookup spotify uris to get information on tracks, albums, artists and
+playlists. You can configure it to do automatic lookup when someone sends it in
+a channel or privately.
+
+Playlist requests require authentication. Get credentials from
+https://developer.spotify.com.
+
+/SET spotify_client_id <string>
+/SET spotify_client_secret <string>
+
+Output format is customizable.
+
+/SET spotify_header_format <string>
+    Header message above lookup result. Available variables:
+        %%uri            requested uri
+
+/SET spotify_album_format <string>
+    Format for album results. Available variables:
+        %%name           name of the album
+        %%artist         album artists
+        %%year           year the album was released
+        %%popularity     album popularity
+        %%territories    territories the album is available in
+
+/SET spotify_artist_format <string>
+    Format for artist results. Available variables:
+        %%name           name of the artist
+        %%popularity     artist popularity
+
+/SET spotify_track_format <string>
+    Format for track results. Available variables:
+        %%name           name of the track
+        %%album          name of the album
+        %%artist         track artists
+        %%popularity     track popularity
+
+/SET spotify_playlist_format <string>
+    Format for playlist results. Available variables:
+        %%name           name of the playlist
+        %%owner          name of the owner
+
+See /SPOTIFY <section> help for more information.
+
+EOF
+}
+
+sub cmd_spotify_help_lookup {
+       Irssi::print(<<'EOF', MSGLEVEL_CLIENTCRAP);
+SPOTIFY LOOKUP [-p | public] <resource>
+
+    -p, public: return result to current window
+    -h, help:   show this help
+
+Lookup the given resource and return the result. If the public
+argument is given, the result is returned to the current window.
+EOF
+}
+
+sub cmd_spotify_help_auto {
+       Irssi::print(<<'EOF', MSGLEVEL_CLIENTCRAP);
+SPOTIFY AUTO [-p | public] [-i | info] [-e | enable] [-d | disable] [-w | whitelist] [-b | blacklist]
+
+    -p, public: see SPOTIFY AUTO PUBLIC
+    -i, info: display current settings for SPOTIFY AUTO
+    -e, enable: enable automatic lookup
+    -d, disable: disable automatic lookup
+
+Configure automatic features. The public section is for
+automatic return results in the window the Spotify resource
+was sent in.
+
+Info will display settings for all automatic features and
+you can use enable or disable to turn automatic functions
+on or off.
+EOF
+}
+
+sub cmd_spotify_help_auto_public {
+       Irssi::print(<<'EOF', MSGLEVEL_CLIENTCRAP);
+SPOTIFY AUTO PUBLIC [OPTION...]
+
+    -a, add: add nick or channel to auto list
+    -d, del: delete nick or channel from auto list
+    -n, nick: use command on nick list
+    -c, channel: use command on channel list
+
+    -i, info: display current settings for SPOTIFY AUTO
+    -w, whitelist: treaten list as a whitelist
+    -b, blacklist: treaten list as a blacklist
+
+Add a nick or channel to search from when matching
+Configure automatic features. The public section is for
+automatic return results in the window the Spotify resource
+was sent in.
+
+Info will display settings for all automatic features and
+you can use enable or disable to turn automatic functions
+on or off.
+
+You can also set if you want the list of nicks and channels
+to be interpretted as a whitelist or blacklist.
+EOF
+}
+
+sub cmd_help {
+       my ($args, $server, $window) = @_;
+
+       if ($args =~ /^spotify\s*$/) {
+               cmd_spotify_help();
+       } elsif ($args =~ /^spotify lookup\s*$/i) {
+               cmd_spotify_help_lookup();
+       } elsif ($args =~ /^spotify auto\s*$/i) {
+               cmd_spotify_help_auto();
+       } elsif ($args =~ /^spotify auto public\s*$/i) {
+               cmd_spotify_help_auto_public();
+       }
+}
+
+sub cmd_spotify_lookup {
+       my ($args, $server, $window) = @_;
+       my @argv = split(/ /, $args);
+
+       my $public = 0;
+
+       # Simple parse arguments (debian still has an old version of irssi..)
+       my $i;
+       for ($i = 0; $i <= $#argv; $i++) {
+               if ($argv[$i] eq '-p' || $argv[$i] eq 'public') { $public = 1; }
+               elsif ($argv[$i] eq '-h' || $argv[$i] eq 'help') {
+                       return Irssi::command_runsub('spotify lookup', $args, $server, $window);
+               }
+               else { last; }
+       }
+
+       # Treat the rest as data argument
+       my $data = join(' ', @argv[$i..$#argv]);
+
+       # Make sure we actually have a window reference and check if we can write
+       if ($public) {
+               if (!$window) {
+                       Irssi::active_win()->print("Must be run run in a valid window (CHANNEL|QUERY)");
+                       return;
+               }
+       } else {
+               $window = Irssi::active_win();
+       }
+
+       # Dispatch the work to be done
+       my @worker_args = ($data, 1);
+       my @output_args = ($server->{tag}, $window->{name}, $public ? $window->{name} : 0);
+       dispatch(\&spotify_lookup, \@worker_args, \@output_args);
+}
+
+sub cmd_spotify_auto {
+       my ($args, $server, $window) = @_;
+
+       if ($args eq 'info' || $args eq '-i') {
+
+               my $lookup = Irssi::settings_get_str('spotify_automatic_lookup');
+               my $lookup_public = Irssi::settings_get_str('spotify_automatic_lookup_public');
+               my $lookup_public_blacklist = Irssi::settings_get_str('spotify_automatic_lookup_public_blacklist');
+               my $lookup_public_channels = Irssi::settings_get_str('spotify_automatic_lookup_public_channels');
+               my $lookup_public_nicks = Irssi::settings_get_str('spotify_automatic_lookup_public_nicks');
+               my $policy = $lookup_public_blacklist eq 'yes' ? 'disable' : 'enable';
+               Irssi::print(<<"EOF", MSGLEVEL_CLIENTCRAP);
+Spotify automatic settings:
+ automatic lookup: %_${lookup}%_
+ automatic lookup to public: %_${lookup_public}%_
+ interpret list of channels and nicks as blacklist: %_${lookup_public_blacklist}%_
+ ${policy} lookup for channels: %_${lookup_public_channels}%_
+ ${policy} lookup for nicks: %_${lookup_public_nicks}%_
+EOF
+       } elsif ($args eq 'enable' || $args eq '-e') {
+               Irssi::settings_set_bool('spotify_automatic_lookup', 1);
+               cmd_spotify_auto('info', $server, $window);
+       } elsif ($args eq 'disable' || $args eq '-d') {
+               Irssi::settings_set_bool('spotify_automatic_lookup', 0);
+               cmd_spotify_auto('info', $server, $window);
+       } else {
+               Irssi::command_runsub('spotify auto', $args, $server, $window);
+       }
+}
+
+sub cmd_spotify_auto_public {
+       my ($args, $server, $window) = @_;
+
+       if ($args eq 'info' || $args eq '-i') {
+               cmd_spotify_auto('info', $server, $window);
+       } elsif ($args eq 'whitelist' || $args eq '-w') {
+               Irssi::settings_set_bool('spotify_automatic_lookup_public_blacklist', 0);
+               cmd_spotify_auto('info', $server, $window);
+       } elsif ($args eq 'blacklist' || $args eq '-b') {
+               Irssi::settings_set_bool('spotify_automatic_lookup_public_blacklist', 1);
+               cmd_spotify_auto('info', $server, $window);
+       } elsif ($args eq 'enable' || $args eq '-e') {
+               Irssi::settings_set_bool('spotify_automatic_lookup_public', 1);
+               cmd_spotify_auto('info', $server, $window);
+       } elsif ($args eq 'disable' || $args eq '-d') {
+               Irssi::settings_set_bool('spotify_automatic_lookup_public', 0);
+               cmd_spotify_auto('info', $server, $window);
+       } else {
+               Irssi::command_runsub('spotify auto public', $args, $server, $window);
+       }
+}
+
+sub cmd_spotify_auto_public_add {
+       my ($args, $server, $window) = @_;
+
+       my @argv = split(/ /, $args);
+       my $type = shift(@argv);
+
+       if ($type eq 'channel' || $type eq '-c') {
+               $type = 'channel';
+       } elsif ($type eq 'nick' || $type eq '-n') {
+               $type = 'nick';
+       } else {
+               return Irssi::command_runsub('spotify auto public add', $args, $server, $window);
+       }
+
+       my @array = settings_get_array("spotify_automatic_lookup_public_${type}s");
+       foreach my $arg (@argv) {
+               push(@array, $arg);
+       }
+       settings_set_array("spotify_automatic_lookup_public_${type}s", @array);
+       cmd_spotify_auto('info', $server, $window);
+}
+
+sub cmd_spotify_auto_public_del {
+       my ($args, $server, $window) = @_;
+
+       my @argv = split(/ /, $args);
+       my $type = shift(@argv);
+
+       if ($type eq 'channel' || $type eq '-c') {
+               $type = 'channel';
+       } elsif ($type eq 'nick' || $type eq '-n') {
+               $type = 'nick';
+       } else {
+               return Irssi::command_runsub('spotify auto public del', $args, $server, $window);
+       }
+
+       my @array = settings_get_array("spotify_automatic_lookup_public_${type}s");
+       foreach my $arg (@argv) {
+               for (my $i = 0; $i <= $#array; $i++) {
+                       if ($array[$i] eq $arg) {
+                               splice(@array, $i, 1);
+                               last;
+                       }
+               }
+       }
+       settings_set_array("spotify_automatic_lookup_public_${type}s", @array);
+       cmd_spotify_auto('info', $server, $window);
+}
+
+sub event_message_topic {
+       my ($server, $channel, $topic, $nick, $address) = @_;
+
+       if ($server->{nick} ne $nick) {
+               event_message($server, $topic, $nick, $address, $channel);
+       }
+
+}
+
+sub event_message {
+       my ($server, $text, $nick, $address, $target) = @_;
+
+       if (!Irssi::settings_get_bool('spotify_automatic_lookup')) {
+               return;
+       }
+
+       # Retrieve window object and decide wether to do lookup public or not
+       my ($window, $public);
+       if (!$server->ischannel($target)) {
+               $window = Irssi::window_item_find($nick);
+               $public = public_lookup_permitted($nick);
+               $target = $nick;
+       } else {
+               $window = Irssi::window_item_find($target);
+               $public = public_lookup_permitted($nick, $target);
+       }
+
+       # Dispatch a lookup for each matching uri
+       my @output_args = ($server->{tag}, $window->{name}, $public ? $target : 0);
+       while ($text =~ m{(https?://(play|open)\.spotify\.com/|spotify:)[^\s<>\[\]()?]+}g) {
+               my @worker_args = ($&, 0);
+               dispatch(\&spotify_lookup, \@worker_args, \@output_args);
+       }
+}
+
+sub dispatch {
+       my $action = shift;
+       my @writer_args = @{$_[0]};
+       my @reader_args = @{$_[1]};
+
+       # Create communication between child and main process
+       my ($reader, $writer);
+       pipe($reader, $writer);
+
+       # Create child process
+       my $pid = fork();
+       if ($pid > 0) {
+               # Main process, close writer and add child pid to waiting list
+               close($writer);
+               Irssi::pidwait_add($pid);
+
+               # Add reader and input pipe tag to arguments and pass it to
+               # dispatch_reader when finished
+               my $input_tag;
+               unshift(@reader_args, $pid);
+               unshift(@reader_args, \$input_tag);
+               unshift(@reader_args, $reader);
+
+               # Wait for child to finish and send result to input_reader
+               $input_tag = Irssi::input_add(fileno($reader), INPUT_READ,
+                                             'input_reader', \@reader_args);
+       } elsif ($pid == 0) {
+               # Child process, close reader and do the work to be done
+               close($reader);
+               my $rc = $action->($writer, \@writer_args) || 0;
+               close($writer);
+               POSIX::_exit($rc);
+       } else {
+               # Fork error, something nasty must have happened
+               Irssi::print('spotify: failed to fork(), aborting $cmd.', MSGLEVEL_CLIENTCRAP);
+               close($reader);
+               close($writer);
+       }
+}
+
+sub input_reader {
+       my ($reader, $input_tag, $pid, $server, $window, $target) = @{$_[0]};
+       my @data = <$reader>;
+
+       # Cleanup before doing anything else
+       close($reader);
+       Irssi::input_remove($$input_tag);
+
+       # Get exit code from child and force non-public output on error
+       while (waitpid($pid, POSIX::WNOHANG) == 0) {
+               sleep 1;
+       }
+       my $rc = POSIX::WEXITSTATUS($?);
+       if ($rc) { $target = 0; }
+
+       # Find output window
+       $server = Irssi::server_find_tag($server);
+       $window = $server ? $server->window_item_find($window) : undef;
+
+       if (!defined($window)) {
+               $window = Irssi::active_win();
+       }
+
+       # Handle result from child
+       foreach my $line (@data) {
+               chomp($line);
+               if ($target) {
+                       # Remove any trace of colors (could probably break things)
+                       $line =~ s/%[krgybmpcwn:|#_]//ig;
+                       $window->command("/NOTICE $target " . $line);
+               }
+               else { $window->print($line); }
+       }
+}
+
+sub spotify_lookup {
+       my $writer = shift;
+       my ($uri, $manual) = @{$_[0]};
+
+       $uri =~ s/^\s+//g;
+       $uri =~ s/[\s\.]+$//g;
+       my $u = URI->new($uri);
+       my @parts = split /[\/:]/, URI->new($uri)->path;
+
+       my $path;
+       my $auth;
+       if ($parts[0] =~ /^(track|album|artist)$/ && @parts == 2) {
+               $path = "v1/$parts[0]\s/$parts[1]";
+       } elsif ($parts[0] eq 'user' && @parts == 2) {
+               $path = "v1/users/$parts[1]";
+       } elsif ($parts[0] eq 'user' && @parts == 4 && $parts[2] == 'playlist') {
+               $path = "v1/users/$parts[1]/playlists/$parts[3]";
+               $auth = 1;
+       } else {
+               print($writer "spotify: unsupported URI: $uri");
+               return 1;
+       }
+
+       my $ua = new LWP::UserAgent(
+               agent => "spotify.pl/$VERSION",
+               timeout => 10
+       );
+       $ua->env_proxy();
+
+       # Fetch access token when needed. The token could be cached and re-used where
+       # possible. Since we're in a forked process, it's not trivial and probably
+       # not worth the effort.
+       my $token;
+       if ($auth) {
+               my $request = POST 'https://accounts.spotify.com/api/token', [
+                       grant_type => 'client_credentials',
+                       client_id => Irssi::settings_get_str('spotify_client_id'),
+                       client_secret => Irssi::settings_get_str('spotify_client_secret')
+               ];
+               my $response = $ua->request($request);
+               my $content_type = $response->header('Content-Type');
+               if (index($content_type, 'application/json') == -1) {
+                       my $body = substr $response->decoded_content(), 0, 128;
+                       print($writer "Unsupported content type: $content_type (" . $response->code . "): $body");
+                       return 1;
+               }
+               my $result = from_json($response->decoded_content(), {utf8 => 1});
+               if (defined $result->{error}) {
+                       my $error = $result->{error_description} || $result->{error};
+                       print($writer "Authorization failed: $error (" . $response->code . ")");
+                       return 1;
+               }
+               $token = $result->{access_token};
+       }
+
+       my $request = GET 'https://api.spotify.com/' . $path;
+       if ($token) {
+               $request->header('Authorization' => 'Bearer ' . $token);
+       }
+       my $response = $ua->request($request);
+       my $content_type = $response->header('Content-Type');
+       if (index($content_type, 'application/json') == -1) {
+               my $body = substr $response->decoded_content(), 0, 128;
+               print($writer "Unsupported content type: $content_type (" . $response->code . "): $body");
+               return 1;
+       }
+       my $result = from_json($response->decoded_content(), {utf8 => 1});
+       if (defined $result->{error}) {
+               print($writer "Lookup failed: $result->{error}->{message} (" . $response->code . ")");
+               return 1;
+       }
+
+       my $message = undef;
+       my %data;
+       if ($result->{type} eq 'track') {
+               $data{'name'} = $result->{name};
+               $data{'artist'} = artists_str($result->{artists});
+               $data{'album'} = $result->{album}->{name};
+               $data{'popularity'} = popularity_str($result->{popularity});
+               $message = Irssi::settings_get_str('spotify_track_format');
+               $message =~ s/%(name|artist|album|popularity)/$data{$1}/ge;
+       } elsif ($result->{type} eq 'album') {
+               $data{'name'} = $result->{name};
+               $data{'artist'} = artists_str($result->{artists});
+               $data{'year'} = substr $result->{release_date}, 0, 4;
+               $data{'popularity'} = popularity_str($result->{popularity});
+               $data{'territories'} = join ', ', $result->{available_markets};
+               $message = Irssi::settings_get_str('spotify_album_format');
+               $message =~ s/%(name|artist|year|popularity|territories)/$data{$1}/ge;
+       } elsif ($result->{type} eq 'artist') {
+               $data{'name'} = $result->{name};
+               $data{'popularity'} = popularity_str($result->{popularity});
+               $message = Irssi::settings_get_str('spotify_artist_format');
+               $message =~ s/%(name|popularity)/$data{$1}/ge;
+       } elsif ($result->{type} eq 'user') {
+               $data{'user'} = $result->{id};
+               $message = Irssi::settings_get_str('spotify_user_format');
+               $message =~ s/%(user)/$data{$1}/ge;
+       } elsif ($result->{type} eq 'playlist') {
+               $data{'name'} = $result->{name};
+               $data{'owner'} = $result->{owner}->{id};
+               $message = Irssi::settings_get_str('spotify_playlist_format');
+               $message =~ s/%(name|owner)/$data{$1}/ge;
+       } else {
+               print($writer "spotify: unhandled result type: " . $result->{type});
+               return 1;
+       }
+
+       my $charset = Irssi::settings_get_str('term_charset');
+       if ($charset =~ /^utf-8/i) {
+               binmode $writer, ':utf8';
+       } else {
+               Encode::from_to($message, "utf-8", Irssi::settings_get_str('term_charset'));
+       }
+
+       # Only write header for manual lookups
+       if ($manual) {
+               my $header = Irssi::settings_get_str('spotify_header_format');
+               $header =~ s/%uri/$uri/ge;
+               if ($header ne "") { print($writer $header . "\n"); }
+       }
+
+       print($writer $message);
+
+       return 0;
+}
+
+sub artists_str {
+       my $artists = shift;
+       my @names;
+       foreach my $artist(@{$artists}) {
+               push(@names, $artist->{name});
+       }
+       return join ', ', @names;
+}
+
+sub popularity_str {
+       my $popularity = shift;
+       my $str = '';
+       for (my $i = 0; $i < 100; $i += 20) {
+               if ($i <= $popularity) {
+                       $str .= '*';
+               } else {
+                       $str .= '-';
+               }
+       }
+       return $str;
+}
+
+sub settings_get_array {
+       my $key = shift;
+       my $value = Irssi::settings_get_str($key);
+       return split(/[ :;,]/, $value);
+}
+
+sub settings_set_array {
+       my ($key, @value) = @_;
+       my $str = join(' ', @value);
+       Irssi::settings_set_str($key, $str);
+}
+
+sub public_lookup_permitted {
+       my ($nick, $channel) = @_;
+
+       if (!Irssi::settings_get_bool('spotify_automatic_lookup_public')) {
+               return 0;
+       }
+
+       # Default lookup policy can either be deny or allow, and matching will
+       # invert the default result. If this is True, the policy list will be used
+       # as a blacklist. If this is False, the policy list is a whitelist.
+       my $blacklist = Irssi::settings_get_bool('spotify_automatic_lookup_public_blacklist');
+
+       sub in_array {
+               my ($type, $target) = @_;
+
+               my @array = settings_get_array("spotify_automatic_lookup_public_${type}s");
+               foreach my $item (@array) {
+                       if ($item eq $target) { return 1; }
+               }
+               return 0;
+       }
+
+       if (defined($channel) && in_array('channel', $channel)) {
+               return $blacklist ? 0 : 1;
+       }
+       if (defined($nick) && in_array('nick', $nick)) {
+               return $blacklist ? 0 : 1;
+       }
+
+       return $blacklist ? 1 : 0;
+}
+
+### Signals
+Irssi::signal_add_last('message public',  'event_message');
+Irssi::signal_add_last('message private', 'event_message');
+Irssi::signal_add_last('message topic', 'event_message_topic');
+
+### Commands
+Irssi::command_bind('spotify' => sub {
+       my ($args, $server, $window) = @_;
+       $args =~ s/\s+$//g;
+       Irssi::command_runsub('spotify', $args, $server, $window);
+});
+
+Irssi::command_bind('spotify help', \&cmd_spotify_help);
+Irssi::command_bind('help', \&cmd_help);
+
+Irssi::command_bind('spotify lookup', \&cmd_spotify_lookup);
+Irssi::command_bind('spotify -l', \&cmd_spotify_lookup);
+Irssi::command_bind('spotify lookup help', \&cmd_spotify_help_lookup);
+Irssi::command_bind('spotify lookup -h', \&cmd_spotify_help_lookup);
+Irssi::command_bind('spotify lookup public', \&cmd_spotify_lookup);
+Irssi::command_bind('spotify lookup -p', \&cmd_spotify_lookup);
+
+Irssi::command_bind('spotify auto', \&cmd_spotify_auto);
+Irssi::command_bind('spotify -a', \&cmd_spotify_auto);
+Irssi::command_bind('spotify auto help', \&cmd_spotify_help_auto);
+Irssi::command_bind('spotify auto -h', \&cmd_spotify_help_auto);
+Irssi::command_bind('spotify auto info', \&cmd_spotify_auto_info);
+Irssi::command_bind('spotify auto -i', \&cmd_spotify_auto);
+Irssi::command_bind('spotify auto enable', \&cmd_spotify_auto);
+Irssi::command_bind('spotify auto -e', \&cmd_spotify_auto);
+Irssi::command_bind('spotify auto disable', \&cmd_spotify_auto);
+Irssi::command_bind('spotify auto -d', \&cmd_spotify_auto);
+
+Irssi::command_bind('spotify auto public', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto -p', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public help', \&cmd_spotify_help_auto_public);
+Irssi::command_bind('spotify auto public -h', \&cmd_spotify_help_auto_public);
+Irssi::command_bind('spotify auto public info', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public -i', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public enable', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public -e', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public disable', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public -d', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public whitelist', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public -w', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public blacklist', \&cmd_spotify_auto_public);
+Irssi::command_bind('spotify auto public -b', \&cmd_spotify_auto_public);
+
+Irssi::command_bind('spotify auto public add', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public -a', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public add nick', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public add -n', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public add channel', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public add -c', \&cmd_spotify_auto_public_add);
+Irssi::command_bind('spotify auto public del', \&cmd_spotify_auto_public_del);
+Irssi::command_bind('spotify auto public -d', \&cmd_spotify_auto_public_del);
+Irssi::command_bind('spotify auto public del nick', \&cmd_spotify_auto_public_del);
+Irssi::command_bind('spotify auto public del -n', \&cmd_spotify_auto_public_del);
+Irssi::command_bind('spotify auto public del channel', \&cmd_spotify_auto_public_del);
+Irssi::command_bind('spotify auto public del -c', \&cmd_spotify_auto_public_del);
+
+### Settings
+Irssi::settings_add_bool('spotify', 'spotify_automatic_lookup', 1);
+Irssi::settings_add_bool('spotify', 'spotify_automatic_lookup_public', 0);
+Irssi::settings_add_bool('spotify', 'spotify_automatic_lookup_public_blacklist', 0);
+
+Irssi::settings_add_str('spotify', 'spotify_client_id', '');
+Irssi::settings_add_str('spotify', 'spotify_client_secret', '');
+
+Irssi::settings_add_str('spotify', 'spotify_header_format',         'Lookup result for %_%uri%_:');
+Irssi::settings_add_str('spotify', 'spotify_track_format',          '%_%name%_ by %_%artist%_ (from %album) [%_%popularity%_]');
+Irssi::settings_add_str('spotify', 'spotify_album_format',          '%_%name%_ by %_%artist%_ (%year) [%_%popularity%_]');
+Irssi::settings_add_str('spotify', 'spotify_artist_format',         '%_%name%_ [%_%popularity%_]');
+Irssi::settings_add_str('spotify', 'spotify_playlist_format',       '%_%name%_ by %_%owner%_');
+Irssi::settings_add_str('spotify', 'spotify_user_format',           'Username: %_%user%_');
+
+Irssi::settings_add_str('spotify', 'spotify_automatic_lookup_public_channels', '');
+Irssi::settings_add_str('spotify', 'spotify_automatic_lookup_public_nicks', '');
git clone https://git.99rst.org/PROJECT