diff --git a/Gemfile b/Gemfile index c4989c538..8c0bb82a5 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem 'time_diff' gem 'tzinfo-data' gem 'valid_email2' gem 'email-provider-info' +gem 'gemoji' # compress javascript config.assets.js_compressor gem 'uglifier' ##-- used for single column multiple binary flags in notification settings/feature flagging --## diff --git a/Gemfile.lock b/Gemfile.lock index 7d29e0b02..d85999b57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -108,8 +108,8 @@ GEM acts-as-taggable-on (12.0.0) activerecord (>= 7.1, < 8.1) zeitwerk (>= 2.4, < 3.0) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) administrate (0.20.1) actionpack (>= 6.0, < 8.0) actionview (>= 6.0, < 8.0) @@ -349,6 +349,7 @@ GEM googleapis-common-protos-types (>= 1.3.1, < 2.a) googleauth (~> 1.0) grpc (~> 1.36) + gemoji (4.1.0) geocoder (1.8.1) gli (2.22.2) ostruct @@ -676,7 +677,7 @@ GEM method_source (~> 1.0) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (6.0.2) + public_suffix (7.0.5) puma (6.4.3) nio4r (~> 2.0) pundit (2.3.0) @@ -1075,6 +1076,7 @@ DEPENDENCIES fcm flag_shih_tzu foreman + gemoji geocoder gmail_xoauth google-cloud-dialogflow-v2 (>= 0.24.0) diff --git a/app/services/messages/mention_service.rb b/app/services/messages/mention_service.rb index 43bc17fb8..8171f8168 100644 --- a/app/services/messages/mention_service.rb +++ b/app/services/messages/mention_service.rb @@ -50,6 +50,8 @@ class Messages::MentionService def generate_notifications_for_mentions(validated_mentioned_ids) validated_mentioned_ids.each do |user_id| + next if self_mention?(user_id) + NotificationBuilder.new( notification_type: 'conversation_mention', user: User.find(user_id), @@ -60,6 +62,10 @@ class Messages::MentionService end end + def self_mention?(user_id) + message.sender_type == 'User' && user_id.to_i == message.sender_id + end + def add_mentioned_users_as_participants(validated_mentioned_ids) validated_mentioned_ids.each do |user_id| message.conversation.conversation_participants.find_or_create_by(user_id: user_id) diff --git a/lib/integrations/slack/emoji_formatter.rb b/lib/integrations/slack/emoji_formatter.rb new file mode 100644 index 000000000..727c14784 --- /dev/null +++ b/lib/integrations/slack/emoji_formatter.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Integrations::Slack::EmojiFormatter + def self.format(text) + return text if text.blank? + + text.gsub(/:([a-zA-Z0-9_+-]+):/) do |match| + short_code = Regexp.last_match(1) + # gemoji exposes find_by_alias; Rails/DynamicFindBy is a false positive because Emoji is not an ActiveRecord model. + # rubocop:disable Rails/DynamicFindBy + emoji = Emoji.find_by_alias(short_code) + # rubocop:enable Rails/DynamicFindBy + emoji ? emoji.raw : match + end + end +end diff --git a/lib/integrations/slack/slack_message_helper.rb b/lib/integrations/slack/slack_message_helper.rb index 3af57a4c3..110156b06 100644 --- a/lib/integrations/slack/slack_message_helper.rb +++ b/lib/integrations/slack/slack_message_helper.rb @@ -35,7 +35,7 @@ module Integrations::Slack::SlackMessageHelper message_type: :outgoing, account_id: conversation.account_id, inbox_id: conversation.inbox_id, - content: Slack::Messages::Formatting.unescape(params[:event][:text] || ''), + content: formatted_message_content, external_source_id_slack: params[:event][:ts], private: private_note?, sender: resolved_sender, @@ -104,6 +104,11 @@ module Integrations::Slack::SlackMessageHelper [nil, nil, nil] end + def formatted_message_content + text = Slack::Messages::Formatting.unescape(params[:event][:text] || '') + Integrations::Slack::EmojiFormatter.format(text) + end + def private_note? params[:event][:text].strip.downcase.starts_with?('note:', 'private:') end diff --git a/spec/lib/integrations/slack/emoji_formatter_spec.rb b/spec/lib/integrations/slack/emoji_formatter_spec.rb new file mode 100644 index 000000000..d34706e43 --- /dev/null +++ b/spec/lib/integrations/slack/emoji_formatter_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe Integrations::Slack::EmojiFormatter do + describe '.format' do + it 'replaces emoji shortcodes with unicode characters' do + expect(described_class.format('Hello :smile:')).to eq('Hello 😄') + expect(described_class.format('Good job :+1:')).to eq('Good job 👍') + expect(described_class.format('Unknown :unknown_emoji:')).to eq('Unknown :unknown_emoji:') + end + + it 'handles nil or empty text' do + expect(described_class.format(nil)).to be_nil + expect(described_class.format('')).to eq('') + end + + it 'replaces multiple emojis' do + expect(described_class.format(':smile: :+1:')).to eq('😄 👍') + end + end +end diff --git a/spec/services/messages/mention_service_spec.rb b/spec/services/messages/mention_service_spec.rb index 7cdb8ffe7..a7bddcc4e 100644 --- a/spec/services/messages/mention_service_spec.rb +++ b/spec/services/messages/mention_service_spec.rb @@ -165,6 +165,36 @@ describe Messages::MentionService do end end + context 'when the message sender mentions themselves' do + it 'skips the sender notification while notifying other mentioned users' do + message = build( + :message, + conversation: conversation, + account: account, + content: "hey (mention://user/#{first_agent.id}/#{first_agent.name}) and (mention://user/#{second_agent.id}/#{second_agent.name})", + private: true, + sender: first_agent + ) + + described_class.new(message: message).perform + + expect(NotificationBuilder).not_to have_received(:new).with( + notification_type: 'conversation_mention', + user: first_agent, + account: account, + primary_actor: message.conversation, + secondary_actor: message + ) + expect(NotificationBuilder).to have_received(:new).with( + notification_type: 'conversation_mention', + user: second_agent, + account: account, + primary_actor: message.conversation, + secondary_actor: message + ) + end + end + context 'when mentioned user is not an inbox member' do let!(:non_member_user) { create(:user, account: account) }