From 959f938dd686eb1529924aaee2d9b583a9899181 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Thu, 12 Mar 2020 21:17:00 -0700 Subject: [PATCH 01/29] initial setup --- lib/slack.rb | 3 ++- test/test_helper.rb | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/slack.rb b/lib/slack.rb index 8a0b659b..c0070c47 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -1,8 +1,9 @@ #!/usr/bin/env ruby +require_relative 'workspace' def main puts "Welcome to the Ada Slack CLI!" - workspace = Workspace.new + workspace = Slack::Workspace.new # TODO project diff --git a/test/test_helper.rb b/test/test_helper.rb index 1fcf2bab..7cbed8b6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -25,5 +25,8 @@ } # Don't leave our token lying around in a cassette file. + config.filter_sensitive_data("") do + ENV["SLACK_TOKEN"] + end end From d3ee1ce04a2b9122373b9eab66ff250f7eb35a4e Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Thu, 12 Mar 2020 21:19:03 -0700 Subject: [PATCH 02/29] added workspace class --- lib/workspace.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/workspace.rb diff --git a/lib/workspace.rb b/lib/workspace.rb new file mode 100644 index 00000000..8f5fed0f --- /dev/null +++ b/lib/workspace.rb @@ -0,0 +1,14 @@ +module Slack + class Workspace + attr_reader :users, :channels + + def initialize + @users = [] + @channels = [] + end + + + + + end +end \ No newline at end of file From 9e33832a9da8bdc75dd7de07ba0aa60d12153d48 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 05:44:11 -0700 Subject: [PATCH 03/29] scaffolding for tests --- test/channel_test.rb | 0 test/conversation_test.rb | 0 test/direct_message_test.rb | 0 test/user_test.rb | 0 test/workspace_test.rb | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/channel_test.rb create mode 100644 test/conversation_test.rb create mode 100644 test/direct_message_test.rb create mode 100644 test/user_test.rb create mode 100644 test/workspace_test.rb diff --git a/test/channel_test.rb b/test/channel_test.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/conversation_test.rb b/test/conversation_test.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/direct_message_test.rb b/test/direct_message_test.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/user_test.rb b/test/user_test.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/workspace_test.rb b/test/workspace_test.rb new file mode 100644 index 00000000..e69de29b From 196822a5a4321c712b446684add9b2e2452a2a8f Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 05:44:46 -0700 Subject: [PATCH 04/29] basic command loop for CLI --- lib/slack.rb | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/slack.rb b/lib/slack.rb index c0070c47..d0195847 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -1,13 +1,45 @@ #!/usr/bin/env ruby require_relative 'workspace' +OPTIONS = [ + ["1", "list users"], + ["2", "list channels"], + ["3", "quit"], +] + def main puts "Welcome to the Ada Slack CLI!" - workspace = Slack::Workspace.new + #workspace = Slack::Workspace.new + + choice = prompt_and_retrieve + + until OPTIONS.any? { |option| option.include? choice } do + puts "Invalid option, try again." + choice = gets.strip.downcase + end - # TODO project + case choice + when *OPTIONS[0] + puts "chose option 1" + when *OPTIONS[1] + puts "chose option 2" + when *OPTIONS[2] + puts "chose option 3" + end + + puts "Thank you for using the Ada Slack CLI" end +def prompt_and_retrieve + puts "MAIN MENU" + OPTIONS.each do |option| + puts option.join(" ") + end + print "What would you like to do? > " + choice = gets.strip.downcase + return choice +end + main if __FILE__ == $PROGRAM_NAME \ No newline at end of file From 04980015eca3d7ce5dc393e84e1127d6bbcd2df5 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 05:46:07 -0700 Subject: [PATCH 05/29] workspace scaffold: requires, methods, NoRecepientError --- lib/workspace.rb | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/workspace.rb b/lib/workspace.rb index 8f5fed0f..6d953124 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -1,14 +1,42 @@ +require "dotenv" +require "httparty" +require_relative "conversation" +require_relative "user" + +Dotenv.load + +SLACK_TOKEN = ENV["SLACK_TOKEN"] + + module Slack class Workspace - attr_reader :users, :channels + attr_reader :users, :channels, :selected def initialize - @users = [] - @channels = [] + @users = User.get_all + @channels = Channels.get_all # TO-DO: replace this variable with CONVERSATIONS and create methods to list by type of conversation + @selected = nil + end + + # Method selects a user or channel using the name or slack ID. + def select_conversation end + # Method shows details of the currently selected conversation. + def show_details + raise NoRecipientError, "Cannot display details." + end + # Method posts a message to the currently selected conversation. + def post_message + raise NoRecipientError, "Cannot send message." + end + end + class NoRecipientError < StandardError + def initialize(msg="No currently selected user or conversation.") + super + end end end \ No newline at end of file From 9d78303a5b532ef0759a681b75913d9588719d24 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 05:46:42 -0700 Subject: [PATCH 06/29] supporting classes scaffold --- lib/channel.rb | 17 +++++++++++++++++ lib/conversation.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/direct_message.rb | 23 +++++++++++++++++++++++ lib/user.rb | 22 ++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 lib/channel.rb create mode 100644 lib/conversation.rb create mode 100644 lib/direct_message.rb create mode 100644 lib/user.rb diff --git a/lib/channel.rb b/lib/channel.rb new file mode 100644 index 00000000..1127eea3 --- /dev/null +++ b/lib/channel.rb @@ -0,0 +1,17 @@ +module Slack + class Channel > Slack::Conversation + attr_reader :name, :topic, :member_count + + def initialize(channel) + super(id) + @name = + @topic = + @member_count = + @type = :CHANNEL + end + + def self.get_all + # HTTP get + # API endpoint https://slack.com/api/conversations.list default "public_channel" + end +end \ No newline at end of file diff --git a/lib/conversation.rb b/lib/conversation.rb new file mode 100644 index 00000000..441de291 --- /dev/null +++ b/lib/conversation.rb @@ -0,0 +1,42 @@ + + + +module Slack + class Conversation + attr_reader :id + + def initialize(id) + @id = id + @type = nil + end + + # Super method to send messages to selected conversation. + def post_message(message) + # API ENDPOINT: https://slack.com/api/chat.postMessage + + end + + # Placeholder method to be defined in child classes. + # Method will return a bunch of details about specified conversation + def details + raise NotImplementedError, "Define DETAILS method in child class." + end + + + # CLASS METHODS + + # Method uses http get to retrieve all Conversation "objects" + def self.get_all + raise NotImplementedError, "Define GET_ALL method in child class." + # could implement here if we want a list of ALL conversation types at some point + end + + # Method takes the results of get_all and pretty-prints them. + def self.list_all + raise NotImplementedError, "Define LIST_ALL method in child class." + # could implement here if we want a pretty-print of ALL conversation types at some point + # perhaps a "show all convos our app is involved in" feature + end + + end +end \ No newline at end of file diff --git a/lib/direct_message.rb b/lib/direct_message.rb new file mode 100644 index 00000000..6a5aaf0b --- /dev/null +++ b/lib/direct_message.rb @@ -0,0 +1,23 @@ + + +module Slack + class DirectMessage > Slack::Conversation + + def initialize(id, user) + super(id) + @type = :IM + @user = user + end + + + + + # CLASS METHODS + def self.get_all + # TO-DO: implement when getting all 1-on-1 direct messages is necessary + # API endpoint https://slack.com/api/conversations.list TYPES= "im" + end + + + end +end \ No newline at end of file diff --git a/lib/user.rb b/lib/user.rb new file mode 100644 index 00000000..3f3b673f --- /dev/null +++ b/lib/user.rb @@ -0,0 +1,22 @@ +module Slack + class User + + def initialize(user) + @id + @user_name + @real_name + end + + + # CLASS METHODS + + def self.list_all + # httparty GET + # API endpoint https://slack.com/api/users.list? + end + + + + + end +end \ No newline at end of file From b726618a5b1e49e14a91787b02bcbaee111b1313 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 06:05:28 -0700 Subject: [PATCH 07/29] scaffolding cleanup --- lib/channel.rb | 34 +++++++++++++++++++++++----------- lib/conversation.rb | 3 --- lib/direct_message.rb | 11 ++++++++--- lib/slack.rb | 6 ++---- lib/user.rb | 8 ++++++-- lib/workspace.rb | 4 ++-- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/lib/channel.rb b/lib/channel.rb index 1127eea3..fb40866f 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -1,17 +1,29 @@ +require_relative "conversation.rb" + module Slack - class Channel > Slack::Conversation + class Channel < Conversation attr_reader :name, :topic, :member_count - def initialize(channel) - super(id) - @name = - @topic = - @member_count = - @type = :CHANNEL - end + def initialize(channel) + super(channel) + @name = + @topic = + @member_count = + @type = :CHANNEL + end + + + # Method uses http get to retrieve all Channel "objects" + # to-do: make it a private method + def self.get_all + # HTTP get + # API endpoint https://slack.com/api/conversations.list default "public_channel" + end + + + # Method gets results of self.get_all and pretty prints it + def self.list_all - def self.get_all - # HTTP get - # API endpoint https://slack.com/api/conversations.list default "public_channel" + end end end \ No newline at end of file diff --git a/lib/conversation.rb b/lib/conversation.rb index 441de291..8de5db26 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -1,6 +1,3 @@ - - - module Slack class Conversation attr_reader :id diff --git a/lib/direct_message.rb b/lib/direct_message.rb index 6a5aaf0b..115aec81 100644 --- a/lib/direct_message.rb +++ b/lib/direct_message.rb @@ -1,7 +1,7 @@ - +require_relative "conversation" module Slack - class DirectMessage > Slack::Conversation + class DirectMessage < Conversation def initialize(id, user) super(id) @@ -9,15 +9,20 @@ def initialize(id, user) @user = user end - # CLASS METHODS + + def self.get_all # TO-DO: implement when getting all 1-on-1 direct messages is necessary # API endpoint https://slack.com/api/conversations.list TYPES= "im" end + def self.list_all + # TO-DO: implement when listing all 1-on-1 direct messages is necessary + end + end end \ No newline at end of file diff --git a/lib/slack.rb b/lib/slack.rb index d0195847..e9ee6efb 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -9,12 +9,12 @@ def main puts "Welcome to the Ada Slack CLI!" - #workspace = Slack::Workspace.new + workspace = Slack::Workspace.new choice = prompt_and_retrieve until OPTIONS.any? { |option| option.include? choice } do - puts "Invalid option, try again." + print "'#{choice}' is an invalid option, try again. > " choice = gets.strip.downcase end @@ -27,8 +27,6 @@ def main puts "chose option 3" end - - puts "Thank you for using the Ada Slack CLI" end diff --git a/lib/user.rb b/lib/user.rb index 3f3b673f..a65ad78f 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -10,13 +10,17 @@ def initialize(user) # CLASS METHODS - def self.list_all + # Method uses http get to retrieve all User "objects" + # TO-DO probably should be a private method??? + def self.get_all # httparty GET # API endpoint https://slack.com/api/users.list? end + # Method takes the results of get_all and pretty-prints them. + def self.list_all - + end end end \ No newline at end of file diff --git a/lib/workspace.rb b/lib/workspace.rb index 6d953124..c1ec5004 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -1,6 +1,6 @@ require "dotenv" require "httparty" -require_relative "conversation" +require_relative "channel" require_relative "user" Dotenv.load @@ -14,7 +14,7 @@ class Workspace def initialize @users = User.get_all - @channels = Channels.get_all # TO-DO: replace this variable with CONVERSATIONS and create methods to list by type of conversation + @channels = Channel.get_all # TO-DO: replace this variable with CONVERSATIONS and create methods to list by type of conversation @selected = nil end From 88ff39891d46147057cdc86b7a927b9b984bba81 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 07:05:22 -0700 Subject: [PATCH 08/29] pseudocode for tests --- test/channel_test.rb | 27 +++++++++++++++++++++++++++ test/conversation_test.rb | 29 +++++++++++++++++++++++++++++ test/direct_message_test.rb | 13 +++++++++++++ test/test_helper.rb | 6 ++++++ test/user_test.rb | 18 ++++++++++++++++++ test/workspace_test.rb | 28 ++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+) diff --git a/test/channel_test.rb b/test/channel_test.rb index e69de29b..84a44901 100644 --- a/test/channel_test.rb +++ b/test/channel_test.rb @@ -0,0 +1,27 @@ +require_relative "test_helper" +require_relative "../lib/channel" + +describe "channel" do + + describe "initialize" do + # it creates a channel object + # instance has an ID + # instance has a name + # instance has a topic + # instance has a member count + # instance type is :CHANNEL + end + + describe "get_all" do + # must raise exception when endpoint returns failure + # returns an array of channels + # array of channels must be of expected length + # one of the channels must be the General channel + end + + describe "list_all" do + # regex matches the General channel details + end + + +end \ No newline at end of file diff --git a/test/conversation_test.rb b/test/conversation_test.rb index e69de29b..dec094a7 100644 --- a/test/conversation_test.rb +++ b/test/conversation_test.rb @@ -0,0 +1,29 @@ +require_relative "test_helper" +require_relative "../lib/conversation" + +describe "conversation" do + + describe "initialize" do + # creates conversation object + # sets ID + end + + describe "post_message" do + # ok must equal true + # raises InvalidRecipientError, no valid conversation with specified ID + end + + describe "details" do + # raises NotImplementedError + end + + describe "self.get_all" do + # raises NotImplementedError + end + + describe "self.list_all" do + # raises NotImplementedError + end + + +end \ No newline at end of file diff --git a/test/direct_message_test.rb b/test/direct_message_test.rb index e69de29b..827c8f5f 100644 --- a/test/direct_message_test.rb +++ b/test/direct_message_test.rb @@ -0,0 +1,13 @@ +require_relative "test_helper" +require_relative "../lib/direct_message" + +describe "direct message" do + + describe "initialize" do + # creates a direct message object + # type is :IM + # has an ID + end + + +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 7cbed8b6..26b2fac5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,6 +9,12 @@ require 'minitest/skip_dsl' require 'vcr' +require_relative '../lib/workspace' +require_relative '../lib/user' +require_relative '../lib/conversation' +require_relative '../lib/channel' +require_relative '../lib/direct_message' + Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new VCR.configure do |config| diff --git a/test/user_test.rb b/test/user_test.rb index e69de29b..f4c153c2 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -0,0 +1,18 @@ +require_relative "test_helper" +require_relative "../lib/user" + +describe "user" do + describe "initialize" do + # creates a user object + # user returns an id + # user has a user_name + end + + describe "self.get_all" do + # raise exception for bad endpoint response + end + + describe "self.list_all" do + # one of the users must be as expected + end +end \ No newline at end of file diff --git a/test/workspace_test.rb b/test/workspace_test.rb index e69de29b..672f332c 100644 --- a/test/workspace_test.rb +++ b/test/workspace_test.rb @@ -0,0 +1,28 @@ +require_relative "test_helper" +require_relative "../lib/workspace" + +describe "workspace" do + + describe "initialize" do + # raises error for bad endpoint response + # creates a workspace object + # @users is an array of user objects + # @channels is an array of channel objects + # @selected is initialized to nil + end + + describe "select conversation" do + # raises an error when given invalid convo ID + # sets @selected to the specified conversation + end + + describe "show details" do + # raises an error when given invalid convo ID + end + + describe "post message" do + # raises an error when given invalid convo ID + end + + +end \ No newline at end of file From d1c15775f17c2187fa34b0ce21d350ced4317d0b Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 07:05:57 -0700 Subject: [PATCH 09/29] pseudocode for classes --- lib/channel.rb | 2 +- lib/conversation.rb | 4 +++- lib/direct_message.rb | 8 ++++---- lib/user.rb | 1 - lib/workspace.rb | 8 ++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/channel.rb b/lib/channel.rb index fb40866f..4e4bcba1 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -14,7 +14,7 @@ def initialize(channel) # Method uses http get to retrieve all Channel "objects" - # to-do: make it a private method + # returns an array of Channels def self.get_all # HTTP get # API endpoint https://slack.com/api/conversations.list default "public_channel" diff --git a/lib/conversation.rb b/lib/conversation.rb index 8de5db26..14f10e09 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -15,6 +15,8 @@ def post_message(message) # Placeholder method to be defined in child classes. # Method will return a bunch of details about specified conversation + # Should probably implement in child classes so we can control the description to be user-friendly + # but if we're ok with a generic details printout, we can do it at parent Conversation level too def details raise NotImplementedError, "Define DETAILS method in child class." end @@ -31,7 +33,7 @@ def self.get_all # Method takes the results of get_all and pretty-prints them. def self.list_all raise NotImplementedError, "Define LIST_ALL method in child class." - # could implement here if we want a pretty-print of ALL conversation types at some point + # could implement here if we want to see ALL conversation types at some point # perhaps a "show all convos our app is involved in" feature end diff --git a/lib/direct_message.rb b/lib/direct_message.rb index 115aec81..b6dd71bf 100644 --- a/lib/direct_message.rb +++ b/lib/direct_message.rb @@ -14,15 +14,15 @@ def initialize(id, user) # CLASS METHODS - def self.get_all + # def self.get_all # TO-DO: implement when getting all 1-on-1 direct messages is necessary # API endpoint https://slack.com/api/conversations.list TYPES= "im" - end + # end - def self.list_all + # def self.list_all # TO-DO: implement when listing all 1-on-1 direct messages is necessary - end + # end end end \ No newline at end of file diff --git a/lib/user.rb b/lib/user.rb index a65ad78f..1150af86 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -11,7 +11,6 @@ def initialize(user) # CLASS METHODS # Method uses http get to retrieve all User "objects" - # TO-DO probably should be a private method??? def self.get_all # httparty GET # API endpoint https://slack.com/api/users.list? diff --git a/lib/workspace.rb b/lib/workspace.rb index c1ec5004..cf6ca008 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -24,18 +24,18 @@ def select_conversation # Method shows details of the currently selected conversation. def show_details - raise NoRecipientError, "Cannot display details." + raise InvalidRecipientError, "No selection made yet. User must have a selected conversation." end # Method posts a message to the currently selected conversation. def post_message - raise NoRecipientError, "Cannot send message." + raise InvalidRecipientError, "No target conversation specified. Cannot send message." end end - class NoRecipientError < StandardError - def initialize(msg="No currently selected user or conversation.") + class InvalidRecipientError < StandardError + def initialize(msg="No valid user or conversation.") super end end From 995cccd13a42c67594089b5d3cff762944ed2f53 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Mon, 16 Mar 2020 22:53:17 -0700 Subject: [PATCH 10/29] workspace tests written, 9/10 fail --- lib/workspace.rb | 3 ++- test/workspace_test.rb | 58 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/workspace.rb b/lib/workspace.rb index cf6ca008..5851d7af 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -1,5 +1,6 @@ require "dotenv" require "httparty" +require_relative "direct_message" require_relative "channel" require_relative "user" @@ -19,7 +20,7 @@ def initialize end # Method selects a user or channel using the name or slack ID. - def select_conversation + def select_by_id(id, type) end # Method shows details of the currently selected conversation. diff --git a/test/workspace_test.rb b/test/workspace_test.rb index 672f332c..827000b6 100644 --- a/test/workspace_test.rb +++ b/test/workspace_test.rb @@ -2,27 +2,63 @@ require_relative "../lib/workspace" describe "workspace" do + before do + @test_workspace = Slack::Workspace.new + end describe "initialize" do - # raises error for bad endpoint response - # creates a workspace object - # @users is an array of user objects - # @channels is an array of channel objects - # @selected is initialized to nil + + it "creates a workspace object" do + expect(@test_workspace).must_be_kind_of Slack::Workspace + end + + it "returns @users as an array" do + expect(@test_workspace.users).must_be_kind_of Array + end + + it "contains only User objects in @user" do + expect(@test_workspace.users.all? { |user| user.kind_of? Slack::User}).must_equal true + end + + it "returns @channels as an array" do + expect(@test_workspace.channels).must_be_kind_of Array + end + + it "contains only Channel objects in @channels" do + expect(@test_workspace.channels.all? { |channel| channel.kind_of? Slack::Channel}).must_equal true + end + + it "initializes @selected to nil" do + expect(@test_workspace.selected).must_be nil + end + end - describe "select conversation" do - # raises an error when given invalid convo ID - # sets @selected to the specified conversation + describe "select_by_id" do + it "raises an error when given invalid ID" do + expect{(@test_workspace.select_by_id("bogus_user_id", :USER))}.must_raise ArgumentError + expect{(@test_workspace.select_by_id("bogus_channel_id", :CHANNEL))}.must_raise ArgumentError + end + + it "sets @selected to the specified object" do + @test_workspace.select_by_id(SLACKBOT ID HERE, :USER) + expect(@test_workspace.selected).must_be_kind_of Slack::User + + @test_workspace.select_by_id(GENERAL CHANNEL ID, :CHANNEL) + expect(@test_workspace.selected).must_be_kind_of Slack::Channel + end end describe "show details" do - # raises an error when given invalid convo ID + it "raises an error when invalid @selected is present" do + expect{(@test_workspace.show_details)}.must_raise ArgumentError + end end describe "post message" do - # raises an error when given invalid convo ID + it "raises an error when invalid @selected is present" do + expect{(@test_workspace.post_message)}.must_raise ArgumentError + end end - end \ No newline at end of file From e480f3c50d57294467003b1c2c1a3448c16df094 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 01:32:20 -0700 Subject: [PATCH 11/29] added User init method tests and code --- lib/user.rb | 36 ++++++++++++++++++++++++++---------- test/user_test.rb | 44 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/user.rb b/lib/user.rb index 1150af86..c355d5a7 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -1,25 +1,41 @@ +require "dotenv" +require "httparty" + +Dotenv.load + +SLACK_TOKEN = ENV["SLACK_TOKEN"] + module Slack class User + attr_reader :id, :user_name, :real_name - def initialize(user) - @id - @user_name + def initialize(member) + @id = member["id"] + @user_name = member[""] @real_name end # CLASS METHODS - # Method uses http get to retrieve all User "objects" - def self.get_all - # httparty GET - # API endpoint https://slack.com/api/users.list? - end - - # Method takes the results of get_all and pretty-prints them. + # Parameter users: collection representing Users + # Returns an array of User objects + def self.list_all + members = get_all + members.map { |member| User.new(member) } + end + private + + # Method uses http get to retrieve all User "objects" + # returns an httparty Response object + def self.get_all + data = httparty.get(https://slack.com/api/users.list?, query: { token: SLACK_TOKEN, }) + raise StandardError, "Users.list endpoint response IS NOT OK." + return data["members"] + end end end \ No newline at end of file diff --git a/test/user_test.rb b/test/user_test.rb index f4c153c2..72f370f8 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -3,13 +3,51 @@ describe "user" do describe "initialize" do - # creates a user object - # user returns an id - # user has a user_name + before do + test_member = + { + "id": "UV614256C", + "team_id": "TV5H57Z7E", + "name": "angethuy", + "is_bot": false, + "color": "9f69e7", + "real_name": "Angela", + } + user = User.new(test_member) + end + + it "raises an exception when trying to create a User with bad parameters" do + expect{(User.new("pumpkin pie"))}.must_raise ArgumentError + end + + it "creates a User object" do + expect(user).is_kind_of? Slack::User + end + + it "returns a slack id" do + expect(user.id).must_equal "UV614256C" + end + + it "returns a username" do + expect(user.name).must_equal "angethuy" + end + + it "returns a real name" do + expect(user.real_name).must_equal "Angela" + end end describe "self.get_all" do # raise exception for bad endpoint response + # API endpoint https://slack.com/api/users.list? + it "successfully receives a response from the users.list endpoint" do + users = {} + VCR.use_cassette(users_list_endpoint) do + users = User.get_all + end + + expect(users) + end end describe "self.list_all" do From 960835844e7a09496a2084174a550071674f1688 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 01:33:26 -0700 Subject: [PATCH 12/29] swapped Channels out for Conversations --- lib/workspace.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/workspace.rb b/lib/workspace.rb index 5851d7af..34da9c05 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -4,18 +4,13 @@ require_relative "channel" require_relative "user" -Dotenv.load - -SLACK_TOKEN = ENV["SLACK_TOKEN"] - - module Slack class Workspace - attr_reader :users, :channels, :selected + attr_reader :users, :conversations, :selected def initialize @users = User.get_all - @channels = Channel.get_all # TO-DO: replace this variable with CONVERSATIONS and create methods to list by type of conversation + @conversations = Conversation.get_all @selected = nil end From 414379655f57b854e275b02c0ad8e01986ef56a4 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 02:27:27 -0700 Subject: [PATCH 13/29] user_test for methods init and get_all passing --- lib/user.rb | 16 +++++++--------- test/user_test.rb | 45 ++++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/lib/user.rb b/lib/user.rb index c355d5a7..6f577e73 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -10,22 +10,20 @@ class User attr_reader :id, :user_name, :real_name def initialize(member) - @id = member["id"] - @user_name = member[""] - @real_name + raise ArgumentError, "Trying to create User object with bad data: #{member}." if member[:id] == nil + @id = member[:id] + @user_name = member[:name] + @real_name = member[:real_name] end # CLASS METHODS # Parameter users: collection representing Users - # Returns an array of User objects - + # Returns an array of User objects def self.list_all members = get_all members.map { |member| User.new(member) } - - end private @@ -33,8 +31,8 @@ def self.list_all # Method uses http get to retrieve all User "objects" # returns an httparty Response object def self.get_all - data = httparty.get(https://slack.com/api/users.list?, query: { token: SLACK_TOKEN, }) - raise StandardError, "Users.list endpoint response IS NOT OK." + data = HTTParty.get("https://slack.com/api/users.list?", query: { token: SLACK_TOKEN, }) + raise StandardError, "Users.list endpoint response IS NOT OK." unless data["ok"] return data["members"] end end diff --git a/test/user_test.rb b/test/user_test.rb index 72f370f8..e1b0e464 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -2,55 +2,54 @@ require_relative "../lib/user" describe "user" do - describe "initialize" do - before do - test_member = - { - "id": "UV614256C", - "team_id": "TV5H57Z7E", - "name": "angethuy", - "is_bot": false, - "color": "9f69e7", - "real_name": "Angela", - } - user = User.new(test_member) - end + before do + member = { "id": "UV614256C", "team_id": "TV5H57Z7E", "name": "angethuy", "is_bot": false, "color": "9f69e7", "real_name": "Angela", } + @user = Slack::User.new(member) + end + + describe "initialize" do it "raises an exception when trying to create a User with bad parameters" do - expect{(User.new("pumpkin pie"))}.must_raise ArgumentError + expect{(Slack::User.new({"pie": "pumpkin"}))}.must_raise ArgumentError end it "creates a User object" do - expect(user).is_kind_of? Slack::User + expect(@user).must_be_kind_of Slack::User end it "returns a slack id" do - expect(user.id).must_equal "UV614256C" + expect(@user.id).must_equal "UV614256C" end it "returns a username" do - expect(user.name).must_equal "angethuy" + expect(@user.user_name).must_equal "angethuy" end it "returns a real name" do - expect(user.real_name).must_equal "Angela" + expect(@user.real_name).must_equal "Angela" end end describe "self.get_all" do + + + # raise exception for bad endpoint response # API endpoint https://slack.com/api/users.list? it "successfully receives a response from the users.list endpoint" do - users = {} - VCR.use_cassette(users_list_endpoint) do - users = User.get_all + @users = {} + + VCR.use_cassette("users_list_endpoint") do + @users = Slack::User.get_all end - expect(users) + expect(@users).must_be_kind_of Array end end describe "self.list_all" do - # one of the users must be as expected + # VCR.use_cassette("users_list_endpoint") do + # @users = Slack::User.list_all + # end end end \ No newline at end of file From c3138f1bc4cc035cd127a5454b98821f74cd65cf Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 02:37:31 -0700 Subject: [PATCH 14/29] all tests and methods for user passing --- lib/user.rb | 9 ++++----- test/user_test.rb | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/user.rb b/lib/user.rb index 6f577e73..4795f48d 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -10,13 +10,12 @@ class User attr_reader :id, :user_name, :real_name def initialize(member) - raise ArgumentError, "Trying to create User object with bad data: #{member}." if member[:id] == nil - @id = member[:id] - @user_name = member[:name] - @real_name = member[:real_name] + raise ArgumentError, "Trying to create User object with bad data: #{member}." if member["id"] == nil + @id = member["id"] + @user_name = member["name"] + @real_name = member["real_name"] end - # CLASS METHODS # Parameter users: collection representing Users diff --git a/test/user_test.rb b/test/user_test.rb index e1b0e464..69d4e5de 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -4,7 +4,7 @@ describe "user" do before do - member = { "id": "UV614256C", "team_id": "TV5H57Z7E", "name": "angethuy", "is_bot": false, "color": "9f69e7", "real_name": "Angela", } + member = {"id"=>"USLACKBOT", "team_id"=>"TV5H57Z7E", "name"=>"slackbot", "deleted"=>false, "color"=>"757575", "real_name"=>"Slackbot",} @user = Slack::User.new(member) end @@ -18,38 +18,40 @@ end it "returns a slack id" do - expect(@user.id).must_equal "UV614256C" + expect(@user.id).must_equal "USLACKBOT" end it "returns a username" do - expect(@user.user_name).must_equal "angethuy" + expect(@user.user_name).must_equal "slackbot" end it "returns a real name" do - expect(@user.real_name).must_equal "Angela" + expect(@user.real_name).must_equal "Slackbot" end end describe "self.get_all" do - - - - # raise exception for bad endpoint response - # API endpoint https://slack.com/api/users.list? it "successfully receives a response from the users.list endpoint" do - @users = {} + users = {} VCR.use_cassette("users_list_endpoint") do - @users = Slack::User.get_all + users = Slack::User.get_all end - - expect(@users).must_be_kind_of Array + + expect(users).must_be_kind_of Array end end describe "self.list_all" do - # VCR.use_cassette("users_list_endpoint") do - # @users = Slack::User.list_all - # end + it "successfully converts data in User objects" do + results = nil + + VCR.use_cassette("users_list_endpoint") do + results = Slack::User.list_all + end + + expect(results).must_be_kind_of Array + expect(results.all? { |result| result.class == Slack::User } ).must_equal true + end end end \ No newline at end of file From 429d1bc3f6e5cbf57084de1c8b2fdc468d6f6e5f Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 02:47:33 -0700 Subject: [PATCH 15/29] fixed bug caused by trying to process deleted users --- lib/user.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/user.rb b/lib/user.rb index 4795f48d..05ffede3 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -10,7 +10,7 @@ class User attr_reader :id, :user_name, :real_name def initialize(member) - raise ArgumentError, "Trying to create User object with bad data: #{member}." if member["id"] == nil + raise ArgumentError, "Trying to create User object with bad data: #{member}." if member["id"] == nil || member["name"] == nil || member["real_name"] == nil @id = member["id"] @user_name = member["name"] @real_name = member["real_name"] @@ -21,7 +21,8 @@ def initialize(member) # Parameter users: collection representing Users # Returns an array of User objects def self.list_all - members = get_all + members_including_deleted = get_all + members = members_including_deleted.reject { |member| member["deleted"] == true } #cover a wonky case members.map { |member| User.new(member) } end From 6573a1b93f77db2807f20224e0cb4a29c4b1f3c1 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 03:11:44 -0700 Subject: [PATCH 16/29] upgraded CLI to prompt for input until user asks to exit --- lib/slack.rb | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/slack.rb b/lib/slack.rb index e9ee6efb..e0d20ef3 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -1,5 +1,6 @@ #!/usr/bin/env ruby require_relative 'workspace' +require "table_print" OPTIONS = [ ["1", "list users"], @@ -9,35 +10,49 @@ def main puts "Welcome to the Ada Slack CLI!" - workspace = Slack::Workspace.new + @workspace = Slack::Workspace.new - choice = prompt_and_retrieve + #MAIN LOOP + choice = get_user_input + until (OPTIONS[-1].include? choice) + perform_action(choice) + choice = get_user_input + end + puts "\n>>>>>> Thank you for using the Slack CLI! Goodbye." +end + +def perform_action(choice) - until OPTIONS.any? { |option| option.include? choice } do + until OPTIONS.any? { |option| option.include? choice } print "'#{choice}' is an invalid option, try again. > " choice = gets.strip.downcase end - + case choice - when *OPTIONS[0] - puts "chose option 1" - when *OPTIONS[1] - puts "chose option 2" - when *OPTIONS[2] - puts "chose option 3" + when *OPTIONS[0] + puts "\n\n>>>>>>> LIST OF USERS" + tp @workspace.users + when *OPTIONS[1] + puts "\n\n>>>>>>> LIST OF CHANNELS" + puts "I'll get you those channels one day." end - puts "Thank you for using the Ada Slack CLI" end -def prompt_and_retrieve - puts "MAIN MENU" +def get_user_input + puts "\n\nMAIN MENU - please select from the following" + OPTIONS.each do |option| puts option.join(" ") end - print "What would you like to do? > " + + print "\nWhat would you like to do? > " choice = gets.strip.downcase return choice end -main if __FILE__ == $PROGRAM_NAME \ No newline at end of file + +main if __FILE__ == $PROGRAM_NAME + + + From 7567b9f4ff9994395f5058ca63723b9603f079d1 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 17 Mar 2020 03:17:19 -0700 Subject: [PATCH 17/29] removed overlapping tests in workspace --- lib/workspace.rb | 7 ++++--- test/workspace_test.rb | 28 +++++++--------------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/lib/workspace.rb b/lib/workspace.rb index 34da9c05..47880f7a 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -6,11 +6,12 @@ module Slack class Workspace - attr_reader :users, :conversations, :selected + # attr_reader :users, :conversations, :selected + attr_reader :users, :selected def initialize - @users = User.get_all - @conversations = Conversation.get_all + @users = User.list_all + #@conversations = Conversation.list_all @selected = nil end diff --git a/test/workspace_test.rb b/test/workspace_test.rb index 827000b6..d16ca668 100644 --- a/test/workspace_test.rb +++ b/test/workspace_test.rb @@ -3,38 +3,24 @@ describe "workspace" do before do + VCR.use_cassette("users_list_endpoint") do @test_workspace = Slack::Workspace.new + end end describe "initialize" do it "creates a workspace object" do - expect(@test_workspace).must_be_kind_of Slack::Workspace - end - - it "returns @users as an array" do - expect(@test_workspace.users).must_be_kind_of Array - end - - it "contains only User objects in @user" do - expect(@test_workspace.users.all? { |user| user.kind_of? Slack::User}).must_equal true - end - - it "returns @channels as an array" do - expect(@test_workspace.channels).must_be_kind_of Array + expect(@test_workspace).must_be_kind_of Slack::Workspace end - it "contains only Channel objects in @channels" do - expect(@test_workspace.channels.all? { |channel| channel.kind_of? Slack::Channel}).must_equal true - end - it "initializes @selected to nil" do - expect(@test_workspace.selected).must_be nil + expect(@test_workspace.selected).must_equal nil end end - describe "select_by_id" do + xdescribe "select_by_id" do it "raises an error when given invalid ID" do expect{(@test_workspace.select_by_id("bogus_user_id", :USER))}.must_raise ArgumentError expect{(@test_workspace.select_by_id("bogus_channel_id", :CHANNEL))}.must_raise ArgumentError @@ -49,13 +35,13 @@ end end - describe "show details" do + xdescribe "show details" do it "raises an error when invalid @selected is present" do expect{(@test_workspace.show_details)}.must_raise ArgumentError end end - describe "post message" do + xdescribe "post message" do it "raises an error when invalid @selected is present" do expect{(@test_workspace.post_message)}.must_raise ArgumentError end From 3657bfb35312cdfb35c09811d52ccd53377d0ff7 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 02:20:55 -0700 Subject: [PATCH 18/29] channel tests filled out --- test/channel_test.rb | 100 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/test/channel_test.rb b/test/channel_test.rb index 84a44901..9515b251 100644 --- a/test/channel_test.rb +++ b/test/channel_test.rb @@ -2,26 +2,100 @@ require_relative "../lib/channel" describe "channel" do + before do + channel_data = { + "id" => "CUT7CLL1Y", + "name" => "general", + "is_channel" => true, + "is_group" => false, + "is_im" => false, + "created" => 1583866138, + "is_archived" => false, + "is_general" => true, + "unlinked" => 0, + "name_normalized" => "general", + "is_shared" => false, + "creator" => "UV614256C", + "is_ext_shared" => false, + "is_org_shared" => false, + "shared_team_ids" => [ + "TV5H57Z7E" + ], + "pending_shared" => [], + "pending_connected_team_ids" => [], + "is_pending_ext_shared" => false, + "is_member" => false, + "is_private" => false, + "is_mpim" => false, + "topic" => { + "value" => "Company-wide announcements and work-based matters", + "creator" => "UV614256C", + "last_set" => 1583866138 + }, + "purpose" => { + "value" => "This channel is for workspace-wide communication and announcements. All members are in this channel.", + "creator" => "UV614256C", + "last_set" => 1583866138 + }, + "previous_names" => [], + "num_members" => 9 + } + @channel = Slack::Channel.new(channel_data) + end describe "initialize" do - # it creates a channel object - # instance has an ID - # instance has a name - # instance has a topic - # instance has a member count - # instance type is :CHANNEL + it "raises an exception when trying to create a User with bad parameters" do + expect{(Slack::Channel.new({"id"=>"USLACKBOT", "team_id"=>"TV5H57Z7E", "name"=>"slackbot"}))}.must_raise ArgumentError + end + + it "creates a new Channel instance" do + expect(@channel).must_be_kind_of Slack::Channel + end + + it "saves id to the Channel instance" do + expect(@channel.id).must_equal "CUT7CLL1Y" + end + + it "saves name to the Channel instance" do + expect(@channel.name).must_equal "general" + end + + it "saves topic to the Channel instance" do + expect(@channel.topic).must_equal "Company-wide announcements and work-based matters" + end + + it "has an accurate count of members" do + expect(@channel.member_count).must_equal 9 + end end describe "get_all" do - # must raise exception when endpoint returns failure - # returns an array of channels - # array of channels must be of expected length - # one of the channels must be the General channel + # returns a non-empty array of channels + it "returns an array of channel data" do + VCR.use_cassette("conversations_list_endpoint") do + data = Slack::Channel.get_all + expect(data).must_be_kind_of Array + expect(data.length > 0).must_equal true + end + end end describe "list_all" do - # regex matches the General channel details - end - + # one of the channels must be the General channel + it "returns an array of Channel objects" do + VCR.use_cassette("conversations_list_endpoint") do + results = Slack::Channel.list_all + expect(results).must_be_kind_of Array + expect(results.all? { |result| result.class == Slack::Channel } ).must_equal true + end + end + it "has a General channel" do + VCR.use_cassette("conversations_list_endpoint") do + results = Slack::Channel.list_all + expect(results.any? { |result| result.name == "general" } ).must_equal true + end + end + end + end \ No newline at end of file From 4488f9df017feaec769a8cf6c5d17472409065ec Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 02:42:44 -0700 Subject: [PATCH 19/29] filled out tests for direct messages --- test/direct_message_test.rb | 61 +++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/test/direct_message_test.rb b/test/direct_message_test.rb index 827c8f5f..8dfcc06f 100644 --- a/test/direct_message_test.rb +++ b/test/direct_message_test.rb @@ -2,11 +2,66 @@ require_relative "../lib/direct_message" describe "direct message" do + before do + dm = { + "id" => "DUYS4LFM0", + "created" => 1584065406, + "is_archived" => false, + "is_im" => true, + "is_org_shared" => false, + "user" => "USLACKBOT", + "is_user_deleted" => false, + "priority" => 0, + } + @direct_message = Slack::DirectMessage.new(dm) + + end describe "initialize" do - # creates a direct message object - # type is :IM - # has an ID + + it "creates a direct message object" do + expect(@direct_message).must_be_kind_of Slack::DirectMessage + end + + it "stores the direct message's ID" do + expect(@direct_message.id).must_equal "DUYS4LFM0" + end + + it "stores the direct message's receiving user" do + expect(@direct_message.user).must_equal "USLACKBOT" + end + + end + + describe "list_all" do + it "returns an array of DirectMessage objects" do + VCR.use_cassette("conversations_list_direct_messages_endpoint") do + results = Slack::DirectMessage.list_all + expect(results).must_be_kind_of Array + expect(results.all? { |result| result.class == Slack::DirectMessage } ).must_equal true + end + end + + end + + describe "get_all" do + + it "returns an array of Channel objects" do + VCR.use_cassette("conversations_list_direct_messages_endpoint") do + results = Slack::DirectMessage.list_all + expect(results).must_be_kind_of Array + expect(results.all? { |result| result.class == Slack::DirectMessage } ).must_equal true + end + end + + # one of the Direct Messages must be with Slackbot + it "has a DirectMessage with the user USLACKBOT" do + VCR.use_cassette("conversations_list_direct_messages_endpoint") do + direct_messages = Slack::DirectMessage.list_all + expect(results.any? { |result| result.user == "USLACKBOT" } ).must_equal true + end + end + end From 4c8dd5bbafe654bfbab519b2cab2465eda155835 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 02:43:26 -0700 Subject: [PATCH 20/29] changed user tests --- test/user_test.rb | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/user_test.rb b/test/user_test.rb index 69d4e5de..ce9b5d6f 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -32,26 +32,35 @@ describe "self.get_all" do it "successfully receives a response from the users.list endpoint" do - users = {} - VCR.use_cassette("users_list_endpoint") do users = Slack::User.get_all + expect(users).must_be_kind_of Array + expect(users.length > 0).must_equal true end - - expect(users).must_be_kind_of Array end + + it "raises an argument when users.list endpoint struggles" do + # VCR.use_cassette("users_list_endpoint_failure") do + # expect{(Slack::User.get_all)}.must_raise Slack::BadResponseError + # end + end + end describe "self.list_all" do + it "raises an argument when users.list endpoint struggles" do + # VCR.use_cassette("users_list_endpoint_failure") do + # expect{(Slack::User.get_all)}.must_raise Slack::BadResponseError + # end + end it "successfully converts data in User objects" do - results = nil VCR.use_cassette("users_list_endpoint") do results = Slack::User.list_all + expect(results).must_be_kind_of Array + expect(results.all? { |result| result.class == Slack::User } ).must_equal true end - expect(results).must_be_kind_of Array - expect(results.all? { |result| result.class == Slack::User } ).must_equal true end end end \ No newline at end of file From c61e5d11977699986de77c5b447aebd93e342a36 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 02:44:25 -0700 Subject: [PATCH 21/29] new bad_reponse class --- lib/bad_response_error.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/bad_response_error.rb diff --git a/lib/bad_response_error.rb b/lib/bad_response_error.rb new file mode 100644 index 00000000..c5d91cd7 --- /dev/null +++ b/lib/bad_response_error.rb @@ -0,0 +1,7 @@ +module Slack + class BadResponseError < StandardError + def initialize(msg="Slack API endpoint is NOT OK.") + super + end + end +end \ No newline at end of file From c97c78632af1c00f9b949ecb8c051c1d155a2c48 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 03:23:40 -0700 Subject: [PATCH 22/29] channels now working --- lib/channel.rb | 34 ++-- lib/conversation.rb | 31 +-- lib/direct_message.rb | 34 ++-- lib/slack.rb | 3 +- lib/user.rb | 4 +- lib/workspace.rb | 12 +- ...rsations_list_direct_messages_endpoint.yml | 85 +++++++++ .../cassettes/conversations_list_endpoint.yml | 85 +++++++++ test/cassettes/users_list_endpoint.yml | 180 ++++++++++++++++++ test/direct_message_test.rb | 2 +- test/user_test.rb | 2 +- test/workspace_test.rb | 2 +- 12 files changed, 423 insertions(+), 51 deletions(-) create mode 100644 test/cassettes/conversations_list_direct_messages_endpoint.yml create mode 100644 test/cassettes/conversations_list_endpoint.yml create mode 100644 test/cassettes/users_list_endpoint.yml diff --git a/lib/channel.rb b/lib/channel.rb index 4e4bcba1..e961ea47 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -2,28 +2,36 @@ module Slack class Channel < Conversation - attr_reader :name, :topic, :member_count + attr_reader :id, :name, :topic, :member_count def initialize(channel) + raise ArgumentError, "Trying to create Channel object with bad data: #{channel}." if channel["id"] == nil || !(channel["is_channel"]) super(channel) - @name = - @topic = - @member_count = - @type = :CHANNEL + @name = channel["name"] + @topic = channel["topic"]["value"] + @member_count = channel["num_members"] end + # def details + # # return string describing: + # # channel name + # # channel topic + # # member_count + # end - # Method uses http get to retrieve all Channel "objects" - # returns an array of Channels - def self.get_all - # HTTP get - # API endpoint https://slack.com/api/conversations.list default "public_channel" - end - - # Method gets results of self.get_all and pretty prints it + # Method takes raw Channel data and converts it into an array of Channel objects. def self.list_all + channels = super.map { |channel| Channel.new(channel)} + end + private + + # Method uses http get to retrieve all Channel "objects" + # returns an array of Channels + def self.get_all + return super["channels"] end + end end \ No newline at end of file diff --git a/lib/conversation.rb b/lib/conversation.rb index 14f10e09..027423e7 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -1,16 +1,16 @@ +require_relative "bad_response_error" + module Slack class Conversation - attr_reader :id - def initialize(id) - @id = id - @type = nil + def initialize(data) + @id = data["id"] #template end # Super method to send messages to selected conversation. def post_message(message) # API ENDPOINT: https://slack.com/api/chat.postMessage - + # query: @id end # Placeholder method to be defined in child classes. @@ -24,17 +24,20 @@ def details # CLASS METHODS - # Method uses http get to retrieve all Conversation "objects" - def self.get_all - raise NotImplementedError, "Define GET_ALL method in child class." - # could implement here if we want a list of ALL conversation types at some point + def self.list_all + data = get_all + # Template, extend this method in child classes. end - # Method takes the results of get_all and pretty-prints them. - def self.list_all - raise NotImplementedError, "Define LIST_ALL method in child class." - # could implement here if we want to see ALL conversation types at some point - # perhaps a "show all convos our app is involved in" feature + private + + def self.get_all + query = { + token: SLACK_TOKEN, + } + data = HTTParty.get("https://slack.com/api/conversations.list?", query: query) + raise BadResponseError, "Conversations.list endpoint response IS NOT OK." unless data["ok"] + return data end end diff --git a/lib/direct_message.rb b/lib/direct_message.rb index b6dd71bf..22882fc6 100644 --- a/lib/direct_message.rb +++ b/lib/direct_message.rb @@ -3,26 +3,28 @@ module Slack class DirectMessage < Conversation - def initialize(id, user) - super(id) - @type = :IM - @user = user + def initialize(data) + super(data) + @user = data["user"] end + # CLASS METHODS + # Method takes raw Channel data and converts it into an array of Channel objects. + def self.list_all + direct_messages = super.map { |data| DirectMessage.new(data)} + end - # CLASS METHODS - - - # def self.get_all - # TO-DO: implement when getting all 1-on-1 direct messages is necessary - # API endpoint https://slack.com/api/conversations.list TYPES= "im" - # end - - - # def self.list_all - # TO-DO: implement when listing all 1-on-1 direct messages is necessary - # end + private + # Method uses http get to retrieve all Channel "objects" + # returns an array of Channels + def self.get_all + query = { + token: SLACK_TOKEN, + types: "im", + } + return super["channels"] #Slack considers direct messages to also be "channels" + end end end \ No newline at end of file diff --git a/lib/slack.rb b/lib/slack.rb index e0d20ef3..6a6d4fce 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -34,7 +34,8 @@ def perform_action(choice) tp @workspace.users when *OPTIONS[1] puts "\n\n>>>>>>> LIST OF CHANNELS" - puts "I'll get you those channels one day." + tp @workspace.channels + # TO-DO tp @workspace.channels.exclude direct messages end end diff --git a/lib/user.rb b/lib/user.rb index 05ffede3..8e52141e 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -22,7 +22,7 @@ def initialize(member) # Returns an array of User objects def self.list_all members_including_deleted = get_all - members = members_including_deleted.reject { |member| member["deleted"] == true } #cover a wonky case + members = members_including_deleted.reject { |member| member["deleted"] } #cover a wonky case members.map { |member| User.new(member) } end @@ -32,7 +32,7 @@ def self.list_all # returns an httparty Response object def self.get_all data = HTTParty.get("https://slack.com/api/users.list?", query: { token: SLACK_TOKEN, }) - raise StandardError, "Users.list endpoint response IS NOT OK." unless data["ok"] + raise BadResponseError, "Users.list endpoint response IS NOT OK." unless data["ok"] return data["members"] end end diff --git a/lib/workspace.rb b/lib/workspace.rb index 47880f7a..d6e86a0b 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -3,15 +3,17 @@ require_relative "direct_message" require_relative "channel" require_relative "user" +require_relative "bad_response_error" module Slack class Workspace # attr_reader :users, :conversations, :selected - attr_reader :users, :selected + attr_reader :users, :channels, :selected def initialize @users = User.list_all - #@conversations = Conversation.list_all + @channels = Channel.list_all + #@direct_messages = DirectMessage.list_all @selected = nil end @@ -29,6 +31,12 @@ def post_message raise InvalidRecipientError, "No target conversation specified. Cannot send message." end + private + + def is_valid_target?(input, type) + # check users and conversations for valid target + end + end class InvalidRecipientError < StandardError diff --git a/test/cassettes/conversations_list_direct_messages_endpoint.yml b/test/cassettes/conversations_list_direct_messages_endpoint.yml new file mode 100644 index 00000000..bb159ea3 --- /dev/null +++ b/test/cassettes/conversations_list_direct_messages_endpoint.yml @@ -0,0 +1,85 @@ +--- +http_interactions: +- request: + method: get + uri: https://slack.com/api/conversations.list?token= + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '718' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:18:35 GMT + Server: + - Apache + X-Slack-Req-Id: + - 644f918e3e8e1975548a12582c670f14 + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - channels:read,groups:read,mpim:read,im:read,read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-nmqw + X-Cache: + - Miss from cloudfront + Via: + - 1.1 be082a2326b7d49643607b097f1e7181.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C2 + X-Amz-Cf-Id: + - MiDgA1kf_O-prL7ZqOQggHPmqEE3fR8hlnO_XEkcvxtULuynHjDoDg== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"channels":[{"id":"CUT7CLL1Y","name":"general","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":true,"unlinked":0,"name_normalized":"general","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Company-wide + announcements and work-based matters","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"This + channel is for workspace-wide communication and announcements. All members + are in this channel.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":9},{"id":"CUT7CM7M0","name":"ada-projects","is_channel":true,"is_group":false,"is_im":false,"created":1583866139,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"ada-projects","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"This + channel is for ada-projects collaboration and communication.","creator":"UV828MW79","last_set":1584052023},"previous_names":[],"num_members":9},{"id":"CV5H594KE","name":"random","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"random","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Non-work + banter and water cooler conversation","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"A + place for non-work-related flimflam, faffing, hodge-podge or jibber-jabber + you''d prefer to keep out of more focused work-related channels.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":10},{"id":"C01046CUVD5","name":"spam","is_channel":true,"is_group":false,"is_im":false,"created":1584353722,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"spam","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"Spam + messages for testing purposes. Recommended mute this channel.","creator":"UV614256C","last_set":1584353723},"previous_names":[],"num_members":1}],"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:18:35 GMT +recorded_with: VCR 5.1.0 diff --git a/test/cassettes/conversations_list_endpoint.yml b/test/cassettes/conversations_list_endpoint.yml new file mode 100644 index 00000000..433959af --- /dev/null +++ b/test/cassettes/conversations_list_endpoint.yml @@ -0,0 +1,85 @@ +--- +http_interactions: +- request: + method: get + uri: https://slack.com/api/conversations.list?token= + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '718' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:04:41 GMT + Server: + - Apache + X-Slack-Req-Id: + - '0083a8e2965c47816b289968ce499f3f' + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - channels:read,groups:read,mpim:read,im:read,read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-stwn + X-Cache: + - Miss from cloudfront + Via: + - 1.1 570075675953459325e00b7bcd171df3.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C2 + X-Amz-Cf-Id: + - Z1VL1UuFDXo8AYTFwpPMD-zrdYcqsUgczo5o3bEKoz8IMg5W3Cy2rQ== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"channels":[{"id":"CUT7CLL1Y","name":"general","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":true,"unlinked":0,"name_normalized":"general","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Company-wide + announcements and work-based matters","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"This + channel is for workspace-wide communication and announcements. All members + are in this channel.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":9},{"id":"CUT7CM7M0","name":"ada-projects","is_channel":true,"is_group":false,"is_im":false,"created":1583866139,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"ada-projects","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"This + channel is for ada-projects collaboration and communication.","creator":"UV828MW79","last_set":1584052023},"previous_names":[],"num_members":9},{"id":"CV5H594KE","name":"random","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"random","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Non-work + banter and water cooler conversation","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"A + place for non-work-related flimflam, faffing, hodge-podge or jibber-jabber + you''d prefer to keep out of more focused work-related channels.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":10},{"id":"C01046CUVD5","name":"spam","is_channel":true,"is_group":false,"is_im":false,"created":1584353722,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"spam","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"Spam + messages for testing purposes. Recommended mute this channel.","creator":"UV614256C","last_set":1584353723},"previous_names":[],"num_members":1}],"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:04:41 GMT +recorded_with: VCR 5.1.0 diff --git a/test/cassettes/users_list_endpoint.yml b/test/cassettes/users_list_endpoint.yml new file mode 100644 index 00000000..d013c434 --- /dev/null +++ b/test/cassettes/users_list_endpoint.yml @@ -0,0 +1,180 @@ +--- +http_interactions: +- request: + method: get + uri: https://slack.com/api/users.list?token= + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2458' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:04:41 GMT + Server: + - Apache + X-Slack-Req-Id: + - 5657f3712a274c687ef4e808df07a6b3 + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - users:read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-vmz3 + X-Cache: + - Miss from cloudfront + Via: + - 1.1 0732be5515ffeda639cfe5f22fb04df6.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C2 + X-Amz-Cf-Id: + - 4cDNrrW6Rwc260N4q0dTyTJ7BNjkZJI6Y1ahzvgTbbUDSLUUJ_sAUg== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"members":[{"id":"USLACKBOT","team_id":"TV5H57Z7E","name":"slackbot","deleted":false,"color":"757575","real_name":"Slackbot","tz":null,"tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Slackbot","real_name_normalized":"Slackbot","display_name":"Slackbot","display_name_normalized":"Slackbot","fields":null,"status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"sv41d8cd98f0","always_active":true,"first_name":"slackbot","last_name":"","image_24":"https:\/\/a.slack-edge.com\/80588\/img\/slackbot_24.png","image_32":"https:\/\/a.slack-edge.com\/80588\/img\/slackbot_32.png","image_48":"https:\/\/a.slack-edge.com\/80588\/img\/slackbot_48.png","image_72":"https:\/\/a.slack-edge.com\/80588\/img\/slackbot_72.png","image_192":"https:\/\/a.slack-edge.com\/80588\/marketing\/img\/avatars\/slackbot\/avatar-slackbot.png","image_512":"https:\/\/a.slack-edge.com\/80588\/img\/slackbot_512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":0},{"id":"UUTGP3482","team_id":"TV5H57Z7E","name":"time_nataliya_api_pro","deleted":false,"color":"e96699","real_name":"time_nataliya_api_pro","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"time_nataliya_api_pro","real_name_normalized":"time_nataliya_api_pro","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"gc03334fdbae","api_app_id":"AV8G3Q5N2","always_active":false,"bot_id":"BUW7Y2VED","image_24":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/c03334fdbae1eeba8e7aad628b582e7d.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0002-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1583875216},{"id":"UUUTBJARH","team_id":"TV5H57Z7E","name":"stephaniejmars","deleted":false,"color":"e0a729","real_name":"Stephanie + Marshall","tz":"America\/Los_Angeles","tz_label":"Pacific Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Stephanie + Marshall","real_name_normalized":"Stephanie Marshall","display_name":"Stephanie + Marshall","display_name_normalized":"Stephanie Marshall","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g8e318281d2d","image_24":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/8e318281d2d27bfd2bd51e9c614dfcef.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1583876740},{"id":"UUY6H3H41","team_id":"TV5H57Z7E","name":"timeangelabot-x","deleted":true,"profile":{"title":"","phone":"","skype":"","real_name":"Angela + Bot","real_name_normalized":"Angela Bot","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g60b25941fca","api_app_id":"AV87VAS85","always_active":true,"bot_id":"BVAF48YSJ","first_name":"Angela","last_name":"Bot","image_24":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/60b25941fca5a453afaba8f047d20372.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_bot":true,"is_app_user":false,"updated":1584065406},{"id":"UV5RH7XE0","team_id":"TV5H57Z7E","name":"n.pogodina","deleted":false,"color":"3c989f","real_name":"Nataliya + Pogodina","tz":"America\/Los_Angeles","tz_label":"Pacific Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Nataliya + Pogodina","real_name_normalized":"Nataliya Pogodina","display_name":"Nataliya + Pogodina","display_name_normalized":"Nataliya Pogodina","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g650ea74f69b","image_24":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/650ea74f69bd715a7dfbc63fc793a46a.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0016-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1583875488},{"id":"UV5RV3UDA","team_id":"TV5H57Z7E","name":"msdenisseanaya","deleted":false,"color":"674b1b","real_name":"Denisse","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Denisse","real_name_normalized":"Denisse","display_name":"Denisse","display_name_normalized":"Denisse","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g20f3289080a","image_24":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/20f3289080a51750693a488117df494d.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1583875484},{"id":"UV614256C","team_id":"TV5H57Z7E","name":"angethuy","deleted":false,"color":"9f69e7","real_name":"Angela","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Angela","real_name_normalized":"Angela","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"ge7f4423d079","first_name":"Angela","last_name":"","image_24":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/e7f4423d079ccf2a1b2ec822f976322f.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":true,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1583868483},{"id":"UV6G4J1FS","team_id":"TV5H57Z7E","name":"time_denisse_api_proj","deleted":false,"color":"684b6c","real_name":"time_denisse_api_proj","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"time_denisse_api_proj","real_name_normalized":"time_denisse_api_proj","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g9b327b43bd3","api_app_id":"AUWAQJ4D7","always_active":false,"bot_id":"BV62RN1JQ","image_24":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/9b327b43bd3ae11aa671605268f00cdf.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0015-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1583883073},{"id":"UV6HC1BU5","team_id":"TV5H57Z7E","name":"space_stephanie_api_p","deleted":false,"color":"5b89d5","real_name":"space_stephanie_api_p","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"space_stephanie_api_p","real_name_normalized":"space_stephanie_api_p","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g334e580430b","api_app_id":"AV885T71V","always_active":false,"bot_id":"BV63EEWCC","image_24":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/334e580430b69b1aa8d821898112ad47.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0003-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1583884239},{"id":"UV828MW79","team_id":"TV5H57Z7E","name":"olgapatrakova","deleted":false,"color":"e7392d","real_name":"Olga","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Olga","real_name_normalized":"Olga","display_name":"Olga","display_name_normalized":"Olga","status_text":"Vacationing","status_emoji":":palm_tree:","status_expiration":0,"avatar_hash":"gf556705f93c","image_24":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/f556705f93c8a46fa9935354e010bc2d.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-512.png","status_text_canonical":"Vacationing","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1584163284},{"id":"UV89SRSAJ","team_id":"TV5H57Z7E","name":"alicia.combs.92","deleted":false,"color":"4bbe2e","real_name":"Alicia + Combs","tz":"America\/Los_Angeles","tz_label":"Pacific Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Alicia + Combs","real_name_normalized":"Alicia Combs","display_name":"Alicia Combs","display_name_normalized":"Alicia + Combs","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g5076c49384a","image_24":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/5076c49384ae19beaa5f247513f5c3e4.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":true,"is_owner":true,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"is_app_user":false,"updated":1583875478},{"id":"UV8JVKASC","team_id":"TV5H57Z7E","name":"time_olga_api_project","deleted":false,"color":"99a949","real_name":"time_olga_api_project","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"time_olga_api_project","real_name_normalized":"time_olga_api_project","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g5ba7db70afe","api_app_id":"AV8D7GT6Z","always_active":false,"bot_id":"BUVR3TZDG","first_name":"time_olga_api_project","last_name":"","image_24":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/5ba7db70afefc9108816dd11f963cfca.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1583965617},{"id":"UVDS2KFGE","team_id":"TV5H57Z7E","name":"timeangelabot","deleted":false,"color":"df3dc0","real_name":"Angela + Bot","tz":"America\/Los_Angeles","tz_label":"Pacific Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"Angela + Bot","real_name_normalized":"Angela Bot","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g7b4a8116903","api_app_id":"AVDFSRG6T","always_active":true,"bot_id":"BV1HJD71P","first_name":"Angela","last_name":"Bot","image_24":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/7b4a8116903e45ce0d21096f13411053.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0004-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1584065546},{"id":"UVDUTN3J9","team_id":"TV5H57Z7E","name":"time_alicia_slack_cli","deleted":false,"color":"4cc091","real_name":"time_alicia_slack_cli","tz":"America\/Los_Angeles","tz_label":"Pacific + Daylight Time","tz_offset":-25200,"profile":{"title":"","phone":"","skype":"","real_name":"time_alicia_slack_cli","real_name_normalized":"time_alicia_slack_cli","display_name":"","display_name_normalized":"","status_text":"","status_emoji":"","status_expiration":0,"avatar_hash":"g618e805dff0","api_app_id":"AUV3WL99Q","always_active":false,"bot_id":"BV153BDS6","first_name":"time_alicia_slack_cli","last_name":"","image_24":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-192.png","image_512":"https:\/\/secure.gravatar.com\/avatar\/618e805dff021547f7dd38096f8be5b6.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0022-512.png","status_text_canonical":"","team":"TV5H57Z7E"},"is_admin":false,"is_owner":false,"is_primary_owner":false,"is_restricted":false,"is_ultra_restricted":false,"is_bot":true,"is_app_user":false,"updated":1584308139}],"cache_ts":1585044281,"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:04:41 GMT +- request: + method: get + uri: https://slack.com/api/conversations.list?token= + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '718' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:04:42 GMT + Server: + - Apache + X-Slack-Req-Id: + - d3f4891281a41c3e156e58710bf2d9c2 + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - channels:read,groups:read,mpim:read,im:read,read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-2nvs + X-Cache: + - Miss from cloudfront + Via: + - 1.1 e36ab1b8726f47aa5adc8e19e66d1bbe.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C2 + X-Amz-Cf-Id: + - mk3RLoTB5eOLfsSL3SE-vggi_jDkTWd289RtRWW-MUGwsZ2Ru6xwbA== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"channels":[{"id":"CUT7CLL1Y","name":"general","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":true,"unlinked":0,"name_normalized":"general","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Company-wide + announcements and work-based matters","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"This + channel is for workspace-wide communication and announcements. All members + are in this channel.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":9},{"id":"CUT7CM7M0","name":"ada-projects","is_channel":true,"is_group":false,"is_im":false,"created":1583866139,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"ada-projects","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"This + channel is for ada-projects collaboration and communication.","creator":"UV828MW79","last_set":1584052023},"previous_names":[],"num_members":9},{"id":"CV5H594KE","name":"random","is_channel":true,"is_group":false,"is_im":false,"created":1583866138,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"random","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"Non-work + banter and water cooler conversation","creator":"UV614256C","last_set":1583866138},"purpose":{"value":"A + place for non-work-related flimflam, faffing, hodge-podge or jibber-jabber + you''d prefer to keep out of more focused work-related channels.","creator":"UV614256C","last_set":1583866138},"previous_names":[],"num_members":10},{"id":"C01046CUVD5","name":"spam","is_channel":true,"is_group":false,"is_im":false,"created":1584353722,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"spam","is_shared":false,"parent_conversation":null,"creator":"UV614256C","is_ext_shared":false,"is_org_shared":false,"shared_team_ids":["TV5H57Z7E"],"pending_shared":[],"pending_connected_team_ids":[],"is_pending_ext_shared":false,"is_member":false,"is_private":false,"is_mpim":false,"topic":{"value":"","creator":"","last_set":0},"purpose":{"value":"Spam + messages for testing purposes. Recommended mute this channel.","creator":"UV614256C","last_set":1584353723},"previous_names":[],"num_members":1}],"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:04:42 GMT +recorded_with: VCR 5.1.0 diff --git a/test/direct_message_test.rb b/test/direct_message_test.rb index 8dfcc06f..d8fc7868 100644 --- a/test/direct_message_test.rb +++ b/test/direct_message_test.rb @@ -1,7 +1,7 @@ require_relative "test_helper" require_relative "../lib/direct_message" -describe "direct message" do +xdescribe "direct message" do before do dm = { "id" => "DUYS4LFM0", diff --git a/test/user_test.rb b/test/user_test.rb index ce9b5d6f..0366e628 100644 --- a/test/user_test.rb +++ b/test/user_test.rb @@ -1,7 +1,7 @@ require_relative "test_helper" require_relative "../lib/user" -describe "user" do +xdescribe "user" do before do member = {"id"=>"USLACKBOT", "team_id"=>"TV5H57Z7E", "name"=>"slackbot", "deleted"=>false, "color"=>"757575", "real_name"=>"Slackbot",} diff --git a/test/workspace_test.rb b/test/workspace_test.rb index d16ca668..d8ff0cb9 100644 --- a/test/workspace_test.rb +++ b/test/workspace_test.rb @@ -4,7 +4,7 @@ describe "workspace" do before do VCR.use_cassette("users_list_endpoint") do - @test_workspace = Slack::Workspace.new + @test_workspace = Slack::Workspace.new end end From 3c807d950fe5c7fa0fbb177e8a46c5c92c1e4901 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 03:38:10 -0700 Subject: [PATCH 23/29] direct messages tests passing --- lib/channel.rb | 9 ++- lib/conversation.rb | 7 +- lib/direct_message.rb | 5 +- lib/workspace.rb | 2 +- ...rsations_list_direct_messages_endpoint.yml | 74 +++++++++++++++++++ test/cassettes/users_list_endpoint.yml | 74 +++++++++++++++++++ test/direct_message_test.rb | 4 +- 7 files changed, 162 insertions(+), 13 deletions(-) diff --git a/lib/channel.rb b/lib/channel.rb index e961ea47..fa5c8432 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -22,15 +22,18 @@ def initialize(channel) # Method takes raw Channel data and converts it into an array of Channel objects. def self.list_all - channels = super.map { |channel| Channel.new(channel)} + channels = get_all.map { |channel| Channel.new(channel)} end - + private # Method uses http get to retrieve all Channel "objects" # returns an array of Channels def self.get_all - return super["channels"] + query = { + token: SLACK_TOKEN, + } + return super(query)["channels"] end end diff --git a/lib/conversation.rb b/lib/conversation.rb index 027423e7..40c6739e 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -25,16 +25,13 @@ def details # CLASS METHODS def self.list_all - data = get_all + # Template, extend this method in child classes. end private - def self.get_all - query = { - token: SLACK_TOKEN, - } + def self.get_all(query) data = HTTParty.get("https://slack.com/api/conversations.list?", query: query) raise BadResponseError, "Conversations.list endpoint response IS NOT OK." unless data["ok"] return data diff --git a/lib/direct_message.rb b/lib/direct_message.rb index 22882fc6..6a47f457 100644 --- a/lib/direct_message.rb +++ b/lib/direct_message.rb @@ -2,6 +2,7 @@ module Slack class DirectMessage < Conversation + attr_reader :id, :user def initialize(data) super(data) @@ -12,7 +13,7 @@ def initialize(data) # CLASS METHODS # Method takes raw Channel data and converts it into an array of Channel objects. def self.list_all - direct_messages = super.map { |data| DirectMessage.new(data)} + direct_messages = get_all.map { |data| DirectMessage.new(data)} end private @@ -24,7 +25,7 @@ def self.get_all token: SLACK_TOKEN, types: "im", } - return super["channels"] #Slack considers direct messages to also be "channels" + return super(query)["channels"] #Slack considers direct messages to also be "channels" end end end \ No newline at end of file diff --git a/lib/workspace.rb b/lib/workspace.rb index d6e86a0b..a807d074 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -13,7 +13,7 @@ class Workspace def initialize @users = User.list_all @channels = Channel.list_all - #@direct_messages = DirectMessage.list_all + @direct_messages = DirectMessage.list_all @selected = nil end diff --git a/test/cassettes/conversations_list_direct_messages_endpoint.yml b/test/cassettes/conversations_list_direct_messages_endpoint.yml index bb159ea3..6e9a6aae 100644 --- a/test/cassettes/conversations_list_direct_messages_endpoint.yml +++ b/test/cassettes/conversations_list_direct_messages_endpoint.yml @@ -82,4 +82,78 @@ http_interactions: messages for testing purposes. Recommended mute this channel.","creator":"UV614256C","last_set":1584353723},"previous_names":[],"num_members":1}],"response_metadata":{"next_cursor":""}}' http_version: null recorded_at: Tue, 24 Mar 2020 10:18:35 GMT +- request: + method: get + uri: https://slack.com/api/conversations.list?token=&types=im + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '292' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:35:37 GMT + Server: + - Apache + X-Slack-Req-Id: + - fa895566b4c1ef0183982e9692732388 + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - channels:read,groups:read,mpim:read,im:read,read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-jfpz + X-Cache: + - Miss from cloudfront + Via: + - 1.1 caf6806821bc479b28a6f1ce3043b8a6.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C2 + X-Amz-Cf-Id: + - _FkDI98PKYis-2DtL5HBPPgOwu8Hd3tEf5ykOmi2zcgwoDWDL21y1g== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"channels":[{"id":"DVDS2KLPQ","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV89SRSAJ","is_user_deleted":false,"priority":0},{"id":"DVBLESY4D","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UUUTBJARH","is_user_deleted":false,"priority":0},{"id":"DVBLESX6Z","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV5RV3UDA","is_user_deleted":false,"priority":0},{"id":"DVBKWG80G","created":1584065407,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV5RH7XE0","is_user_deleted":false,"priority":0},{"id":"DV1HJDEDP","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV828MW79","is_user_deleted":false,"priority":0},{"id":"DV065488K","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV614256C","is_user_deleted":false,"priority":0},{"id":"DUYS4LFM0","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"USLACKBOT","is_user_deleted":false,"priority":0}],"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:35:37 GMT recorded_with: VCR 5.1.0 diff --git a/test/cassettes/users_list_endpoint.yml b/test/cassettes/users_list_endpoint.yml index d013c434..cfb31302 100644 --- a/test/cassettes/users_list_endpoint.yml +++ b/test/cassettes/users_list_endpoint.yml @@ -177,4 +177,78 @@ http_interactions: messages for testing purposes. Recommended mute this channel.","creator":"UV614256C","last_set":1584353723},"previous_names":[],"num_members":1}],"response_metadata":{"next_cursor":""}}' http_version: null recorded_at: Tue, 24 Mar 2020 10:04:42 GMT +- request: + method: get + uri: https://slack.com/api/conversations.list?token=&types=im + body: + encoding: US-ASCII + string: '' + 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: + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '292' + Connection: + - keep-alive + Date: + - Tue, 24 Mar 2020 10:37:05 GMT + Server: + - Apache + X-Slack-Req-Id: + - dc5c81c0e103eb9389147920a9c9c38b + X-Oauth-Scopes: + - chat:write.public,chat:write,channels:read,users:read,im:read + X-Accepted-Oauth-Scopes: + - channels:read,groups:read,mpim:read,im:read,read + Access-Control-Expose-Headers: + - x-slack-req-id, retry-after + X-Slack-Backend: + - h + 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' + Vary: + - Accept-Encoding + Pragma: + - no-cache + 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 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Referrer-Policy: + - no-referrer + Access-Control-Allow-Origin: + - "*" + X-Via: + - haproxy-www-9e4s + X-Cache: + - Miss from cloudfront + Via: + - 1.1 68eedbdfabd017beabf28d04341fec21.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - SEA19-C1 + X-Amz-Cf-Id: + - cgjGaI0lA28IQjLRGZAgNXYq2gZ3barcEsFVbguEEjEH10bhk5_SWg== + body: + encoding: ASCII-8BIT + string: '{"ok":true,"channels":[{"id":"DVDS2KLPQ","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV89SRSAJ","is_user_deleted":false,"priority":0},{"id":"DVBLESY4D","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UUUTBJARH","is_user_deleted":false,"priority":0},{"id":"DVBLESX6Z","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV5RV3UDA","is_user_deleted":false,"priority":0},{"id":"DVBKWG80G","created":1584065407,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV5RH7XE0","is_user_deleted":false,"priority":0},{"id":"DV1HJDEDP","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV828MW79","is_user_deleted":false,"priority":0},{"id":"DV065488K","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"UV614256C","is_user_deleted":false,"priority":0},{"id":"DUYS4LFM0","created":1584065406,"is_archived":false,"is_im":true,"is_org_shared":false,"user":"USLACKBOT","is_user_deleted":false,"priority":0}],"response_metadata":{"next_cursor":""}}' + http_version: null + recorded_at: Tue, 24 Mar 2020 10:37:05 GMT recorded_with: VCR 5.1.0 diff --git a/test/direct_message_test.rb b/test/direct_message_test.rb index d8fc7868..8a12f62b 100644 --- a/test/direct_message_test.rb +++ b/test/direct_message_test.rb @@ -1,7 +1,7 @@ require_relative "test_helper" require_relative "../lib/direct_message" -xdescribe "direct message" do +describe "direct message" do before do dm = { "id" => "DUYS4LFM0", @@ -58,7 +58,7 @@ it "has a DirectMessage with the user USLACKBOT" do VCR.use_cassette("conversations_list_direct_messages_endpoint") do direct_messages = Slack::DirectMessage.list_all - expect(results.any? { |result| result.user == "USLACKBOT" } ).must_equal true + expect(direct_messages.any? { |direct_message| direct_message.user == "USLACKBOT" } ).must_equal true end end From dfad56ee00a94730f33e95ef7e9dc37a48aa0151 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 04:01:07 -0700 Subject: [PATCH 24/29] modified CLI driver, cleaned up spacing --- lib/channel.rb | 4 ++-- lib/conversation.rb | 4 ++-- lib/direct_message.rb | 2 +- lib/slack.rb | 22 +++++++++++++++++++--- lib/workspace.rb | 3 ++- test/conversation_test.rb | 25 +++++++++++++++++-------- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/channel.rb b/lib/channel.rb index fa5c8432..b3ecf6cc 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -2,7 +2,7 @@ module Slack class Channel < Conversation - attr_reader :id, :name, :topic, :member_count + attr_reader :name, :topic, :member_count def initialize(channel) raise ArgumentError, "Trying to create Channel object with bad data: #{channel}." if channel["id"] == nil || !(channel["is_channel"]) @@ -24,7 +24,7 @@ def initialize(channel) def self.list_all channels = get_all.map { |channel| Channel.new(channel)} end - + private # Method uses http get to retrieve all Channel "objects" diff --git a/lib/conversation.rb b/lib/conversation.rb index 40c6739e..aef37170 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -2,6 +2,7 @@ module Slack class Conversation + attr_reader :id def initialize(data) @id = data["id"] #template @@ -25,8 +26,7 @@ def details # CLASS METHODS def self.list_all - - # Template, extend this method in child classes. + # Extend this method in child classes. end private diff --git a/lib/direct_message.rb b/lib/direct_message.rb index 6a47f457..df8ffdb8 100644 --- a/lib/direct_message.rb +++ b/lib/direct_message.rb @@ -2,7 +2,7 @@ module Slack class DirectMessage < Conversation - attr_reader :id, :user + attr_reader :user def initialize(data) super(data) diff --git a/lib/slack.rb b/lib/slack.rb index 6a6d4fce..b68ae5ae 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -5,7 +5,9 @@ OPTIONS = [ ["1", "list users"], ["2", "list channels"], - ["3", "quit"], + ["3", "select user"], + ["4", "select a channel"], + ["5", "quit"], ] def main @@ -34,8 +36,16 @@ def perform_action(choice) tp @workspace.users when *OPTIONS[1] puts "\n\n>>>>>>> LIST OF CHANNELS" - tp @workspace.channels - # TO-DO tp @workspace.channels.exclude direct messages + tp @workspace.channels, :include => :id + when *OPTIONS[2] + puts "\n\n>>>>>>> SELECTING A USER" + make_selection("user") + + when *OPTIONS[3] + puts "\n\n>>>>>>> SELECTING A CHANNEL" + make_selection("channel") + + end end @@ -52,6 +62,12 @@ def get_user_input return choice end +def make_selection(type) + print "Please enter the id for the #{type} > " + input = user.get + @workspace.select_by_id(input) +end + main if __FILE__ == $PROGRAM_NAME diff --git a/lib/workspace.rb b/lib/workspace.rb index a807d074..6aa78002 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -18,7 +18,8 @@ def initialize end # Method selects a user or channel using the name or slack ID. - def select_by_id(id, type) + def select_by_id(type, id) + end # Method shows details of the currently selected conversation. diff --git a/test/conversation_test.rb b/test/conversation_test.rb index dec094a7..3e5b1bef 100644 --- a/test/conversation_test.rb +++ b/test/conversation_test.rb @@ -2,10 +2,22 @@ require_relative "../lib/conversation" describe "conversation" do + before do + fake_convo = { + "id" => "fake_convo" + } + @conversation = Slack::Conversation.new(fake_convo) + + end describe "initialize" do - # creates conversation object - # sets ID + it "creates a Conversation object" do + expect(@conversation).must_be_kind_of Slack::Conversation + end + + it "sets the conversation id" do + expect(@conversation.id).must_equal "fake_convo" + end end describe "post_message" do @@ -18,12 +30,9 @@ end describe "self.get_all" do - # raises NotImplementedError - end - - describe "self.list_all" do - # raises NotImplementedError + it "gets all conversations" do + + end end - end \ No newline at end of file From b9239d9e271c4d348a2b12778703f6700fe7f9a0 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 23:18:56 -0700 Subject: [PATCH 25/29] select user/channel working --- lib/slack.rb | 40 ++++++++++++++++++++++++++++++---------- lib/user.rb | 4 ++-- lib/workspace.rb | 28 ++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/lib/slack.rb b/lib/slack.rb index b68ae5ae..67eed17c 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -33,17 +33,20 @@ def perform_action(choice) case choice when *OPTIONS[0] puts "\n\n>>>>>>> LIST OF USERS" - tp @workspace.users + count = 0 + my_proc = Proc.new{count = count + 1} + tp @workspace.users, {user: lambda{ |u| my_proc.call }}, :id, :user_name, :real_name => {:display_method => :name} when *OPTIONS[1] puts "\n\n>>>>>>> LIST OF CHANNELS" - tp @workspace.channels, :include => :id + # tp @workspace.channels, :include => :id + count = 0 + my_proc = Proc.new{count = count + 1} + tp @workspace.channels, {channel: lambda{ |u| my_proc.call }}, :id, :name, :topic, :member_count when *OPTIONS[2] - puts "\n\n>>>>>>> SELECTING A USER" - make_selection("user") + make_selection("user", @workspace.users ) when *OPTIONS[3] - puts "\n\n>>>>>>> SELECTING A CHANNEL" - make_selection("channel") + make_selection("channel", @workspace.channels ) end @@ -62,10 +65,27 @@ def get_user_input return choice end -def make_selection(type) - print "Please enter the id for the #{type} > " - input = user.get - @workspace.select_by_id(input) +def make_selection(type, list) + puts "\n\n>>>>>>> SELECTING A #{type.upcase}" + print "Please enter the #{type} number or the ID as listed > " + input = gets.strip + + if input != 0 && input.to_i == 0 #gave us an ID + @workspace.select_by_id(type, input) + else #gave us an int + while input == 0 || input.to_i > list.length + validate_selection(input) + end + @workspace.select_by_index(type, input.to_i-1) + end + + puts "Selected #{type}: #{@workspace.selected.name}" + +end + +def validate_selection(input) + print "#{input} is not a valid choice, re-enter the number > " + return gets.strip end diff --git a/lib/user.rb b/lib/user.rb index 8e52141e..e1254eee 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -7,13 +7,13 @@ module Slack class User - attr_reader :id, :user_name, :real_name + attr_reader :id, :user_name, :name def initialize(member) raise ArgumentError, "Trying to create User object with bad data: #{member}." if member["id"] == nil || member["name"] == nil || member["real_name"] == nil @id = member["id"] @user_name = member["name"] - @real_name = member["real_name"] + @name = member["real_name"] end # CLASS METHODS diff --git a/lib/workspace.rb b/lib/workspace.rb index 6aa78002..93978495 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -10,6 +10,8 @@ class Workspace # attr_reader :users, :conversations, :selected attr_reader :users, :channels, :selected + + def initialize @users = User.list_all @channels = Channel.list_all @@ -19,7 +21,27 @@ def initialize # Method selects a user or channel using the name or slack ID. def select_by_id(type, id) - + result = nil + + case type + when "channel" + result = @channels.index { |channel| channel.id == id } + raise ArgumentError, "no channel with id: #{id}" if result.nil? + @selected = @channels[result] + when "user" + result = @users.index { |user| user.id == id } + raise ArgumentError, "no user with id: #{id}" if result.nil? + @selected = @users[result] + end + end + + def select_by_index(type, index) + case type + when "channel" + @selected = @channels[index] + when "user" + @selected = @users[index] + end end # Method shows details of the currently selected conversation. @@ -34,7 +56,9 @@ def post_message private - def is_valid_target?(input, type) + def find_id + return + # check users and conversations for valid target end From 3fe9b897c323cbc0f7684355f16867fbcea7ddfc Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Tue, 24 Mar 2020 23:40:46 -0700 Subject: [PATCH 26/29] details and posting messages now working --- lib/channel.rb | 9 +++------ lib/conversation.rb | 2 ++ lib/slack.rb | 24 +++++++++++++++++++++--- lib/user.rb | 11 +++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/channel.rb b/lib/channel.rb index b3ecf6cc..a4a35009 100644 --- a/lib/channel.rb +++ b/lib/channel.rb @@ -12,12 +12,9 @@ def initialize(channel) @member_count = channel["num_members"] end - # def details - # # return string describing: - # # channel name - # # channel topic - # # member_count - # end + def details + return "Details for this channel... \n name: #{name} \n topic: #{topic} \n number of members: #{member_count}" + end # Method takes raw Channel data and converts it into an array of Channel objects. diff --git a/lib/conversation.rb b/lib/conversation.rb index aef37170..7d00776c 100644 --- a/lib/conversation.rb +++ b/lib/conversation.rb @@ -12,6 +12,8 @@ def initialize(data) def post_message(message) # API ENDPOINT: https://slack.com/api/chat.postMessage # query: @id + results = HTTParty.post("https://slack.com/api/chat.postMessage", query: { token: SLACK_TOKEN, channel: id, text: message}) + raise BadResponseError, "chat.postMessage endpoint response IS NOT OK." unless results["ok"] end # Placeholder method to be defined in child classes. diff --git a/lib/slack.rb b/lib/slack.rb index 67eed17c..cdad03b1 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -7,7 +7,9 @@ ["2", "list channels"], ["3", "select user"], ["4", "select a channel"], - ["5", "quit"], + ["5", "show details"], + ["6", "post a message"], + ["7", "quit"], ] def main @@ -36,19 +38,35 @@ def perform_action(choice) count = 0 my_proc = Proc.new{count = count + 1} tp @workspace.users, {user: lambda{ |u| my_proc.call }}, :id, :user_name, :real_name => {:display_method => :name} + when *OPTIONS[1] puts "\n\n>>>>>>> LIST OF CHANNELS" # tp @workspace.channels, :include => :id count = 0 my_proc = Proc.new{count = count + 1} tp @workspace.channels, {channel: lambda{ |u| my_proc.call }}, :id, :name, :topic, :member_count + when *OPTIONS[2] make_selection("user", @workspace.users ) when *OPTIONS[3] make_selection("channel", @workspace.channels ) - - + + when *OPTIONS[4] #details + if @workspace.selected.nil? + puts "Oops, you haven't made a selection yet. Make a selection first." + else + puts @workspace.selected.details + end + + when *OPTIONS[5] #post message + if @workspace.selected.nil? + puts "Who ya trying to send a message to? Pick someone first, silly." + else + print "Enter the message that you want to send to #{@workspace.selected.name} > " + message = gets + @workspace.selected.post_message(message) + end end end diff --git a/lib/user.rb b/lib/user.rb index e1254eee..a2f255f9 100644 --- a/lib/user.rb +++ b/lib/user.rb @@ -16,6 +16,17 @@ def initialize(member) @name = member["real_name"] end + def details + return "Details for this user: \n id: #{id} \n user name: #{user_name} \n real name: #{name}" + end + + def post_message(message) + # API ENDPOINT: https://slack.com/api/chat.postMessage + # query: @id + results = HTTParty.post("https://slack.com/api/chat.postMessage", query: { token: SLACK_TOKEN, channel: id, text: message}) + raise BadResponseError, "chat.postMessage endpoint response IS NOT OK." unless results["ok"] + end + # CLASS METHODS # Parameter users: collection representing Users From 5f75a98721f807f5f001690878323ac70861106c Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Fri, 27 Mar 2020 03:46:38 -0700 Subject: [PATCH 27/29] added error handling for bad user input --- lib/slack.rb | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/slack.rb b/lib/slack.rb index cdad03b1..555f040b 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -12,22 +12,23 @@ ["7", "quit"], ] +# main loop of the CLI program def main puts "Welcome to the Ada Slack CLI!" @workspace = Slack::Workspace.new - #MAIN LOOP choice = get_user_input - until (OPTIONS[-1].include? choice) + until (OPTIONS[-1].include? choice) #checks for quit command from user perform_action(choice) choice = get_user_input end puts "\n>>>>>> Thank you for using the Slack CLI! Goodbye." end +# prompts for valid commands from user, then executes the command def perform_action(choice) - until OPTIONS.any? { |option| option.include? choice } + until OPTIONS.any? { |option| option.include? choice } #validates user input print "'#{choice}' is an invalid option, try again. > " choice = gets.strip.downcase end @@ -47,21 +48,21 @@ def perform_action(choice) tp @workspace.channels, {channel: lambda{ |u| my_proc.call }}, :id, :name, :topic, :member_count when *OPTIONS[2] - make_selection("user", @workspace.users ) + make_selection("user", @workspace.users) when *OPTIONS[3] - make_selection("channel", @workspace.channels ) + make_selection("channel", @workspace.channels) when *OPTIONS[4] #details if @workspace.selected.nil? - puts "Oops, you haven't made a selection yet. Make a selection first." + puts "ERROR: Oops, you haven't made a selection yet. Make a selection first." else puts @workspace.selected.details end when *OPTIONS[5] #post message if @workspace.selected.nil? - puts "Who ya trying to send a message to? Pick someone first, silly." + puts "ERROR: Who ya trying to send a message to? Pick someone first, silly." else print "Enter the message that you want to send to #{@workspace.selected.name} > " message = gets @@ -71,6 +72,7 @@ def perform_action(choice) end +# presents the main menu and grabs user input def get_user_input puts "\n\nMAIN MENU - please select from the following" @@ -83,26 +85,34 @@ def get_user_input return choice end +# gets user input when they're selecting a user or channel def make_selection(type, list) puts "\n\n>>>>>>> SELECTING A #{type.upcase}" print "Please enter the #{type} number or the ID as listed > " input = gets.strip if input != 0 && input.to_i == 0 #gave us an ID + puts "checking id #{input}..." + begin @workspace.select_by_id(type, input) - else #gave us an int + rescue ArgumentError + puts "No #{type} with that ID exists." + else + puts "Selected #{type}: #{@workspace.selected.name}" + end + else #gave us an integer + puts "checking for user ##{input}..." while input == 0 || input.to_i > list.length validate_selection(input) end @workspace.select_by_index(type, input.to_i-1) + puts "Selected #{type}: #{@workspace.selected.name}" end - - puts "Selected #{type}: #{@workspace.selected.name}" - end +# helper method to validate def validate_selection(input) - print "#{input} is not a valid choice, re-enter the number > " + print "#{input} is not a valid choice, re-enter the number or ID > " return gets.strip end From d0f2b7040477b88a207243d708b281b301a968ee Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Fri, 27 Mar 2020 03:50:58 -0700 Subject: [PATCH 28/29] cleaned up spacing --- lib/workspace.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/workspace.rb b/lib/workspace.rb index 93978495..4da0b64a 100644 --- a/lib/workspace.rb +++ b/lib/workspace.rb @@ -10,8 +10,6 @@ class Workspace # attr_reader :users, :conversations, :selected attr_reader :users, :channels, :selected - - def initialize @users = User.list_all @channels = Channel.list_all From 398925277570cd1ecfad47b754f34d334fe7cd83 Mon Sep 17 00:00:00 2001 From: Angela Nguyen Date: Fri, 27 Mar 2020 15:56:32 -0700 Subject: [PATCH 29/29] fixed a bug where the recovery prompt wasn't looping correctly --- lib/slack.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slack.rb b/lib/slack.rb index 555f040b..3d726069 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -103,7 +103,7 @@ def make_selection(type, list) else #gave us an integer puts "checking for user ##{input}..." while input == 0 || input.to_i > list.length - validate_selection(input) + input = validate_selection(input) end @workspace.select_by_index(type, input.to_i-1) puts "Selected #{type}: #{@workspace.selected.name}"