-
Notifications
You must be signed in to change notification settings - Fork 31
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
base: master
Are you sure you want to change the base?
Changes from all commits
2b6c2ca
56401fe
660e263
d35a786
1ba5769
f03138d
ba97b42
9bf9be8
e52353d
c49ccc8
0ce5a32
dad996c
9723559
7ccc2a1
14cc009
03cd82b
0509517
7a5c126
12b6c6e
e044a86
18fb075
1f40e2f
53c5c90
80b886b
a67a28f
b797ec7
9320a03
6fc0370
6728752
e153bc8
60a6065
48e553c
3a4daf0
20cc803
2e1da0b
548752e
a53e54a
d2610b5
6280336
113ac54
45b9c78
07dba27
5275735
d1acf8b
6b0c9db
74d7cb9
564a4d1
be1cf5c
e38f46c
69a6be4
a7528ad
fdf0980
5d639de
1584681
8fac170
39da3ab
f95cefb
d6c315a
8be0ff4
aa96ec5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'] | ||
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"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice touch filtering out the bot users. |
||
return bots | ||
end | ||
end |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should leave the actual printing of things up to |
||
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| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of |
||
Channel.new(slack_id: channel["id"], | ||
name: channel["name"], | ||
topic: channel["topic"]["value"], | ||
member_count: channel["num_members"]) | ||
end | ||
return all_channels | ||
end | ||
end |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to |
||
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 |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if I want to send a message like |
||
begin | ||
workspace.send_message(message) | ||
save_message_history(message, workspace.selected) | ||
rescue SlackApiError => error | ||
puts error.message | ||
end | ||
Comment on lines
+75
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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!" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ! |
||
end | ||
|
||
main if __FILE__ == $PROGRAM_NAME |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class SlackApiError < StandardError | ||
|
||
end |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might raise a You can forget to check for a |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pauline.chane,U01BKP7MGVD,yay |
There was a problem hiding this comment.
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.