diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index a252660..f23528e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -44,6 +44,7 @@ en: ####################################### chat_integration_mattermost_enabled: "Enable the Mattermost chat-integration provider" chat_integration_mattermost_webhook_url: 'URL for the Mattermost webhook' + chat_integration_mattermost_incoming_webhook_token: 'The verification token used to authenticate incoming requests' chat_integration_mattermost_icon_url: "Icon for posts to mattermost (defaults to forum logo)" chat_integration_mattermost_excerpt_length: "Mattermost post excerpt length" @@ -144,3 +145,39 @@ en: ####################################### hipchat: message: %{user} posted in %{title} + + ####################################### + ######## MATTERMOST STRINGS ########### + ####################################### + mattermost: + status: + header: | + *Rules for this channel* + (if multiple rules match a post, the topmost rule is executed) + no_rules: "There are no rules set up for this channel. Run `/discourse help` for instructions." + rule_string: "*%{index})* *%{filter}* posts in *%{category}*" + rule_string_tags_suffix: " with tags: *%{tags}*" + parse_error: "Sorry, I didn't understand that. Run `/discourse help` for instructions." + create: + created: "Rule created successfully" + updated: "Rule updated successfully" + error: "Sorry, an error occured while creating that rule." + delete: + success: "Rule deleted successfully" + error: "Sorry, an error occured while deleting that rule. Run `/discourse status` for a list of rules." + not_found: + tag: "The *%{name}* tag cannot be found." + category: "The *%{name}* category cannot be found. Available categories: *%{list}*" + help: | + *New rule:* `/discourse [watch|follow|mute] [category] [tag:name]` + (you must specify a rule type and at least one category or tag) + - *watch* – notify this channel for new topics and new replies + - *follow* – notify this channel for new topics + - *mute* – block notifications to this channel + + *Remove rule:* `/discourse remove [rule number]` + (`[rule number]` can be found by running `/discourse status`) + + *List rules:* `/discourse status` + + *Help:* `/discourse help` diff --git a/config/settings.yml b/config/settings.yml index 20a09f1..9ebffb2 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -66,6 +66,8 @@ plugins: default: false chat_integration_mattermost_webhook_url: default: '' + chat_integration_mattermost_incoming_webhook_token: + default: '' chat_integration_mattermost_icon_url: default: '' chat_integration_mattermost_excerpt_length: diff --git a/lib/discourse_chat/provider/mattermost/mattermost_command_controller.rb b/lib/discourse_chat/provider/mattermost/mattermost_command_controller.rb new file mode 100644 index 0000000..b03390b --- /dev/null +++ b/lib/discourse_chat/provider/mattermost/mattermost_command_controller.rb @@ -0,0 +1,71 @@ +module DiscourseChat::Provider::MattermostProvider + class MattermostCommandController < DiscourseChat::Provider::HookController + requires_provider ::DiscourseChat::Provider::MattermostProvider::PROVIDER_NAME + + before_filter :mattermost_token_valid?, only: :command + + skip_before_filter :check_xhr, + :preload_json, + :verify_authenticity_token, + :redirect_to_login_if_required, + only: :command + + def command + text = process_command(params) + + render json: { + response_type: 'ephemeral', + text: text + } + end + + def process_command(params) + + tokens = params[:text].split(" ") + + # channel name fix + channel_id = + case params[:channel_name] + when 'directmessage' + "@#{params[:user_name]}" + when 'privategroup' + params[:channel_id] + else + "##{params[:channel_name]}" + end + + provider = DiscourseChat::Provider::MattermostProvider::PROVIDER_NAME + + channel = DiscourseChat::Channel.with_provider(provider).with_data_value('identifier',channel_id).first + + # Create channel if doesn't exist + channel ||= DiscourseChat::Channel.create!(provider:provider, data:{identifier: channel_id}) + + return ::DiscourseChat::Helper.process_command(channel, tokens) + + end + + def mattermost_token_valid? + params.require(:token) + + if SiteSetting.chat_integration_mattermost_incoming_webhook_token.blank? || + SiteSetting.chat_integration_mattermost_incoming_webhook_token != params[:token] + + raise Discourse::InvalidAccess.new + end + end + end + + class MattermostEngine < ::Rails::Engine + engine_name DiscourseChat::PLUGIN_NAME+"-mattermost" + isolate_namespace DiscourseChat::Provider::MattermostProvider + end + + MattermostEngine.routes.draw do + post "command" => "mattermost_command#command" + end + +end + + + diff --git a/lib/discourse_chat/provider/mattermost/mattermost_provider.rb b/lib/discourse_chat/provider/mattermost/mattermost_provider.rb index 9806349..c3d5292 100644 --- a/lib/discourse_chat/provider/mattermost/mattermost_provider.rb +++ b/lib/discourse_chat/provider/mattermost/mattermost_provider.rb @@ -81,4 +81,6 @@ module DiscourseChat end end -end \ No newline at end of file +end + +require_relative "mattermost_command_controller.rb" \ No newline at end of file diff --git a/lib/discourse_chat/provider/slack/slack_provider.rb b/lib/discourse_chat/provider/slack/slack_provider.rb index 3146b1f..23405a6 100644 --- a/lib/discourse_chat/provider/slack/slack_provider.rb +++ b/lib/discourse_chat/provider/slack/slack_provider.rb @@ -152,4 +152,4 @@ module DiscourseChat::Provider::SlackProvider end require_relative "slack_message_formatter.rb" -require_relative "slack_command_controller.rb" \ No newline at end of file +require_relative "slack_command_controller.rb" diff --git a/spec/lib/discourse_chat/provider/mattermost/mattermost_command_controller_spec.rb b/spec/lib/discourse_chat/provider/mattermost/mattermost_command_controller_spec.rb new file mode 100644 index 0000000..f069c16 --- /dev/null +++ b/spec/lib/discourse_chat/provider/mattermost/mattermost_command_controller_spec.rb @@ -0,0 +1,115 @@ +require 'rails_helper' + +describe 'Mattermost Command Controller', type: :request do + let(:category) { Fabricate(:category) } + let(:tag) { Fabricate(:tag) } + let(:tag2) { Fabricate(:tag) } + let!(:chan1){DiscourseChat::Channel.create!(provider:'mattermost', data:{identifier: '#welcome'})} + + describe 'with plugin disabled' do + it 'should return a 404' do + post '/chat-integration/mattermost/command.json' + expect(response.status).to eq(404) + end + end + + describe 'with plugin enabled and provider disabled' do + before do + SiteSetting.chat_integration_enabled = true + SiteSetting.chat_integration_mattermost_enabled = false + end + + it 'should return a 404' do + post '/chat-integration/mattermost/command.json' + expect(response.status).to eq(404) + end + end + + describe 'slash commands endpoint' do + before do + SiteSetting.chat_integration_enabled = true + SiteSetting.chat_integration_mattermost_webhook_url = "https://hooks.mattermost.com/services/abcde" + SiteSetting.chat_integration_mattermost_enabled = true + end + + describe 'when forum is private' do + it 'should not redirect to login page' do + SiteSetting.login_required = true + token = 'sometoken' + SiteSetting.chat_integration_mattermost_incoming_webhook_token = token + + post '/chat-integration/mattermost/command.json', text: 'help', token: token + + expect(response.status).to eq(200) + end + end + + describe 'when the token is invalid' do + it 'should raise the right error' do + expect { post '/chat-integration/mattermost/command.json', text: 'help' } + .to raise_error(ActionController::ParameterMissing) + end + end + + describe 'when incoming webhook token has not been set' do + it 'should raise the right error' do + post '/chat-integration/mattermost/command.json', text: 'help', token: 'some token' + + expect(response.status).to eq(403) + end + end + + describe 'when token is valid' do + let(:token) { "Secret Sauce" } + + # No need to test every single command here, that's tested + # by helper_spec upstream + + before do + SiteSetting.chat_integration_mattermost_incoming_webhook_token = token + end + + describe 'add new rule' do + + it 'should add a new rule correctly' do + post "/chat-integration/mattermost/command.json", + text: "watch #{category.slug}", + channel_name: 'welcome', + token: token + + json = JSON.parse(response.body) + + expect(json["text"]).to eq(I18n.t("chat_integration.provider.mattermost.create.created")) + + rule = DiscourseChat::Rule.all.first + expect(rule.channel).to eq(chan1) + expect(rule.filter).to eq('watch') + expect(rule.category_id).to eq(category.id) + expect(rule.tags).to eq(nil) + end + + context 'from an unknown channel' do + it 'creates the channel' do + post "/chat-integration/mattermost/command.json", + text: "watch #{category.slug}", + channel_name: 'general', + token: token + + json = JSON.parse(response.body) + + expect(json["text"]).to eq(I18n.t("chat_integration.provider.mattermost.create.created")) + + chan = DiscourseChat::Channel.with_provider('mattermost').with_data_value('identifier','#general').first + expect(chan.provider).to eq('mattermost') + + rule = chan.rules.first + expect(rule.filter).to eq('watch') + expect(rule.category_id).to eq(category.id) + expect(rule.tags).to eq(nil) + end + end + end + + end + end +end