From 81cec35dbf1b348d23363559e3f4e6b1ec3415c5 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Tue, 19 Sep 2017 02:42:40 +0200
Subject: [PATCH] Custom emoji (#4988)

* Custom emoji

- In OStatus: `<link rel="emoji" name="coolcat" href="http://..." />`
- In ActivityPub: `{ type: "Emoji", name: ":coolcat:", href: "http://..." }`
- In REST API: Status object includes `emojis` array (`shortcode`, `url`)
- Domain blocks with reject media stop emojis
- Emoji file up to 50KB
- Web UI handles custom emojis
- Static pages render custom emojis as `<img />` tags

Side effects:

- Undo #4500 optimization, as I needed to modify it to restore
  shortcode handling in emojify()
- Formatter#plaintext should now make sure stripped out line-breaks
  and paragraphs are replaced with newlines

* Fix emoji at the start not being converted
---
 app/javascript/mastodon/emoji.js              |  60 +++++++++-----
 app/javascript/mastodon/reducers/statuses.js  |   9 +-
 app/lib/activitypub/activity/create.rb        |  13 +++
 app/lib/formatter.rb                          |  54 +++++++++++-
 app/lib/ostatus/activity/creation.rb          |  20 +++++
 app/lib/ostatus/atom_serializer.rb            |   4 +
 app/models/custom_emoji.rb                    |  38 +++++++++
 app/models/status.rb                          |   4 +
 .../activitypub/note_serializer.rb            |  20 ++++-
 app/serializers/rest/status_serializer.rb     |  11 +++
 .../stream_entries/_detailed_status.html.haml |   2 +-
 .../stream_entries/_simple_status.html.haml   |   2 +-
 .../20170917153509_create_custom_emojis.rb    |  13 +++
 db/schema.rb                                  |  14 +++-
 spec/fabricators/custom_emoji_fabricator.rb   |   5 ++
 spec/fixtures/files/emojo.png                 | Bin 0 -> 29814 bytes
 spec/lib/activitypub/activity/create_spec.rb  |  25 ++++++
 spec/lib/formatter_spec.rb                    |  78 ++++++++++++++++++
 spec/lib/ostatus/atom_serializer_spec.rb      |  16 +++-
 spec/models/custom_emoji_spec.rb              |  25 ++++++
 20 files changed, 382 insertions(+), 31 deletions(-)
 create mode 100644 app/models/custom_emoji.rb
 create mode 100644 db/migrate/20170917153509_create_custom_emojis.rb
 create mode 100644 spec/fabricators/custom_emoji_fabricator.rb
 create mode 100644 spec/fixtures/files/emojo.png
 create mode 100644 spec/models/custom_emoji_spec.rb

diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js
index a41dfdd1d..865b85b61 100644
--- a/app/javascript/mastodon/emoji.js
+++ b/app/javascript/mastodon/emoji.js
@@ -3,28 +3,48 @@ import Trie from 'substring-trie';
 
 const trie = new Trie(Object.keys(unicodeMapping));
 
-const emojify = str => {
-  let rtn = '';
-  for (;;) {
-    let match, i = 0;
-    while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
-      i += str.codePointAt(i) < 65536 ? 1 : 2;
-    }
-    if (i === str.length)
-      break;
-    else if (str[i] === '<') {
-      let tagend = str.indexOf('>', i + 1) + 1;
-      if (!tagend)
-        break;
-      rtn += str.slice(0, tagend);
-      str = str.slice(tagend);
-    } else {
-      const [filename, shortCode] = unicodeMapping[match];
-      rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
-      str = str.slice(i + match.length);
+const emojify = (str, customEmojis = {}) => {
+  // This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
+  // and replacing valid unicode strings
+  // that _aren't_ within tags with an <img> version.
+  // The goal is to be the same as an emojione.regUnicode replacement, but faster.
+  let i = -1;
+  let insideTag = false;
+  let insideShortname = false;
+  let shortnameStartIndex = -1;
+  let match;
+  while (++i < str.length) {
+    const char = str.charAt(i);
+    if (insideShortname && char === ':') {
+      const shortname = str.substring(shortnameStartIndex, i + 1);
+      if (shortname in customEmojis) {
+        const replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${customEmojis[shortname]}" />`;
+        str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1);
+        i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string
+      } else {
+        i--;
+      }
+      insideShortname = false;
+    } else if (insideTag && char === '>') {
+      insideTag = false;
+    } else if (char === '<') {
+      insideTag = true;
+      insideShortname = false;
+    } else if (!insideTag && char === ':') {
+      insideShortname = true;
+      shortnameStartIndex = i;
+    } else if (!insideTag && (match = trie.search(str.substring(i)))) {
+      const unicodeStr = match;
+      if (unicodeStr in unicodeMapping) {
+        const [filename, shortCode] = unicodeMapping[unicodeStr];
+        const alt      = unicodeStr;
+        const replacement =  `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
+        str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
+        i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
+      }
     }
   }
-  return rtn + str;
+  return str;
 };
 
 export default emojify;
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 7f906bef6..38b23504e 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -58,9 +58,14 @@ const normalizeStatus = (state, status) => {
   }
 
   const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
+  const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
+    obj[`:${emoji.shortcode}:`] = emoji.url;
+    return obj;
+  }, {});
+
   normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
-  normalStatus.contentHtml = emojify(normalStatus.content);
-  normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''));
+  normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
+  normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
 
   return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
 };
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 894759d9a..41f2b0bad 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -61,6 +61,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
         process_hashtag tag, status
       when 'Mention'
         process_mention tag, status
+      when 'Emoji'
+        process_emoji tag, status
       end
     end
   end
@@ -79,6 +81,17 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     account.mentions.create(status: status)
   end
 
+  def process_emoji(tag, _status)
+    shortcode = tag['name'].delete(':')
+    emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
+
+    return if !emoji.nil? || skip_download?
+
+    emoji = CustomEmoji.new(domain: @account.domain, shortcode: shortcode)
+    emoji.image_remote_url = tag['href']
+    emoji.save
+  end
+
   def process_attachments(status)
     return unless @object['attachment'].is_a?(Array)
 
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 575830190..29fea27de 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -9,7 +9,7 @@ class Formatter
 
   include ActionView::Helpers::TextHelper
 
-  def format(status)
+  def format(status, options = {})
     if status.reblog?
       prepend_reblog = status.reblog.account.acct
       status         = status.proper
@@ -19,7 +19,11 @@ class Formatter
 
     raw_content = status.text
 
-    return reformat(raw_content) unless status.local?
+    unless status.local?
+      html = reformat(raw_content)
+      html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
+      return html
+    end
 
     linkable_accounts = status.mentions.map(&:account)
     linkable_accounts << status.account
@@ -27,6 +31,7 @@ class Formatter
     html = raw_content
     html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
     html = encode_and_link_urls(html, linkable_accounts)
+    html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
     html = simple_format(html, {}, sanitize: false)
     html = html.delete("\n")
 
@@ -39,7 +44,9 @@ class Formatter
 
   def plaintext(status)
     return status.text if status.local?
-    strip_tags(status.text)
+
+    text = status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" }
+    strip_tags(text)
   end
 
   def simplified_format(account)
@@ -76,6 +83,47 @@ class Formatter
     end
   end
 
+  def encode_custom_emojis(html, emojis)
+    return html if emojis.empty?
+
+    emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h
+
+    i                     = -1
+    inside_tag            = false
+    inside_shortname      = false
+    shortname_start_index = -1
+
+    while i + 1 < html.size
+      i += 1
+
+      if inside_shortname && html[i] == ':'
+        shortcode = html[shortname_start_index + 1..i - 1]
+        emoji     = emoji_map[shortcode]
+
+        if emoji
+          replacement = "<img draggable=\"false\" class=\"emojione\" alt=\":#{shortcode}:\" title=\":#{shortcode}:\" src=\"#{emoji}\" />"
+          before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
+          html        = before_html + replacement + html[i + 1..-1]
+          i          += replacement.size - (shortcode.size + 2) - 1
+        else
+          i -= 1
+        end
+
+        inside_shortname = false
+      elsif inside_tag && html[i] == '>'
+        inside_tag = false
+      elsif html[i] == '<'
+        inside_tag       = true
+        inside_shortname = false
+      elsif !inside_tag && html[i] == ':'
+        inside_shortname      = true
+        shortname_start_index = i
+      end
+    end
+
+    html
+  end
+
   def rewrite(text, entities)
     chars = text.to_s.to_char_a
 
diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb
index 1a23c9efa..d3f1629c4 100644
--- a/app/lib/ostatus/activity/creation.rb
+++ b/app/lib/ostatus/activity/creation.rb
@@ -42,6 +42,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
       save_mentions(status)
       save_hashtags(status)
       save_media(status)
+      save_emojis(status)
     end
 
     if thread? && status.thread.nil?
@@ -150,6 +151,25 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
     end
   end
 
+  def save_emojis(parent)
+    do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
+
+    return if do_not_download
+
+    @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: TagManager::XMLNS).each do |link|
+      next unless link['href'] && link['name']
+
+      shortcode = link['name'].delete(':')
+      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
+
+      next unless emoji.nil?
+
+      emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
+      emoji.image_remote_url = link['href']
+      emoji.save
+    end
+  end
+
   def account_from_href(href)
     url = Addressable::URI.parse(href).normalize
 
diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb
index b8e22a381..a6a5cb0c4 100644
--- a/app/lib/ostatus/atom_serializer.rb
+++ b/app/lib/ostatus/atom_serializer.rb
@@ -368,5 +368,9 @@ class OStatus::AtomSerializer
     end
 
     append_element(entry, 'mastodon:scope', status.visibility)
+
+    status.emojis.each do |emoji|
+      append_element(entry, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
+    end
   end
 end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
new file mode 100644
index 000000000..f4d3b16a0
--- /dev/null
+++ b/app/models/custom_emoji.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: custom_emojis
+#
+#  id                 :integer          not null, primary key
+#  shortcode          :string           default(""), not null
+#  domain             :string
+#  image_file_name    :string
+#  image_content_type :string
+#  image_file_size    :integer
+#  image_updated_at   :datetime
+#  created_at         :datetime         not null
+#  updated_at         :datetime         not null
+#
+
+class CustomEmoji < ApplicationRecord
+  SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'
+
+  SCAN_RE = /(?<=[^[:alnum:]:]|\n|^)
+    :(#{SHORTCODE_RE_FRAGMENT}):
+    (?=[^[:alnum:]:]|$)/x
+
+  has_attached_file :image
+
+  validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes }
+  validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 }
+
+  include Remotable
+
+  class << self
+    def from_text(text, domain)
+      return [] if text.blank?
+      shortcodes = text.scan(SCAN_RE).map(&:first)
+      where(shortcode: shortcodes, domain: domain)
+    end
+  end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index 2a2cdcf6e..326d128d6 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -131,6 +131,10 @@ class Status < ApplicationRecord
     !sensitive? && media_attachments.any?
   end
 
+  def emojis
+    CustomEmoji.from_text(text, account.domain)
+  end
+
   after_create :store_uri, if: :local?
 
   before_validation :prepare_contents, if: :local?
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 166214eee..e5d8e3f03 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -57,7 +57,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   end
 
   def virtual_tags
-    object.mentions + object.tags
+    object.mentions + object.tags + object.emojis
   end
 
   def atom_uri
@@ -137,4 +137,22 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
       "##{object.name}"
     end
   end
+
+  class CustomEmojiSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    attributes :type, :href, :name
+
+    def type
+      'Emoji'
+    end
+
+    def href
+      full_asset_url(object.image.url)
+    end
+
+    def name
+      ":#{object.shortcode}:"
+    end
+  end
 end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 298a3bb40..d8efa8e60 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -17,6 +17,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
   has_many :mentions
   has_many :tags
+  has_many :emojis
 
   def current_user?
     !current_user.nil?
@@ -106,4 +107,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
       tag_url(object)
     end
   end
+
+  class CustomEmojiSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    attributes :shortcode, :url
+
+    def url
+      full_asset_url(object.image.url)
+    end
+  end
 end
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index dd9456260..692d5a6d5 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -17,7 +17,7 @@
       %p{ style: 'margin-bottom: 0' }<
         %span.p-summary> #{status.spoiler_text}&nbsp;
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true)
 
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 55aa97f32..f9a530d38 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -18,7 +18,7 @@
       %p{ style: 'margin-bottom: 0' }<
         %span.p-summary> #{status.spoiler_text}&nbsp;
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+    .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true)
 
   - unless status.media_attachments.empty?
     - if status.media_attachments.first.video?
diff --git a/db/migrate/20170917153509_create_custom_emojis.rb b/db/migrate/20170917153509_create_custom_emojis.rb
new file mode 100644
index 000000000..4040c8312
--- /dev/null
+++ b/db/migrate/20170917153509_create_custom_emojis.rb
@@ -0,0 +1,13 @@
+class CreateCustomEmojis < ActiveRecord::Migration[5.1]
+  def change
+    create_table :custom_emojis do |t|
+      t.string :shortcode, null: false, default: ''
+      t.string :domain
+      t.attachment :image
+
+      t.timestamps
+    end
+
+    add_index :custom_emojis, [:shortcode, :domain], unique: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f2ca2af69..9f42d46dd 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20170913000752) do
+ActiveRecord::Schema.define(version: 20170917153509) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -89,6 +89,18 @@ ActiveRecord::Schema.define(version: 20170913000752) do
     t.index ["uri"], name: "index_conversations_on_uri", unique: true
   end
 
+  create_table "custom_emojis", force: :cascade do |t|
+    t.string "shortcode", default: "", null: false
+    t.string "domain"
+    t.string "image_file_name"
+    t.string "image_content_type"
+    t.integer "image_file_size"
+    t.datetime "image_updated_at"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
+  end
+
   create_table "domain_blocks", id: :serial, force: :cascade do |t|
     t.string "domain", default: "", null: false
     t.datetime "created_at", null: false
diff --git a/spec/fabricators/custom_emoji_fabricator.rb b/spec/fabricators/custom_emoji_fabricator.rb
new file mode 100644
index 000000000..18a7d23dc
--- /dev/null
+++ b/spec/fabricators/custom_emoji_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:custom_emoji) do
+  shortcode 'coolcat'
+  domain    nil
+  image     { File.open(Rails.root.join('spec', 'fixtures', 'files', 'emojo.png')) }
+end
diff --git a/spec/fixtures/files/emojo.png b/spec/fixtures/files/emojo.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb5993499f059fa4118504aee1e71c9181810e3e
GIT binary patch
literal 29814
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

literal 0
HcmV?d00001

diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index fcb044ebc..1a9520f04 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe ActivityPub::Activity::Create do
 
   before do
     stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt'))
+    stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png'))
   end
 
   describe '#perform' do
@@ -217,5 +218,29 @@ RSpec.describe ActivityPub::Activity::Create do
         expect(status.tags.map(&:name)).to include('test')
       end
     end
+
+    context 'with emojis' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum :tinking:',
+          tag: [
+            {
+              type: 'Emoji',
+              href: 'http://example.com/emoji.png',
+              name: 'tinking',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.emojis.map(&:shortcode)).to include('tinking')
+      end
+    end
   end
 end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index b714b317a..71b6b78d2 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -223,6 +223,45 @@ RSpec.describe Formatter do
 
         include_examples 'encode and link URLs'
       end
+
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji) }
+        let(:status) { Fabricate(:status, account: local_account, text: text) }
+
+        subject { Formatter.instance.format(status, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { ':coolcat: Beep boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { 'Beep :coolcat: boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { ':coolcat::coolcat:' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/:coolcat::coolcat:/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { 'Beep boop :coolcat:' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
     end
 
     context 'with remote status' do
@@ -231,6 +270,45 @@ RSpec.describe Formatter do
       it 'reformats' do
         is_expected.to eq 'Beep boop'
       end
+
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
+        let(:status) { Fabricate(:status, account: remote_account, text: text) }
+
+        subject { Formatter.instance.format(status, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { '<p>:coolcat: Beep boop<br />' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { '<p>Beep :coolcat: boop</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { '<p>:coolcat::coolcat:</p>' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
     end
   end
 
diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb
index 0451eceeb..b2480a53b 100644
--- a/spec/lib/ostatus/atom_serializer_spec.rb
+++ b/spec/lib/ostatus/atom_serializer_spec.rb
@@ -97,11 +97,23 @@ RSpec.describe OStatus::AtomSerializer do
 
       mentioned = element.nodes.find do |node|
         node.name == 'link' &&
-        node[:rel] == 'mentioned' &&
-        node['ostatus:object-type'] == TagManager::TYPES[:person]
+          node[:rel] == 'mentioned' &&
+          node['ostatus:object-type'] == TagManager::TYPES[:person]
       end
+
       expect(mentioned[:href]).to eq 'https://cb6e6126.ngrok.io/users/username'
     end
+
+    it 'appends link elements for emojis' do
+      Fabricate(:custom_emoji)
+
+      status  = Fabricate(:status, text: ':coolcat:')
+      element = serialize(status)
+      emoji   = element.nodes.find { |node| node.name == 'link' && node[:rel] == 'emoji' }
+
+      expect(emoji[:name]).to eq 'coolcat'
+      expect(emoji[:href]).to_not be_blank
+    end
   end
 
   describe 'render' do
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
new file mode 100644
index 000000000..cb51e9519
--- /dev/null
+++ b/spec/models/custom_emoji_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.describe CustomEmoji, type: :model do
+  describe '.from_text' do
+    let!(:emojo) { Fabricate(:custom_emoji) }
+
+    subject { described_class.from_text(text, nil) }
+
+    context 'with plain text' do
+      let(:text) { 'Hello :coolcat:' }
+
+      it 'returns records used via shortcodes in text' do
+        is_expected.to include(emojo)
+      end
+    end
+
+    context 'with html' do
+      let(:text) { '<p>Hello :coolcat:</p>' }
+
+      it 'returns records used via shortcodes in text' do
+        is_expected.to include(emojo)
+      end
+    end
+  end
+end