From 180011c097457631f0a6735a947e26765c380239 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 8 Apr 2018 02:26:36 +0100 Subject: [PATCH 1/4] Fix slash commands in private groups --- lib/discourse_chat/provider/slack/slack_command_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb index f36defd..3d8d86a 100644 --- a/lib/discourse_chat/provider/slack/slack_command_controller.rb +++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb @@ -26,8 +26,6 @@ module DiscourseChat::Provider::SlackProvider case params[:channel_name] when 'directmessage' "@#{params[:user_name]}" - when 'privategroup' - params[:channel_id] else "##{params[:channel_name]}" end From 427cfc230564828dc2dc913e05adeee634576e1d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 8 Apr 2018 02:27:49 +0100 Subject: [PATCH 2/4] =?UTF-8?q?Use=20generic=20=E2=80=98conversations?= =?UTF-8?q?=E2=80=99=20API=20endpoint=20to=20enable=20transcripts=20for=20?= =?UTF-8?q?slack=20IMs=20and=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/discourse_chat/provider/slack/slack_transcript.rb | 4 ++-- .../provider/slack/slack_command_controller_spec.rb | 2 +- .../provider/slack/slack_transcript_spec.rb | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/discourse_chat/provider/slack/slack_transcript.rb b/lib/discourse_chat/provider/slack/slack_transcript.rb index f24c23d..23dd610 100644 --- a/lib/discourse_chat/provider/slack/slack_transcript.rb +++ b/lib/discourse_chat/provider/slack/slack_transcript.rb @@ -201,12 +201,12 @@ module DiscourseChat::Provider::SlackProvider http = Net::HTTP.new("slack.com", 443) http.use_ssl = true - req = Net::HTTP::Post.new(URI('https://slack.com/api/channels.history')) + req = Net::HTTP::Post.new(URI('https://slack.com/api/conversations.history')) data = { token: SiteSetting.chat_integration_slack_access_token, channel: @channel_id, - count: count + limit: count } req.set_form_data(data) diff --git a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb index 1c2def9..f116879 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb @@ -196,7 +196,7 @@ describe 'Slack Command Controller', type: :request do context "with valid slack responses" do before do stub_request(:post, "https://slack.com/api/users.list").to_return(body: '{"ok":true,"members":[{"id":"U5Z773QLS","name":"david","profile":{"icon_24":"https://example.com/avatar"}}]}') - stub_request(:post, "https://slack.com/api/channels.history").to_return(body: { ok: true, messages: messages_fixture }.to_json) + stub_request(:post, "https://slack.com/api/conversations.history").to_return(body: { ok: true, messages: messages_fixture }.to_json) end it 'generates the transcript UI properly' do diff --git a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb index 0cf262f..86ad694 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb @@ -105,7 +105,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do describe 'loading history' do it 'loads messages correctly' do - stub_request(:post, "https://slack.com/api/channels.history") + stub_request(:post, "https://slack.com/api/conversations.history") .with(body: hash_including(token: "abcde", channel: 'G1234')) .to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) @@ -113,14 +113,14 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do end it 'handles failed connection' do - stub_request(:post, "https://slack.com/api/channels.history") + stub_request(:post, "https://slack.com/api/conversations.history") .to_return(status: 500, body: {}.to_json) expect(transcript.load_chat_history).to be_falsey end it 'handles slack failure' do - stub_request(:post, "https://slack.com/api/channels.history") + stub_request(:post, "https://slack.com/api/conversations.history") .to_return(status: 200, body: { ok: false }.to_json) expect(transcript.load_chat_history).to be_falsey @@ -129,7 +129,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do context 'with loaded messages' do before do - stub_request(:post, "https://slack.com/api/channels.history") + stub_request(:post, "https://slack.com/api/conversations.history") .with(body: hash_including(token: "abcde", channel: 'G1234')) .to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) transcript.load_chat_history From 234203dc2be3406b629c02b36fb73e7fee740e35 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 8 Apr 2018 03:15:44 +0100 Subject: [PATCH 3/4] Fix travis build --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6fecb4d..d787f9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ services: before_install: - plugin_name=${PWD##*/} && echo $plugin_name +install: true # Prevent travis doing bundle install + script: - > docker run From 0ed634387479947da6bedf402aab95594a21c5da Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 8 Apr 2018 03:16:36 +0100 Subject: [PATCH 4/4] Add basic support for thread transcripts --- .../slack/slack_command_controller.rb | 11 ++++- .../provider/slack/slack_transcript.rb | 20 +++++++--- .../slack/slack_command_controller_spec.rb | 40 +++++++++++++++++++ .../provider/slack/slack_transcript_spec.rb | 20 ++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb index 3d8d86a..a504359 100644 --- a/lib/discourse_chat/provider/slack/slack_command_controller.rb +++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb @@ -56,9 +56,16 @@ module DiscourseChat::Provider::SlackProvider Scheduler::Defer.later "Processing slack transcript request" do requested_messages = nil first_message_ts = nil + requested_thread_ts = nil + thread_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p[0-9]{16}\?thread_ts=([0-9]{10}.[0-9]{6})\S*$/ slack_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p([0-9]{16})\/?$/ - if tokens.size > 1 && match = slack_url_regex.match(tokens[1]) + + if tokens.size > 2 && tokens[1] == "thread" && match = slack_url_regex.match(tokens[2]) + requested_thread_ts = match.captures[0].insert(10, '.') + elsif tokens.size > 1 && match = thread_url_regex.match(tokens[1]) + requested_thread_ts = match.captures[0] + elsif tokens.size > 1 && match = slack_url_regex.match(tokens[1]) first_message_ts = match.captures[0].insert(10, '.') elsif tokens.size > 1 begin @@ -70,7 +77,7 @@ module DiscourseChat::Provider::SlackProvider error_message = { text: I18n.t("chat_integration.provider.slack.transcript.error") } - return error_message unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id) + return error_message unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id, requested_thread_ts: requested_thread_ts) return error_message unless transcript.load_user_data return error_message unless transcript.load_chat_history diff --git a/lib/discourse_chat/provider/slack/slack_transcript.rb b/lib/discourse_chat/provider/slack/slack_transcript.rb index 23dd610..fe6d1e4 100644 --- a/lib/discourse_chat/provider/slack/slack_transcript.rb +++ b/lib/discourse_chat/provider/slack/slack_transcript.rb @@ -2,9 +2,10 @@ module DiscourseChat::Provider::SlackProvider class SlackTranscript attr_reader :users, :channel_id, :messages - def initialize(channel_name:, channel_id:) + def initialize(channel_name:, channel_id:, requested_thread_ts: nil) @channel_name = channel_name @channel_id = channel_id + @requested_thread_ts = requested_thread_ts @first_message_index = 0 @last_message_index = -1 # We can use negative array indicies to select the last message - fancy! @@ -30,6 +31,7 @@ module DiscourseChat::Provider::SlackProvider # Apply a heuristic to decide which is the first message in the current conversation def guess_first_message(skip_messages: 5) # Can skip the last n messages + return true if @requested_thread_ts # Always start thread on first message possible_first_messages = @messages[0..-skip_messages] @@ -121,6 +123,8 @@ module DiscourseChat::Provider::SlackProvider secret = DiscourseChat::Helper.save_transcript(post_content) link = "#{Discourse.base_url}/chat-transcript/#{secret}" + return { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>" } if @requested_thread_ts + { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>", attachments: [ @@ -201,7 +205,9 @@ module DiscourseChat::Provider::SlackProvider http = Net::HTTP.new("slack.com", 443) http.use_ssl = true - req = Net::HTTP::Post.new(URI('https://slack.com/api/conversations.history')) + endpoint = @requested_thread_ts ? "replies" : "history" + + req = Net::HTTP::Post.new(URI("https://slack.com/api/conversations.#{endpoint}")) data = { token: SiteSetting.chat_integration_slack_access_token, @@ -209,21 +215,25 @@ module DiscourseChat::Provider::SlackProvider limit: count } + data[:ts] = @requested_thread_ts if @requested_thread_ts + req.set_form_data(data) response = http.request(req) return false unless response.kind_of? Net::HTTPSuccess json = JSON.parse(response.body) return false unless json['ok'] - raw_messages = json['messages'].reverse + raw_messages = json['messages'] + raw_messages = raw_messages.reverse unless @requested_thread_ts # Build some message objects @messages = [] raw_messages.each_with_index do |message, index| # Only load messages next unless message["type"] == "message" - # Don't load responses to threads (if ts==thread_ts then it's the thread parent) - next if message["thread_ts"] && message["thread_ts"] != message["ts"] + + # Don't load responses to threads unless specifically requested (if ts==thread_ts then it's the thread parent) + next if !@requested_thread_ts && message["thread_ts"] && message["thread_ts"] != message["ts"] this_message = SlackMessage.new(message, self) @messages << this_message diff --git a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb index f116879..9b74604 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb @@ -231,6 +231,46 @@ describe 'Slack Command Controller', type: :request do expect(command_stub).to have_been_requested end + it 'can select by url with thread parameter' do + replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") + .with(body: /1501801629\.052212/) + .to_return(body: { ok: true, messages: messages_fixture }.to_json) + + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .to_return(body: { ok: true }.to_json) + + post "/chat-integration/slack/command.json", params: { + text: "post https://sometestslack.slack.com/archives/C6029G78F/p1501201669054212?thread_ts=1501801629.052212", + response_url: 'https://hooks.slack.com/commands/1234', + channel_name: 'general', + channel_id: 'C6029G78F', + token: token + } + + expect(command_stub).to have_been_requested + expect(replies_stub).to have_been_requested + end + + it 'can select by thread' do + replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") + .with(body: /1501801629\.052212/) + .to_return(body: { ok: true, messages: messages_fixture }.to_json) + + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .to_return(body: { ok: true }.to_json) + + post "/chat-integration/slack/command.json", params: { + text: "post thread https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", + response_url: 'https://hooks.slack.com/commands/1234', + channel_name: 'general', + channel_id: 'C6029G78F', + token: token + } + + expect(command_stub).to have_been_requested + expect(replies_stub).to have_been_requested + end + it 'can select by count' do command_stub = stub_request(:post, "https://slack.com/commands/1234") .with(body: /1501801629\.052212/) diff --git a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb index 86ad694..a764c70 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb @@ -127,6 +127,26 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do end end + context 'with thread_ts specified' do + let(:thread_transcript) { described_class.new(channel_name: "#general", channel_id: "G1234", requested_thread_ts: "1501801629.052212") } + + before do + stub_request(:post, "https://slack.com/api/conversations.replies") + .with(body: hash_including(token: "abcde", channel: 'G1234', ts: "1501801629.052212")) + .to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json) + thread_transcript.load_chat_history + end + + it 'includes messages in a thread' do + expect(thread_transcript.messages.length).to eq(7) + end + + it 'loads in chronological order' do # replies API presents messages in actual chronological order + expect(thread_transcript.messages.first.ts).to eq('1501801665.062694') + end + + end + context 'with loaded messages' do before do stub_request(:post, "https://slack.com/api/conversations.history")