Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fire - Roshni and Pauline - Slack CLI #6

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2b6c2ca
Worked through design exercise and setup files
roshni-patel Oct 6, 2020
56401fe
Moved class files to lib folder
roshni-patel Oct 6, 2020
660e263
First draft of channel.rb
roshni-patel Oct 6, 2020
d35a786
v0 of user.rb User class, added recipient get method
ghostfruitleaf Oct 6, 2020
1ba5769
Merge branch 'master' of https://github.com/PaulineChane/slack-cli
ghostfruitleaf Oct 6, 2020
f03138d
Edited channel.rb list_all and details methods
roshni-patel Oct 6, 2020
ba97b42
added empty test files and SlackApiError custom exception
ghostfruitleaf Oct 6, 2020
9bf9be8
oops, committed from wrong directory, see last commit's comments
ghostfruitleaf Oct 6, 2020
e52353d
removed quick script to test token, reorganized files to work with rake
ghostfruitleaf Oct 7, 2020
c49ccc8
debugging User and pushing for collaborators to test issues with
ghostfruitleaf Oct 7, 2020
0ce5a32
Finished list_all and details methods
roshni-patel Oct 7, 2020
dad996c
Wrote basic CLI program for wave 1
roshni-patel Oct 7, 2020
9723559
added tests for recipient.rb
ghostfruitleaf Oct 7, 2020
7ccc2a1
working nominal test for self.get method in recipient
ghostfruitleaf Oct 7, 2020
14cc009
Edited CLI program, workspace.rb, working and able to get info from t…
roshni-patel Oct 7, 2020
03cd82b
fixed initialize param in recipient to keywords
ghostfruitleaf Oct 7, 2020
0509517
forgot to delete the two tests files we don't really need, oops (again)
ghostfruitleaf Oct 7, 2020
7a5c126
First draft of channel.rb tests
roshni-patel Oct 7, 2020
12b6c6e
First draft of channel.rb tests
roshni-patel Oct 7, 2020
e044a86
added more to list_all Channel test
ghostfruitleaf Oct 7, 2020
18fb075
Updated channel test file details method and details method in channe…
roshni-patel Oct 7, 2020
1f40e2f
completed tests for user.rb
ghostfruitleaf Oct 7, 2020
53c5c90
Added some more assertions to channel.rb test
roshni-patel Oct 7, 2020
80b886b
added workspace.rb test for constructor; wave 1 has been finishedga .…
ghostfruitleaf Oct 7, 2020
a67a28f
added and tested select_user and send_message in workspace.rb, added …
ghostfruitleaf Oct 7, 2020
b797ec7
wrote a quick test for SlackApiError
ghostfruitleaf Oct 7, 2020
9320a03
Updated CLI for wave 2, wrote select channel method, and wrote select…
roshni-patel Oct 8, 2020
6fc0370
added send_message method to workspace and recipient, minor edits to …
ghostfruitleaf Oct 8, 2020
6728752
Added error handling for details in case statement
roshni-patel Oct 8, 2020
e153bc8
laid out tests for send_message, added extra space to url recipient v…
ghostfruitleaf Oct 8, 2020
60a6065
combining edits Merge branch 'master' of https://github.com/PaulineCh…
ghostfruitleaf Oct 8, 2020
48e553c
Added send message option to CLI
roshni-patel Oct 8, 2020
3a4daf0
removed reset test and reset funciton in workspace.show_details
ghostfruitleaf Oct 8, 2020
20cc803
adding SLACK_TOKEN class constant to recipient
ghostfruitleaf Oct 8, 2020
2e1da0b
added clear_selection method to workspace
ghostfruitleaf Oct 8, 2020
548752e
fixed a lot of tests and added token method to recipient.rb, fixed Sl…
ghostfruitleaf Oct 8, 2020
a53e54a
added working tests for workspace.rb send_message method
ghostfruitleaf Oct 8, 2020
d2610b5
Updated slack CLI with clear selection method
roshni-patel Oct 8, 2020
6280336
modified send_message in recipient to return parsed response, added t…
ghostfruitleaf Oct 8, 2020
113ac54
created bot class for optional, modified user to have more fields to …
ghostfruitleaf Oct 8, 2020
45b9c78
clean up tests and user after field modifications
ghostfruitleaf Oct 8, 2020
07dba27
added save message history and get message history methods, not curre…
roshni-patel Oct 8, 2020
5275735
completed v0 of bot class for optional
ghostfruitleaf Oct 8, 2020
d1acf8b
adding slack.rb updates to merge branch 'master' of https://github.co…
ghostfruitleaf Oct 8, 2020
6b0c9db
fixed current_bot method for bot.rb
ghostfruitleaf Oct 8, 2020
74d7cb9
removed redundant methods in workspace that can be directly called on…
ghostfruitleaf Oct 8, 2020
564a4d1
Started writing bot tests
roshni-patel Oct 8, 2020
be1cf5c
Merge branch 'master' of https://github.com/PaulineChane/slack-cli
roshni-patel Oct 8, 2020
e38f46c
fixed some bot functions, one test is not working still
ghostfruitleaf Oct 9, 2020
69a6be4
mostly working CLI, will need to change a few things
roshni-patel Oct 9, 2020
a7528ad
minor fixes to worksapce set_user and bot tests
ghostfruitleaf Oct 9, 2020
fdf0980
adding more slack functionalities; Merge branch 'master' of https://g…
ghostfruitleaf Oct 9, 2020
5d639de
fixed select_user method to account for current bot to send with cust…
ghostfruitleaf Oct 9, 2020
1584681
updated cli to combine emoji and username
roshni-patel Oct 9, 2020
8fac170
caught error in implementation of sending customized message; modifie…
ghostfruitleaf Oct 9, 2020
39da3ab
fixed parameters for send_message in workspace
ghostfruitleaf Oct 9, 2020
f95cefb
fixed test for bot to send with customized emoji
ghostfruitleaf Oct 9, 2020
d6c315a
very minor fixes to slack.rb, should be working now :D
ghostfruitleaf Oct 9, 2020
8be0ff4
fixed emoji check regex and bug from opening empty message history file
ghostfruitleaf Oct 9, 2020
aa96ec5
fixed failing test on send_message methods
ghostfruitleaf Oct 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions lib/bot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
require_relative 'user'
# an excessive but object oriented solution
class Bot < User
attr_reader :slack_id, :name, :real_name, :time_zone, :is_bot, :send_as, :emoji
def initialize(slack_id: , name:, real_name:, time_zone: ,is_bot: , send_as: '', emoji: '')
super(slack_id: slack_id,
name: name,
real_name: real_name,
time_zone: time_zone,
is_bot: is_bot)
@send_as = send_as
@emoji = emoji
end

def self.current_bot
users = User.list_all
url = 'https://slack.com/api/auth.test'
query = {token: Bot.token}
sleep(0.5)
response = HTTParty.get(url, query: query)['user_id']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check the response code of this request.

user = users.find{ |user| user.slack_id == response}
return Bot.new(slack_id: user.slack_id,
name: user.name,
real_name: user.real_name,
time_zone: user.time_zone,
is_bot: user.is_bot)
end

def set_emoji(emoji)
# regex checks for correct emoji format:
# (:EMOJI:, EMOJI, alphanumeric and dashes only (can also have exactly TWO colons on either side))
raise ArgumentError, "invalid emoji" unless /^(:[a-zA-Z0-9_]+:)/ =~ emoji || /^([a-zA-Z0-9_]+)/ =~ emoji
@emoji = emoji
return emoji
end

def set_send_as (username)
@send_as = username
return username
end

def self.list_all
bots = super
bots = bots.filter{ |user| user.is_bot || user.slack_id == "USLACKBOT"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice touch filtering out the bot users.

return bots
end
end
34 changes: 34 additions & 0 deletions lib/channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require_relative 'recipient'
require 'table_print'

class Channel < Recipient
attr_reader :topic, :member_count

def initialize(slack_id:, name:, topic:, member_count:)
super(slack_id: slack_id, name: name)
@topic = topic
@member_count = member_count
end

def details
tp self, :slack_id, :name, :topic, :member_count

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should leave the actual printing of things up to slack.rb. (This is why your unit tests print out tables in the middle.)

return {"SLACK_ID": @slack_id,
"NAME": @name,
"TOPIC": @topic,
"MEMBER_COUNT": @member_count
}
end

def self.list_all
url = "https://slack.com/api/conversations.list"
param = {token: Channel.token}
raw_channels = Channel.get(url, param)['channels']
all_channels = raw_channels.map do |channel|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of map!

Channel.new(slack_id: channel["id"],
name: channel["name"],
topic: channel["topic"]["value"],
member_count: channel["num_members"])
end
return all_channels
end
end
64 changes: 64 additions & 0 deletions lib/recipient.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'httparty'
require_relative 'slack_api_error'


class Recipient
attr_reader :slack_id, :name
# SLACK_TOKEN = ENV['SLACK_TOKEN']
def initialize(slack_id:, name:)
@slack_id = slack_id
@name = name
end
# methods

def send_message(message, emoji: "", send_as: "")
# as recommended by Slack API for slack apps
if message.length > 4000 # message too long
# we don't want to break the program unless the API can't connect
# so we do a puts
raise SlackApiError, "Message too long. Try again."
return false
end
url = 'https://slack.com/api/chat.postMessage'
query = { token: Recipient.token,
text: message,
channel: @slack_id,
icon_emoji: emoji,
username: send_as} # to post to both users and channel
sleep(1)
response = HTTParty.post(url, query: query)

# check for successful post
unless response.parsed_response['ok'] && response.code == 200
# we don't want to break the program here because the reason
# could be anything
raise SlackApiError, "Error: #{response.parsed_response['error']}. Please try again"
return false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to return false here. The raise will make sure this line never runs.

end

return response.parsed_response
end

def self.token
return ENV['SLACK_TOKEN']
end
# we are assuming, because the CLI user won't see the code and only the program
# itself uses it, that the url is a valid url.
def self.get(url, params)
response = HTTParty.get(url, query: params)
sleep(0.5)
if response.code != 200 || !response.parsed_response['ok']
raise SlackApiError, "Error: #{response.parsed_response['error']}. Please try again"
end
return response
end

# template methods
def details
raise NotImplementedError, "implement me in child class"
end

def self.list_all
raise NotImplementedError, "implement me in child class"
end
end
110 changes: 108 additions & 2 deletions lib/slack.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,118 @@
#!/usr/bin/env ruby
require_relative 'workspace'
require 'table_print'
require 'dotenv'
require 'csv'
require 'awesome_print'

def save_message_history(message, recipient)
CSV.open("message_history.csv", "a") do |file|
file << [recipient.name, recipient.slack_id, message]
end
end

def get_message_history(selected)
history = CSV.read('message_history.csv').map { |row| row.to_a }
selected_recipients = history.select { |recipient| recipient[0] == selected.name || recipient[1] == selected.slack_id}
messages = selected_recipients.map{ |recipient_arr| recipient_arr[2]}
return messages
end

def main
Dotenv.load
puts "Welcome to the Ada Slack CLI!"
workspace = Workspace.new
bot = workspace.current_bot
selected_recipient = nil
selected_emoji = nil
selected_username = nil
Comment on lines +26 to +28

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This state probably belongs inside of your bot or workspace (and shouldn't be duplicated here).

It's always best to have a "single source of truth" so that you don't have two places that can get out of sync.

history_file = false

puts
puts "This workspace has #{workspace.users.length} users and #{workspace.channels.length} channels"
puts
puts "Choose from the following \n 1. list channels \n 2. list users \n 3. select user \n 4. select channel \n 5. details \n 6. send message \n 7. clear selection \n 8. message history \n 9. set emoji and username \n 10. quit \nSelected Recipient(NONE if blank): #{selected_recipient} \nSelected Emoji(NONE if blank and only for current bot): #{selected_emoji} \nSelected Username(NONE if blank and only for current bot): #{selected_username}"
puts

user_input = ""
until user_input == "quit"
puts "What would you like to do?"
user_input = gets.strip.downcase

case user_input
when "list channels"
tp workspace.channels, :slack_id, :name, :topic, :member_count
when "list users"
tp workspace.users, :slack_id, :name, :real_name, :time_zone, :is_bot
when "select user"
puts "Which user would you like to select?"
selected_recipient = gets.strip
workspace.select_user(selected_recipient)
if workspace.selected.nil?
puts "No user by the name #{selected_recipient} can be found. Please try again."
end
when "select channel"
puts "Which channel would you like to select?"
selected_recipient = gets.strip
workspace.select_channel(selected_recipient)
if workspace.selected.nil?
puts "No channel by the name #{selected_recipient} can be found. Please try again."
end
when "details"
if workspace.selected.nil?
puts "Please select a channel or user before asking for details."
end
workspace.show_details
when "send message"
if workspace.selected.nil?
puts "Please select a channel or user before sending a message."
else
puts "What is the message you would like to send?"
message = gets.strip

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I want to send a message like Hello! ? 😛

begin
workspace.send_message(message)
save_message_history(message, workspace.selected)
rescue SlackApiError => error
puts error.message
end
Comment on lines +75 to +77

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of rescue here!

history_file = true
end
when "clear selection"
workspace.clear_selection
selected_recipient = nil
when "quit"
user_input = "quit"
break
when "message history"
if workspace.selected.nil?
puts "Please select a channel or user before sending a message."
elsif !history_file
puts "No messages sent yet."
else
message_history = get_message_history(workspace.selected)
ap message_history
end
when "set emoji and username"
puts "Which emoji would you like to add?"
emoji = gets.strip
selected_emoji = emoji
begin
bot.set_emoji(emoji)
rescue ArgumentError => error
puts error.message
end
puts "What would you like to set the username of the bot as?"
username = gets.strip
selected_username = username
bot.set_send_as(username)
else
puts "That's not a valid option. Please try again."
end

# TODO project
puts "Choose from the following: \n 1. list channels \n 2. list users \n 3. select user \n 4. select channel \n 5. details \n 6. send message \n 7. clear selection \n 8. message history \n 9. set emoji and username \n 10. quit \nSelected Recipient(NONE if blank): #{selected_recipient} \nSelected Emoji(NONE if blank and only for current bot): #{selected_emoji} \nSelected Username(NONE if blank and only for current bot): #{selected_username}"
end

puts "Thank you for using the Ada Slack CLI"
puts "Thank you for using the Ada Slack CLI!"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

end

main if __FILE__ == $PROGRAM_NAME
3 changes: 3 additions & 0 deletions lib/slack_api_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class SlackApiError < StandardError

end
42 changes: 42 additions & 0 deletions lib/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require_relative 'recipient'
require 'table_print'

class User < Recipient

attr_reader :real_name, :time_zone, :is_bot

def initialize(slack_id:, name:, real_name:, time_zone: "Pacific Daylight Time", is_bot: false)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configurable timezones! 🎉

super(slack_id: slack_id,name: name)
@real_name = real_name
@time_zone = time_zone
@is_bot = is_bot
end

# reader methods
def details
tp self, :slack_id, :name, :real_name, :time_zone, :is_bot
return {"SLACK_ID": @slack_id,
"NAME": @name,
"REAL_NAME": @real_name,
"TIME_ZONE": @time_zone,
"IS_BOT": @is_bot
}
end

# class methods
def self.list_all
url = 'https://slack.com/api/users.list'
param = {token: User.token}
raw_users = User.get(url, param)['members']
all_users = [] # to skip over deleted users
raw_users.each do |member|
next if member['deleted']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like your attention to detail with skipping bad entries like this.

all_users << User.new(slack_id: member['id'],
name: member['name'],
real_name: member['real_name'],
time_zone: member['tz_label'],
is_bot: member['is_bot'])
end
return all_users
end
end
70 changes: 70 additions & 0 deletions lib/workspace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require_relative 'user'
require_relative 'recipient'
require_relative 'channel'
require_relative 'bot'

class Workspace
attr_reader :users, :channels, :selected, :current_bot

def initialize
@users = User.list_all
@channels = Channel.list_all
@selected = nil
@current_bot = Bot.current_bot
end

def select_channel(channel_info)
unless channel_info.nil?
channel_s = channel_info.to_s
@channels.each do |channel|
if channel.slack_id == channel_s || channel.name == channel_s
@selected = channel
return channel_info
end
end
end
return
end

def select_user(user_info)
unless user_info.nil? # to use nil to reset selected field
user_s = user_info.to_s # in case a string isn't input
@users.each do |user|
# matches to slack_id or name (NOT real_name)
# returns input if successfully changed
if user.slack_id == user_s || user.name == user_s
@selected = user
return user_info
end
if @current_bot.slack_id == user_s || @current_bot.name == user_s
@selected = @current_bot
return user_info
end
end
end
return nil # returns nil to indicate user not found

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might raise a UserNotFoundError instead of returning nil.

You can forget to check for a nil return value but you notice right away if you got an exception.

end

# clear selected field
def clear_selection
@selected = nil
end

def send_message(message)
# returns nil if nothing is selected (sanity check)
# prints boolean from Recipient.send_message
return nil if @selected.nil?
# the driver will reset recipient if true
# otherwise will not reset until valid message
return @selected.send_message(message, emoji: @current_bot.emoji, send_as: @current_bot.send_as) # the driver will reset recipient if true
end

def show_details
# returns nil if nothing is selected (sanity check)
# returns respective details for selected user or channel otherwise
return @selected.nil? ? nil : @selected.details
end

end
1 change: 1 addition & 0 deletions message_history.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pauline.chane,U01BKP7MGVD,yay
Loading