diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index 63e9b9e..42879f8 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -36,6 +36,7 @@ class DiscourseChat::ChatController < ApplicationController rescue Discourse::InvalidParameters, ActiveRecord::RecordNotFound => e render json: {errors: [e.message]}, status: 422 rescue DiscourseChat::ProviderError => e + Rails.logger.error("Test provider failed #{e.info}") if e.info.key?(:error_key) and !e.info[:error_key].nil? render json: {error_key: e.info[:error_key]}, status: 422 else diff --git a/app/helpers/helper.rb b/app/helpers/helper.rb index 50c0479..d27d587 100644 --- a/app/helpers/helper.rb +++ b/app/helpers/helper.rb @@ -1,6 +1,77 @@ module DiscourseChat module Helper + def self.process_command(channel, tokens) + guardian = DiscourseChat::Manager.guardian + + provider = channel.provider + + cmd = tokens.shift if tokens.size >= 1 + + error_text = I18n.t("chat_integration.provider.#{provider}.parse_error") + + case cmd + when "watch", "follow", "mute" + return error_text if tokens.empty? + # If the first token in the command is a tag, this rule applies to all categories + category_name = tokens[0].start_with?('tag:') ? nil : tokens.shift + + if category_name + category = Category.find_by(slug: category_name) + unless category + cat_list = (CategoryList.new(guardian).categories.map(&:slug)).join(', ') + return I18n.t("chat_integration.provider.#{provider}.not_found.category", name: category_name, list:cat_list) + end + else + category = nil # All categories + end + + tags = [] + # Every remaining token must be a tag. If not, abort and send help text + while tokens.size > 0 + token = tokens.shift + if token.start_with?('tag:') + tag_name = token.sub(/^tag:/, '') + else + return error_text # Abort and send help text + end + + tag = Tag.find_by(name: tag_name) + unless tag # If tag doesn't exist, abort + return I18n.t("chat_integration.provider.#{provider}.not_found.tag", name: tag_name) + end + tags.push(tag.name) + end + + category_id = category.nil? ? nil : category.id + case DiscourseChat::Helper.smart_create_rule(channel:channel, filter:cmd, category_id: category_id, tags:tags) + when :created + return I18n.t("chat_integration.provider.#{provider}.create.created") + when :updated + return I18n.t("chat_integration.provider.#{provider}.create.updated") + else + return I18n.t("chat_integration.provider.#{provider}.create.error") + end + when "remove" + return error_text unless tokens.size == 1 + + rule_number = tokens[0].to_i + return error_text unless rule_number.to_s == tokens[0] # Check we were given a number + + if DiscourseChat::Helper.delete_by_index(channel, rule_number) + return I18n.t("chat_integration.provider.#{provider}.delete.success") + else + return I18n.t("chat_integration.provider.#{provider}.delete.error") + end + when "status" + return DiscourseChat::Helper.status_for_channel(channel) + when "help" + return I18n.t("chat_integration.provider.#{provider}.help") + else + return error_text + end + end + # Produce a string with a list of all rules associated with a channel def self.status_for_channel(channel) rules = channel.rules.order_by_precedence diff --git a/app/models/channel.rb b/app/models/channel.rb index 2337ade..ca97194 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -50,6 +50,6 @@ class DiscourseChat::Channel < DiscourseChat::PluginModel scope :with_provider, ->(provider) { where("value::json->>'provider'=?", provider)} - scope :with_data_value, ->(key, value) { where("(value::json->>'data')::json->>?=?", key, value)} + scope :with_data_value, ->(key, value) { where("(value::json->>'data')::json->>?=?", key.to_s, value.to_s)} end \ No newline at end of file diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 63ac6ce..c6158ab 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -63,4 +63,13 @@ en: ####################################### telegram: title: "Telegram" - channel_instructions: "Enter a chat_id. You will need to add the bot to the chat first." \ No newline at end of file + param: + name: + title: "Name" + help: "A name to describe the channel. It is not used for the connection for telegram." + chat_id: + title: Chat ID + help: A number given to you by the bot, or a broadcast channel identifier in the form @channelname + errors: + channel_not_found: "The specified channel does not exist on Telegram" + forbidden: "The bot does not have permission to post to this channel" \ No newline at end of file diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 26b32d0..acf7fdf 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -10,7 +10,7 @@ en: ####################################### chat_integration_slack_enabled: 'Enable the slack chat-integration provider' chat_integration_slack_outbound_webhook_url: 'URL for outbound slack requests' - chat_integration_slack_excerpt_length: 'Post excerpt length' + chat_integration_slack_excerpt_length: 'Slack post excerpt length' chat_integration_slack_icon_url: 'Icon to post to slack with (defaults to forum logo)' chat_integration_slack_access_token: 'Token if you are using the Web API instead of webhooks' @@ -20,7 +20,10 @@ en: ####################################### ######### TELEGRAM SETTINGS ########### ####################################### - chat_integration_telegram_enabled: "Enable the telegram chat-integration provider" + chat_integration_telegram_enabled: "Enable the Telegram chat-integration provider" + chat_integration_telegram_access_token: "Your bot's access token from the Telegram botfather" + chat_integration_telegram_excerpt_length: "Telegram post excerpt length" + chat_integration_telegram_enable_slash_commands: "Allow telegram subscriptions to be managed using 'slash commands'" chat_integration: @@ -47,7 +50,6 @@ en: 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) @@ -60,4 +62,44 @@ en: *List rules:* `/discourse status` - *Help:* `/discourse help` \ No newline at end of file + *Help:* `/discourse help` + + telegram: + unknown_chat: "This chat isn't setup on %{site_title}. Ask an administrator to add a channel with 'Chat ID' %{chat_id}." + known_chat: "This chat is setup on %{site_title}. Configure it in the admin panel. (Chat ID: %{chat_id})" + message: |- + %{user} posted in %{title} + +
%{post_excerpt}+ + 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
/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 /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 /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: /[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: /remove [rule number]
+ ([rule number]
can be found by running /status
)
+
+ List rules: /status
+
+ Help: /help
\ No newline at end of file
diff --git a/config/settings.yml b/config/settings.yml
index 61caed7..07b2cbd 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -30,3 +30,11 @@ plugins:
#######################################
chat_integration_telegram_enabled:
default: false
+ chat_integration_telegram_access_token:
+ default: ''
+ chat_integration_telegram_excerpt_length:
+ default: 400
+ chat_integration_telegram_enable_slash_commands:
+ default: true
+ chat_integration_telegram_secret:
+ hidden: true
\ No newline at end of file
diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb
index 7eb8ca3..7410250 100644
--- a/lib/discourse_chat/provider/slack/slack_command_controller.rb
+++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb
@@ -17,7 +17,6 @@ module DiscourseChat::Provider::SlackProvider
end
def process_command(params)
- guardian = DiscourseChat::Manager.guardian
tokens = params[:text].split(" ")
@@ -39,70 +38,7 @@ module DiscourseChat::Provider::SlackProvider
# Create channel if doesn't exist
channel ||= DiscourseChat::Channel.create!(provider:provider, data:{identifier: channel_id})
- cmd = tokens.shift if tokens.size >= 1
-
- error_text = I18n.t("chat_integration.provider.slack.parse_error")
-
- case cmd
- when "watch", "follow", "mute"
- return error_text if tokens.empty?
- # If the first token in the command is a tag, this rule applies to all categories
- category_name = tokens[0].start_with?('tag:') ? nil : tokens.shift
-
- if category_name
- category = Category.find_by(slug: category_name)
- unless category
- cat_list = (CategoryList.new(guardian).categories.map(&:slug)).join(', ')
- return I18n.t("chat_integration.provider.slack.not_found.category", name: category_name, list:cat_list)
- end
- else
- category = nil # All categories
- end
-
- tags = []
- # Every remaining token must be a tag. If not, abort and send help text
- while tokens.size > 0
- token = tokens.shift
- if token.start_with?('tag:')
- tag_name = token.sub(/^tag:/, '')
- else
- return error_text # Abort and send help text
- end
-
- tag = Tag.find_by(name: tag_name)
- unless tag # If tag doesn't exist, abort
- return I18n.t("chat_integration.provider.slack.not_found.tag", name: tag_name)
- end
- tags.push(tag.name)
- end
-
- category_id = category.nil? ? nil : category.id
- case DiscourseChat::Helper.smart_create_rule(channel:channel, filter:cmd, category_id: category_id, tags:tags)
- when :created
- return I18n.t("chat_integration.provider.slack.create.created")
- when :updated
- return I18n.t("chat_integration.provider.slack.create.updated")
- else
- return I18n.t("chat_integration.provider.slack.create.error")
- end
- when "remove"
- return error_text unless tokens.size == 1
-
- rule_number = tokens[0].to_i
- return error_text unless rule_number.to_s == tokens[0] # Check we were given a number
-
- if DiscourseChat::Helper.delete_by_index(channel, rule_number)
- return I18n.t("chat_integration.provider.slack.delete.success")
- else
- return I18n.t("chat_integration.provider.slack.delete.error")
- end
- when "status"
- return DiscourseChat::Helper.status_for_channel(channel)
- when "help"
- return I18n.t("chat_integration.provider.slack.help")
- else
- return error_text
- end
+ return ::DiscourseChat::Helper.process_command(channel, tokens)
end
diff --git a/lib/discourse_chat/provider/telegram/telegram_command_controller.rb b/lib/discourse_chat/provider/telegram/telegram_command_controller.rb
index c63c22e..f3aba2f 100644
--- a/lib/discourse_chat/provider/telegram/telegram_command_controller.rb
+++ b/lib/discourse_chat/provider/telegram/telegram_command_controller.rb
@@ -2,9 +2,79 @@ module DiscourseChat::Provider::TelegramProvider
class TelegramCommandController < DiscourseChat::Provider::HookController
requires_provider ::DiscourseChat::Provider::TelegramProvider::PROVIDER_NAME
- def say_hello
+ before_filter :telegram_token_valid?, only: :command
- render json: {hello: "from telegram"}
+ skip_before_filter :check_xhr,
+ :preload_json,
+ :verify_authenticity_token,
+ :redirect_to_login_if_required,
+ only: :command
+
+ def command
+
+ # If it's a new message (telegram also sends hooks for other reasons that we don't care about)
+ if params.key?('message')
+ chat_id = params['message']['chat']['id']
+
+ message_text = process_command(params['message'])
+
+ message = {
+ chat_id: chat_id,
+ text: message_text,
+ parse_mode: "html",
+ disable_web_page_preview: true,
+ }
+
+ DiscourseChat::Provider::TelegramProvider.sendMessage(message)
+
+ end
+
+ # Always give telegram a success message, otherwise we'll stop receiving webhooks
+ data = {
+ success: true
+ }
+ render json: data
+ end
+
+ def process_command(message)
+ chat_id = params['message']['chat']['id']
+
+ provider = DiscourseChat::Provider::TelegramProvider::PROVIDER_NAME
+
+ channel = DiscourseChat::Channel.with_provider(provider).with_data_value('chat_id',chat_id).first
+
+ if channel.nil?
+ return I18n.t(
+ "chat_integration.provider.telegram.unknown_chat",
+ site_title: CGI::escapeHTML(SiteSetting.title),
+ chat_id: chat_id,
+ )
+ end
+
+ # If slash commands disabled, send a generic message
+ if !SiteSetting.chat_integration_telegram_enable_slash_commands
+ return I18n.t(
+ "chat_integration.provider.telegram.known_chat",
+ site_title: CGI::escapeHTML(SiteSetting.title),
+ chat_id: chat_id,
+ )
+ end
+
+ tokens = message['text'].split(" ")
+
+ tokens[0][0] = '' # Remove the slash from the first token
+
+ return ::DiscourseChat::Helper.process_command(channel, tokens)
+ end
+
+ def telegram_token_valid?
+ params.require(:token)
+
+ if SiteSetting.chat_integration_telegram_secret.blank? ||
+ SiteSetting.chat_integration_telegram_secret != params[:token]
+
+ raise Discourse::InvalidAccess.new
+ end
end
end
@@ -14,6 +84,6 @@ module DiscourseChat::Provider::TelegramProvider
end
TelegramEngine.routes.draw do
- get "command" => "telegram_command#say_hello"
+ post "command/:token" => "telegram_command#command"
end
end
\ No newline at end of file
diff --git a/lib/discourse_chat/provider/telegram/telegram_initializer.rb b/lib/discourse_chat/provider/telegram/telegram_initializer.rb
new file mode 100644
index 0000000..cff63b4
--- /dev/null
+++ b/lib/discourse_chat/provider/telegram/telegram_initializer.rb
@@ -0,0 +1,13 @@
+DiscourseEvent.on(:site_setting_saved) do |sitesetting|
+ isEnabledSetting = sitesetting.name == 'chat_integration_telegram_enabled'
+ isAccessToken = sitesetting.name == 'chat_integration_telegram_access_token'
+
+ if (isEnabledSetting or isAccessToken)
+ enabled = isEnabledSetting ? sitesetting.value == 't' : SiteSetting.chat_integration_telegram_enabled
+ if enabled
+ Scheduler::Defer.later("Setup Telegram Webhook") do
+ DiscourseChat::Provider::TelegramProvider.setup_webhook()
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/discourse_chat/provider/telegram/telegram_provider.rb b/lib/discourse_chat/provider/telegram/telegram_provider.rb
index a681603..2bf505a 100644
--- a/lib/discourse_chat/provider/telegram/telegram_provider.rb
+++ b/lib/discourse_chat/provider/telegram/telegram_provider.rb
@@ -3,10 +3,107 @@ module DiscourseChat
module TelegramProvider
PROVIDER_NAME = "telegram".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_telegram_enabled
- CHANNEL_PARAMETERS = []
+ CHANNEL_PARAMETERS = [
+ {key: "name", regex: '^\S+'},
+ {key: "chat_id", regex: '^(-?[0-9]+|@\S+)$'}
+ ]
+
+ def self.setup_webhook
+ newSecret = SecureRandom.hex
+ SiteSetting.chat_integration_telegram_secret = newSecret
+
+ message = {
+ url: Discourse.base_url+'/chat-integration/telegram/command/'+newSecret,
+ }
+
+ response = self.do_api_request('setWebhook', message)
+
+ if not response['ok'] == true
+ # If setting up webhook failed, disable provider
+ SiteSetting.chat_integration_telegram_enabled = false
+ Rails.logger.error("Failed to setup telegram webhook. Message data= "+message.to_json+ " response="+response.to_json)
+ end
+
+ end
+
+ def self.sendMessage(message)
+ return self.do_api_request('sendMessage', message)
+ end
+
+ def self.do_api_request(methodName, message)
+ http = Net::HTTP.new("api.telegram.org", 443)
+ http.use_ssl = true
+
+ access_token = SiteSetting.chat_integration_telegram_access_token
+
+ uri = URI("https://api.telegram.org/bot#{access_token}/#{methodName}")
+
+ req = Net::HTTP::Post.new(uri, 'Content-Type' =>'application/json')
+ req.body = message.to_json
+ response = http.request(req)
+
+ responseData = JSON.parse(response.body)
+
+ return responseData
+ end
+
+ def self.message_text(post)
+ display_name = "@#{post.user.username}"
+ full_name = post.user.name || ""
+
+ if !(full_name.strip.empty?) && (full_name.strip.gsub(' ', '_').casecmp(post.user.username) != 0) && (full_name.strip.gsub(' ', '').casecmp(post.user.username) != 0)
+ display_name = "#{full_name} @#{post.user.username}"
+ end
+
+ topic = post.topic
+
+ category = ''
+ if topic.category
+ category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]": "[#{topic.category.name}]"
+ end
+
+ tags = ''
+ if topic.tags.present?
+ tags = topic.tags.map(&:name).join(', ')
+ end
+
+ return I18n.t(
+ "chat_integration.provider.telegram.message",
+ user: display_name,
+ post_url: "https://meta.discourse.org",
+ title: CGI::escapeHTML(topic.title),
+ post_excerpt: post.excerpt(SiteSetting.chat_integration_telegram_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true),
+ )
+
+ end
+
+ def self.trigger_notification(post, channel)
+ chat_id = channel.data['chat_id']
+
+ message = {
+ chat_id: chat_id,
+ text: message_text(post),
+ parse_mode: "html",
+ disable_web_page_preview: true,
+ }
+
+ response = sendMessage(message)
+
+ if not response['ok'] == true
+ error_key = nil
+ if response['description'].include? 'chat not found'
+ error_key = 'chat_integration.provider.telegram.channel_not_found'
+ elsif response['description'].include? 'Forbidden'
+ error_key = 'chat_integration.provider.telegram.forbidden'
+ end
+ raise ::DiscourseChat::ProviderError.new info: {error_key: error_key, message: message, response_body:response}
+ end
+
+ end
end
end
end
-require_relative "telegram_command_controller.rb"
\ No newline at end of file
+require_relative "telegram_command_controller.rb"
+require_relative "telegram_initializer.rb"
\ No newline at end of file
diff --git a/spec/helpers/helper_spec.rb b/spec/helpers/helper_spec.rb
index d890b06..86cdb6e 100644
--- a/spec/helpers/helper_spec.rb
+++ b/spec/helpers/helper_spec.rb
@@ -14,6 +14,129 @@ RSpec.describe DiscourseChat::Manager do
let(:tag2){Fabricate(:tag)}
let(:tag3){Fabricate(:tag)}
+ describe '.process_command' do
+
+ describe 'add new rule' do
+ # Not testing how filters are merged here, that's done in .smart_create_rule
+ # We just want to make sure the commands are being interpretted correctly
+
+ it 'should add a new rule correctly' do
+ response = DiscourseChat::Helper.process_command(chan1, ['watch',category.slug])
+
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.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
+
+ it 'should work with all three filter types' do
+ response = DiscourseChat::Helper.process_command(chan1, ['watch',category.slug])
+
+ rule = DiscourseChat::Rule.all.first
+ expect(rule.filter).to eq('watch')
+
+ response = DiscourseChat::Helper.process_command(chan1, ['follow',category.slug])
+
+ rule = DiscourseChat::Rule.all.first
+ expect(rule.filter).to eq('follow')
+
+ response = DiscourseChat::Helper.process_command(chan1, ['mute',category.slug])
+
+ rule = DiscourseChat::Rule.all.first
+ expect(rule.filter).to eq('mute')
+ end
+
+ it 'errors on incorrect categories' do
+ response = DiscourseChat::Helper.process_command(chan1, ['watch','blah'])
+
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.not_found.category", name:'blah', list:'uncategorized'))
+ end
+
+ context 'with tags enabled' do
+ before do
+ SiteSetting.tagging_enabled = true
+ end
+
+ it 'should add a new tag rule correctly' do
+ response = DiscourseChat::Helper.process_command(chan1, ['watch',"tag:#{tag1.name}"])
+
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.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(nil)
+ expect(rule.tags).to eq([tag1.name])
+ end
+
+ it 'should work with a category and multiple tags' do
+
+ response = DiscourseChat::Helper.process_command(chan1, ['watch',category.slug, "tag:#{tag1.name}", "tag:#{tag2.name}"])
+
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.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 contain_exactly(tag1.name, tag2.name)
+ end
+
+ it 'errors on incorrect tags' do
+ response = DiscourseChat::Helper.process_command(chan1, ['watch',category.slug, "tag:blah"])
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.not_found.tag", name:"blah"))
+ end
+ end
+ end
+
+ describe 'remove rule' do
+ it 'removes the rule' do
+ rule1 = DiscourseChat::Rule.create(channel: chan1,
+ filter: 'watch',
+ category_id: category.id,
+ tags: [tag1.name, tag2.name]
+ )
+
+ expect(DiscourseChat::Rule.all.size).to eq(1)
+
+ response = DiscourseChat::Helper.process_command(chan1, ['remove','1'])
+
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.success"))
+
+ expect(DiscourseChat::Rule.all.size).to eq(0)
+ end
+
+ it 'errors correctly' do
+ response = DiscourseChat::Helper.process_command(chan1, ['remove','1'])
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.delete.error"))
+ end
+ end
+
+ describe 'help command' do
+ it 'should return the right response' do
+ response = DiscourseChat::Helper.process_command(chan1, ["help"])
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.help"))
+ end
+ end
+
+ describe 'status command' do
+ it 'should return the right response' do
+ response = DiscourseChat::Helper.process_command(chan1, ['status'])
+ expect(response).to eq(DiscourseChat::Helper.status_for_channel(chan1))
+ end
+ end
+
+ describe 'unknown command' do
+ it 'should return the right response' do
+ response = DiscourseChat::Helper.process_command(chan1, ['somerandomtext'])
+ expect(response).to eq(I18n.t("chat_integration.provider.dummy.parse_error"))
+ end
+ end
+ end
+
describe '.status_for_channel' do
context 'with no rules' do
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 bc4e9f7..33ed12d 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
@@ -62,14 +62,15 @@ describe 'Slack Command Controller', type: :request do
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_slack_incoming_webhook_token = token
end
describe 'add new rule' do
- # Not testing how filters are merged here, that's done upstream in helper_spec
- # We just want to make sure the slash commands are being interpretted correctly
-
+
it 'should add a new rule correctly' do
post "/chat-integration/slack/command.json",
text: "watch #{category.slug}",
@@ -87,100 +88,6 @@ describe 'Slack Command Controller', type: :request do
expect(rule.tags).to eq(nil)
end
- it 'should work with all three filter types' do
- post "/chat-integration/slack/command.json",
- text: "watch #{category.slug}",
- channel_name: 'welcome',
- token: token
-
- rule = DiscourseChat::Rule.all.first
- expect(rule.filter).to eq('watch')
-
- post "/chat-integration/slack/command.json",
- text: "follow #{category.slug}",
- channel_name: 'welcome',
- token: token
-
- rule = DiscourseChat::Rule.all.first
- expect(rule.filter).to eq('follow')
-
- post "/chat-integration/slack/command.json",
- text: "mute #{category.slug}",
- channel_name: 'welcome',
- token: token
-
- rule = DiscourseChat::Rule.all.first
- expect(rule.filter).to eq('mute')
- end
-
- it 'errors on incorrect categories' do
- post "/chat-integration/slack/command.json",
- text: "watch blah",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
- json = JSON.parse(response.body)
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.not_found.category", name:'blah', list:'uncategorized'))
- end
-
- context 'with tags enabled' do
- before do
- SiteSetting.tagging_enabled = true
- end
-
- it 'should add a new tag rule correctly' do
- post "/chat-integration/slack/command.json",
- text: "watch tag:#{tag.name}",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.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(nil)
- expect(rule.tags).to eq([tag.name])
- end
-
- it 'should work with a category and multiple tags' do
- post "/chat-integration/slack/command.json",
- text: "watch #{category.slug} tag:#{tag.name} tag:#{tag2.name}",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.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 contain_exactly(tag.name, tag2.name)
- end
-
- it 'errors on incorrect tags' do
- post "/chat-integration/slack/command.json",
- text: "watch tag:blah",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.not_found.tag", name:"blah"))
- end
- end
-
context 'from an unknown channel' do
it 'creates the channel' do
post "/chat-integration/slack/command.json",
@@ -202,92 +109,6 @@ describe 'Slack Command Controller', type: :request do
end
end
end
-
- describe 'remove rule' do
- it 'removes the rule' do
- rule1 = DiscourseChat::Rule.create(channel: chan1,
- filter: 'watch',
- category_id: category.id,
- tags: [tag.name, tag2.name]
- )
-
- expect(DiscourseChat::Rule.all.size).to eq(1)
- post "/chat-integration/slack/command.json",
- text: "remove 1",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.delete.success"))
-
- expect(DiscourseChat::Rule.all.size).to eq(0)
- end
-
- it 'errors correctly' do
- post "/chat-integration/slack/command.json",
- text: "remove 1",
- channel_name: 'welcome',
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.delete.error"))
- end
- end
-
- describe 'help command' do
- it 'should return the right response' do
- post '/chat-integration/slack/command.json', text: "help", channel_name: "welcome", token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.help"))
- end
- end
-
- describe 'status command' do
- # No need to test this with different combinations of rules
- # That's done upstream in helper_spec
-
- it 'should return the right response' do
- post '/chat-integration/slack/command.json',
- text: "status",
- channel_name: "welcome",
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(DiscourseChat::Helper.status_for_channel(chan1))
- end
- end
-
- describe 'unknown command' do
- # No need to test this with different combinations of rules
- # That's done upstream in helper_spec
-
- it 'should return the right response' do
- post '/chat-integration/slack/command.json',
- text: "somerandomtext",
- channel_name: "welcome",
- token: token
-
- expect(response).to be_success
-
- json = JSON.parse(response.body)
-
- expect(json["text"]).to eq(I18n.t("chat_integration.provider.slack.parse_error"))
- end
- end
-
end
end
end
diff --git a/spec/lib/discourse_chat/provider/telegram/telegram_command_controller_spec.rb b/spec/lib/discourse_chat/provider/telegram/telegram_command_controller_spec.rb
new file mode 100644
index 0000000..d995a26
--- /dev/null
+++ b/spec/lib/discourse_chat/provider/telegram/telegram_command_controller_spec.rb
@@ -0,0 +1,93 @@
+require 'rails_helper'
+
+describe 'Telegram Command Controller', type: :request do
+ let(:category) { Fabricate(:category) }
+ let!(:chan1){DiscourseChat::Channel.create!(provider:'telegram', data:{name: 'Amazing Channel', chat_id:'123'})}
+
+ describe 'with plugin disabled' do
+ it 'should return a 404' do
+ post '/chat-integration/telegram/command/abcd.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_telegram_enabled = false
+ end
+
+ it 'should return a 404' do
+ post '/chat-integration/telegram/command/abcd.json'
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'slash commands endpoint' do
+ before do
+ SiteSetting.chat_integration_enabled = true
+ SiteSetting.chat_integration_telegram_secret = "shhh"
+ SiteSetting.chat_integration_telegram_access_token = "TOKEN"
+ SiteSetting.chat_integration_telegram_enabled = true
+ end
+
+ let!(:stub){stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":true}")}
+
+ describe 'when forum is private' do
+ it 'should not redirect to login page' do
+ SiteSetting.login_required = true
+ post '/chat-integration/telegram/command/shhh.json', message: {chat: {id:123}, text: '/help' }
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe 'when the token is invalid' do
+ it 'should raise the right error' do
+ post '/chat-integration/telegram/command/blah.json', message: {chat: {id:123}, text: '/help' }
+ expect(response.status).to eq(403)
+ end
+ end
+
+ describe 'when token has not been set' do
+ it 'should raise the right error' do
+ SiteSetting.chat_integration_telegram_access_token = ""
+ post '/chat-integration/telegram/command/blah.json', message: {chat: {id:123}, text: '/help' }
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ describe 'when token is valid' do
+ let(:token) { "TOKEN" }
+
+ before do
+ SiteSetting.chat_integration_telegram_enable_slash_commands = true
+ end
+
+ describe 'add new rule' do
+
+ it 'should add a new rule correctly' do
+ post '/chat-integration/telegram/command/shhh.json', message: {chat: {id:123}, text: "/watch #{category.slug}" }
+
+ expect(response.status).to eq(200)
+ expect(stub).to have_been_requested.once
+
+ 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 'does nothing' do
+ post '/chat-integration/telegram/command/shhh.json', message: {chat: {id:456}, text: "/watch #{category.slug}" }
+ expect(DiscourseChat::Rule.all.size).to eq(0)
+ expect(DiscourseChat::Channel.all.size).to eq(1)
+ end
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/discourse_chat/provider/telegram/telegram_provider_spec.rb b/spec/lib/discourse_chat/provider/telegram/telegram_provider_spec.rb
new file mode 100644
index 0000000..38f4966
--- /dev/null
+++ b/spec/lib/discourse_chat/provider/telegram/telegram_provider_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+RSpec.describe DiscourseChat::Provider::TelegramProvider do
+ let(:post) { Fabricate(:post) }
+
+ describe '.trigger_notifications' do
+ before do
+ SiteSetting.chat_integration_telegram_access_token = "TOKEN"
+ SiteSetting.chat_integration_telegram_secret = 'shhh'
+ SiteSetting.chat_integration_telegram_enabled = true
+ end
+
+ let(:chan1){DiscourseChat::Channel.create!(provider:'telegram', data:{name: "Awesome Channel", chat_id: '123'})}
+
+ it 'sends a webhook request' do
+ stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":true}")
+ described_class.trigger_notification(post, chan1)
+ expect(stub1).to have_been_requested.once
+ end
+
+ it 'handles errors correctly' do
+ stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":false, \"description\":\"chat not found\"}")
+ expect(stub1).to have_been_requested.times(0)
+ expect{described_class.trigger_notification(post, chan1)}.to raise_exception(::DiscourseChat::ProviderError)
+ expect(stub1).to have_been_requested.once
+ end
+
+ end
+
+end