DEV: Introduce syntax_tree for ruby formatting (#149)

This commit is contained in:
David Taylor
2022-12-29 13:31:05 +01:00
committed by GitHub
parent f6dde41cba
commit 49956bf829
70 changed files with 3078 additions and 2236 deletions
+17 -8
View File
@@ -20,7 +20,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: yarn
- name: Yarn install
@@ -33,15 +33,15 @@ jobs:
bundler-cache: true
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
if: ${{ !cancelled() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets,admin/assets}/javascripts
- name: Prettier
if: ${{ always() }}
if: ${{ !cancelled() }}
shell: bash
run: |
yarn prettier -v
if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
if [ 0 -lt $(find assets admin/assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "assets/**/*.{scss,js,es6}"
fi
if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
@@ -49,9 +49,18 @@ jobs:
fi
- name: Ember template lint
if: ${{ always() }}
run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts
if: ${{ !cancelled() }}
run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts admin/assets/javascripts
- name: Rubocop
if: ${{ always() }}
if: ${{ !cancelled() }}
run: bundle exec rubocop .
- name: Syntax Tree
if: ${{ !cancelled() }}
run: |
if test -f .streerc; then
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
else
echo "Stree config not detected for this repository. Skipping."
fi
+3 -3
View File
@@ -80,7 +80,7 @@ jobs:
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Yarn cache
uses: actions/cache@v3
@@ -130,7 +130,7 @@ jobs:
shell: bash
run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true"
echo "files_exist=true" >> $GITHUB_OUTPUT
fi
- name: Plugin RSpec
@@ -142,7 +142,7 @@ jobs:
shell: bash
run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true"
echo "files_exist=true" >> $GITHUB_OUTPUT
fi
- name: Plugin QUnit
+1 -1
View File
@@ -1,2 +1,2 @@
inherit_gem:
rubocop-discourse: default.yml
rubocop-discourse: stree-compat.yml
+2
View File
@@ -0,0 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma
+4 -3
View File
@@ -1,8 +1,9 @@
# frozen_string_literal: true
source 'https://rubygems.org'
source "https://rubygems.org"
group :development do
gem 'translations-manager', git: 'https://github.com/discourse/translations-manager.git'
gem 'rubocop-discourse'
gem "translations-manager", git: "https://github.com/discourse/translations-manager.git"
gem "rubocop-discourse"
gem "syntax_tree"
end
+4
View File
@@ -12,6 +12,7 @@ GEM
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
prettier_print (1.2.0)
rainbow (3.1.1)
regexp_parser (2.6.0)
rexml (3.2.5)
@@ -33,6 +34,8 @@ GEM
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
ruby-progressbar (1.11.0)
syntax_tree (5.1.0)
prettier_print (>= 1.2.0)
unicode-display_width (2.3.0)
PLATFORMS
@@ -40,6 +43,7 @@ PLATFORMS
DEPENDENCIES
rubocop-discourse
syntax_tree
translations-manager!
BUNDLED WITH
+33 -34
View File
@@ -8,15 +8,16 @@ class DiscourseChatIntegration::ChatController < ApplicationController
end
def list_providers
providers = ::DiscourseChatIntegration::Provider.enabled_providers.map do |x|
{
name: x::PROVIDER_NAME,
id: x::PROVIDER_NAME,
channel_parameters: (defined? x::CHANNEL_PARAMETERS) ? x::CHANNEL_PARAMETERS : []
}
end
providers =
::DiscourseChatIntegration::Provider.enabled_providers.map do |x|
{
name: x::PROVIDER_NAME,
id: x::PROVIDER_NAME,
channel_parameters: (defined?(x::CHANNEL_PARAMETERS)) ? x::CHANNEL_PARAMETERS : [],
}
end
render json: providers, root: 'providers'
render json: providers, root: "providers"
end
def test
@@ -28,9 +29,7 @@ class DiscourseChatIntegration::ChatController < ApplicationController
provider = ::DiscourseChatIntegration::Provider.get_by_name(channel.provider)
if !DiscourseChatIntegration::Provider.is_enabled(provider)
raise Discourse::NotFound
end
raise Discourse::NotFound if !DiscourseChatIntegration::Provider.is_enabled(provider)
post = Topic.find(topic_id.to_i).posts.first
@@ -56,34 +55,36 @@ class DiscourseChatIntegration::ChatController < ApplicationController
raise Discourse::InvalidParameters if !providers.include?(requested_provider)
channels = DiscourseChatIntegration::Channel.with_provider(requested_provider)
render_serialized channels, DiscourseChatIntegration::ChannelSerializer, root: 'channels'
render_serialized channels, DiscourseChatIntegration::ChannelSerializer, root: "channels"
end
def create_channel
begin
providers = ::DiscourseChatIntegration::Provider.enabled_providers.map { |x| x::PROVIDER_NAME }
providers =
::DiscourseChatIntegration::Provider.enabled_providers.map { |x| x::PROVIDER_NAME }
if !defined?(params[:channel]) && defined?(params[:channel][:provider])
raise Discourse::InvalidParameters, 'Provider is not valid'
raise Discourse::InvalidParameters, "Provider is not valid"
end
requested_provider = params[:channel][:provider]
if !providers.include?(requested_provider)
raise Discourse::InvalidParameters, 'Provider is not valid'
raise Discourse::InvalidParameters, "Provider is not valid"
end
allowed_keys = DiscourseChatIntegration::Provider.get_by_name(requested_provider)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym }
allowed_keys =
DiscourseChatIntegration::Provider.get_by_name(
requested_provider,
)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym }
hash = params.require(:channel).permit(:provider, data: allowed_keys)
channel = DiscourseChatIntegration::Channel.new(hash)
if !channel.save
raise Discourse::InvalidParameters, 'Channel is not valid'
end
raise Discourse::InvalidParameters, "Channel is not valid" if !channel.save
render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: 'channel'
render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: "channel"
rescue Discourse::InvalidParameters => e
render json: { errors: [e.message] }, status: 422
end
@@ -94,15 +95,16 @@ class DiscourseChatIntegration::ChatController < ApplicationController
channel = DiscourseChatIntegration::Channel.find(params[:id].to_i)
channel.error_key = nil # Reset any error on the rule
allowed_keys = DiscourseChatIntegration::Provider.get_by_name(channel.provider)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym }
allowed_keys =
DiscourseChatIntegration::Provider.get_by_name(
channel.provider,
)::CHANNEL_PARAMETERS.map { |p| p[:key].to_sym }
hash = params.require(:channel).permit(data: allowed_keys)
if !channel.update(hash)
raise Discourse::InvalidParameters, 'Channel is not valid'
end
raise Discourse::InvalidParameters, "Channel is not valid" if !channel.update(hash)
render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: 'channel'
render_serialized channel, DiscourseChatIntegration::ChannelSerializer, root: "channel"
rescue Discourse::InvalidParameters => e
render json: { errors: [e.message] }, status: 422
end
@@ -118,14 +120,13 @@ class DiscourseChatIntegration::ChatController < ApplicationController
def create_rule
begin
hash = params.require(:rule).permit(:channel_id, :type, :filter, :group_id, :category_id, tags: [])
hash =
params.require(:rule).permit(:channel_id, :type, :filter, :group_id, :category_id, tags: [])
rule = DiscourseChatIntegration::Rule.new(hash)
if !rule.save
raise Discourse::InvalidParameters, 'Rule is not valid'
end
raise Discourse::InvalidParameters, "Rule is not valid" if !rule.save
render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: 'rule'
render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: "rule"
rescue Discourse::InvalidParameters => e
render json: { errors: [e.message] }, status: 422
end
@@ -136,11 +137,9 @@ class DiscourseChatIntegration::ChatController < ApplicationController
rule = DiscourseChatIntegration::Rule.find(params[:id].to_i)
hash = params.require(:rule).permit(:type, :filter, :group_id, :category_id, tags: [])
if !rule.update(hash)
raise Discourse::InvalidParameters, 'Rule is not valid'
end
raise Discourse::InvalidParameters, "Rule is not valid" if !rule.update(hash)
render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: 'rule'
render_serialized rule, DiscourseChatIntegration::RuleSerializer, root: "rule"
rescue Discourse::InvalidParameters => e
render json: { errors: [e.message] }, status: 422
end
+37 -20
View File
@@ -2,7 +2,6 @@
module DiscourseChatIntegration
module Helper
def self.process_command(channel, tokens)
guardian = DiscourseChatIntegration::Manager.guardian
@@ -16,13 +15,19 @@ module DiscourseChatIntegration
when "thread", "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
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)
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
@@ -32,8 +37,8 @@ module DiscourseChatIntegration
# 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:/, '')
if token.start_with?("tag:")
tag_name = token.sub(/^tag:/, "")
else
return error_text
end
@@ -47,7 +52,12 @@ module DiscourseChatIntegration
end
category_id = category.nil? ? nil : category.id
case DiscourseChatIntegration::Helper.smart_create_rule(channel: channel, filter: cmd, category_id: category_id, tags: tags)
case DiscourseChatIntegration::Helper.smart_create_rule(
channel: channel,
filter: cmd,
category_id: category_id,
tags: tags,
)
when :created
I18n.t("chat_integration.provider.#{provider}.create.created")
when :updated
@@ -107,23 +117,25 @@ module DiscourseChatIntegration
end
end
text << I18n.t("chat_integration.provider.#{provider}.status.rule_string",
text << I18n.t(
"chat_integration.provider.#{provider}.status.rule_string",
index: i,
filter: rule.filter,
category: category_name
category: category_name,
)
if SiteSetting.tagging_enabled && (!rule.tags.nil?)
text << I18n.t("chat_integration.provider.#{provider}.status.rule_string_tags_suffix", tags: rule.tags.join(', '))
text << I18n.t(
"chat_integration.provider.#{provider}.status.rule_string_tags_suffix",
tags: rule.tags.join(", "),
)
end
text << "\n"
i += 1
end
if rules.size == 0
text << I18n.t("chat_integration.provider.#{provider}.status.no_rules")
end
text << I18n.t("chat_integration.provider.#{provider}.status.no_rules") if rules.size == 0
text
end
@@ -144,12 +156,15 @@ module DiscourseChatIntegration
# :created if a new rule has been created
# false if there was an error
def self.smart_create_rule(channel:, filter:, category_id: nil, tags: nil)
existing_rules = DiscourseChatIntegration::Rule.with_channel(channel).with_type('normal')
existing_rules = DiscourseChatIntegration::Rule.with_channel(channel).with_type("normal")
# Select the ones that have the same category
same_category = existing_rules.select { |rule| rule.category_id == category_id }
same_category_and_tags = same_category.select { |rule| (rule.tags.nil? ? [] : rule.tags.sort) == (tags.nil? ? [] : tags.sort) }
same_category_and_tags =
same_category.select do |rule|
(rule.tags.nil? ? [] : rule.tags.sort) == (tags.nil? ? [] : tags.sort)
end
if same_category_and_tags.size > 0
# These rules have exactly the same criteria as what we're trying to create
@@ -185,7 +200,9 @@ module DiscourseChatIntegration
end
# This rule is unique! Create a new one:
return :created if Rule.new(channel: channel, filter: filter, category_id: category_id, tags: tags).save
if Rule.new(channel: channel, filter: filter, category_id: category_id, tags: tags).save
return :created
end
false
end
@@ -197,13 +214,13 @@ module DiscourseChatIntegration
end
def self.formatted_display_name(user)
if !SiteSetting.enable_names || user.name.blank?
return "@#{user.username}"
end
return "@#{user.username}" if !SiteSetting.enable_names || user.name.blank?
full_name = user.name
full_name_normalized = User.normalize_username(full_name.strip)
similar = full_name_normalized.gsub(' ', '_') == user.username_lower || full_name_normalized.gsub(' ', '') == user.username_lower
similar =
full_name_normalized.gsub(" ", "_") == user.username_lower ||
full_name_normalized.gsub(" ", "") == user.username_lower
if similar && SiteSetting.prioritize_username_in_ux?
"@#{user.username}"
elsif similar
+1 -3
View File
@@ -3,9 +3,7 @@
module Jobs
class DiscourseChatAddTypeField < ::Jobs::Onceoff
def execute_onceoff(args)
DiscourseChatIntegration::Rule.find_each do |rule|
rule.save(validate: false)
end
DiscourseChatIntegration::Rule.find_each { |rule| rule.save(validate: false) }
end
end
end
+63 -44
View File
@@ -3,26 +3,26 @@
module Jobs
class DiscourseChatMigrateFromSlackOfficial < ::Jobs::Onceoff
def execute_onceoff(args)
slack_installed = PluginStoreRow.where(plugin_name: 'discourse-slack-official').exists?
slack_installed = PluginStoreRow.where(plugin_name: "discourse-slack-official").exists?
if slack_installed
already_setup_rules = DiscourseChatIntegration::Channel.with_provider('slack').exists?
already_setup_rules = DiscourseChatIntegration::Channel.with_provider("slack").exists?
already_setup_sitesettings =
SiteSetting.chat_integration_slack_enabled ||
SiteSetting.chat_integration_slack_access_token.present? ||
SiteSetting.chat_integration_slack_incoming_webhook_token.present? ||
SiteSetting.chat_integration_slack_outbound_webhook_url.present?
SiteSetting.chat_integration_slack_access_token.present? ||
SiteSetting.chat_integration_slack_incoming_webhook_token.present? ||
SiteSetting.chat_integration_slack_outbound_webhook_url.present?
if !already_setup_rules && !already_setup_sitesettings
ActiveRecord::Base.transaction do
migrate_settings
migrate_data
is_slack_enabled = site_settings_value('slack_enabled')
is_slack_enabled = site_settings_value("slack_enabled")
if is_slack_enabled
slack_enabled = SiteSetting.find_by(name: 'slack_enabled')
slack_enabled.update!(value: 'f')
slack_enabled = SiteSetting.find_by(name: "slack_enabled")
slack_enabled.update!(value: "f")
SiteSetting.chat_integration_slack_enabled = true
SiteSetting.chat_integration_enabled = true
@@ -30,44 +30,55 @@ module Jobs
end
end
end
end
def migrate_data
rows = []
PluginStoreRow.where(plugin_name: 'discourse-slack-official')
PluginStoreRow
.where(plugin_name: "discourse-slack-official")
.where("key ~* :pat", pat: "^category_.*")
.each do |row|
PluginStore
.cast_value(row.type_name, row.value)
.each do |rule|
category_id =
if row.key == "category_*"
nil
else
row.key.gsub!("category_", "")
row.key.to_i
end
PluginStore.cast_value(row.type_name, row.value).each do |rule|
category_id =
if row.key == 'category_*'
nil
else
row.key.gsub!('category_', '')
row.key.to_i
next if !category_id.nil? && !Category.exists?(id: category_id)
valid_tags = []
valid_tags = Tag.where(name: rule[:tags]).pluck(:name) if rule[:tags]
rows << {
category_id: category_id,
channel: rule[:channel],
filter: rule[:filter],
tags: valid_tags,
}
end
next if !category_id.nil? && !Category.exists?(id: category_id)
valid_tags = []
valid_tags = Tag.where(name: rule[:tags]).pluck(:name) if rule[:tags]
rows << {
category_id: category_id,
channel: rule[:channel],
filter: rule[:filter],
tags: valid_tags
}
end
end
rows.each do |row|
# Load an existing channel with this identifier. If none, create it
row[:channel] = "##{row[:channel]}" unless row[:channel].start_with?("#")
channel = DiscourseChatIntegration::Channel.with_provider('slack').with_data_value('identifier', row[:channel]).first
channel =
DiscourseChatIntegration::Channel
.with_provider("slack")
.with_data_value("identifier", row[:channel])
.first
if !channel
channel = DiscourseChatIntegration::Channel.create(provider: 'slack', data: { identifier: row[:channel] })
channel =
DiscourseChatIntegration::Channel.create(
provider: "slack",
data: {
identifier: row[:channel],
},
)
if !channel.id
Rails.logger.warn("Error creating channel for #{row}")
next
@@ -75,50 +86,58 @@ module Jobs
end
# Create the rule, with clever logic for avoiding duplicates
success = DiscourseChatIntegration::Helper.smart_create_rule(channel: channel, filter: row[:filter], category_id: row[:category_id], tags: row[:tags])
success =
DiscourseChatIntegration::Helper.smart_create_rule(
channel: channel,
filter: row[:filter],
category_id: row[:category_id],
tags: row[:tags],
)
end
end
private
def migrate_settings
if !(slack_access_token = site_settings_value('slack_access_token')).nil?
if !(slack_access_token = site_settings_value("slack_access_token")).nil?
SiteSetting.chat_integration_slack_access_token = slack_access_token
end
if !(slack_incoming_webhook_token = site_settings_value('slack_incoming_webhook_token')).nil?
if !(slack_incoming_webhook_token = site_settings_value("slack_incoming_webhook_token")).nil?
SiteSetting.chat_integration_slack_incoming_webhook_token = slack_incoming_webhook_token
end
if !(slack_discourse_excerpt_length = site_settings_value('slack_discourse_excerpt_length')).nil?
if !(
slack_discourse_excerpt_length = site_settings_value("slack_discourse_excerpt_length")
).nil?
SiteSetting.chat_integration_slack_excerpt_length = slack_discourse_excerpt_length
end
if !(slack_outbound_webhook_url = site_settings_value('slack_outbound_webhook_url')).nil?
if !(slack_outbound_webhook_url = site_settings_value("slack_outbound_webhook_url")).nil?
SiteSetting.chat_integration_slack_outbound_webhook_url = slack_outbound_webhook_url
end
if !(slack_icon_url = site_settings_value('slack_icon_url')).nil?
if !(slack_icon_url = site_settings_value("slack_icon_url")).nil?
SiteSetting.chat_integration_slack_icon_url = slack_icon_url
end
if !(post_to_slack_window_secs = site_settings_value('post_to_slack_window_secs')).nil?
if !(post_to_slack_window_secs = site_settings_value("post_to_slack_window_secs")).nil?
SiteSetting.chat_integration_delay_seconds = post_to_slack_window_secs
end
if !(slack_discourse_username = site_settings_value('slack_discourse_username')).nil?
if !(slack_discourse_username = site_settings_value("slack_discourse_username")).nil?
username = User.find_by(username: slack_discourse_username.downcase)&.username
SiteSetting.chat_integration_discourse_username = (username || Discourse.system_user.username)
SiteSetting.chat_integration_discourse_username =
(username || Discourse.system_user.username)
end
end
def site_settings_value(name)
value = SiteSetting.find_by(name: name)&.value
if value == 't'
if value == "t"
value = true
elsif value == 'f'
elsif value == "f"
value = false
end
+6 -9
View File
@@ -2,10 +2,11 @@
class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel
# Setup ActiveRecord::Store to use the JSON field to read/write these values
store :value, accessors: [ :provider, :error_key, :error_info, :data ], coder: JSON
store :value, accessors: %i[provider error_key error_info data], coder: JSON
scope :with_provider, ->(provider) { where("value::json->>'provider'=?", provider) }
scope :with_data_value, ->(key, value) { where("(value::json->>'data')::json->>?=?", key.to_s, value.to_s) }
scope :with_data_value,
->(key, value) { where("(value::json->>'data')::json->>?=?", key.to_s, value.to_s) }
after_initialize :init_data
after_destroy :destroy_rules
@@ -13,7 +14,7 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel
validate :provider_valid?, :data_valid?
def self.key_prefix
'channel:'.freeze
"channel:".freeze
end
def rules
@@ -52,9 +53,7 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel
data.each do |key, value|
regex_string = params.find { |p| p[:key] == key }[:regex]
if !Regexp.new(regex_string).match(value)
errors.add(:data, "data.#{key} is invalid")
end
errors.add(:data, "data.#{key} is invalid") if !Regexp.new(regex_string).match(value)
unique = params.find { |p| p[:key] == key }[:unique]
if unique
@@ -63,8 +62,6 @@ class DiscourseChatIntegration::Channel < DiscourseChatIntegration::PluginModel
end
end
if check_unique && matching_channels.exists?
errors.add(:data, "matches an existing channel")
end
errors.add(:data, "matches an existing channel") if check_unique && matching_channels.exists?
end
end
+7 -7
View File
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class DiscourseChatIntegration::PluginModel < PluginStoreRow
PLUGIN_NAME = 'discourse-chat-integration'
PLUGIN_NAME = "discourse-chat-integration"
default_scope { self.default_scope }
@@ -9,13 +9,14 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow
before_save :set_key
def self.default_scope
where(type_name: 'JSON')
.where(plugin_name: self::PLUGIN_NAME)
.where("key LIKE ?", "#{self.key_prefix}%")
where(type_name: "JSON").where(plugin_name: self::PLUGIN_NAME).where(
"key LIKE ?",
"#{self.key_prefix}%",
)
end
def self.key_prefix
raise 'Not implemented'
raise "Not implemented"
end
private
@@ -25,7 +26,7 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow
end
def init_plugin_model
self.type_name ||= 'JSON'
self.type_name ||= "JSON"
self.plugin_name ||= PLUGIN_NAME
end
@@ -37,5 +38,4 @@ class DiscourseChatIntegration::PluginModel < PluginStoreRow
"#{self.key_prefix}#{max_id}"
end
end
end
+40 -31
View File
@@ -2,54 +2,65 @@
class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel
# Setup ActiveRecord::Store to use the JSON field to read/write these values
store :value, accessors: [ :channel_id, :type, :group_id, :category_id, :tags, :filter ], coder: JSON
store :value, accessors: %i[channel_id type group_id category_id tags filter], coder: JSON
scope :with_type, ->(type) { where("value::json->>'type'=?", type.to_s) }
scope :with_channel, ->(channel) { with_channel_id(channel.id) }
scope :with_channel_id, ->(channel_id) { where("value::json->>'channel_id'=?", channel_id.to_s) }
scope :with_category_id, ->(category_id) do
if category_id.nil?
where("(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'")
else
where("value::json->>'category_id'=?", category_id.to_s)
end
end
scope :with_category_id,
->(category_id) {
if category_id.nil?
where(
"(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'",
)
else
where("value::json->>'category_id'=?", category_id.to_s)
end
}
scope :with_group_ids, ->(group_id) do
where("value::json->>'group_id' IN (?)", group_id.map!(&:to_s))
end
scope :with_group_ids,
->(group_id) { where("value::json->>'group_id' IN (?)", group_id.map!(&:to_s)) }
scope :order_by_precedence, -> {
order("
scope :order_by_precedence,
-> {
order(
"
CASE
WHEN value::json->>'type' = 'group_mention' THEN 1
WHEN value::json->>'type' = 'group_message' THEN 2
ELSE 3
END
",
"
"
CASE
WHEN value::json->>'filter' = 'mute' THEN 1
WHEN value::json->>'filter' = 'thread' THEN 2
WHEN value::json->>'filter' = 'watch' THEN 3
WHEN value::json->>'filter' = 'follow' THEN 4
END
")
}
",
)
}
after_initialize :init_filter
validates :filter, inclusion: { in: %w(thread watch follow mute),
message: "%{value} is not a valid filter" }
validates :filter,
inclusion: {
in: %w[thread watch follow mute],
message: "%{value} is not a valid filter",
}
validates :type, inclusion: { in: %w(normal group_message group_mention),
message: "%{value} is not a valid filter" }
validates :type,
inclusion: {
in: %w[normal group_message group_mention],
message: "%{value} is not a valid filter",
}
validate :channel_valid?, :category_valid?, :group_valid?, :tags_valid?
def self.key_prefix
'rule:'.freeze
"rule:".freeze
end
# We never want an empty array, set it to nil instead
@@ -62,7 +73,7 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel
end
# These are only allowed to be integers
%w(channel_id category_id group_id).each do |name|
%w[channel_id category_id group_id].each do |name|
define_method "#{name}=" do |val|
if val.nil? || val.blank?
super(nil)
@@ -91,11 +102,11 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel
end
def category_valid?
if type != 'normal' && !category_id.nil?
if type != "normal" && !category_id.nil?
errors.add(:category_id, "cannot be specified for that type of rule")
end
return unless type == 'normal'
return unless type == "normal"
if !(category_id.nil? || Category.where(id: category_id).exists?)
errors.add(:category_id, "#{category_id} is not a valid category id")
@@ -103,11 +114,11 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel
end
def group_valid?
if type == 'normal' && !group_id.nil?
if type == "normal" && !group_id.nil?
errors.add(:group_id, "cannot be specified for that type of rule")
end
return if type == 'normal'
return if type == "normal"
if !Group.where(id: group_id).exists?
errors.add(:group_id, "#{group_id} is not a valid group id")
@@ -118,14 +129,12 @@ class DiscourseChatIntegration::Rule < DiscourseChatIntegration::PluginModel
return if tags.nil?
tags.each do |tag|
if !Tag.where(name: tag).exists?
errors.add(:tags, "#{tag} is not a valid tag")
end
errors.add(:tags, "#{tag} is not a valid tag") if !Tag.where(name: tag).exists?
end
end
def init_filter
self.filter ||= 'watch'
self.type ||= 'normal'
self.filter ||= "watch"
self.type ||= "normal"
end
end
+7 -4
View File
@@ -1,10 +1,13 @@
# frozen_string_literal: true
Discourse::Application.routes.append do
mount ::DiscourseChatIntegration::AdminEngine, at: '/admin/plugins/chat-integration', constraints: AdminConstraint.new
mount ::DiscourseChatIntegration::PublicEngine, at: '/chat-transcript/', as: 'chat-transcript'
mount ::DiscourseChatIntegration::Provider::HookEngine, at: '/chat-integration/'
mount ::DiscourseChatIntegration::AdminEngine,
at: "/admin/plugins/chat-integration",
constraints: AdminConstraint.new
mount ::DiscourseChatIntegration::PublicEngine, at: "/chat-transcript/", as: "chat-transcript"
mount ::DiscourseChatIntegration::Provider::HookEngine, at: "/chat-integration/"
# For backwards compatibility with Slack plugin
post "/slack/command" => "discourse_chat_integration/provider/slack_provider/slack_command#command"
post "/slack/command" =>
"discourse_chat_integration/provider/slack_provider/slack_command#command"
end
+11 -13
View File
@@ -1,26 +1,24 @@
# frozen_string_literal: true
require_dependency 'admin_constraint'
require_dependency "admin_constraint"
module ::DiscourseChatIntegration
AdminEngine.routes.draw do
get "" => "chat#respond"
get '/providers' => "chat#list_providers"
post '/test' => "chat#test"
get "/providers" => "chat#list_providers"
post "/test" => "chat#test"
get '/channels' => "chat#list_channels"
post '/channels' => "chat#create_channel"
put '/channels/:id' => "chat#update_channel"
delete '/channels/:id' => "chat#destroy_channel"
get "/channels" => "chat#list_channels"
post "/channels" => "chat#create_channel"
put "/channels/:id" => "chat#update_channel"
delete "/channels/:id" => "chat#destroy_channel"
post '/rules' => "chat#create_rule"
put '/rules/:id' => "chat#update_rule"
delete '/rules/:id' => "chat#destroy_rule"
post "/rules" => "chat#create_rule"
put "/rules/:id" => "chat#update_rule"
delete "/rules/:id" => "chat#destroy_rule"
get "/:provider" => "chat#respond"
end
PublicEngine.routes.draw do
get '/:secret' => "public#post_transcript"
end
PublicEngine.routes.draw { get "/:secret" => "public#post_transcript" }
end
+1 -1
View File
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative './rule_serializer'
require_relative "./rule_serializer"
class DiscourseChatIntegration::ChannelSerializer < ApplicationSerializer
attributes :id, :provider, :error_key, :error_info, :data, :rules
+28 -22
View File
@@ -2,7 +2,6 @@
module DiscourseChatIntegration
module Manager
def self.guardian
Guardian.new(User.find_by(username: SiteSetting.chat_integration_discourse_username))
end
@@ -23,19 +22,26 @@ module DiscourseChatIntegration
if topic.archetype == Archetype.private_message
group_ids_with_access = topic.topic_allowed_groups.pluck(:group_id)
return if group_ids_with_access.empty?
matching_rules = DiscourseChatIntegration::Rule.with_type('group_message').with_group_ids(group_ids_with_access)
matching_rules =
DiscourseChatIntegration::Rule.with_type("group_message").with_group_ids(
group_ids_with_access,
)
else
matching_rules = DiscourseChatIntegration::Rule.with_type('normal').with_category_id(topic.category_id)
matching_rules =
DiscourseChatIntegration::Rule.with_type("normal").with_category_id(topic.category_id)
if topic.category # Also load the rules for the wildcard category
matching_rules += DiscourseChatIntegration::Rule.with_type('normal').with_category_id(nil)
matching_rules += DiscourseChatIntegration::Rule.with_type("normal").with_category_id(nil)
end
# If groups are mentioned, check for any matching rules and append them
mentions = post.raw_mentions
if mentions && mentions.length > 0
groups = Group.where('LOWER(name) IN (?)', mentions)
groups = Group.where("LOWER(name) IN (?)", mentions)
if groups.exists?
matching_rules += DiscourseChatIntegration::Rule.with_type('group_mention').with_group_ids(groups.map(&:id))
matching_rules +=
DiscourseChatIntegration::Rule.with_type("group_mention").with_group_ids(
groups.map(&:id),
)
end
end
end
@@ -43,17 +49,19 @@ module DiscourseChatIntegration
# If tagging is enabled, thow away rules that don't apply to this topic
if SiteSetting.tagging_enabled
topic_tags = topic.tags.present? ? topic.tags.pluck(:name) : []
matching_rules = matching_rules.select do |rule|
next true if rule.tags.nil? || rule.tags.empty? # Filter has no tags specified
any_tags_match = !((rule.tags & topic_tags).empty?)
next any_tags_match # If any tags match, keep this filter, otherwise throw away
end
matching_rules =
matching_rules.select do |rule|
next true if rule.tags.nil? || rule.tags.empty? # Filter has no tags specified
any_tags_match = !((rule.tags & topic_tags).empty?)
next any_tags_match # If any tags match, keep this filter, otherwise throw away
end
end
# Sort by order of precedence
t_prec = { 'group_message' => 0, 'group_mention' => 1, 'normal' => 2 } # Group things win
f_prec = { 'mute' => 0, 'thread' => 1, 'watch' => 2, 'follow' => 3 } #(mute always wins; thread beats watch beats follow)
sort_func = proc { |a, b| [t_prec[a.type], f_prec[a.filter]] <=> [t_prec[b.type], f_prec[b.filter]] }
t_prec = { "group_message" => 0, "group_mention" => 1, "normal" => 2 } # Group things win
f_prec = { "mute" => 0, "thread" => 1, "watch" => 2, "follow" => 3 } #(mute always wins; thread beats watch beats follow)
sort_func =
proc { |a, b| [t_prec[a.type], f_prec[a.filter]] <=> [t_prec[b.type], f_prec[b.filter]] }
matching_rules = matching_rules.sort(&sort_func)
# Take the first rule for each channel
@@ -81,14 +89,15 @@ module DiscourseChatIntegration
begin
provider.trigger_notification(post, channel, rule)
channel.update_attribute('error_key', nil) if channel.error_key
channel.update_attribute("error_key", nil) if channel.error_key
rescue => e
if e.class == (DiscourseChatIntegration::ProviderError) && e.info.key?(:error_key) && !e.info[:error_key].nil?
channel.update_attribute('error_key', e.info[:error_key])
if e.class == (DiscourseChatIntegration::ProviderError) && e.info.key?(:error_key) &&
!e.info[:error_key].nil?
channel.update_attribute("error_key", e.info[:error_key])
else
channel.update_attribute('error_key', 'chat_integration.channel_exception')
channel.update_attribute("error_key", "chat_integration.channel_exception")
end
channel.update_attribute('error_info', JSON.pretty_generate(e.try(:info)))
channel.update_attribute("error_info", JSON.pretty_generate(e.try(:info)))
# Log the error
# Discourse.handle_job_exception(e,
@@ -99,10 +108,7 @@ module DiscourseChatIntegration
# error_info: e.class == DiscourseChatIntegration::ProviderError ? e.info : nil }
# )
end
end
end
end
end
@@ -2,9 +2,10 @@
class AddUniqueIndexToSlackThreadTs < ActiveRecord::Migration[6.1]
def up
add_index :topic_custom_fields, [:topic_id, :name],
unique: true,
where: "(name LIKE 'slack_thread_id_%')",
name: "index_topic_custom_fields_on_topic_id_and_slack_thread_id"
add_index :topic_custom_fields,
%i[topic_id name],
unique: true,
where: "(name LIKE 'slack_thread_id_%')",
name: "index_topic_custom_fields_on_topic_id_and_slack_thread_id"
end
end
+14 -19
View File
@@ -12,15 +12,11 @@ module DiscourseChatIntegration
module Provider
def self.providers
constants.select do |constant|
constant.to_s =~ /Provider$/
end.map(&method(:const_get))
constants.select { |constant| constant.to_s =~ /Provider$/ }.map(&method(:const_get))
end
def self.enabled_providers
self.providers.select do |provider|
self.is_enabled(provider)
end
self.providers.select { |provider| self.is_enabled(provider) }
end
def self.provider_names
@@ -36,7 +32,7 @@ module DiscourseChatIntegration
end
def self.is_enabled(provider)
if defined? provider::PROVIDER_ENABLED_SETTING
if defined?(provider::PROVIDER_ENABLED_SETTING)
SiteSetting.public_send(provider::PROVIDER_ENABLED_SETTING)
else
false
@@ -51,9 +47,10 @@ module DiscourseChatIntegration
class HookController < ::ApplicationController
requires_plugin DiscourseChatIntegration::PLUGIN_NAME
class ProviderDisabled < StandardError; end
class ProviderDisabled < StandardError
end
rescue_from ProviderDisabled do
rescue_from ProviderDisabled do
rescue_discourse_actions(:not_found, 404)
end
@@ -72,22 +69,20 @@ module DiscourseChatIntegration
def self.mount_engines
engines = []
DiscourseChatIntegration::Provider.providers.each do |provider|
engine = provider.constants.select do |constant|
constant.to_s =~ (/Engine$/) && (constant.to_s != "HookEngine")
end.map(&provider.method(:const_get)).first
engine =
provider
.constants
.select { |constant| constant.to_s =~ (/Engine$/) && (constant.to_s != "HookEngine") }
.map(&provider.method(:const_get))
.first
if engine
engines.push(engine: engine, name: provider::PROVIDER_NAME)
end
engines.push(engine: engine, name: provider::PROVIDER_NAME) if engine
end
DiscourseChatIntegration::Provider::HookEngine.routes.draw do
engines.each do |engine|
mount engine[:engine], at: engine[:name]
end
engines.each { |engine| mount engine[:engine], at: engine[:name] }
end
end
end
end
@@ -8,7 +8,12 @@ module DiscourseChatIntegration
CHANNEL_PARAMETERS = [
{ key: "name", regex: '^\S+' },
{ key: "webhook_url", regex: '^https:\/\/discord\.com\/api\/webhooks\/', unique: true, hidden: true }
{
key: "webhook_url",
regex: '^https:\/\/discord\.com\/api\/webhooks\/',
unique: true,
hidden: true,
},
].freeze
def self.send_message(url, message)
@@ -17,7 +22,7 @@ module DiscourseChatIntegration
uri = URI(url)
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
@@ -25,7 +30,7 @@ module DiscourseChatIntegration
end
def self.ensure_protocol(url)
return url if !url.start_with?('//')
return url if !url.start_with?("//")
"http:#{url}"
end
@@ -34,24 +39,40 @@ module DiscourseChatIntegration
topic = post.topic
category = ''
category = ""
if topic.category
category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
category =
(
if (topic.category.parent_category)
"[#{topic.category.parent_category.name}/#{topic.category.name}]"
else
"[#{topic.category.name}]"
end
)
end
message = {
content: SiteSetting.chat_integration_discord_message_content,
embeds: [{
title: "#{topic.title} #{(category == '[uncategorized]') ? '' : category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}",
color: topic.category ? topic.category.color.to_i(16) : nil,
description: post.excerpt(SiteSetting.chat_integration_discord_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true),
url: post.full_url,
author: {
name: display_name,
url: Discourse.base_url + "/u/" + post.user.username,
icon_url: ensure_protocol(post.user.small_avatar_url)
}
}]
embeds: [
{
title:
"#{topic.title} #{(category == "[uncategorized]") ? "" : category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}",
color: topic.category ? topic.category.color.to_i(16) : nil,
description:
post.excerpt(
SiteSetting.chat_integration_discord_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
url: post.full_url,
author: {
name: display_name,
url: Discourse.base_url + "/u/" + post.user.username,
icon_url: ensure_protocol(post.user.small_avatar_url),
},
},
],
}
message
@@ -59,17 +80,20 @@ module DiscourseChatIntegration
def self.trigger_notification(post, channel, rule)
# Adding ?wait=true means that we actually get a success/failure response, rather than returning asynchronously
webhook_url = "#{channel.data['webhook_url']}?wait=true"
webhook_url = "#{channel.data["webhook_url"]}?wait=true"
message = generate_discord_message(post)
response = send_message(webhook_url, message)
if !response.kind_of?(Net::HTTPSuccess)
raise ::DiscourseChatIntegration::ProviderError.new(info: {
error_key: nil, message: message, response_body: response.body
})
raise ::DiscourseChatIntegration::ProviderError.new(
info: {
error_key: nil,
message: message,
response_body: response.body,
},
)
end
end
end
end
end
@@ -1,12 +1,9 @@
# frozen_string_literal: true
module DiscourseChatIntegration::Provider::FlowdockProvider
PROVIDER_NAME = "flowdock".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_flowdock_enabled
CHANNEL_PARAMETERS = [
{ key: "flow_token", regex: '^\S+', unique: true, hidden: true },
]
CHANNEL_PARAMETERS = [{ key: "flow_token", regex: '^\S+', unique: true, hidden: true }]
def self.send_message(url, message)
uri = URI(url)
@@ -14,7 +11,7 @@ module DiscourseChatIntegration::Provider::FlowdockProvider
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
@@ -29,15 +26,21 @@ module DiscourseChatIntegration::Provider::FlowdockProvider
event: "discussion",
author: {
name: display_name,
avatar: post.user.small_avatar_url
avatar: post.user.small_avatar_url,
},
title: I18n.t("chat_integration.provider.flowdock.message_title"),
external_thread_id: post.topic.id,
body: post.excerpt(SiteSetting.chat_integration_flowdock_excerpt_length, text_entities: true, strip_links: false, remap_emoji: true),
body:
post.excerpt(
SiteSetting.chat_integration_flowdock_excerpt_length,
text_entities: true,
strip_links: false,
remap_emoji: true,
),
thread: {
title: post.topic.title,
external_url: post.full_url
}
external_url: post.full_url,
},
}
message
@@ -50,7 +53,11 @@ module DiscourseChatIntegration::Provider::FlowdockProvider
unless response.kind_of?(Net::HTTPSuccess)
error_key = nil
raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
error_key: error_key,
message: message,
response_body: response.body,
}
end
end
end
@@ -3,19 +3,28 @@
module DiscourseChatIntegration
module Provider
module GitterProvider
PROVIDER_NAME = 'gitter'.freeze
PROVIDER_NAME = "gitter".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_gitter_enabled
CHANNEL_PARAMETERS = [
{ key: "name", regex: '^\S+$', unique: true },
{ key: "webhook_url", regex: '^https://webhooks\.gitter\.im/e/\S+$', unique: true, hidden: true }
{
key: "webhook_url",
regex: '^https://webhooks\.gitter\.im/e/\S+$',
unique: true,
hidden: true,
},
]
def self.trigger_notification(post, channel, rule)
message = gitter_message(post)
response = Net::HTTP.post_form(URI(channel.data['webhook_url']), message: message)
response = Net::HTTP.post_form(URI(channel.data["webhook_url"]), message: message)
unless response.kind_of? Net::HTTPSuccess
error_key = nil
raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
error_key: error_key,
message: message,
response_body: response.body,
}
end
end
@@ -23,7 +32,14 @@ module DiscourseChatIntegration
display_name = post.user.username
topic = post.topic
parent_category = topic.category.try :parent_category
category_name = parent_category ? "[#{parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
category_name =
(
if parent_category
"[#{parent_category.name}/#{topic.category.name}]"
else
"[#{topic.category.name}]"
end
)
"[__#{display_name}__ - #{topic.title} - #{category_name}](#{post.full_url})"
end
@@ -3,26 +3,35 @@
module DiscourseChatIntegration
module Provider
module GoogleProvider
PROVIDER_NAME = 'google'.freeze
PROVIDER_NAME = "google".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_google_enabled
CHANNEL_PARAMETERS = [
{ key: "name", regex: '^\S+$', unique: true },
{ key: "webhook_url", regex: '^https:\/\/chat.googleapis.com\/v1\/\S+$', unique: true, hidden: true }
{
key: "webhook_url",
regex: '^https:\/\/chat.googleapis.com\/v1\/\S+$',
unique: true,
hidden: true,
},
]
def self.trigger_notification(post, channel, rule)
message = get_message(post)
uri = URI(channel.data['webhook_url'])
uri = URI(channel.data["webhook_url"])
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.use_ssl = (uri.scheme == "https")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
unless response.kind_of? Net::HTTPSuccess
raise ::DiscourseChatIntegration::ProviderError.new info: { request: req.body, response_code: response.code, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
request: req.body,
response_code: response.code,
response_body: response.body,
}
end
end
@@ -35,49 +44,67 @@ module DiscourseChatIntegration
widgets: [
{
keyValue: {
"topLabel": I18n.t("chat_integration.provider.google.new_#{post.is_first_post? ? "topic" : "post"}", site_title: SiteSetting.title),
"content": post.topic.title,
"contentMultiline": "false",
"bottomLabel": I18n.t("chat_integration.provider.google.author", username: post.user.username),
"onClick": {
"openLink": {
"url": post.full_url
}
}
}
topLabel:
I18n.t(
"chat_integration.provider.google.new_#{post.is_first_post? ? "topic" : "post"}",
site_title: SiteSetting.title,
),
content: post.topic.title,
contentMultiline: "false",
bottomLabel:
I18n.t(
"chat_integration.provider.google.author",
username: post.user.username,
),
onClick: {
openLink: {
url: post.full_url,
},
},
},
},
]
],
},
{
widgets: [
{
textParagraph: {
text: post.excerpt(SiteSetting.chat_integration_google_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)
}
text:
post.excerpt(
SiteSetting.chat_integration_google_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
},
},
]
],
},
{
widgets: [
{
buttons: [
{
"textButton": {
"text": I18n.t("chat_integration.provider.google.link", site_title: SiteSetting.title),
"onClick": {
"openLink": {
"url": post.full_url
}
}
}
textButton: {
text:
I18n.t(
"chat_integration.provider.google.link",
site_title: SiteSetting.title,
),
onClick: {
openLink: {
url: post.full_url,
},
},
},
},
]
}
]
],
},
],
},
],
}
]
},
],
}
end
end
@@ -2,27 +2,32 @@
module DiscourseChatIntegration::Provider::GroupmeProvider
PROVIDER_NAME = "groupme".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_groupme_enabled
CHANNEL_PARAMETERS = [
{ key: "groupme_instance_name", regex: '[\s\S]*', unique: true }
]
CHANNEL_PARAMETERS = [{ key: "groupme_instance_name", regex: '[\s\S]*', unique: true }]
def self.generate_groupme_message(post)
display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user)
topic = post.topic
category = ''
category = ""
if topic.category&.uncategorized?
category = "#{I18n.t('uncategorized_category_name')}"
category = "#{I18n.t("uncategorized_category_name")}"
elsif topic.category
category = (topic.category.parent_category) ? "#{topic.category.parent_category.name}/#{topic.category.name}" : "#{topic.category.name}"
category =
(
if (topic.category.parent_category)
"#{topic.category.parent_category.name}/#{topic.category.name}"
else
"#{topic.category.name}"
end
)
end
pre_post_text = "#{display_name} Posted to #{SiteSetting.title}\n\nTopic: #{topic.title} [#{category}]"
pre_post_text =
"#{display_name} Posted to #{SiteSetting.title}\n\nTopic: #{topic.title} [#{category}]"
read_more = "(Read More: #{post.full_url})"
post_excerpt = "#{post.excerpt(SiteSetting.chat_integration_groupme_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)}"
data = {
text: "#{pre_post_text}\n\n#{post_excerpt}\n#{read_more}"
}
post_excerpt =
"#{post.excerpt(SiteSetting.chat_integration_groupme_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true)}"
data = { text: "#{pre_post_text}\n\n#{post_excerpt}\n#{read_more}" }
data
end
@@ -35,33 +40,39 @@ module DiscourseChatIntegration::Provider::GroupmeProvider
instance_names = SiteSetting.chat_integration_groupme_instance_names.split(/\s*,\s*/)
unless instance_names.length() == bot_ids.length()
instance_names = [I18n.t('chat_integration.provider.groupme.errors.instance_names_issue')] * bot_ids.length()
instance_names =
[I18n.t("chat_integration.provider.groupme.errors.instance_names_issue")] * bot_ids.length()
end
name_to_id = Hash[instance_names.zip(bot_ids)]
user_input_channel = channel.data['groupme_instance_name'].strip
unless user_input_channel.eql? 'all'
instance_names = [user_input_channel]
end
instance_names.each { |instance_name|
user_input_channel = channel.data["groupme_instance_name"].strip
instance_names = [user_input_channel] unless user_input_channel.eql? "all"
instance_names.each do |instance_name|
bot_id = name_to_id["#{instance_name}"]
uri = URI("https://api.groupme.com/v3/bots/post")
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
http.use_ssl = (uri.scheme == "https")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
message[:bot_id] = bot_id
req.body = message.to_json
response = http.request(req)
unless response.kind_of? Net::HTTPSuccess
num_errors += 1
if response.code.to_s == '404'
error_key = 'chat_integration.provider.groupme.errors.not_found'
if response.code.to_s == "404"
error_key = "chat_integration.provider.groupme.errors.not_found"
else
error_key = nil
end
last_error_raised = { error_key: error_key, groupme_name: instance_name, bot_id: bot_id, request: req.body, response_code: response.code, response_body: response.body }
last_error_raised = {
error_key: error_key,
groupme_name: instance_name,
bot_id: bot_id,
request: req.body,
response_code: response.code,
response_body: response.body,
}
end
}
end
if last_error_raised
successfully_sent = instance_names.length() - num_errors
last_error_raised[:success_rate] = "#{successfully_sent}/#{instance_names.length()}"
@@ -7,27 +7,43 @@ module DiscourseChatIntegration
PROVIDER_ENABLED_SETTING = :chat_integration_guilded_enabled
CHANNEL_PARAMETERS = [
{ key: "name", regex: '^\S+' },
{ key: "webhook_url", regex: '^https:\/\/media\.guilded\.gg\/webhooks\/', unique: true, hidden: true }
{ key: "name", regex: '^\S+' },
{
key: "webhook_url",
regex: '^https:\/\/media\.guilded\.gg\/webhooks\/',
unique: true,
hidden: true,
},
].freeze
def self.trigger_notification(post, channel, rule)
webhook_url = channel.data['webhook_url']
webhook_url = channel.data["webhook_url"]
message = generate_guilded_message(post)
response = send_message(webhook_url, message)
if !response.kind_of?(Net::HTTPSuccess)
raise ::DiscourseChatIntegration::ProviderError.new(info: {
error_key: nil, message: message, response_body: response.body
})
raise ::DiscourseChatIntegration::ProviderError.new(
info: {
error_key: nil,
message: message,
response_body: response.body,
},
)
end
end
def self.generate_guilded_message(post)
topic = post.topic
category = ''
category = ""
if topic.category
category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
category =
(
if (topic.category.parent_category)
"[#{topic.category.parent_category.name}/#{topic.category.name}]"
else
"[#{topic.category.name}]"
end
)
end
display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user)
@@ -37,15 +53,24 @@ module DiscourseChatIntegration
end
message = {
embeds: [{
title: "#{topic.title} #{(category == '[uncategorized]') ? '' : category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}",
url: post.full_url,
description: post.excerpt(SiteSetting.chat_integration_guilded_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true),
footer: {
icon_url: ensure_protocol(post.user.small_avatar_url),
text: "#{display_name} | #{post.created_at}"
}
}]
embeds: [
{
title:
"#{topic.title} #{(category == "[uncategorized]") ? "" : category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}",
url: post.full_url,
description:
post.excerpt(
SiteSetting.chat_integration_guilded_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
footer: {
icon_url: ensure_protocol(post.user.small_avatar_url),
text: "#{display_name} | #{post.created_at}",
},
},
],
}
message
@@ -54,9 +79,9 @@ module DiscourseChatIntegration
def self.send_message(url, message)
uri = URI(url)
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.use_ssl = (uri.scheme == "https")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
@@ -64,10 +89,9 @@ module DiscourseChatIntegration
end
def self.ensure_protocol(url)
return url if !url.start_with?('//')
return url if !url.start_with?("//")
"http:#{url}"
end
end
end
end
@@ -6,25 +6,27 @@ module DiscourseChatIntegration
PROVIDER_NAME = "matrix".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_matrix_enabled
CHANNEL_PARAMETERS = [
{ key: "name", regex: '^\S+' },
{ key: "room_id", regex: '^\!\S+:\S+$', unique: true, hidden: true }
]
{ key: "name", regex: '^\S+' },
{ key: "room_id", regex: '^\!\S+:\S+$', unique: true, hidden: true },
]
def self.send_message(room_id, message)
homeserver = SiteSetting.chat_integration_matrix_homeserver
event_type = 'm.room.message'
event_type = "m.room.message"
uid = Time.now.to_i
url_params = URI.encode_www_form(access_token: SiteSetting.chat_integration_matrix_access_token)
url_params =
URI.encode_www_form(access_token: SiteSetting.chat_integration_matrix_access_token)
url = "#{homeserver}/_matrix/client/r0/rooms/#{CGI::escape(room_id)}/send/#{event_type}/#{uid}"
url =
"#{homeserver}/_matrix/client/r0/rooms/#{CGI.escape(room_id)}/send/#{event_type}/#{uid}"
uri = URI([url, url_params].join('?'))
uri = URI([url, url_params].join("?"))
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Put.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
@@ -35,16 +37,29 @@ module DiscourseChatIntegration
display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user)
message = {
msgtype: SiteSetting.chat_integration_matrix_use_notice ? 'm.notice' : 'm.text',
body: I18n.t('chat_integration.provider.matrix.text_message', user: display_name,
post_url: post.full_url,
title: post.topic.title),
format: 'org.matrix.custom.html',
formatted_body: I18n.t('chat_integration.provider.matrix.formatted_message', user: display_name,
post_url: post.full_url,
title: post.topic.title,
excerpt: post.excerpt(SiteSetting.chat_integration_matrix_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true))
msgtype: SiteSetting.chat_integration_matrix_use_notice ? "m.notice" : "m.text",
body:
I18n.t(
"chat_integration.provider.matrix.text_message",
user: display_name,
post_url: post.full_url,
title: post.topic.title,
),
format: "org.matrix.custom.html",
formatted_body:
I18n.t(
"chat_integration.provider.matrix.formatted_message",
user: display_name,
post_url: post.full_url,
title: post.topic.title,
excerpt:
post.excerpt(
SiteSetting.chat_integration_matrix_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
),
}
message
@@ -53,24 +68,26 @@ module DiscourseChatIntegration
def self.trigger_notification(post, channel, rule)
message = generate_matrix_message(post)
response = send_message(channel.data['room_id'], message)
response = send_message(channel.data["room_id"], message)
if !response.kind_of?(Net::HTTPSuccess)
error_key = nil
begin
responseData = JSON.parse(response.body)
if responseData['errcode'] == "M_UNKNOWN_TOKEN"
error_key = 'chat_integration.provider.matrix.errors.unknown_token'
elsif responseData['errcode'] == "M_UNKNOWN"
error_key = 'chat_integration.provider.matrix.errors.unknown_room'
if responseData["errcode"] == "M_UNKNOWN_TOKEN"
error_key = "chat_integration.provider.matrix.errors.unknown_token"
elsif responseData["errcode"] == "M_UNKNOWN"
error_key = "chat_integration.provider.matrix.errors.unknown_room"
end
ensure
raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, message: message, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
error_key: error_key,
message: message,
response_body: response.body,
}
end
end
end
end
end
end
@@ -15,22 +15,18 @@ module DiscourseChatIntegration::Provider::MattermostProvider
def command
text = process_command(params)
render json: {
response_type: 'ephemeral',
text: text
}
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'
when "directmessage"
"@#{params[:user_name]}"
when 'privategroup'
when "privategroup"
params[:channel_id]
else
"##{params[:channel_name]}"
@@ -38,21 +34,29 @@ module DiscourseChatIntegration::Provider::MattermostProvider
provider = DiscourseChatIntegration::Provider::MattermostProvider::PROVIDER_NAME
channel = DiscourseChatIntegration::Channel.with_provider(provider).with_data_value('identifier', channel_id).first
channel =
DiscourseChatIntegration::Channel
.with_provider(provider)
.with_data_value("identifier", channel_id)
.first
# Create channel if doesn't exist
channel ||= DiscourseChatIntegration::Channel.create!(provider: provider, data: { identifier: channel_id })
channel ||=
DiscourseChatIntegration::Channel.create!(
provider: provider,
data: {
identifier: channel_id,
},
)
::DiscourseChatIntegration::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]
SiteSetting.chat_integration_mattermost_incoming_webhook_token != params[:token]
raise Discourse::InvalidAccess.new
end
end
@@ -63,8 +67,5 @@ module DiscourseChatIntegration::Provider::MattermostProvider
isolate_namespace DiscourseChatIntegration::Provider::MattermostProvider
end
MattermostEngine.routes.draw do
post "command" => "mattermost_command#command"
end
MattermostEngine.routes.draw { post "command" => "mattermost_command#command" }
end
@@ -5,29 +5,30 @@ module DiscourseChatIntegration
module MattermostProvider
PROVIDER_NAME = "mattermost".freeze
PROVIDER_ENABLED_SETTING = :chat_integration_mattermost_enabled
CHANNEL_PARAMETERS = [
{ key: "identifier", regex: '^[@#]\S*$', unique: true }
]
CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }]
def self.send_via_webhook(message)
uri = URI(SiteSetting.chat_integration_mattermost_webhook_url)
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
http.use_ssl = (uri.scheme == "https")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
unless response.kind_of? Net::HTTPSuccess
if response.body.include? "Couldn't find the channel"
error_key = 'chat_integration.provider.mattermost.errors.channel_not_found'
error_key = "chat_integration.provider.mattermost.errors.channel_not_found"
else
error_key = nil
end
raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
error_key: error_key,
request: req.body,
response_code: response.code,
response_body: response.body,
}
end
end
def self.mattermost_message(post, channel)
@@ -35,17 +36,26 @@ module DiscourseChatIntegration
topic = post.topic
category = ''
category = ""
if topic.category&.uncategorized?
category = "[#{I18n.t('uncategorized_category_name')}]"
category = "[#{I18n.t("uncategorized_category_name")}]"
elsif topic.category
category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
category =
(
if (topic.category.parent_category)
"[#{topic.category.parent_category.name}/#{topic.category.name}]"
else
"[#{topic.category.name}]"
end
)
end
icon_url =
if SiteSetting.chat_integration_mattermost_icon_url.present?
UrlHelper.absolute(SiteSetting.chat_integration_mattermost_icon_url)
elsif (url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url)).present?
elsif (
url = (SiteSetting.try(:site_logo_small_url) || SiteSetting.logo_small_url)
).present?
UrlHelper.absolute(url)
end
@@ -53,7 +63,7 @@ module DiscourseChatIntegration
channel: channel,
username: SiteSetting.title || "Discourse",
icon_url: icon_url,
attachments: []
attachments: [],
}
summary = {
@@ -61,8 +71,15 @@ module DiscourseChatIntegration
author_name: display_name,
author_icon: post.user.small_avatar_url,
color: topic.category ? "##{topic.category.color}" : nil,
text: post.excerpt(SiteSetting.chat_integration_mattermost_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true),
title: "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}",
text:
post.excerpt(
SiteSetting.chat_integration_mattermost_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
title:
"#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}",
title_link: post.full_url,
}
@@ -71,12 +88,11 @@ module DiscourseChatIntegration
end
def self.trigger_notification(post, channel, rule)
channel_id = channel.data['identifier']
channel_id = channel.data["identifier"]
message = mattermost_message(post, channel_id)
self.send_via_webhook(message)
end
end
end
end
@@ -5,36 +5,45 @@ module DiscourseChatIntegration::Provider::RocketchatProvider
PROVIDER_ENABLED_SETTING = :chat_integration_rocketchat_enabled
CHANNEL_PARAMETERS = [
{ key: "identifier", regex: '^[@#]\S*$', unique: true }
]
CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }]
def self.rocketchat_message(post, channel)
display_name = ::DiscourseChatIntegration::Helper.formatted_display_name(post.user)
topic = post.topic
category = ''
category = ""
if topic.category&.uncategorized?
category = "[#{I18n.t('uncategorized_category_name')}]"
category = "[#{I18n.t("uncategorized_category_name")}]"
elsif topic.category
category = (topic.category.parent_category) ? "[#{topic.category.parent_category.name}/#{topic.category.name}]" : "[#{topic.category.name}]"
category =
(
if (topic.category.parent_category)
"[#{topic.category.parent_category.name}/#{topic.category.name}]"
else
"[#{topic.category.name}]"
end
)
end
message = {
channel: channel,
attachments: []
}
message = { channel: channel, attachments: [] }
summary = {
fallback: "#{topic.title} - #{display_name}",
author_name: display_name,
author_icon: post.user.small_avatar_url,
color: topic.category ? "##{topic.category.color}" : nil,
text: post.excerpt(SiteSetting.chat_integration_rocketchat_excerpt_length, text_entities: true, strip_links: true, remap_emoji: true),
text:
post.excerpt(
SiteSetting.chat_integration_rocketchat_excerpt_length,
text_entities: true,
strip_links: true,
remap_emoji: true,
),
mrkdwn_in: ["text"],
title: "#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(', ') : ''}",
title_link: post.full_url
title:
"#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}",
title_link: post.full_url,
}
message[:attachments].push(summary)
@@ -46,25 +55,29 @@ module DiscourseChatIntegration::Provider::RocketchatProvider
uri = URI(SiteSetting.chat_integration_rocketchat_webhook_url)
http = FinalDestination::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.use_ssl = (uri.scheme == "https")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = message.to_json
response = http.request(req)
unless response.kind_of? Net::HTTPSuccess
if response.body.include?('invalid-channel')
error_key = 'chat_integration.provider.rocketchat.errors.invalid_channel'
if response.body.include?("invalid-channel")
error_key = "chat_integration.provider.rocketchat.errors.invalid_channel"
else
error_key = nil
end
raise ::DiscourseChatIntegration::ProviderError.new info: { error_key: error_key, request: req.body, response_code: response.code, response_body: response.body }
raise ::DiscourseChatIntegration::ProviderError.new info: {
error_key: error_key,
request: req.body,
response_code: response.code,
response_body: response.body,
}
end
end
def self.trigger_notification(post, channel, rule)
channel_id = channel.data['identifier']
channel_id = channel.data["identifier"]
message = rocketchat_message(post, channel_id)
self.send_via_webhook(message)
@@ -11,7 +11,7 @@ module DiscourseChatIntegration::Provider::SlackProvider
:preload_json,
:verify_authenticity_token,
:redirect_to_login_if_required,
only: [:command, :interactive]
only: %i[command interactive]
def command
message = process_command(params)
@@ -28,15 +28,14 @@ module DiscourseChatIntegration::Provider::SlackProvider
private
def process_command(params)
tokens = params[:text].split(" ")
# channel name fix
channel_id =
case params[:channel_name]
when 'directmessage'
when "directmessage"
"@#{params[:user_name]}"
when 'privategroup'
when "privategroup"
params[:channel_id]
else
"##{params[:channel_name]}"
@@ -44,17 +43,28 @@ module DiscourseChatIntegration::Provider::SlackProvider
provider = DiscourseChatIntegration::Provider::SlackProvider::PROVIDER_NAME
channel = DiscourseChatIntegration::Channel.with_provider(provider)
.with_data_value('identifier', channel_id)
.first
channel =
DiscourseChatIntegration::Channel
.with_provider(provider)
.with_data_value("identifier", channel_id)
.first
channel ||= DiscourseChatIntegration::Channel.create!(
provider: provider,
data: { identifier: channel_id }
)
channel ||=
DiscourseChatIntegration::Channel.create!(
provider: provider,
data: {
identifier: channel_id,
},
)
if tokens[0] == 'post'
process_post_request(channel, tokens, params[:channel_id], channel_id, params[:response_url])
if tokens[0] == "post"
process_post_request(
channel,
tokens,
params[:channel_id],
channel_id,
params[:response_url],
)
else
{ text: ::DiscourseChatIntegration::Helper.process_command(channel, tokens) }
end
@@ -66,9 +76,10 @@ module DiscourseChatIntegration::Provider::SlackProvider
end
Scheduler::Defer.later "Processing slack transcript request" do
response = build_post_request_response(channel, tokens, slack_channel_id, channel_name, response_url)
response =
build_post_request_response(channel, tokens, slack_channel_id, channel_name, response_url)
http = DiscourseChatIntegration::Provider::SlackProvider.slack_api_http
req = Net::HTTP::Post.new(URI(response_url), 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(URI(response_url), "Content-Type" => "application/json")
req.body = response.to_json
http.request(req)
end
@@ -81,15 +92,16 @@ module DiscourseChatIntegration::Provider::SlackProvider
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*$/
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 > 2 && tokens[1] == "thread" && match = slack_url_regex.match(tokens[2])
requested_thread_ts = match.captures[0].insert(10, '.')
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, '.')
first_message_ts = match.captures[0].insert(10, ".")
elsif tokens.size > 1
begin
requested_messages = Integer(tokens[1], 10)
@@ -100,12 +112,21 @@ module DiscourseChatIntegration::Provider::SlackProvider
error_key = "chat_integration.provider.slack.transcript.error"
return { text: I18n.t(error_key) } unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id, requested_thread_ts: requested_thread_ts)
unless transcript =
SlackTranscript.new(
channel_name: channel_name,
channel_id: slack_channel_id,
requested_thread_ts: requested_thread_ts,
)
return { text: I18n.t(error_key) }
end
return { text: I18n.t("#{error_key}_users") } unless transcript.load_user_data
return { text: I18n.t("#{error_key}_history") } unless transcript.load_chat_history
if first_message_ts
return { text: I18n.t("#{error_key}_ts") } unless transcript.set_first_message_by_ts(first_message_ts)
unless transcript.set_first_message_by_ts(first_message_ts)
return { text: I18n.t("#{error_key}_ts") }
end
elsif requested_messages
transcript.set_first_message_by_index(-requested_messages)
else
@@ -123,22 +144,21 @@ module DiscourseChatIntegration::Provider::SlackProvider
# Do nothing
elsif json[:type] == "message_action" && json[:message][:thread_ts]
# Context menu used on a threaded message
transcript = SlackTranscript.new(
channel_name: "##{json[:channel][:name]}",
channel_id: json[:channel][:id],
requested_thread_ts: json[:message][:thread_ts]
)
transcript =
SlackTranscript.new(
channel_name: "##{json[:channel][:name]}",
channel_id: json[:channel][:id],
requested_thread_ts: json[:message][:thread_ts],
)
# Send a loading modal within 3 seconds:
req = Net::HTTP::Post.new(
"https://slack.com/api/views.open",
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}"
)
req.body = {
"trigger_id": json[:trigger_id],
"view": transcript.build_modal_ui
}.to_json
req =
Net::HTTP::Post.new(
"https://slack.com/api/views.open",
"Content-Type" => "application/json",
"Authorization" => "Bearer #{SiteSetting.chat_integration_slack_access_token}",
)
req.body = { trigger_id: json[:trigger_id], view: transcript.build_modal_ui }.to_json
response = http.request(req)
view_id = JSON.parse(response.body).dig("view", "id")
@@ -147,19 +167,17 @@ module DiscourseChatIntegration::Provider::SlackProvider
error_view = generate_error_view("history") unless transcript.load_chat_history
# Then update the modal with the transcript link:
req = Net::HTTP::Post.new(
"https://slack.com/api/views.update",
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}"
)
req.body = {
"view_id": view_id,
"view": error_view || transcript.build_modal_ui
}.to_json
req =
Net::HTTP::Post.new(
"https://slack.com/api/views.update",
"Content-Type" => "application/json",
"Authorization" => "Bearer #{SiteSetting.chat_integration_slack_access_token}",
)
req.body = { view_id: view_id, view: error_view || transcript.build_modal_ui }.to_json
response = http.request(req)
else
# Button clicked in one of our interactive messages
req = Net::HTTP::Post.new(URI(json[:response_url]), 'Content-Type' => 'application/json')
req = Net::HTTP::Post.new(URI(json[:response_url]), "Content-Type" => "application/json")
req.body = build_interactive_response(json).to_json
response = http.request(req)
end
@@ -177,26 +195,33 @@ module DiscourseChatIntegration::Provider::SlackProvider
constant_val = json[:callback_id]
changed_val = json[:actions][0][:selected_options][0][:value]
first_message = (action_name == 'first_message') ? changed_val : constant_val
last_message = (action_name == 'first_message') ? constant_val : changed_val
first_message = (action_name == "first_message") ? changed_val : constant_val
last_message = (action_name == "first_message") ? constant_val : changed_val
end
error_key = "chat_integration.provider.slack.transcript.error"
return { text: I18n.t(error_key) } unless transcript = SlackTranscript.new(
channel_name: "##{json[:channel][:name]}",
channel_id: json[:channel][:id],
requested_thread_ts: requested_thread
)
unless transcript =
SlackTranscript.new(
channel_name: "##{json[:channel][:name]}",
channel_id: json[:channel][:id],
requested_thread_ts: requested_thread,
)
return { text: I18n.t(error_key) }
end
return { text: I18n.t("#{error_key}_users") } unless transcript.load_user_data
return { text: I18n.t("#{error_key}_history") } unless transcript.load_chat_history
if first_message
return { text: I18n.t("#{error_key}_ts") } unless transcript.set_first_message_by_ts(first_message)
unless transcript.set_first_message_by_ts(first_message)
return { text: I18n.t("#{error_key}_ts") }
end
end
if last_message
return { text: I18n.t("#{error_key}_ts") } unless transcript.set_last_message_by_ts(last_message)
unless transcript.set_last_message_by_ts(last_message)
return { text: I18n.t("#{error_key}_ts") }
end
end
transcript.build_slack_ui
@@ -210,17 +235,11 @@ module DiscourseChatIntegration::Provider::SlackProvider
type: "modal",
title: {
type: "plain_text",
text: I18n.t("chat_integration.provider.slack.transcript.modal_title")
text: I18n.t("chat_integration.provider.slack.transcript.modal_title"),
},
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: ":warning: *#{I18n.t(error_key)}*"
}
}
]
{ type: "section", text: { type: "mrkdwn", text: ":warning: *#{I18n.t(error_key)}*" } },
],
}
end
@@ -228,8 +247,7 @@ module DiscourseChatIntegration::Provider::SlackProvider
params.require(:token)
if SiteSetting.chat_integration_slack_incoming_webhook_token.blank? ||
SiteSetting.chat_integration_slack_incoming_webhook_token != params[:token]
SiteSetting.chat_integration_slack_incoming_webhook_token != params[:token]
raise Discourse::InvalidAccess.new
end
end
@@ -240,8 +258,7 @@ module DiscourseChatIntegration::Provider::SlackProvider
json = JSON.parse(params[:payload], symbolize_names: true)
if SiteSetting.chat_integration_slack_incoming_webhook_token.blank? ||
SiteSetting.chat_integration_slack_incoming_webhook_token != json[:token]
SiteSetting.chat_integration_slack_incoming_webhook_token != json[:token]
raise Discourse::InvalidAccess.new
end
end
@@ -256,5 +273,4 @@ module DiscourseChatIntegration::Provider::SlackProvider
post "command" => "slack_command#command"
post "interactive" => "slack_command#interactive"
end
end

Some files were not shown because too many files have changed in this diff Show More