diff --git a/.gitignore b/.gitignore index 3ff4fada..ba5c552f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # Ignore environemnt variables .env +coverage diff --git a/lib/channel.rb b/lib/channel.rb new file mode 100644 index 00000000..48a4be79 --- /dev/null +++ b/lib/channel.rb @@ -0,0 +1,37 @@ +require 'httparty' +require 'prettyprint' +require 'dotenv' + +require_relative 'recipient' + +Dotenv.load + +class Channel < Recipient + + attr_reader :topic,:member_count + + def initialize(slack_id, name, topic, member_count) + super(slack_id, name) + @topic = topic + @member_count = member_count + end + def self.list_all + channel_url = 'https://slack.com/api/conversations.list' + response = self.get(channel_url, query: { token: ENV['SLACK_API_TOKEN']}) + + channels = response["channels"] + + list = [] + channels.each do |channel| + list << Channel.new(channel["id"], channel["name"], channel["purpose"]["value"], channel["num_members"]) + end + return list + end + + def details + return "\n channel name: #{self.name} \n topic: #{self.topic} \n user count: #{self.member_count} \n slack_id: #{self.slack_id}" + end + + +end + diff --git a/lib/recipient.rb b/lib/recipient.rb new file mode 100644 index 00000000..3d2e505a --- /dev/null +++ b/lib/recipient.rb @@ -0,0 +1,34 @@ +require 'httparty' +require 'prettyprint' +require 'dotenv' + +Dotenv.load + +class SlackApiError < StandardError; end + +class Recipient + + attr_reader :slack_id, :name + + def initialize(slack_id, name) + @slack_id = slack_id + @name = name + end + + def self.get(url, params) + return HTTParty.get(url, params) + end + + def send_message(message) + response = HTTParty.post( + "https://slack.com/api/chat.postMessage", + body: { + token: ENV['SLACK_API_TOKEN'], + text: message, + channel: @slack_id + }) + unless response.code == 200 && response.parsed_response["ok"] + raise SlackApiError, "Error when posting #{message} to #{@name}, error: #{response.parsed_response["error"]}" + end + end +end \ No newline at end of file diff --git a/lib/slack.rb b/lib/slack.rb index 8a0b659b..99ece09e 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -1,12 +1,110 @@ -#!/usr/bin/env ruby + +require 'prettyprint' +require 'awesome_print' +require 'httparty' +require 'dotenv' + +Dotenv.load + +require_relative 'workspace' def main - puts "Welcome to the Ada Slack CLI!" + + begin workspace = Workspace.new + rescue SlackApiError => error + puts "error occurred: #{error.message}" + return + end + + puts "\nWelcome to the Ada Slack CLI!" + puts "There are #{workspace.users.length} users and #{workspace.channels.length} channels." + + statement = "\nPlease enter one of the following commands:\n• list users\n• list channels\n• select user\n• select channel\n• show details\n• send a message\n• quit\n" + + until false + + puts statement + puts "\n" + input = gets.chomp.downcase + + begin + case input + when "list users" + puts workspace.list_users + when "list channels" + puts workspace.list_channels + + when "select channel" + puts "Please enter a channel name or ID:" + input = gets.chomp + workspace.select_channel(input) - # TODO project + until workspace.select_channel(input) != nil + puts "Invalid channel #{input}. Please try again." + input = gets.chomp + workspace.select_channel(input) + end - puts "Thank you for using the Ada Slack CLI" + puts "\n Selected channel: #{workspace.selected.name} \n ID: #{workspace.selected.slack_id}" + puts " Enter #{"show details"} for more details on #{workspace.selected.name}" + + when "select user" + puts "Please enter a username or ID:" + input = gets.chomp + workspace.select_user(input) + + until workspace.select_user(input) != nil + puts "Invalid user #{input}. Please try again." + input = gets.chomp + workspace.select_user(input) + end + + puts "\n Selected user: #{workspace.selected.name} \n ID: #{workspace.selected.slack_id}" + puts " Enter #{"show details"} for more details on #{workspace.selected.name}" + + when "show details" + if workspace.selected == nil + puts "Details for whom? Please enter a user or channel" + input = gets.chomp + workspace.ask_to_select(input) + until workspace.ask_to_select(input) != nil + puts "Invalid input #{input}. Please try again." + input = gets.chomp + workspace.ask_to_select(input) + end + end + + puts workspace.show_details + + when "send a message" + if workspace.selected == nil + puts "To whom?" + input = gets.chomp + workspace.ask_to_select(input) + until workspace.ask_to_select(input) != nil + puts "Invalid input #{input}. Please try again." + input = gets.chomp + workspace.ask_to_select(input) + end + end + + puts "What message would you like to send?" + message = gets.chomp.to_s + puts workspace.send_message(message) + puts "message #{message} sent successfully!" + + when "quit" + puts "Thank you for using the Ada Slack CLI" + break + else + puts "Invalid input #{input}" + end + rescue SlackApiError => error + puts "error occurred: #{error.message}" + end + end end -main if __FILE__ == $PROGRAM_NAME \ No newline at end of file + +main if __FILE__ == $PROGRAM_NAME diff --git a/lib/user.rb b/lib/user.rb new file mode 100644 index 00000000..de246ab6 --- /dev/null +++ b/lib/user.rb @@ -0,0 +1,39 @@ +require 'httparty' +require 'prettyprint' +require 'dotenv' + +require_relative 'recipient' + +Dotenv.load + +class User < Recipient + + attr_reader :real_name, :status_text, :status_emoji + + def initialize(slack_id, name, real_name, status_text, status_emoji) + super(slack_id, name) + @real_name = real_name + @status_text = status_text + @status_emoji = status_emoji + end + + def self.list_all + user_url = 'https://slack.com/api/users.list' + response = self.get(user_url, query: { token: ENV['SLACK_API_TOKEN']}) + + raise ArgumentError.new("invalid request") if response["ok"] == false + + users = response["members"] + + list = [] + users.each do |user| + list << User.new(user["id"], user["name"], user["profile"]["real_name"], user["profile"]["status_text"], user["profile"]["status_emoji"]) + end + return list + end + + def details + return "\n username: #{self.name} \n real name: #{self.real_name} \n slack ID: #{self.slack_id} \n status: #{self.status_text} \n emoji: #{self.status_emoji}" + end + +end \ No newline at end of file diff --git a/lib/workspace.rb b/lib/workspace.rb new file mode 100644 index 00000000..ffa3b28e --- /dev/null +++ b/lib/workspace.rb @@ -0,0 +1,64 @@ +require 'httparty' +require 'dotenv' + +require_relative 'user' +require_relative 'channel' + +Dotenv.load + +class Workspace + + attr_reader :users,:channels,:selected + + def initialize + @users = User.list_all + @channels = Channel.list_all + @selected = nil + end + + def list_users + user_data = [] + @users.each do |user| + user_data << user.details + end + return user_data + end + + def list_channels + channel_data = [] + @channels.each do |channel| + channel_data << channel.details + end + return channel_data + end + + def select_channel(input) + @selected = @channels.find { |channel| input == channel.name || input == channel.slack_id} + return @selected + end + + def select_user(input) + @selected = @users.find { |user| input == user.name || input == user.slack_id} + return @selected + end + + def ask_to_select(input) + select_channel(input) + if @selected == nil + select_user(input) + end + return @selected + end + + def show_details + raise SlackApiError.new("no user or channel selected") if @selected == nil + return @selected.details + end + + def send_message(message) + raise SlackApiError.new("no user or channel selected") if @selected == nil + @selected.send_message(message) + end +end + + diff --git a/test/cassettes/channel_send_not_authed.yml b/test/cassettes/channel_send_not_authed.yml new file mode 100644 index 00000000..a602e196 --- /dev/null +++ b/test/cassettes/channel_send_not_authed.yml @@ -0,0 +1,66 @@ +--- +http_interactions: +- request: + method: post + uri: https://slack.com/api/chat.postMessage + body: + encoding: UTF-8 + string: token=&text=This%20post%20should%20not%20work&channel=C01BTVCEXCN + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 09 Oct 2020 23:42:38 GMT + Server: + - Apache + X-Slack-Req-Id: + - f7906d9bd24d64df60e2678a99967159 + X-Oauth-Scopes: + - identify,channels:read,groups:read,im:read,users:read,chat:write + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + Access-Control-Allow-Origin: + - "*" + X-Slack-Backend: + - r + X-Content-Type-Options: + - nosniff + Expires: + - Mon, 26 Jul 1997 05:00:00 GMT + Cache-Control: + - private, no-cache, no-store, must-revalidate + X-Xss-Protection: + - '0' + X-Accepted-Oauth-Scopes: + - chat:write + Access-Control-Allow-Headers: + - slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, + x-b3-sampled, x-b3-flags + Vary: + - Accept-Encoding + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Content-Length: + - '337' + Content-Type: + - application/json; charset=utf-8 + X-Via: + - haproxy-www-wtop,haproxy-edge-iad-gqnl + body: + encoding: ASCII-8BIT + string: '{"ok":false,"error":"not_authed"}' + recorded_at: Fri, 09 Oct 2020 23:42:38 GMT +recorded_with: VCR 6.0.0 diff --git a/test/channel_test.rb b/test/channel_test.rb new file mode 100644 index 00000000..7d8c17aa --- /dev/null +++ b/test/channel_test.rb @@ -0,0 +1,43 @@ +require_relative 'test_helper' +require_relative '../lib/slack' + +describe "Channel class" do + describe "channel list_all" do + it "can call the API and receive a response" do + VCR.use_cassette("channel_instance") do + all_channels = Channel.list_all + + expect(all_channels).must_be_instance_of Array + + all_channels.each do |channel| + expect(channel).must_be_instance_of Channel + end + end + end + end + + describe "send message" do + + it "can send a message to a channel" do + VCR.use_cassette("channel_send_message") do + channel = Channel.new("C01BTVCEXCN", "general", "topic", 3) + channel.send_message("This post should work") + end + end + + it "will not send a message for invalid channel ID" do + VCR.use_cassette("channel_send_message") do + channel = Channel.new(99999, "test_channel", "this is a fake channel", 3) + expect { channel.send_message("This post should not work") }.must_raise SlackApiError + end + end + + it "returns an error for an invalid token" do + VCR.use_cassette("channel_send_not_authed") do + channel = Channel.new("C01BTVCEXCN", "general", "topic", 3) + + expect { channel.send_message("This post should not work") }.must_raise SlackApiError + end + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 1fcf2bab..c2c5e1d8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,27 +3,30 @@ add_filter 'test/' end +require 'dotenv' require 'minitest' require 'minitest/autorun' require 'minitest/reporters' require 'minitest/skip_dsl' require 'vcr' -Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new +require_relative "../lib/channel" +require_relative "../lib/user" +require_relative "../lib/workspace" -VCR.configure do |config| - config.cassette_library_dir = "test/cassettes" - config.hook_into :webmock -end + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new VCR.configure do |config| config.cassette_library_dir = "test/cassettes" # folder where casettes will be located config.hook_into :webmock # tie into this other tool called webmock config.default_cassette_options = { - :record => :new_episodes, # record new data when we don't have it yet - :match_requests_on => [:method, :uri, :body], # The http method, URI and body of a request all need to match + :record => :new_episodes, # record new data when we don't have it yet + :match_requests_on => [:method, :uri, :body], # The http method, URI and body of a request all need to match } # Don't leave our token lying around in a cassette file. - + config.filter_sensitive_data("") do + ENV["SLACK_API_TOKEN"] + end end diff --git a/test/user_test.rb b/test/user_test.rb new file mode 100644 index 00000000..35ae9a56 --- /dev/null +++ b/test/user_test.rb @@ -0,0 +1,44 @@ +require_relative 'test_helper' +require_relative '../lib/slack' + +describe "User class" do + describe "user list_all" do + it "can call the API and receive a response" do + VCR.use_cassette("user_instance") do + all_users = User.list_all + + expect(all_users).must_be_instance_of Array + + all_users.each do |user| + expect(user).must_be_instance_of User + end + end + end + end + + describe "send message" do + + it "can send a message to a user" do + VCR.use_cassette("user_send_message") do + user = User.new("U01CQGU5LKS", "mahaelmais", "mahaelmais", "", "") + user.send_message("This post should work") + end + end + + it "will not send a message for invalid user ID" do + VCR.use_cassette("user_send_message") do + user = User.new(99999, "test_user", "fake_user", "", "") + expect { user.send_message("This post should not work") }.must_raise SlackApiError + end + end + + it "returns an error for an invalid token" do + VCR.use_cassette("user_send_not_authed") do + user = User.new("U01CQGU5LKS", "mahaelmais", "mahaelmais", "", "") + + expect { user.send_message("This post should not work") }.must_raise SlackApiError + end + end + end + +end \ No newline at end of file diff --git a/test/workspace_test.rb b/test/workspace_test.rb new file mode 100644 index 00000000..9f11c7d8 --- /dev/null +++ b/test/workspace_test.rb @@ -0,0 +1,170 @@ +require_relative 'test_helper' +require_relative '../lib/slack' + +describe "Workspace" do + describe "can initialize a workspace" do + before do + VCR.use_cassette("workspace") do + @workspace = Workspace.new + end + end + + it "can initialize a workspace" do + + expect(@workspace).must_be_kind_of Workspace + expect(@workspace.users).must_be_kind_of Array + expect(@workspace.channels).must_be_kind_of Array + expect(@workspace.selected).must_be_nil + end + + it "initializes with user and channel objects" do + + expect(@workspace.users.first).must_be_kind_of User + expect(@workspace.channels.first).must_be_kind_of Channel + end + end + + describe "can list users and channels" do + before do + VCR.use_cassette("workspace_list_all") do + @workspace = Workspace.new + end + end + + it "can list all users" do + + expect(@workspace.list_users).must_be_kind_of Array + expect(@workspace.list_users.length).must_equal 6 + expect(@workspace.list_users.first).must_be_kind_of String + + end + + it "can list all channels" do + + expect(@workspace.list_channels).must_be_kind_of Array + expect(@workspace.list_channels.length).must_equal 4 + expect(@workspace.list_channels.first).must_be_kind_of String + + end + end + + describe "can select users or channels" do + before do + VCR.use_cassette("select_channels") do + @workspace = Workspace.new + end + end + + it "can select a user by name" do + user = @workspace.select_user("slackbot") + expect(user).must_be_instance_of User + expect(user.name).must_equal "slackbot" + + user = @workspace.select_user("mahaelmais") + expect(user).must_be_instance_of User + expect(user.name).must_equal "mahaelmais" + expect(user.slack_id).must_equal "U01CQGU5LKS" + end + + it "can select a user by ID" do + user = @workspace.select_user("U01BXJZPYAH") + expect(user).must_be_instance_of User + expect(user.name).must_equal "sophie.e.messing" + expect(user.slack_id).must_equal "U01BXJZPYAH" + + user = @workspace.select_user("U01CQHU8WBS") + expect(user).must_be_instance_of User + expect(user.name).must_equal "earth_mahaelmais_api_" + expect(user.slack_id).must_equal "U01CQHU8WBS" + end + + it "can select a channel by name" do + channel = @workspace.select_channel("random") + expect(channel).must_be_instance_of Channel + expect(channel.name).must_equal "random" + expect(channel.slack_id).must_equal "C01C0TJK6G3" + + end + + it "can select a channel by ID" do + channel = @workspace.select_channel("C01BXNGFDTP") + expect(channel).must_be_instance_of Channel + expect(channel.name).must_equal "test-channel" + expect(channel.slack_id).must_equal "C01BXNGFDTP" + + end + + it "will return nil for invalid user input" do + user = @workspace.select_user("xxx") + expect(user).must_be_nil + end + + it "will return nil for invalid channel input" do + user = @workspace.select_user("00001111") + expect(user).must_be_nil + end + + it "can show details for selected user" do + user = @workspace.select_user("mahaelmais") + user_details = @workspace.show_details + + expect(user_details).must_be_kind_of String + expect(user_details).must_include "U01CQGU5LKS" + end + + it "can show details for selected channel" do + channel = @workspace.select_channel("general") + channel_details = @workspace.show_details + + expect(channel_details).must_be_kind_of String + expect(channel_details).must_include "C01BTVCEXCN" + end + end + + describe "send_message" do + before do + VCR.use_cassette("send_message") do + @workspace = Workspace.new + end + end + + it "can send a message" do + VCR.use_cassette("send_message") do + @workspace.select_user("sophie.e.messing") + @workspace.send_message("This post should work") + end + end + + it "will raise an error if we send a message with no channel or user" do + expect { @workspace.send_message("This post should not work") }.must_raise SlackApiError + end + end + + describe "ask_to_select" do + it"can select a user" do + VCR.use_cassette("ask_to_select") do + @workspace = Workspace.new + end + expect(@workspace.ask_to_select("sophie.e.messing")).wont_be_nil + end + + it"can select a channel" do + VCR.use_cassette("ask_to_select") do + @workspace = Workspace.new + end + expect(@workspace.ask_to_select("general")).wont_be_nil + end + + it"can't select invalid input" do + VCR.use_cassette("ask_to_select") do + @workspace = Workspace.new + end + expect(@workspace.ask_to_select("xxxxxxxxxx")).must_be_nil + end + + end +end + + + +