diff --git a/lib/lita-slack.rb b/lib/lita-slack.rb index 85426dd..abafffc 100644 --- a/lib/lita-slack.rb +++ b/lib/lita-slack.rb @@ -4,4 +4,5 @@ File.join("..", "..", "locales", "*.yml"), __FILE__ )] -require "lita/adapters/slack" +require_relative "lita/source" +require_relative "lita/adapters/slack" diff --git a/lib/lita/adapters/slack.rb b/lib/lita/adapters/slack.rb index 6107cb6..f98eb36 100644 --- a/lib/lita/adapters/slack.rb +++ b/lib/lita/adapters/slack.rb @@ -1,5 +1,5 @@ -require 'lita/adapters/slack/chat_service' -require 'lita/adapters/slack/rtm_connection' +require_relative 'slack/chat_service' +require_relative 'slack/rtm_connection' module Lita module Adapters @@ -40,7 +40,11 @@ def roster(target) def send_messages(target, strings) api = API.new(config) - api.send_messages(channel_for(target), strings) + if target.respond_to?(:thread) + api.send_messages(target.room || target.user&.id, strings, thread_ts: target.thread) + else + api.send_messages(target.room || target.user&.id, strings) + end end def set_topic(target, topic) @@ -60,14 +64,6 @@ def shut_down attr_reader :rtm_connection - def channel_for(target) - if target.private_message? - rtm_connection.im_for(target.user.id) - else - target.room - end - end - def channel_roster(room_id, api) response = api.channels_info room_id response['channel']['members'] diff --git a/lib/lita/adapters/slack/api.rb b/lib/lita/adapters/slack/api.rb index 3bb437c..8567a46 100644 --- a/lib/lita/adapters/slack/api.rb +++ b/lib/lita/adapters/slack/api.rb @@ -1,9 +1,9 @@ require 'faraday' -require 'lita/adapters/slack/team_data' -require 'lita/adapters/slack/slack_im' -require 'lita/adapters/slack/slack_user' -require 'lita/adapters/slack/slack_channel' +require_relative 'team_data' +require_relative 'slack_im' +require_relative 'slack_user' +require_relative 'slack_channel' module Lita module Adapters @@ -55,29 +55,31 @@ def send_attachments(room_or_user, attachments) ) end - def send_messages(channel_id, messages) - call_api( - "chat.postMessage", - **post_message_config, - as_user: true, + def send_messages(channel_id, messages, additional_payload = {}) + data = { channel: channel_id, + as_user: true, text: messages.join("\n"), - ) + }.merge(post_message_config) + .merge(additional_payload) + + call_api( "chat.postMessage", **data ) end def set_topic(channel, topic) call_api("channels.setTopic", channel: channel, topic: topic) end - def rtm_start - response_data = call_api("rtm.start") + def rtm_connect + response_data = call_api("rtm.connect") + + raise RuntimeError, response_data["error"] if response_data["ok"] != true TeamData.new( - SlackIM.from_data_array(response_data["ims"]), + response_data["team"]["id"], + response_data["team"]["name"], + response_data["team"]["domain"], SlackUser.from_data(response_data["self"]), - SlackUser.from_data_array(response_data["users"]), - SlackChannel.from_data_array(response_data["channels"]) + - SlackChannel.from_data_array(response_data["groups"]), response_data["url"], ) end diff --git a/lib/lita/adapters/slack/chat_service.rb b/lib/lita/adapters/slack/chat_service.rb index 317997b..48b7475 100644 --- a/lib/lita/adapters/slack/chat_service.rb +++ b/lib/lita/adapters/slack/chat_service.rb @@ -1,4 +1,4 @@ -require "lita/adapters/slack/attachment" +require_relative "attachment" module Lita module Adapters diff --git a/lib/lita/adapters/slack/message_handler.rb b/lib/lita/adapters/slack/message_handler.rb index b1572b2..721db71 100644 --- a/lib/lita/adapters/slack/message_handler.rb +++ b/lib/lita/adapters/slack/message_handler.rb @@ -112,9 +112,13 @@ def channel data["channel"] end + def thread + data["thread_ts"] + end + def dispatch_message(user) room = Lita::Room.find_by_id(channel) - source = Source.new(user: user, room: room || channel) + source = Source.new(user: user, room: room || channel, thread: thread) source.private_message! if channel && channel[0] == "D" message = Message.new(robot, body, source) message.command! if source.private_message? diff --git a/lib/lita/adapters/slack/rtm_connection.rb b/lib/lita/adapters/slack/rtm_connection.rb index 90bae4d..cb9f661 100644 --- a/lib/lita/adapters/slack/rtm_connection.rb +++ b/lib/lita/adapters/slack/rtm_connection.rb @@ -1,12 +1,12 @@ require 'faye/websocket' require 'multi_json' -require 'lita/adapters/slack/api' -require 'lita/adapters/slack/event_loop' -require 'lita/adapters/slack/im_mapping' -require 'lita/adapters/slack/message_handler' -require 'lita/adapters/slack/room_creator' -require 'lita/adapters/slack/user_creator' +require_relative 'api' +require_relative 'event_loop' +require_relative 'im_mapping' +require_relative 'message_handler' +require_relative 'room_creator' +require_relative 'user_creator' module Lita module Adapters @@ -17,23 +17,15 @@ class RTMConnection class << self def build(robot, config) - new(robot, config, API.new(config).rtm_start) + new(robot, config, API.new(config).rtm_connect) end end def initialize(robot, config, team_data) @robot = robot @config = config - @im_mapping = IMMapping.new(API.new(config), team_data.ims) @websocket_url = team_data.websocket_url @robot_id = team_data.self.id - - UserCreator.create_users(team_data.users, robot, robot_id) - RoomCreator.create_rooms(team_data.channels, robot) - end - - def im_for(user_id) - im_mapping.im_for(user_id) end def run(queue = nil, options = {}) diff --git a/lib/lita/adapters/slack/team_data.rb b/lib/lita/adapters/slack/team_data.rb index e77b3b3..8cdb326 100644 --- a/lib/lita/adapters/slack/team_data.rb +++ b/lib/lita/adapters/slack/team_data.rb @@ -2,7 +2,7 @@ module Lita module Adapters # @api private class Slack < Adapter - TeamData = Struct.new(:ims, :self, :users, :channels, :websocket_url) + TeamData = Struct.new(:id, :name, :domain, :self, :websocket_url) end end end diff --git a/lib/lita/source.rb b/lib/lita/source.rb new file mode 100644 index 0000000..72c826e --- /dev/null +++ b/lib/lita/source.rb @@ -0,0 +1,35 @@ +module Lita + class Source + + # The room the message came from or should be sent to, as a string. + # @return [String, NilClass] A string uniquely identifying the room. + attr_reader :thread + + # @param user [Lita::User] The user who sent the message or should receive + # the outgoing message. + # @param room [Lita::Room, String] A string or {Lita::Room} uniquely identifying the room + # the user sent the message from, or the room where a reply should go. The format of this + # string (or the ID of the {Lita::Room} object) will differ depending on the chat service. + # @param private_message [Boolean] A flag indicating whether or not the + # message was sent privately. + def initialize(user: nil, room: nil, thread: nil, private_message: false) + @user = user + @thread = thread + + case room + when String + @room = room + @room_object = Room.new(room) + when Room + @room = room.id + @room_object = room + end + + @private_message = private_message + + raise ArgumentError, I18n.t("lita.source.user_or_room_required") if user.nil? && room.nil? + + @private_message = true if room.nil? + end + end +end diff --git a/lita-slack.gemspec b/lita-slack.gemspec index 59a929f..0c0aa55 100644 --- a/lita-slack.gemspec +++ b/lita-slack.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "lita", ">= 4.7.1" spec.add_runtime_dependency "multi_json" - spec.add_development_dependency "bundler", "~> 1.3" + spec.add_development_dependency "bundler" spec.add_development_dependency "pry-byebug" spec.add_development_dependency "rack-test" spec.add_development_dependency "rake" diff --git a/spec/lita/adapters/slack/api_spec.rb b/spec/lita/adapters/slack/api_spec.rb index 771df2e..a012369 100644 --- a/spec/lita/adapters/slack/api_spec.rb +++ b/spec/lita/adapters/slack/api_spec.rb @@ -637,11 +637,11 @@ def stubs(postMessage_options = {}) end end - describe "#rtm_start" do + describe "#rtm_connect" do let(:http_status) { 200 } let(:stubs) do Faraday::Adapter::Test::Stubs.new do |stub| - stub.post('https://slack.com/api/rtm.start', token: token) do + stub.post('https://slack.com/api/rtm.connect', token: token) do [http_status, {}, http_response] end end @@ -652,34 +652,19 @@ def stubs(postMessage_options = {}) MultiJson.dump({ ok: true, url: 'wss://example.com/', - users: [{ id: 'U023BECGF' }], - ims: [{ id: 'D024BFF1M' }], self: { id: 'U12345678' }, - channels: [{ id: 'C1234567890' }], - groups: [{ id: 'G0987654321' }], + team: { id: 'T0987654321' }, }) end it "has data on the bot user" do - response = subject.rtm_start + response = subject.rtm_connect expect(response.self.id).to eq('U12345678') end - it "has an array of IMs" do - response = subject.rtm_start - - expect(response.ims[0].id).to eq('D024BFF1M') - end - - it "has an array of users" do - response = subject.rtm_start - - expect(response.users[0].id).to eq('U023BECGF') - end - it "has a WebSocket URL" do - response = subject.rtm_start + response = subject.rtm_connect expect(response.websocket_url).to eq('wss://example.com/') end diff --git a/spec/lita/adapters/slack/message_handler_spec.rb b/spec/lita/adapters/slack/message_handler_spec.rb index c449b0c..aae8568 100644 --- a/spec/lita/adapters/slack/message_handler_spec.rb +++ b/spec/lita/adapters/slack/message_handler_spec.rb @@ -1,10 +1,13 @@ require "spec_helper" +# require_relative '../../../../lib/lita/adapters/slack/slack_source' describe Lita::Adapters::Slack::MessageHandler, lita: true do subject { described_class.new(robot, robot_id, data) } before do allow(robot).to receive(:trigger) + allow(robot).to receive(:alias) + allow(robot).to receive(:receive) Lita::Adapters::Slack::RoomCreator.create_room(channel, robot) end @@ -44,7 +47,8 @@ allow(Lita::Room).to receive(:find_by_id).and_return(room) allow(Lita::Source).to receive(:new).with( user: user, - room: room + room: room, + thread: nil, ).and_return(source) allow(Lita::Message).to receive(:new).with(robot, "Hello", source).and_return(message) allow(robot).to receive(:receive).with(message) @@ -74,7 +78,8 @@ allow(Lita::Room).to receive(:find_by_id).and_return(nil) allow(Lita::Source).to receive(:new).with( user: user, - room: "D2147483705" + room: "D2147483705", + thread: nil, ).and_return(source) allow(source).to receive(:private_message!).and_return(true) allow(source).to receive(:private_message?).and_return(true) @@ -156,6 +161,32 @@ end end + context "when the message is in a thread" do + let(:data) do + { + "type" => "message", + "channel" => "C2147483705", + "user" => "U023BECGF", + "text" => "Hello", + "ts" => "1234.5678", + "thread_ts" => "1234.5678", + } + end + let(:source) { instance_double('Lita::Source', room: room, private_message?: false) } + + before do + allow(Lita::Source).to receive(:new).with( + user: user, + room: room, + thread: "1234.5678", + ).and_return(source) + end + + it "records the room thread" do + subject.handle + end + end + describe "Removing message formatting" do let(:user) { instance_double('Lita::User', id: 'U123',name: 'name', mention_name: 'label') } diff --git a/spec/lita/adapters/slack/rtm_connection_spec.rb b/spec/lita/adapters/slack/rtm_connection_spec.rb index 32188f3..6219fd7 100644 --- a/spec/lita/adapters/slack/rtm_connection_spec.rb +++ b/spec/lita/adapters/slack/rtm_connection_spec.rb @@ -9,21 +9,18 @@ def with_websocket(subject, queue) thread.join end - subject { described_class.new(robot, config, rtm_start_response) } + subject { described_class.new(robot, config, rtm_connect_response) } let(:api) { instance_double("Lita::Adapters::Slack::API") } let(:registry) { Lita::Registry.new } let(:robot) { Lita::Robot.new(registry) } - let(:raw_user_data) { Hash.new } - let(:channel) { Lita::Adapters::Slack::SlackChannel.new('C2147483705', 'general', 1360782804, 'U023BECGF', metadata) } - let(:metadata) { Hash.new } - let(:rtm_start_response) do + let(:rtm_connect_response) do Lita::Adapters::Slack::TeamData.new( - [], - Lita::Adapters::Slack::SlackUser.new('U12345678', 'carl', nil, raw_user_data), - [Lita::Adapters::Slack::SlackUser.new('U12345678', 'carl', '', raw_user_data)], - [channel], + 'T2U81E2FP', + 'SlackDemo', + 'slackdemo', + Lita::Adapters::Slack::SlackUser.new('U12345678', 'carl', nil, {}), "wss://example.com/" ) end @@ -39,42 +36,12 @@ def with_websocket(subject, queue) describe ".build" do before do allow(Lita::Adapters::Slack::API).to receive(:new).with(config).and_return(api) - allow(api).to receive(:rtm_start).and_return(rtm_start_response) + allow(api).to receive(:rtm_connect).and_return(rtm_connect_response) end - it "constructs a new RTMConnection with the results of rtm.start data" do + it "constructs a new RTMConnection with the results of rtm.connect data" do expect(described_class.build(robot, config)).to be_an_instance_of(described_class) end - - it "creates users with the results of rtm.start data" do - expect(Lita::Adapters::Slack::UserCreator).to receive(:create_users) - - described_class.build(robot, config) - end - - it "creates rooms with the results of rtm.start data" do - expect(Lita::Adapters::Slack::RoomCreator).to receive(:create_rooms) - - described_class.build(robot, config) - end - end - - describe "#im_for" do - before do - allow(Lita::Adapters::Slack::API).to receive(:new).with(config).and_return(api) - allow( - Lita::Adapters::Slack::IMMapping - ).to receive(:new).with(api, []).and_return(im_mapping) - allow(im_mapping).to receive(:im_for).with('U12345678').and_return('D024BFF1M') - end - - let(:im_mapping) { instance_double('Lita::Adapters::Slack::IMMapping') } - - it "delegates to the IMMapping" do - with_websocket(subject, queue) do |websocket| - expect(subject.im_for('U12345678')).to eq('D024BFF1M') - end - end end describe "#run" do @@ -118,6 +85,7 @@ def with_websocket(subject, queue) context "when the WebSocket is closed from outside" do it "shuts down the reactor" do with_websocket(subject, queue) do |websocket| + sleep 0.1 # Since this code is run in a thread, we need to wait for it to finish websocket.close expect(EM.stopping?).to be_truthy end diff --git a/spec/lita/adapters/slack_spec.rb b/spec/lita/adapters/slack_spec.rb index fbbbd23..830063b 100644 --- a/spec/lita/adapters/slack_spec.rb +++ b/spec/lita/adapters/slack_spec.rb @@ -133,10 +133,37 @@ it "does not send via the RTM api" do expect(rtm_connection).to_not receive(:send_messages) - expect(api).to receive(:send_messages).with(room_source.room, ['foo']) + expect(api).to receive(:send_messages).with(room_source.room, ['foo'], { thread_ts: nil }) subject.send_messages(room_source, ['foo']) end + + context "when thread is set" do + let(:room) { Lita::Room.new('C024BE91L') } + let(:room_source) { Lita::Source.new(room: room, thread: '12345.67890') } + + it "sends the message to the Web API with thread_ts" do + expect(api).to receive(:send_messages).with(room_source.room, ['foo'], { thread_ts: '12345.67890' }) + + subject.send_messages(room_source, ['foo']) + end + end + + context "with optional thread" do + it "sends the message to the Web API without thread_ts" do + expect(api).to receive(:send_messages).with(private_message_source.room, ['foo']) + + subject.send_messages(private_message_source, ['foo']) + end + end + + context "with user source" do + it "sends direct message to the Web API" do + expect(api).to receive(:send_messages).with(user_source.user.id, ['foo']) + + subject.send_messages(user_source, ['foo']) + end + end end end diff --git a/spec/lita/adapters/source_spec.rb b/spec/lita/adapters/source_spec.rb new file mode 100644 index 0000000..170967e --- /dev/null +++ b/spec/lita/adapters/source_spec.rb @@ -0,0 +1,10 @@ +require "spec_helper" + +describe Lita::Source do + subject { described_class.new(room: 'R0000', thread: 'thread') } + + it 'records message source thread' do + expect(subject.thread).to eq('thread') + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6632cfb..d52bf12 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,7 +6,7 @@ ] SimpleCov.start { add_filter "/spec/" } -require "lita-slack" +require_relative "../lib/lita-slack" require "lita/rspec" require "pry"