From 80fccbc5267e0dbfeceb4732d911e694298a6df6 Mon Sep 17 00:00:00 2001 From: Lomuzord Date: Wed, 29 Apr 2026 19:49:52 +0200 Subject: [PATCH] fix: render slack emoji shortcodes as unicode characters (#12928) This PR fixes an issue where Slack emojis are rendered as text shortcodes (e.g. :rocket:) instead of the actual emoji characters in Chatwoot messages. It introduces a new EmojiFormatter class that uses the emoji-data mapping to convert shortcodes to unicode characters. --------- Co-authored-by: Sony Mathew Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com> --- Gemfile | 1 + Gemfile.lock | 2 ++ lib/integrations/slack/emoji_formatter.rb | 16 +++++++++++++++ .../slack/slack_message_helper.rb | 7 ++++++- .../slack/emoji_formatter_spec.rb | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/integrations/slack/emoji_formatter.rb create mode 100644 spec/lib/integrations/slack/emoji_formatter_spec.rb 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 5912172f5..d85999b57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 @@ -1075,6 +1076,7 @@ DEPENDENCIES fcm flag_shih_tzu foreman + gemoji geocoder gmail_xoauth google-cloud-dialogflow-v2 (>= 0.24.0) 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