# slack_profile.pl # # Get user profile information from the Slack API. # # Note: # Before using this script, a Slack API token must be added: # # /set slack_profile_token TOKEN # # Tokens can be created here: https://api.slack.com/docs/oauth-test-tokens # # Usage: # # Get profile information for a given nick: # # /swhois nick # /swhois @nick # # Get the latest user profile data from Slack: # # /slack_profile_sync # # Update your Slack profile fields: # # /slack_profile_set last_name Farnsworth # # # Copyright (c) 2016 Teddy Wing # # 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 3 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, see . use strict; use 5.010; use JSON; use HTTP::Tiny; use Mozilla::CA; use Storable; use URI; use vars qw($VERSION %IRSSI); use Irssi; $VERSION = '1.01'; %IRSSI = { authors => 'Teddy Wing', contact => 'irssi@teddywing.com', name => 'Slack Profile', description => 'Update and retrieve user profile information from Slack.', license => 'GPL', }; # Keeps an in-memory collection of Slack team members and their profile info my @users_list; # Provides help for the commands provided by the script from within Irssi. # # Examples: # /help swhois # /help slack_profile_sync # /help slack_profile_set sub help { my ($args) = @_; my $is_helping = 0; my $help = ''; if ($args =~ /^swhois\s*$/) { $is_helping = 1; $help = <] %9Description:%9 Displays WHOIS-style profile information from Slack for the given nick. If no nick argument is provided, the current nick is used. %9Examples:%9 /SWHOIS /SWHOIS farnsworth /SWHOIS \@farnsworth HELP } elsif ($args =~ /^slack_profile_sync\s*$/) { $is_helping = 1; $help = < %9Description:%9 Update the given Slack profile field for the current nick. %9Examples:%9 /SLACK_PROFILE_SET first_name Lisa HELP } if ($is_helping) { Irssi::print($help, MSGLEVEL_CLIENTCRAP); Irssi::signal_stop(); } } # The location of the on-disk cache where user profile data is stored sub users_list_cache { Irssi::get_irssi_dir() . '/scripts/slack_profile-users.list.plstore'; } # Call Slack API methods # # Requires a Slack API token to be added to the Irssi config. Take a Slack API # method and an optional hash of API arguments. If the request is successful, # the API response is returned. sub slack_api { my $token = Irssi::settings_get_str('slack_profile_token'); die 'Requires a Slack API token. Generate one from ' . 'https://api.slack.com/docs/oauth-test-tokens. ' . 'Set it with `/set slack_profile_token TOKEN`.' if !$token; my ($method, $args) = @_; $args ||= {}; $args->{'token'} = $token; my $url = URI->new("https://slack.com/api/$method"); $url->query_form($args); my $http = HTTP::Tiny->new( default_headers => { 'content-type' => 'application/json', }, verify_SSL => 1, ); my $resp = $http->get($url); if ($resp->{'success'}) { my $payload = decode_json($resp->{'content'}); if ($payload->{'ok'}) { return $payload; } else { Irssi::print("Error from the Slack API: $payload->{'error'}"); } } else { Irssi::print("Error calling the Slack API: ($resp->{'status'}) $resp->{'reason'} | $resp->{'content'}"); } } # Requests the entire list of users on the Slack team # # Stores this list in memory in `@users_list` and in an on-disk cache file. sub fetch_users_list { Irssi::print('Fetching users list from Slack. This could take a while...'); my $resp = slack_api('users.list') or die 'Unable to retrieve users from the Slack API'; @users_list = @{$resp->{'members'}}; store \@users_list, users_list_cache; } # Re-fetch the users list for the most up-to-date information sub sync { fetch_users_list(); Irssi::print('Done.'); } # Get profile data for a single user # # Takes a user object of the kind provided by the Slack 'users.list' API # method. The user's unique id is extracted from this object and sent to Slack's # profile API method in order to retrieve custom field values from the user. sub fetch_user_profile { my ($user) = @_; my $resp = slack_api('users.profile.get', { user => $user->{'id'}, include_labels => 1 }); return $resp->{'profile'}; } # Get presence information about a user # # Given a user, request Slack's presence API method to find out whether that # user is away or active/online. sub fetch_user_presence { my ($user) = @_; my $resp = slack_api('users.getPresence', { user => $user->{'id'} }); return $resp->{'presence'}; } # Convert a given string to a key-friendly format # # Takes a string, lowercases it, removes non-alphabetic characters, and converts # spaces into underscores. # # Examples: # is( # underscorize("This isn't a key"), # 'this_isnt_a_key' # ) sub underscorize { my ($string) = @_; my $result = lc $string; $result =~ s/ /_/g; $result =~ s/[^a-z_]//g; return $result; } # Completion for profile fields names # # When using the `slack_profile_set` command, allow the names of profile fields # to be tab completed. This allows users to browse the possible fields they can # update, frees them from having to type the full field name, and gives them # confidence that they're not misspelling a field name. sub complete_profile_field { my ($complist, $window, $word, $linestart, $want_space) = @_; my $slash = Irssi::parse_special('$k'); return unless $linestart =~ /^\Q${slash}\Eslack_profile_set\b/i; my @profile_fields = qw(first_name last_name email phone skype title); if ($window->{'active_server'}) { my $user = find_user($window->{'active_server'}->{'nick'}); for my $custom_field (keys %{$user->{'fields'}}) { push @profile_fields, underscorize($user->{'fields'}->{$custom_field}->{'label'}); } } if ($word ne '') { for my $field (@profile_fields) { if ($field =~ /^\Q${word}\E/i) { push @$complist, $field; } } } else { @$complist = @profile_fields; } Irssi::signal_stop(); } # Update a profile field # # Given a string nick, a field name, and a value, set the profile field to the # given value for the specified user. sub update_user_profile { my ($nick, $key, $value) = @_; my $user = find_user($nick); my @profile_fields = qw(first_name last_name email phone skype title); # If $key is a custom field, find the custom field's id and use # that as the key instead. unless (grep { $_ eq $key } @profile_fields) { # Find key in custom field labels for my $custom_field (keys %{$user->{'fields'}}) { if (underscorize($user->{'fields'}->{$custom_field}->{'label'}) eq $key) { $key = $custom_field; last; } } } my $resp = slack_api('users.profile.set', { user => $user->{'id'}, name => $key, value => $value, }); } # Irssi command handler for updating profile fields # # Extracts the field name and value from Irssi command arguments, finds the # current user's nick, and offloads the actual work onto `update_user_profile`. sub cmd_set { my ($data, $server) = @_; my ($key, $value) = split /\s+/, $data, 2; my $nick = $server->{'nick'}; if ($key) { update_user_profile($nick, $key, $value); } } # Given a nick, return a corresponding user object # # Looks for the given nick in the Slack users list. If a match is found, the # associated user object is returned. Additionally, custom profile fields and # presence information is requested and attached to the user object if not # already there. sub find_user { my ($username) = @_; if (!@users_list) { if (!-s users_list_cache) { fetch_users_list(); } else { @users_list = retrieve(users_list_cache); @users_list = @{@users_list[0]}; } } for my $user (@users_list) { if ($user->{'name'} eq $username) { unless (exists $user->{'fields'}) { my $profile = fetch_user_profile($user); $user->{'fields'} = $profile->{'fields'}; } unless (exists $user->{'presence'}) { my $presence = fetch_user_presence($user); $user->{'presence'} = $presence; } return $user; } } } # Prints user profile information to the Irssi console sub print_whois { my ($user) = @_; # Append spaces to the end of $label such that the length of the result is # equal to $length # # Examples: # is( # pad_label('name', 7), # 'name ' # ) sub pad_label { my ($label, $length) = @_; my $padding = $length - length $label; $label . ' ' x $padding; } my $bot = ''; if ($user->{'is_bot'}) { $bot = ' (bot)'; } my @fields = ( { label => 'name', value => $user->{'real_name'}, }, { label => 'title', value => $user->{'profile'}->{'title'}, }, { label => 'email', value => $user->{'profile'}->{'email'}, }, { label => 'phone', value => $user->{'profile'}->{'phone'}, }, { label => 'skype', value => $user->{'profile'}->{'skype'}, }, { label => 'tz', value => $user->{'tz_label'}, }, ); foreach my $key (keys %{$user->{'fields'}}) { push @fields, { label => $user->{'fields'}->{$key}->{'label'}, value => $user->{'fields'}->{$key}->{'value'}, }; } push @fields, { label => 'status', value => $user->{'presence'}, }; # Determine the longest label so we can pad others accordingly my $max_label_length = 0; for my $field (@fields) { my $length = length $field->{'label'}; if ($length > $max_label_length) { $max_label_length = $length; } } Irssi::print($user->{'name'} . $bot); for my $field (@fields) { if ($field->{'value'}) { # Pad field labels so that the colons line up vertically my $label = pad_label($field->{'label'}, $max_label_length); Irssi::print(" $label : $field->{'value'}"); } } Irssi::print('End of SWHOIS'); } # Irssi command handler for getting profile information for a nick # # Given a nick, the associated profile information for that nick will be fetched # and printed to the Irssi console. If no nick is passed, profile information # for the current user's nick is printed. sub swhois { my ($username, $server, $window_item) = @_; if (!$username) { if (!$server || !$server->{connected}) { Irssi::print("Not connected to server"); return; } $username = $server->{'nick'}; } # If $username starts with @, strip it $username =~ s/^@//; # Trim leading and trailing whitespace $username =~ s/^\s+//; $username =~ s/\s+$//; if (my $user = find_user($username)) { print_whois($user); } } Irssi::command_bind('swhois', 'swhois'); Irssi::command_bind('slack_profile_sync', 'sync'); Irssi::command_bind('slack_profile_set', 'cmd_set'); Irssi::command_bind('help', 'help'); Irssi::signal_add('complete word', 'complete_profile_field'); Irssi::settings_add_str('slack_profile', 'slack_profile_token', '');