diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index f5735421c7c90bccc616cacbbd89b312eeb4629c..2e0c0ccc398abd7c2359a72d0451e82c419587a1 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -51,7 +51,7 @@ class StatusesIndex < Chewy::Index
       field :id, type: 'long'
       field :account_id, type: 'long'
 
-      field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
+      field :text, type: 'text', value: ->(status) { status.index_text } do
         field :stemmed, type: 'text', analyzer: 'content'
       end
 
diff --git a/app/controllers/api/v1/account_subscribes_controller.rb b/app/controllers/api/v1/account_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5da428bbd991a300a69713f7d20fedc676e82f74
--- /dev/null
+++ b/app/controllers/api/v1/account_subscribes_controller.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class Api::V1::AccountSubscribesController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:follows' }, only: [:index, :show]
+  before_action -> { doorkeeper_authorize! :write, :'write:follows' }, except: [:index, :show]
+
+  before_action :require_user!
+  before_action :set_account_subscribe, except: [:index, :create]
+
+  def index
+    @account_subscribes = AccountSubscribe.where(account: current_account).all
+    render json: @account_subscribes, each_serializer: REST::AccountSubscribeSerializer
+  end
+
+  def show
+    render json: @account_subscribe, serializer: REST::AccountSubscribeSerializer
+  end
+
+  def create
+    @account_subscribe = AccountSubscribe.create!(account_subscribe_params.merge(account: current_account))
+    render json: @account_subscribe, serializer: REST::AccountSubscribeSerializer
+  end
+
+  def update
+    @account_subscribe.update!(account_subscribe_params)
+    render json: @account_subscribe, serializer: REST::AccountSubscribeSerializer
+  end
+
+  def destroy
+    @account_subscribe.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_account_subscribe
+    @account_subscribe = AccountSubscribe.where(account: current_account).find(params[:id])
+  end
+
+  def account_subscribe_params
+    params.permit(:acct)
+  end
+end
diff --git a/app/controllers/api/v1/domain_subscribes_controller.rb b/app/controllers/api/v1/domain_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40cf753330e0d687eb55439f72725b55fca5ef9c
--- /dev/null
+++ b/app/controllers/api/v1/domain_subscribes_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Api::V1::DomainSubscribesController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:follows' }, only: :index
+  before_action -> { doorkeeper_authorize! :write, :'write:follows' }, except: :index
+
+  before_action :require_user!
+  before_action :set_domain_subscribe, except: [:index, :create]
+
+  def index
+    @domain_subscribes = DomainSubscribe.where(account: current_account).all
+    render json: @domain_subscribes, each_serializer: REST::DomainSubscribeSerializer
+  end
+
+  def create
+    @domain_subscribe = DomainSubscribe.create!(domain_subscribe_params.merge(account: current_account))
+    render json: @domain_subscribe, serializer: REST::DomainSubscribeSerializer
+  end
+
+  def destroy
+    @domain_subscribe.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_domain_subscribe
+    @domain_subscribe = DomainSubscribe.where(account: current_account).find(params[:id])
+  end
+
+  def domain_subscribe_params
+    params.permit(:domain, :list_id, :exclude_reblog)
+  end
+end
diff --git a/app/controllers/api/v1/follow_tags_controller.rb b/app/controllers/api/v1/follow_tags_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95682ec7d2286323cb803952968cab56c9d5f90c
--- /dev/null
+++ b/app/controllers/api/v1/follow_tags_controller.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class Api::V1::FollowTagsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:follows' }, only: [:index, :show]
+  before_action -> { doorkeeper_authorize! :write, :'write:follows' }, except: [:index, :show]
+
+  before_action :require_user!
+  before_action :set_follow_tag, except: [:index, :create]
+
+  def index
+    @follow_tags = FollowTag.where(account: current_account).all
+    render json: @follow_tags, each_serializer: REST::FollowTagSerializer
+  end
+
+  def show
+    render json: @follow_tag, serializer: REST::FollowTagSerializer
+  end
+
+  def create
+    @follow_tag = FollowTag.create!(follow_tag_params.merge(account: current_account))
+    render json: @follow_tag, serializer: REST::FollowTagSerializer
+  end
+
+  def update
+    @follow_tag.update!(follow_tag_params)
+    render json: @follow_tag, serializer: REST::FollowTagSerializer
+  end
+
+  def destroy
+    @follow_tag.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_follow_tag
+    @follow_tag = FollowTag.where(account: current_account).find(params[:id])
+  end
+
+  def follow_tag_params
+    params.permit(:name)
+  end
+end
diff --git a/app/controllers/api/v1/keyword_subscribes_controller.rb b/app/controllers/api/v1/keyword_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec80143ce55483a1e69ab7b4c4fff757cd11ab1c
--- /dev/null
+++ b/app/controllers/api/v1/keyword_subscribes_controller.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+class Api::V1::KeywordSubscribeController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:follows' }, only: [:index, :show]
+  before_action -> { doorkeeper_authorize! :write, :'write:follows' }, except: [:index, :show]
+  before_action :require_user!
+  before_action :set_keyword_subscribes, only: :index
+  before_action :set_keyword_subscribe, only: [:show, :update, :destroy]
+
+  respond_to :json
+
+  def index
+    render json: @keyword_subscribes, each_serializer: REST::KeywordSubscribeSerializer
+  end
+
+  def create
+    @keyword_subscribe = current_account.keyword_subscribes.create!(resource_params)
+    render json: @keyword_subscribe, serializer: REST::KeywordSubscribeSerializer
+  end
+
+  def show
+    render json: @keyword_subscribe, serializer: REST::KeywordSubscribeSerializer
+  end
+
+  def update
+    @keyword_subscribe.update!(resource_params)
+    render json: @keyword_subscribe, serializer: REST::KeywordSubscribeSerializer
+  end
+
+  def destroy
+    @keyword_subscribe.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_keyword_subscribes
+    @keyword_subscribes = current_account.keyword_subscribes
+  end
+
+  def set_keyword_subscribe
+    @keyword_subscribe = current_account.keyword_subscribes.find(params[:id])
+  end
+
+  def resource_params
+    params.permit(:name, :keyword, :exclude_keyword, :ignorecase, :regexp, :ignore_block, :disabled, :list_id)
+  end
+end
diff --git a/app/controllers/settings/account_subscribes_controller.rb b/app/controllers/settings/account_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4ca838ac3cb96f40e3e35047de6396af3e16be6f
--- /dev/null
+++ b/app/controllers/settings/account_subscribes_controller.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+class Settings::AccountSubscribesController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_account_subscribings, only: :index
+
+  class AccountInput
+    include ActiveModel::Model
+    include ActiveModel::Attributes
+
+    attribute :acct, :string
+  end
+
+  def index
+    @account_input = AccountInput.new
+  end
+
+  def create
+    acct = account_subscribe_params[:acct].strip
+    acct = acct[1..-1] if acct.start_with?("@")
+
+    begin
+      target_account = AccountSubscribeService.new.call(current_account, acct)
+    rescue
+      target_account = nil
+    end
+
+    if target_account
+      redirect_to settings_account_subscribes_path
+    else
+      set_account_subscribings
+
+      render :index
+    end
+  end
+
+  def destroy
+    target_account = current_account.active_subscribes.find(params[:id]).target_account
+    UnsubscribeAccountService.new.call(current_account, target_account)
+    redirect_to settings_account_subscribes_path
+  end
+
+  private
+
+  def set_account_subscribings
+    @account_subscribings = current_account.active_subscribes.order(:updated_at).reject(&:new_record?).map do |subscribing|
+      {id: subscribing.id, acct: subscribing.target_account.acct}
+    end
+  end
+
+  def account_subscribe_params
+    params.require(:account_input).permit(:acct)
+  end
+end
diff --git a/app/controllers/settings/domain_subscribes_controller.rb b/app/controllers/settings/domain_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..42fcbc6e42f3964b7dbe4a016ccb71af1ddb26a5
--- /dev/null
+++ b/app/controllers/settings/domain_subscribes_controller.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+class Settings::DomainSubscribesController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_lists, only: [:index, :new, :edit, :update]
+  before_action :set_domain_subscribes, only: :index
+  before_action :set_domain_subscribe, only: [:edit, :update, :destroy]
+
+  def index
+    @domain_subscribe = DomainSubscribe.new
+  end
+
+  def new
+    @domain_subscribe = current_account.domain_subscribes.build
+  end
+
+  def create
+    @domain_subscribe = current_account.domain_subscribes.new(domain_subscribe_params)
+
+    if @domain_subscribe.save
+      redirect_to settings_domain_subscribes_path
+    else
+      set_domain_subscribe
+
+      render :index
+    end
+  end
+
+  def edit; end
+
+  def update
+    if @domain_subscribe.update(domain_subscribe_params)
+      redirect_to settings_domain_subscribes_path
+    else
+      render action: :edit
+    end
+  end
+
+  def destroy
+    @domain_subscribe.destroy!
+    redirect_to settings_domain_subscribes_path
+  end
+
+  private
+
+  def set_domain_subscribe
+    @domain_subscribe = current_account.domain_subscribes.find(params[:id])
+  end
+
+  def set_domain_subscribes
+    @domain_subscribes = current_account.domain_subscribes.includes(:list).order('list_id NULLS FIRST', :domain).reject(&:new_record?)
+  end
+
+  def set_lists
+    @lists = List.where(account: current_account).all
+  end
+
+  def domain_subscribe_params
+    new_params = resource_params.permit!.to_h
+
+    if resource_params[:list_id] == '-1'
+      list = List.find_or_create_by!({ account: current_account, title: new_params[:domain] })
+      new_params.merge!({list_id: list.id})
+    end
+
+    new_params
+  end
+
+  def resource_params
+    params.require(:domain_subscribe).permit(:domain, :list_id, :exclude_reblog)
+  end
+end
diff --git a/app/controllers/settings/follow_tags_controller.rb b/app/controllers/settings/follow_tags_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..776a4850426e1fd94a04397b3c15354f06dd4984
--- /dev/null
+++ b/app/controllers/settings/follow_tags_controller.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class Settings::FollowTagsController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_follow_tags, only: :index
+  before_action :set_follow_tag, except: [:index, :create]
+
+  def index
+    @follow_tag = FollowTag.new
+  end
+
+  def create
+    @follow_tag = current_account.follow_tags.new(follow_tag_params)
+
+    if @follow_tag.save
+      redirect_to settings_follow_tags_path
+    else
+      set_follow_tags
+
+      render :index
+    end
+  end
+
+  def destroy
+    @follow_tag.destroy!
+    redirect_to settings_follow_tags_path
+  end
+
+  private
+
+  def set_follow_tag
+    @follow_tag = current_account.follow_tags.find(params[:id])
+  end
+
+  def set_follow_tags
+    @follow_tags = current_account.follow_tags.order(:updated_at).reject(&:new_record?)
+  end
+
+  def follow_tag_params
+    params.require(:follow_tag).permit(:name)
+  end
+end
diff --git a/app/controllers/settings/keyword_subscribes_controller.rb b/app/controllers/settings/keyword_subscribes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd38581d9e2cfd7ff4ed302af6aaf940b13594d0
--- /dev/null
+++ b/app/controllers/settings/keyword_subscribes_controller.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+class Settings::KeywordSubscribesController < ApplicationController
+  include Authorization
+
+  layout 'admin'
+
+  before_action :set_lists, only: [:index, :new, :edit, :update]
+  before_action :set_keyword_subscribes, only: :index
+  before_action :set_keyword_subscribe, only: [:edit, :update, :destroy]
+  before_action :set_body_classes
+
+  def index
+    @keyword_subscribe = KeywordSubscribe.new
+  end
+
+  def new
+    @keyword_subscribe = current_account.keyword_subscribes.build
+  end
+
+  def create
+    @keyword_subscribe = current_account.keyword_subscribes.build(keyword_subscribe_params)
+
+    if @keyword_subscribe.save
+      redirect_to settings_keyword_subscribes_path
+    else
+      render action: :new
+    end
+  end
+
+  def edit; end
+
+  def update
+    if @keyword_subscribe.update(keyword_subscribe_params)
+      redirect_to settings_keyword_subscribes_path
+    else
+      render action: :edit
+    end
+  end
+
+  def destroy
+    @keyword_subscribe.destroy
+    redirect_to settings_keyword_subscribes_path
+  end
+
+  private
+
+  def set_keyword_subscribe
+    @keyword_subscribe = current_account.keyword_subscribes.find(params[:id])
+  end
+
+  def set_keyword_subscribes
+    @keyword_subscribes = current_account.keyword_subscribes.includes(:list).order('list_id NULLS FIRST', :name).reject(&:new_record?)
+  end
+
+  def set_lists
+    @lists = List.where(account: current_account).all
+  end
+
+  def keyword_subscribe_params
+    new_params = resource_params.permit!.to_h
+
+    if resource_params[:list_id] == '-1'
+      list = List.find_or_create_by!({ account: current_account, title: new_params[:name].presence || "keyword_#{Time.now.strftime('%Y%m%d%H%M%S')}" })
+      new_params.merge!({list_id: list.id})
+    end
+
+    new_params
+  end
+
+  def resource_params
+    params.require(:keyword_subscribe).permit(:name, :keyword, :exclude_keyword, :ignorecase, :regexp, :ignore_block, :disabled, :list_id)
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+end
diff --git a/app/helpers/lists_helper.rb b/app/helpers/lists_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b85405be4897de516b69a6dc3f64a4c41c93f58
--- /dev/null
+++ b/app/helpers/lists_helper.rb
@@ -0,0 +1,7 @@
+module ListsHelper
+  def home_list_new(lists)
+    items = { nil => t('column.home') }
+    items.merge!(lists&.pluck(:id, :title).to_h)
+    items.merge!({ -1 => t('lists.add_new') })
+  end
+end
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 7f22f58a1de074f88e8eeb8f196663163614600a..5468b337074937c347bd0cbef7c195ed0eaa2a63 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -883,3 +883,7 @@ a.name-tag,
 .center-text {
   text-align: center;
 }
+
+.exclude-keyword {
+  color: $error-value-color;
+}
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 62f5554ffc0886fc357765574c75836e977c34a2..eb070e0d86937254e7a500eb43294b84e7c8b13c 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -49,6 +49,11 @@
     }
   }
 
+  th.nowrap,
+  td.nowrap {
+    white-space: nowrap;
+  }
+
   &.inline-table {
     & > tbody > tr:nth-child(odd) {
       & > td,
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index e979d2866adbdf201b3bb97cb751afbc34bb5b3f..a598e7d500a136d9b2e8a0ee08ed76a950a96c0c 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -88,10 +88,10 @@ class FeedManager
     end
   end
 
-  def merge_into_timeline(from_account, into_account)
+  def merge_into_timeline(from_account, into_account, public_only = false)
     timeline_key = key(:home, into_account.id)
     aggregate    = into_account.user&.aggregates_reblogs?
-    query        = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
+    query        = from_account.statuses.where(visibility: public_only ? :public : [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
 
     if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
       oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
@@ -188,21 +188,32 @@ class FeedManager
 
     return true if check_for_blocks.any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] }
 
-    if status.reply? && !status.in_reply_to_account_id.nil?                                                                      # Filter out if it's a reply
-      should_filter   = !crutches[:following][status.in_reply_to_account_id]                                                     # and I'm not following the person it's a reply to
-      should_filter &&= receiver_id != status.in_reply_to_account_id                                                             # and it's not a reply to me
-      should_filter &&= status.account_id != status.in_reply_to_account_id                                                       # and it's not a self-reply
-
-      return !!should_filter
-    elsif status.reblog?                                                                                                         # Filter out a reblog
+    if status.reblog?                                                                                                            # Filter out a reblog
       should_filter   = crutches[:hiding_reblogs][status.account_id]                                                             # if the reblogger's reblogs are suppressed
+      should_filter ||= crutches[:domain_blocking][status.account.domain]                                                        # or the reblogger's domain is blocked
       should_filter ||= crutches[:blocked_by][status.reblog.account_id]                                                          # or if the author of the reblogged status is blocking me
-      should_filter ||= crutches[:domain_blocking][status.reblog.account.domain]                                                 # or the author's domain is blocked
+      should_filter ||= crutches[:domain_blocking_r][status.reblog.account.domain]                                               # or the author's domain is blocked
 
       return !!should_filter
-    end
+    else
+      if status.reply?                                                                                                           # Filter out a reply
+        should_filter   = !crutches[:following][status.in_reply_to_account_id]                                                   # and I'm not following the person it's a reply to
+        should_filter &&= receiver_id != status.in_reply_to_account_id                                                           # and it's not a reply to me
+        should_filter &&= status.account_id != status.in_reply_to_account_id                                                     # and it's not a self-reply
+        should_filter &&= !status.tags.any? { |tag| crutches[:following_tag_by][tag.id] }                                        # and It's not follow tag
+        should_filter &&= !KeywordSubscribe.match?(status.index_text, account_id: receiver_id)                                   # and It's not subscribe keywords
+        should_filter &&= !crutches[:domain_subscribe][status.account.domain]                                                    # and It's not domain subscribes
+        
+        return true if should_filter
+      end
 
-    false
+      should_filter   = crutches[:domain_blocking][status.account.domain]
+      should_filter &&= !crutches[:following][status.account_id]
+      should_filter &&= !crutches[:account_subscribe][status.account_id]
+      should_filter &&= !KeywordSubscribe.match?(status.index_text, account_id: receiver_id, as_ignore_block: true)
+
+      return !!should_filter
+    end
   end
 
   def filter_from_mentions?(status, receiver_id)
@@ -349,13 +360,16 @@ class FeedManager
       arr
     end
 
-    crutches[:following]       = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:hiding_reblogs]  = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:blocking]        = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:muting]          = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
-    crutches[:blocked_by]      = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-
+    crutches[:following]         = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:hiding_reblogs]    = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:blocking]          = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:muting]            = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:domain_blocking]   = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
+    crutches[:domain_blocking_r] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
+    crutches[:blocked_by]        = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:following_tag_by]  = FollowTag.where(account_id: receiver_id, tag: statuses.map { |s| s.tags }.flatten.uniq.compact).pluck(:tag_id).each_with_object({}) { |tag_id, mapping| mapping[tag_id] = true }
+    crutches[:domain_subscribe]  = DomainSubscribe.where(account_id: receiver_id, list_id: nil, domain: statuses.map { |s| s&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
+    crutches[:account_subscribe] = AccountSubscribe.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
     crutches
   end
 end
diff --git a/app/models/account_subscribe.rb b/app/models/account_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..09775878641d1af7eab1e395daa9ceb53f4bb44b
--- /dev/null
+++ b/app/models/account_subscribe.rb
@@ -0,0 +1,23 @@
+# == Schema Information
+#
+# Table name: account_subscribes
+#
+#  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  target_account_id :bigint(8)
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#  list_id           :bigint(8)
+#
+
+class AccountSubscribe < ApplicationRecord
+  belongs_to :account
+  belongs_to :target_account, class_name: 'Account'
+  belongs_to :list, optional: true
+
+  validates :account_id, uniqueness: { scope: [:target_account_id, :list_id] }
+
+  scope :recent, -> { reorder(id: :desc) }
+  scope :subscribed_lists, ->(account) { AccountSubscribe.where(target_account_id: account.id).where.not(list_id: nil).select(:list_id).uniq }
+
+end
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index f901c446d1ae46cc898b5a4209a930ca9e1ecdd5..3dc2f0a18e0b77da6594a2232015705caf78562b 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -60,5 +60,12 @@ module AccountAssociations
     has_and_belongs_to_many :tags
     has_many :favourite_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
     has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
+    has_many :follow_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
+
+    # KeywordSubscribes
+    has_many :keyword_subscribes, inverse_of: :account, dependent: :destroy
+
+    # DomainSubscribes
+    has_many :domain_subscribes, inverse_of: :account, dependent: :destroy
   end
 end
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index f27d39483a365fc523ea5a9367eaeb44693fd9ea..e4b2bef32ba1ada1ee1545734c2f45a0f2c427ce 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -84,6 +84,13 @@ module AccountInteractions
     has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
     has_many :conversation_mutes, dependent: :destroy
     has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
+
+    # Subscribers
+    has_many :active_subscribes,  class_name: 'AccountSubscribe', foreign_key: 'account_id',        dependent: :destroy
+    has_many :passive_subscribes, class_name: 'AccountSubscribe', foreign_key: 'target_account_id', dependent: :destroy
+
+    has_many :subscribing, through: :active_subscribes,  source: :target_account
+    has_many :subscribers, through: :passive_subscribes, source: :account
   end
 
   def follow!(other_account, reblogs: nil, uri: nil)
@@ -150,6 +157,10 @@ module AccountInteractions
     block&.destroy
   end
 
+  def subscribe!(other_account)
+    active_subscribes.find_or_create_by!(target_account: other_account)
+  end
+
   def following?(other_account)
     active_relationships.where(target_account: other_account).exists?
   end
@@ -202,12 +213,22 @@ module AccountInteractions
     account_pins.where(target_account: account).exists?
   end
 
+  def subscribing?(other_account)
+    active_subscribes.where(target_account: other_account).exists?
+  end
+
   def followers_for_local_distribution
     followers.local
              .joins(:user)
              .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
   end
 
+  def subscribers_for_local_distribution
+    subscribers.local
+             .joins(:user)
+             .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
+  end
+
   def lists_for_local_distribution
     lists.joins(account: :user)
          .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
diff --git a/app/models/domain_subscribe.rb b/app/models/domain_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48eef4fb51ebc9e803166eebf0c776c352f5dbae
--- /dev/null
+++ b/app/models/domain_subscribe.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: domain_subscribes
+#
+#  id             :bigint(8)        not null, primary key
+#  account_id     :bigint(8)
+#  list_id        :bigint(8)
+#  domain         :string           default(""), not null
+#  created_at     :datetime         not null
+#  updated_at     :datetime         not null
+#  exclude_reblog :boolean          default(TRUE)
+#
+
+class DomainSubscribe < ApplicationRecord
+  belongs_to :account
+  belongs_to :list, optional: true
+
+  validates :domain, presence: true
+  validates :account_id, uniqueness: { scope: [:domain, :list_id] }
+
+  scope :domain_to_home, ->(domain) { where(domain: domain).where(list_id: nil) }
+  scope :domain_to_list, ->(domain) { where(domain: domain).where.not(list_id: nil) }
+  scope :with_reblog, ->(reblog) { where(exclude_reblog: false) if reblog }
+end
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 87fa114253b4e6b4b454cfd53d9f65c937646e23..7f2d3e5b671929483f5036be60cdd105e506c0e8 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -10,6 +10,7 @@
 #  target_account_id :bigint(8)        not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #  uri               :string
+#  private           :boolean          default(TRUE), not null
 #
 
 class Follow < ApplicationRecord
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index 96ac7eaa593d9fac53f30d3a9444c7ba3d30ea99..23ade6215378d6217309e1f762744121eb119794 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -26,6 +26,7 @@ class FollowRequest < ApplicationRecord
 
   def authorize!
     account.follow!(target_account, reblogs: show_reblogs, uri: uri)
+    UnsubscribeAccountService.new.call(account, target_account) if account.subscribing?(target_account)
     MergeWorker.perform_async(target_account.id, account.id) if account.local?
     destroy!
   end
diff --git a/app/models/follow_tag.rb b/app/models/follow_tag.rb
new file mode 100644
index 0000000000000000000000000000000000000000..90b428b7455628dc62b7c9df8009f94b9060bdcf
--- /dev/null
+++ b/app/models/follow_tag.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: follow_tags
+#
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
+#  tag_id     :bigint(8)
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  list_id    :bigint(8)
+#
+
+class FollowTag < ApplicationRecord
+  belongs_to :account, inverse_of: :follow_tags, required: true
+  belongs_to :tag, inverse_of: :follow_tags, required: true
+  belongs_to :list, optional: true
+
+  delegate :name, to: :tag, allow_nil: true
+
+  validates_associated :tag, on: :create
+  validates :name, presence: true, on: :create
+  validates :account_id, uniqueness: { scope: [:tag_id, :list_id] }
+
+  def name=(str)
+    self.tag = Tag.find_or_create_by_names(str.strip)&.first
+  end
+end
diff --git a/app/models/keyword_subscribe.rb b/app/models/keyword_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1df016eecc19f51fa9927739158557fd53b90c6f
--- /dev/null
+++ b/app/models/keyword_subscribe.rb
@@ -0,0 +1,107 @@
+# == Schema Information
+#
+# Table name: keyword_subscribes
+#
+#  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  keyword         :string           not null
+#  ignorecase      :boolean          default(TRUE)
+#  regexp          :boolean          default(FALSE)
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#  name            :string           default(""), not null
+#  ignore_block    :boolean          default(FALSE)
+#  disabled        :boolean          default(FALSE)
+#  exclude_keyword :string           default(""), not null
+#  list_id         :bigint(8)
+#
+
+class KeywordSubscribe < ApplicationRecord
+  belongs_to :account, inverse_of: :keyword_subscribes, required: true
+  belongs_to :list, optional: true
+
+  validates :keyword, presence: true
+  validate :validate_subscribes_limit, on: :create
+  validate :validate_keyword_regexp_syntax
+  validate :validate_exclude_keyword_regexp_syntax
+  validate :validate_uniqueness_in_account, on: :create
+
+  scope :active, -> { where(disabled: false) }
+  scope :ignore_block, -> { where(ignore_block: true) }
+  scope :home, -> { where(list_id: nil) }
+  scope :list, -> { where.not(list_id: nil) }
+  scope :without_local_followed_home, ->(account) { home.where.not(account: account.followers.local).where.not(account: account.subscribers.local) }
+  scope :without_local_followed_list, ->(account) { list.where.not(list_id: ListAccount.followed_lists(account)).where.not(list_id: AccountSubscribe.subscribed_lists(account)) }
+
+  def keyword=(val)
+    super(regexp ? val : keyword_normalization(val))
+  end
+
+  def exclude_keyword=(val)
+    super(regexp ? val : keyword_normalization(val))
+  end
+
+  def match?(text)
+    keyword_regexp.match?(text) && (exclude_keyword.empty? || !exclude_keyword_regexp.match?(text))
+  end
+
+  def keyword_regexp
+    to_regexp keyword
+  end
+
+  def exclude_keyword_regexp
+    to_regexp exclude_keyword
+  end
+
+  class << self
+    def match?(text, account_id: account_id = nil, as_ignore_block: as_ignore_block = false)
+      target = KeywordSubscribe.active
+      target = target.where(account_id: account_id) if account_id.present?
+      target = target.ignore_block                  if as_ignore_block
+      !target.find{ |t| t.match?(text) }.nil?
+    end
+  end
+
+  private
+
+  def keyword_normalization(val)
+    val.to_s.strip.gsub(/\s{2,}/, ' ').split(/\s*,\s*/).reject(&:blank?).uniq.join(',')
+  end
+
+  def to_regexp(words)
+    Regexp.new(regexp ? words : "(?<![#])(#{words.split(',').map do |k|
+      sb = k =~ /\A[A-Za-z0-9]/ ? '\b' : k !~ /\A[\/\.]/ ? '(?<![\/\.])' : ''
+      eb = k =~ /[A-Za-z0-9]\z/ ? '\b' : k !~ /[\/\.]\z/ ? '(?![\/\.])' : ''
+
+      /(?m#{ignorecase ? 'i': ''}x:#{sb}#{Regexp.quote(k).gsub("\\ ", "[[:space:]]+")}#{eb})/
+    end.join('|')})", ignorecase)
+  end
+
+  def validate_keyword_regexp_syntax
+    return unless regexp
+
+    begin
+      Regexp.compile(keyword, ignorecase)
+    rescue RegexpError => exception
+      errors.add(:base, I18n.t('keyword_subscribes.errors.regexp', message: exception.message))
+    end
+  end
+
+  def validate_exclude_keyword_regexp_syntax
+    return unless regexp
+
+    begin
+      Regexp.compile(exclude_keyword, ignorecase)
+    rescue RegexpError => exception
+      errors.add(:base, I18n.t('keyword_subscribes.errors.regexp', message: exception.message))
+    end
+  end
+
+  def validate_subscribes_limit
+    errors.add(:base, I18n.t('keyword_subscribes.errors.limit')) if account.keyword_subscribes.count >= 100
+  end
+
+  def validate_uniqueness_in_account
+    errors.add(:base, I18n.t('keyword_subscribes.errors.duplicate')) if account.keyword_subscribes.find_by(keyword: keyword, exclude_keyword: exclude_keyword, list_id: list_id)
+  end
+end
diff --git a/app/models/list_account.rb b/app/models/list_account.rb
index 785923c4cf0260b71d54e8c5b6c446c9433bca70..0cb4339be1c91394c591570e9c020c4d9ecd1207 100644
--- a/app/models/list_account.rb
+++ b/app/models/list_account.rb
@@ -18,6 +18,8 @@ class ListAccount < ApplicationRecord
 
   before_validation :set_follow
 
+  scope :followed_lists, ->(account) { ListAccount.includes(:follow).where(follows: { account_id: account.id }).select(:list_id).uniq }
+
   private
 
   def set_follow
diff --git a/app/models/status.rb b/app/models/status.rb
index 81f7e6576a06bb697a4ea012bb0815ab632d8674..862755c17e54bff65c9e75f8d68a18d003ea1b7d 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -242,6 +242,10 @@ class Status < ApplicationRecord
     @emojis = CustomEmoji.from_text(fields.join(' '), account.domain) + (quote? ? CustomEmoji.from_text([quote.spoiler_text, quote.text].join(' '), quote.account.domain) : [])
   end
 
+  def index_text
+    @index_text ||= [spoiler_text, Formatter.instance.plaintext(self)].concat(media_attachments.map(&:description)).concat(preloadable_poll ? preloadable_poll.options : []).join("\n\n")
+  end
+
   def mark_for_mass_destruction!
     @marked_for_mass_destruction = true
   end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 8e6fc404dbeb779e4fabc7c502f207ea1175b676..29891e0c53200f3398afa5cb045f3ead3fc94a60 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -24,6 +24,7 @@ class Tag < ApplicationRecord
 
   has_many :favourite_tags, dependent: :destroy, inverse_of: :tag
   has_many :featured_tags, dependent: :destroy, inverse_of: :tag
+  has_many :follow_tags, dependent: :destroy, inverse_of: :tag
   has_one :account_tag_stat, dependent: :destroy
 
   HASHTAG_SEPARATORS = "_\u00B7\u200c"
diff --git a/app/serializers/rest/account_subscribe_serializer.rb b/app/serializers/rest/account_subscribe_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..50ef86ae344ccc088bbb64aa5c4126d160eedd6d
--- /dev/null
+++ b/app/serializers/rest/account_subscribe_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::AccountSubscribeSerializer < ActiveModel::Serializer
+  attributes :id, :target_account, :updated_at
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/domain_subscribe_serializer.rb b/app/serializers/rest/domain_subscribe_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fef4e3b83acd4fdd75047c2d4967cddba06c1269
--- /dev/null
+++ b/app/serializers/rest/domain_subscribe_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::DomainSubscribeSerializer < ActiveModel::Serializer
+  attributes :id, :list_id, :domain, :updated_at
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/follow_tag_serializer.rb b/app/serializers/rest/follow_tag_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea9db960135ff24adb6238389baabc30179e7d0f
--- /dev/null
+++ b/app/serializers/rest/follow_tag_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::FollowTagSerializer < ActiveModel::Serializer
+  attributes :id, :name, :updated_at
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/keyword_subscribes_serializer.rb b/app/serializers/rest/keyword_subscribes_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4253d02308da548abb036c8b96e19c65e5a6f231
--- /dev/null
+++ b/app/serializers/rest/keyword_subscribes_serializer.rb
@@ -0,0 +1,9 @@
+ # frozen_string_literal: true
+
+class REST::KeywordSubscribeSerializer < ActiveModel::Serializer
+  attributes :id, :name, :keyword, :exclude_keyword, :ignorecase, :regexp, :ignore_block, :disabled, :exclude_home
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/services/account_subscribe_service.rb b/app/services/account_subscribe_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8e9b0adf3217eb44e9a139d2b00c02d00c9b977f
--- /dev/null
+++ b/app/services/account_subscribe_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class AccountSubscribeService < BaseService
+  # Subscribe a remote user
+  # @param [Account] source_account From which to subscribe
+  # @param [String, Account] uri User URI to subscribe in the form of username@domain (or account record)
+  def call(source_account, target_account)
+    begin
+      target_account = ResolveAccountService.new.call(target_account, skip_webfinger: false)
+    rescue
+      target_account = nil
+    end
+
+    raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
+    raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) || (!target_account.local? && target_account.ostatus?) || source_account.domain_blocking?(target_account.domain)
+
+    if source_account.following?(target_account)
+      return
+    elsif source_account.subscribing?(target_account)
+      return
+    end
+
+    ActivityTracker.increment('activity:interactions')
+
+    subscribe = source_account.subscribe!(target_account)
+    MergeWorker.perform_async(target_account.id, source_account.id, true)
+    subscribe
+  end
+end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 7fd4e6903586e6e77cb0209220894886130c9f14..5e12c035f460f1acfbed335a82b72e12585e1551 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -18,9 +18,16 @@ class FanOutOnWriteService < BaseService
       deliver_to_lists(status)
     end
 
-    return if status.account.silenced? || !status.public_visibility? || status.reblog?
+    return if status.account.silenced? || !status.public_visibility?
+
+    deliver_to_domain_subscribers(status)
+
+    return if status.reblog?
 
     deliver_to_hashtags(status)
+    deliver_to_hashtag_followers(status)
+    deliver_to_subscribers(status)
+    deliver_to_keyword_subscribers(status)
 
     return if status.reply? && status.in_reply_to_account_id != status.account_id
 
@@ -45,6 +52,72 @@ class FanOutOnWriteService < BaseService
     end
   end
 
+  def deliver_to_subscribers(status)
+    Rails.logger.debug "Delivering status #{status.id} to subscribers"
+
+    status.account.subscribers_for_local_distribution.select(:id).reorder(nil).find_in_batches do |subscribings|
+      FeedInsertWorker.push_bulk(subscribings) do |subscribing|
+        [status.id, subscribing.id, :home]
+      end
+    end
+  end
+
+  def deliver_to_domain_subscribers(status)
+    Rails.logger.debug "Delivering status #{status.id} to domain subscribers"
+
+    deliver_to_domain_subscribers_home(status)
+    deliver_to_domain_subscribers_list(status)
+  end
+
+  def deliver_to_domain_subscribers_home(status)
+    DomainSubscribe.domain_to_home(status.account.domain).with_reblog(status.reblog?).select(:id, :account_id).find_in_batches do |subscribes|
+      FeedInsertWorker.push_bulk(subscribes) do |subscribe|
+        [status.id, subscribe.account_id, :home]
+      end
+    end
+  end
+
+  def deliver_to_domain_subscribers_list(status)
+    DomainSubscribe.domain_to_list(status.account.domain).with_reblog(status.reblog?).select(:id, :list_id).find_in_batches do |subscribes|
+      FeedInsertWorker.push_bulk(subscribes) do |subscribe|
+        [status.id, subscribe.list_id, :list]
+      end
+    end
+  end
+
+  def deliver_to_keyword_subscribers(status)
+    Rails.logger.debug "Delivering status #{status.id} to keyword subscribers"
+
+    deliver_to_keyword_subscribers_home(status)
+    deliver_to_keyword_subscribers_list(status)
+  end
+
+  def deliver_to_keyword_subscribers_home(status)
+    match_accounts = []
+
+    KeywordSubscribe.active.without_local_followed_home(status.account).order(:account_id).each do |keyword_subscribe|
+      next if match_accounts[-1] == keyword_subscribe.account_id
+      match_accounts << keyword_subscribe.account_id if keyword_subscribe.match?(status.index_text)
+    end
+
+    FeedInsertWorker.push_bulk(match_accounts) do |match_account|
+      [status.id, match_account, :home]
+    end
+  end
+
+  def deliver_to_keyword_subscribers_list(status)
+    match_lists = []
+
+    KeywordSubscribe.active.without_local_followed_list(status.account).order(:list_id).each do |keyword_subscribe|
+      next if match_lists[-1] == keyword_subscribe.list_id
+      match_lists << keyword_subscribe.list_id if keyword_subscribe.match?(status.index_text)
+    end
+
+    FeedInsertWorker.push_bulk(match_lists) do |match_list|
+      [status.id, match_list, :list]
+    end
+  end
+
   def deliver_to_lists(status)
     Rails.logger.debug "Delivering status #{status.id} to lists"
 
@@ -82,6 +155,14 @@ class FanOutOnWriteService < BaseService
     end
   end
 
+  def deliver_to_hashtag_followers(status)
+    Rails.logger.debug "Delivering status #{status.id} to hashtag followers"
+
+    FeedInsertWorker.push_bulk(FollowTag.where(tag: status.tags).pluck(:account_id).uniq) do |follower|
+      [status.id, follower, :home]
+    end
+  end
+
   def deliver_to_public(status)
     Rails.logger.debug "Delivering status #{status.id} to public timeline"
 
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index dc47804c05bc4b1e6dd385356aa8d43ad807a8ed..91c8e2e6d375fa41c5583770f0b5b3edeaddd18b 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -54,6 +54,7 @@ class FollowService < BaseService
   def direct_follow(source_account, target_account, reblogs: true)
     follow = source_account.follow!(target_account, reblogs: reblogs)
 
+    UnsubscribeAccountService.new.call(source_account, target_account) if source_account.subscribing?(target_account)
     LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
     MergeWorker.perform_async(target_account.id, source_account.id)
 
diff --git a/app/services/unsubscribe_account_service.rb b/app/services/unsubscribe_account_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..534f5d366802f79196289ce4044ebcd003610ccf
--- /dev/null
+++ b/app/services/unsubscribe_account_service.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class UnsubscribeAccountService < BaseService
+  # UnsubscribeAccount
+  # @param [Account] source_account Where to unsubscribe from
+  # @param [Account] target_account Which to unsubscribe
+  def call(source_account, target_account)
+    subscribe = AccountSubscribe.find_by(account: source_account, target_account: target_account)
+
+    return unless subscribe
+
+    subscribe.destroy!
+    UnmergeWorker.perform_async(target_account.id, source_account.id)
+    subscribe
+  end
+end
diff --git a/app/views/settings/account_subscribes/index.html.haml b/app/views/settings/account_subscribes/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0cbb8aab51d439dc019b529227f9ed062c3dc01a
--- /dev/null
+++ b/app/views/settings/account_subscribes/index.html.haml
@@ -0,0 +1,25 @@
+- content_for :page_title do
+  = t('settings.account_subscribes')
+
+%p= t('account_subscribes.hint_html')
+
+%hr.spacer/
+
+= simple_form_for :account_input, url: settings_account_subscribes_path do |f|
+
+  .fields-group
+    = f.input :acct, wrapper: :with_block_label, hint: false
+
+  .actions
+    = f.button :button, t('account_subscribes.add_new'), type: :submit
+
+%hr.spacer/
+
+- @account_subscribings.each do |account_subscribing|
+  .directory__tag
+    %div
+      %h4
+        = fa_icon 'user'
+        = account_subscribing[:acct]
+        %small
+          = table_link_to 'trash', t('filters.index.delete'), settings_account_subscribe_path(account_subscribing), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/settings/domain_subscribes/_fields.html.haml b/app/views/settings/domain_subscribes/_fields.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..30c5b566d77c305730169d3e99940ca4912f43a2
--- /dev/null
+++ b/app/views/settings/domain_subscribes/_fields.html.haml
@@ -0,0 +1,11 @@
+.fields-group
+  = f.input :domain, wrapper: :with_label
+
+.fields-group
+  = f.input :exclude_reblog, wrapper: :with_label
+
+.fields-group
+  = f.label :list_id
+  = f.collection_select :list_id, home_list_new(@lists), :first, :last
+
+%hr.spacer/
diff --git a/app/views/settings/domain_subscribes/edit.html.haml b/app/views/settings/domain_subscribes/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..12e3c0a1b0a391db197389bfb381c97f4e2d4db4
--- /dev/null
+++ b/app/views/settings/domain_subscribes/edit.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+  = t('domain_subscribes.edit.title')
+
+= simple_form_for @domain_subscribe, url: settings_domain_subscribe_path(@domain_subscribe), method: :put do |f|
+  = render 'shared/error_messages', object: @domain_subscribe
+  = render 'fields', f: f
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/domain_subscribes/index.html.haml b/app/views/settings/domain_subscribes/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7602eff3f334b3b5f182c3aab966a62d2e5c498a
--- /dev/null
+++ b/app/views/settings/domain_subscribes/index.html.haml
@@ -0,0 +1,35 @@
+- content_for :page_title do
+  = t('settings.domain_subscribes')
+
+%p= t('domain_subscribes.hint_html')
+
+%hr.spacer/
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th= t('simple_form.labels.domain_subscribe.domain')
+        %th.nowrap= t('simple_form.labels.domain_subscribe.reblog')
+        %th.nowrap= t('simple_form.labels.domain_subscribe.timeline')
+        %th.nowrap
+    %tbody
+      - @domain_subscribes.each do |domain_subscribe|
+        %tr
+          %td
+            = domain_subscribe.domain
+          %td.nowrap
+            - if domain_subscribe.exclude_reblog
+              = fa_icon('times')
+          %td.nowrap
+            - if domain_subscribe.list_id
+              = fa_icon 'list-ul'
+              = domain_subscribe.list&.title
+            - else
+              = fa_icon 'home'
+              = t 'domain_subscribes.home'
+          %td.nowrap
+            = table_link_to 'pencil', t('domain_subscribes.edit.title'), edit_settings_domain_subscribe_path(domain_subscribe)
+            = table_link_to 'trash', t('filters.index.delete'), settings_domain_subscribe_path(domain_subscribe), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
+
+= link_to t('domain_subscribes.new.title'), new_settings_domain_subscribe_path, class: 'button'
diff --git a/app/views/settings/domain_subscribes/new.html.haml b/app/views/settings/domain_subscribes/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..023a9888dd2a2846b35954f502a461a6a3db28bb
--- /dev/null
+++ b/app/views/settings/domain_subscribes/new.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+  = t('domain_subscribes.new.title')
+
+= simple_form_for @domain_subscribe, url: settings_domain_subscribes_path do |f|
+  = render 'shared/error_messages', object: @domain_subscribe
+  = render 'fields', f: f
+
+  .actions
+    = f.button :button, t('domain_subscribes.new.title'), type: :submit
diff --git a/app/views/settings/follow_tags/index.html.haml b/app/views/settings/follow_tags/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..18fce3987213badc75c3676b55dccd61e5c2998e
--- /dev/null
+++ b/app/views/settings/follow_tags/index.html.haml
@@ -0,0 +1,26 @@
+- content_for :page_title do
+  = t('settings.follow_tags')
+
+%p= t('follow_tags.hint_html')
+
+%hr.spacer/
+
+= simple_form_for @follow_tag, url: settings_follow_tags_path do |f|
+  = render 'shared/error_messages', object: @follow_tag
+
+  .fields-group
+    = f.input :name, wrapper: :with_block_label, hint: false
+
+  .actions
+    = f.button :button, t('follow_tags.add_new'), type: :submit
+
+%hr.spacer/
+
+- @follow_tags.each do |follow_tag|
+  .directory__tag{ class: params[:tag] == follow_tag.name ? 'active' : nil }
+    %div
+      %h4
+        = fa_icon 'hashtag'
+        = follow_tag.name
+        %small
+          = table_link_to 'trash', t('filters.index.delete'), settings_follow_tag_path(follow_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/settings/keyword_subscribes/_fields.html.haml b/app/views/settings/keyword_subscribes/_fields.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c40939e1bd6f5db031783db3d1e8b0d8e01f461e
--- /dev/null
+++ b/app/views/settings/keyword_subscribes/_fields.html.haml
@@ -0,0 +1,24 @@
+.fields-group
+  = f.input :name, as: :string, wrapper: :with_label
+
+.fields-group
+  = f.input :keyword, as: :string, wrapper: :with_label
+
+.fields-group
+  = f.input :exclude_keyword, as: :string, wrapper: :with_label
+
+.fields-group
+  = f.input :ignorecase, wrapper: :with_label
+
+.fields-group
+  = f.input :regexp, wrapper: :with_label
+
+.fields-group
+  = f.input :ignore_block, wrapper: :with_label
+
+.fields-group
+  = f.label :list_id
+  = f.collection_select :list_id, home_list_new(@lists), :first, :last
+
+.fields-group
+  = f.input :disabled, wrapper: :with_label
diff --git a/app/views/settings/keyword_subscribes/edit.html.haml b/app/views/settings/keyword_subscribes/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..d11d94e2d3542abf5c8e0a5806ef3500bd92ef10
--- /dev/null
+++ b/app/views/settings/keyword_subscribes/edit.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+  = t('keyword_subscribes.edit.title')
+
+= simple_form_for @keyword_subscribe, url: settings_keyword_subscribe_path(@keyword_subscribe), method: :put do |f|
+  = render 'shared/error_messages', object: @keyword_subscribe
+  = render 'fields', f: f
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/keyword_subscribes/index.html.haml b/app/views/settings/keyword_subscribes/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..750a816a4b7126596586e9ab0c7991b7f2cadf3f
--- /dev/null
+++ b/app/views/settings/keyword_subscribes/index.html.haml
@@ -0,0 +1,66 @@
+- content_for :page_title do
+  = t('keyword_subscribes.index.title')
+
+%p= t('keyword_subscribes.hint_html')
+
+%hr.spacer/
+
+= render 'shared/error_messages', object: @keyword_subscribe
+
+.table-wrapper
+  %table.table
+    %thead
+      %tr
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.name')
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.regexp')
+        %th= t('simple_form.labels.keyword_subscribes.keyword')
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.ignorecase')
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.ignore_block')
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.timeline')
+        %th.nowrap= t('simple_form.labels.keyword_subscribes.disabled')
+        %th.nowrap
+    %tbody
+      - @keyword_subscribes.each do |keyword_subscribe|
+        %tr
+          %td.nowrap= keyword_subscribe.name
+          %td.nowrap
+            - if keyword_subscribe.regexp
+              = t 'keyword_subscribes.regexp.enabled'
+            - else
+              = t 'keyword_subscribes.regexp.disabled'
+          %td
+            .include-keyword
+              = keyword_subscribe.keyword
+            .exclude-keyword
+              = keyword_subscribe.exclude_keyword
+          %td.nowrap
+            - if keyword_subscribe.ignorecase
+              = t 'keyword_subscribes.ignorecase.enabled'
+            - else
+              = t 'keyword_subscribes.ignorecase.disabled'
+          %td.nowrap
+            - if keyword_subscribe.ignore_block
+              = t 'keyword_subscribes.ignore_block'
+          %td.nowrap
+            - if keyword_subscribe.list_id
+              = fa_icon 'list-ul'
+              = keyword_subscribe.list&.title
+            - else
+              = fa_icon 'home'
+              = t 'keyword_subscribe.home'
+          %td.nowrap
+            - if !keyword_subscribe.disabled
+              %span.positive-hint
+                = fa_icon('check')
+                = ' '
+                = t 'keyword_subscribes.enabled'
+            - else
+              %span.negative-hint
+                = fa_icon('times')
+                = ' '
+                = t 'keyword_subscribes.disabled'
+          %td.nowrap
+            = table_link_to 'pencil', t('keyword_subscribes.edit.title'), edit_settings_keyword_subscribe_path(keyword_subscribe)
+            = table_link_to 'times', t('keyword_subscribes.index.delete'), settings_keyword_subscribe_path(keyword_subscribe), method: :delete
+
+= link_to t('keyword_subscribes.new.title'), new_settings_keyword_subscribe_path, class: 'button'
diff --git a/app/views/settings/keyword_subscribes/new.html.haml b/app/views/settings/keyword_subscribes/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b6e97912760e9c3c8b7fef9a45c2af9f64da9407
--- /dev/null
+++ b/app/views/settings/keyword_subscribes/new.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+  = t('keyword_subscribes.new.title')
+
+= simple_form_for @keyword_subscribe, url: settings_keyword_subscribes_path do |f|
+  = render 'shared/error_messages', object: @keyword_subscribe
+  = render 'fields', f: f
+
+  .actions
+    = f.button :button, t('keyword_subscribes.new.title'), type: :submit
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index d745cb99c7b9aada475223d9250a0315dc0c6c5b..0de0acc9a977b180f1881c0bb6af0feec852cca4 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -5,7 +5,7 @@ class MergeWorker
 
   sidekiq_options queue: 'pull'
 
-  def perform(from_account_id, into_account_id)
-    FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id))
+  def perform(from_account_id, into_account_id, public_only = false)
+    FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id), public_only)
   end
 end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 172bc51d697e2624dddf8c06e4f477d11fa1f9c3..bf01a12f6751a0cebdc300d034912c5bac962c8c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -82,6 +82,9 @@ en:
       moderator: Mod
     unavailable: Profile unavailable
     unfollow: Unfollow
+  account_subscribes:
+    add_new: Add
+    hint_html: "<strong>What are account subscription?</strong> Insert public posts from the specified account into the home timeline. Posts received by the server (federated timeline) are targets. You cannot subscribe if you are following."
   admin:
     account_actions:
       action: Perform action
@@ -693,6 +696,33 @@ en:
     directory: Profile directory
     explanation: Discover users based on their interests
     explore_mastodon: Explore %{title}
+  domain_blocks:
+    blocked_domains: List of limited and blocked domains
+    description: This is the list of servers that %{instance} limits or reject federation with.
+    domain: Domain
+    media_block: Media block
+    no_domain_blocks: "(No domain blocks)"
+    severity: Severity
+    severity_legend:
+      media_block: Media files coming from the server are neither fetched, stored, or displayed to the user.
+      silence: Accounts from silenced servers can be found, followed and interacted with, but their toots will not appear in the public timelines, and notifications from them will not reach local users who are not following them.
+      suspension: No content from suspended servers is stored or displayed, nor is any content sent to them. Interactions from suspended servers are ignored.
+      suspension_disclaimer: Suspended servers may occasionally retrieve public content from this server.
+      title: Severities
+    show_rationale: Show rationale
+    silence: Silence
+    suspension: Suspension
+    title: "%{instance} List of blocked instances"
+  domain_subscribes:
+    add_new: Add
+    edit:
+      title: Edit
+    exclude_reblog: Exclude
+    hint_html: "<strong>What are domain subscription?</strong> Insert public posts from the specified domain into the home or list timeline. Posts received by the server (federated timeline) are targets."
+    home: Home
+    include_reblog: Include
+    new:
+      title: Add new domain subscription
   domain_validator:
     invalid_domain: is not a valid domain name
   errors:
@@ -754,6 +784,9 @@ en:
       title: Filters
     new:
       title: Add new filter
+  follow_tags:
+    add_new: Add new
+    hint_html: "<strong>What are follow hashtags?</strong> They are a collection of hashtags you follow. From the posts with hashtags received by the server, the one with the hashtag specified here is inserted into the home timeline."
   footer:
     developers: Developers
     more: More…
@@ -826,7 +859,32 @@ en:
       expires_at: Expires
       uses: Uses
     title: Invite people
+  keyword_subscribes:
+    add_new: Add
+    disabled: Disabled
+    edit:
+      title: Edit
+    enabled: Enabled
+    errors:
+      duplicate: The same content has already been registered
+      limit: You have reached the maximum number of "Keyword subscribes" that can be registered
+      regexp: "Regular expression error: %{message}"
+    hint_html: "<strong>What is a keyword subscribes?</strong> Inserts a public post that matches one of the specified words or a regular expression into the home timeline. Posts received by the server (federated timeline) are targets."
+    home: Home
+    ignorecase:
+      enabled: Ignore
+      disabled: Sensitive
+    ignore_block: Ignore
+    index:
+      delete: Delete
+      title: Keyword subscribe
+    new:
+      title: Add new keyword subscribe
+    regexp:
+      enabled: Regex
+      disabled: Keyword
   lists:
+    add_new: Add new list
     errors:
       limit: You have reached the maximum amount of lists
   media_attachments:
@@ -1015,19 +1073,24 @@ en:
   settings:
     account: Account
     account_settings: Account settings
+    account_subscribes: Account subscribes
     aliases: Account aliases
     appearance: Appearance
     authorized_apps: Authorized apps
     back: Back to Mastodon
     delete: Account deletion
     development: Development
+    domain_subscribes: Domain subscribes
     edit_profile: Edit profile
     export: Data export
     favourite_tags: Favourite hashtags
     featured_tags: Featured hashtags
+    follow_and_subscriptions: Follows and subscriptions
+    follow_tags: Following hashtags
     identity_proofs: Identity proofs
     import: Import
     import_and_export: Import and export
+    keyword_subscribes: Keyword subscribes
     migrate: Account migration
     notifications: Notifications
     preferences: Preferences
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 5993feb6490fb28987d7ed834a832e0bfff57fed..b020d5ddcf75e8f6c826eb92e186b17011e87d62 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -78,6 +78,9 @@ ja:
       moderator: Mod
     unavailable: プロフィールは利用できません
     unfollow: フォロー解除
+  account_subscribes:
+    add_new: 追加
+    hint_html: "<strong>アカウントの購読とは何ですか?</strong> 指定したアカウントの公開投稿をホームタイムラインに挿入します。サーバが受け取っている投稿(連合タイムライン)が対象です。フォローしている場合は購読できません。"
   admin:
     account_actions:
       action: アクションを実行
@@ -685,6 +688,16 @@ ja:
     directory: ディレクトリ
     explanation: 関心を軸にユーザーを発見しよう
     explore_mastodon: "%{title}を探索"
+  domain_subscribes:
+    add_new: 追加
+    edit:
+      title: 編集
+    exclude_reblog: 含めない
+    hint_html: "<strong>ドメインの購読とは何ですか?</strong> 指定したドメインの公開投稿をホームタイムラインまたはリストに挿入します。サーバが受け取っている投稿(連合タイムライン)が対象です。"
+    home: ホーム
+    include_reblog: 含める
+    new:
+      title: 新規ドメイン購読を追加
   domain_validator:
     invalid_domain: は無効なドメイン名です
   errors:
@@ -729,6 +742,9 @@ ja:
     errors:
       limit: 注目のハッシュタグの上限に達しました
     hint_html: "<strong>注目のハッシュタグとは?</strong>プロフィールページに目立つ形で表示され、そのハッシュタグのついたあなたの公開投稿だけを抽出して閲覧できるようにします。クリエイティブな仕事や長期的なプロジェクトを追うのに優れた機能です。"
+  follow_tags:
+    add_new: 追加
+    hint_html: "<strong>ハッシュタグのフォローとは何ですか?</strong> それらはあなたがフォローするハッシュタグのコレクションです。サーバが受け取ったハッシュタグ付きの投稿の中から、ここで指定したハッシュタグのついた投稿をホームタイムラインに挿入します。"
   filters:
     contexts:
       home: ホームタイムライン
@@ -816,7 +832,32 @@ ja:
       expires_at: 有効期限
       uses: 使用
     title: 新規ユーザーの招待
+  keyword_subscribes:
+    add_new: 追加
+    disabled: 無効
+    edit:
+      title: 編集
+    enabled: 有効
+    errors:
+      duplicate: 既に同じ内容が登録されています
+      limit: キーワード購読の登録可能数の上限に達しました
+      regexp: "正規表現に誤りがあります: %{message}"
+    hint_html: "<strong>キーワードの購読とは何ですか?</strong> 指定した単語のいずれか、または正規表現に一致する公開投稿をホームタイムラインに挿入します。サーバが受け取っている投稿(連合タイムライン)が対象です。"
+    home: ホーム
+    ignorecase:
+      enabled: 無視
+      disabled: 区別
+    ignore_block: 無視
+    index:
+      delete: 削除
+      title: キーワードの購読
+    new:
+      title: 新規キーワード購読を追加
+    regexp:
+      enabled: 正規表現
+      disabled: キーワード
   lists:
+    add_new: 新しいリストを追加
     errors:
       limit: リストの上限に達しました
   media_attachments:
@@ -1001,19 +1042,24 @@ ja:
   settings:
     account: アカウント
     account_settings: アカウント設定
+    account_subscribes: アカウントの購読
     aliases: アカウントエイリアス
     appearance: 外観
     authorized_apps: 認証済みアプリ
     back: Mastodon に戻る
     delete: アカウントの削除
     development: 開発
+    domain_subscribes: ドメインの購読
     edit_profile: プロフィールを編集
     export: データのエクスポート
     favourite_tags: お気に入りハッシュタグ
     featured_tags: 注目のハッシュタグ
+    follow_and_subscriptions: フォロー・購読
+    follow_tags: ハッシュタグのフォロー
     identity_proofs: Identity proofs
     import: データのインポート
     import_and_export: インポート・エクスポート
+    keyword_subscribes: キーワードの購読
     migrate: アカウントの引っ越し
     notifications: 通知
     preferences: ユーザー設定
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 66f518c1b9cba8b215382903b30a2f297fe2dc5d..4d07bd6ee4e93108ecf3a3c2b0a146f8b9ea34b2 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -47,6 +47,9 @@ en:
         whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
       domain_allow:
         domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored
+      domain_subscribe:
+        domain: Specify the domain name of the server you want to subscribe to
+        exclude_reblog: Exclude boosted posts from subscription
       featured_tag:
         name: 'You might want to use one of these:'
       form_challenge:
@@ -55,6 +58,11 @@ en:
         data: CSV file exported from another Mastodon server
       invite_request:
         text: This will help us review your application
+      keyword_subscribe:
+        exclude_keyword: List multiple excluded keywords separated by commas (or use regular expressions)
+        ignore_block: You can prioritize keyword subscriptions while keeping the entire domain block
+        keyword: List multiple keywords separated by commas (or use regular expressions)
+        name: Optional
       sessions:
         otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
       tag:
@@ -68,6 +76,8 @@ en:
           value: Content
       account_alias:
         acct: Handle of the old account
+      account_input:
+        acct: Account
       account_migration:
         acct: Handle of the new account
       account_warning_preset:
@@ -138,6 +148,12 @@ en:
         username: Username
         username_or_email: Username or Email
         whole_word: Whole word
+      domain_subscribe:
+        domain: Domain
+        exclude_reblog: Exclude boost
+        list_id: Target timeline
+        timeline: Timeline
+        reblog: Boost
       featured_tag:
         name: Hashtag
       interactions:
@@ -148,6 +164,23 @@ en:
         comment: Comment
       invite_request:
         text: Why do you want to join?
+      keyword_subscribe:
+        disabled: Temporarily disable subscription
+        exclude_keyword: Excluded keyword list or regular expression
+        ignorecase: Ignore case
+        ignore_block: Ignore User's domain blocking
+        keyword: Keyword list or regular expression
+        list_id: Target timeline
+        name: Name
+        regexp: Use regular expressions for keywords
+      keyword_subscribes:
+        disabled: State
+        ignorecase: Case
+        ignore_block: Block
+        keyword: String
+        name: Name
+        regexp: Type
+        timeline: Timeline
       notification_emails:
         digest: Send digest e-mails
         favourite: Someone favourited your status
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index fdccc13448b94f4603eceabfd19934b9b7c2649f..271d6496c6286304536ea6ec989ffd0dec0eb448 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -47,6 +47,9 @@ ja:
         whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります
       domain_allow:
         domain: 登録するとこのサーバーからデータを受信したり、このドメインから受信するデータを処理して保存できるようになります
+      domain_subscribe:
+        domain: 購読したいサーバのドメイン名を指定します
+        exclude_reblog: ブーストされた投稿を購読から除外します
       featured_tag:
         name: 'これらを使うといいかもしれません:'
       form_challenge:
@@ -55,6 +58,11 @@ ja:
         data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
       invite_request:
         text: このサーバーは現在承認制です。申請を承認する際に役立つメッセージを添えてください
+      keyword_subscribe:
+        exclude_keyword: カンマで区切って複数の除外するキーワードを並べます(または正規表現で指定します)
+        ignore_block: ドメイン全体を非表示にしたまま、キーワードの購読を優先することができます
+        keyword: カンマで区切って複数のキーワードを並べます(または正規表現で指定します)
+        name: オプションです
       sessions:
         otp: '携帯電話のアプリで生成された二段階認証コードを入力するか、リカバリーコードを使用してください:'
       tag:
@@ -68,6 +76,8 @@ ja:
           value: 内容
       account_alias:
         acct: 引っ越し元のユーザー ID
+      account_input:
+        acct: アカウント (account@domain.tld)
       account_migration:
         acct: 引っ越し先のユーザー ID
       account_warning_preset:
@@ -138,10 +148,18 @@ ja:
         username: ユーザー名
         username_or_email: ユーザー名またはメールアドレス
         whole_word: 単語全体にマッチ
+      domain_subscribe:
+        domain: ドメイン
+        exclude_reblog: ブースト除外
+        list_id: 対象タイムライン
+        reblog: ブースト
+        timeline: タイムライン
       favourite_tag:
         name: ハッシュタグ
       featured_tag:
         name: ハッシュタグ
+      follow_tag:
+        name: ハッシュタグ
       interactions:
         must_be_follower: フォロワー以外からの通知をブロック
         must_be_following: フォローしていないユーザーからの通知をブロック
@@ -150,6 +168,23 @@ ja:
         comment: コメント
       invite_request:
         text: 意気込みをお聞かせください
+      keyword_subscribe:
+        disabled: 一時的に購読を無効にする
+        exclude_keyword: 除外するキーワードまたは正規表現
+        ignorecase: 大文字と小文字を区別しない
+        ignore_block: ユーザーによるドメインブロックを無視する
+        keyword: キーワードまたは正規表現
+        list_id: 対象タイムライン
+        name: 名称
+        regexp: キーワードに正規表現を使う
+      keyword_subscribes:
+        disabled: 状態
+        ignorecase: 大小
+        ignore_block: ブロック
+        keyword: 設定値
+        name: 名称
+        regexp: 種別
+        timeline: タイムライン
       notification_emails:
         digest: タイムラインからピックアップしてメールで通知する
         favourite: お気に入り登録された時
diff --git a/config/navigation.rb b/config/navigation.rb
index f67e6dcc3440c64974b1ad375e28a161b2efa609..2cb2cdc3f7e56c0da5c659b42c204a44c3628f4b 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -17,7 +17,14 @@ SimpleNavigation::Configuration.run do |navigation|
       s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_url
     end
 
-    n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url, if: -> { current_user.functional? }
+    n.item :follow_and_subscriptions, safe_join([fa_icon('users fw'), t('settings.follow_and_subscriptions')]), relationships_url, if: -> { current_user.functional? } do |s|
+      s.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url, highlights_on: %r{/relationships}
+      s.item :follow_tags, safe_join([fa_icon('hashtag fw'), t('settings.follow_tags')]), settings_follow_tags_url
+      s.item :account_subscribes, safe_join([fa_icon('users fw'), t('settings.account_subscribes')]), settings_account_subscribes_url
+      s.item :domain_subscribes, safe_join([fa_icon('server fw'), t('settings.domain_subscribes')]), settings_domain_subscribes_url
+      s.item :keyword_subscribes, safe_join([fa_icon('search fw'), t('settings.keyword_subscribes')]), settings_keyword_subscribes_url
+    end
+
     n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
 
     n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_url do |s|
diff --git a/config/routes.rb b/config/routes.rb
index 6b245a1eb7668e09616282f27a3366d6b5b1d9ff..0e13d8cb2bf5bd2ca4dbc7698005927eb82a4ebb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -147,6 +147,10 @@ Rails.application.routes.draw do
     resources :sessions, only: [:destroy]
     resources :featured_tags, only: [:index, :create, :destroy]
     resources :favourite_tags, only: [:index, :create, :destroy]
+    resources :follow_tags, only: [:index, :create, :destroy]
+    resources :account_subscribes, only: [:index, :create, :destroy]
+    resources :domain_subscribes, except: [:show]
+    resources :keyword_subscribes, except: [:show]
   end
 
   resources :media, only: [:show] do
@@ -402,6 +406,11 @@ Rails.application.routes.draw do
       end
 
       resources :featured_tags, only: [:index, :create, :destroy]
+      resources :favourite_tags, only: [:index, :create, :show, :update, :destroy]
+      resources :follow_tags, only: [:index, :create, :show, :update, :destroy]
+      resources :account_subscribes, only: [:index, :create, :show, :update, :destroy]
+      resources :domain_subscribes, only: [:index, :create, :show, :update, :destroy]
+      resources :keyword_subscribes, only: [:index, :create, :show, :update, :destroy]
 
       resources :polls, only: [:create, :show] do
         resources :votes, only: :create, controller: 'polls/votes'
diff --git a/db/migrate/20190829202944_create_follow_tags.rb b/db/migrate/20190829202944_create_follow_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1116ad640c64a6842ae4a8103accfd6cef6041bb
--- /dev/null
+++ b/db/migrate/20190829202944_create_follow_tags.rb
@@ -0,0 +1,10 @@
+class CreateFollowTags < ActiveRecord::Migration[5.2]
+  def change
+    create_table :follow_tags do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }
+      t.references :tag, foreign_key: { on_delete: :cascade }
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190901090544_create_account_subscribes.rb b/db/migrate/20190901090544_create_account_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1fbb20ec09f3df8bd1c5186055feb0fa1ebca7a0
--- /dev/null
+++ b/db/migrate/20190901090544_create_account_subscribes.rb
@@ -0,0 +1,10 @@
+class CreateAccountSubscribes < ActiveRecord::Migration[5.2]
+  def change
+    create_table :account_subscribes do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }
+      t.references :target_account, foreign_key: { to_table: 'accounts', on_delete: :cascade }
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190903113117_create_keyword_subscribes.rb b/db/migrate/20190903113117_create_keyword_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6fd1f1f87c8c61bb7f59c3a7eddb3b2fcbaf3369
--- /dev/null
+++ b/db/migrate/20190903113117_create_keyword_subscribes.rb
@@ -0,0 +1,12 @@
+class CreateKeywordSubscribes < ActiveRecord::Migration[5.2]
+  def change
+    create_table :keyword_subscribes do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }
+      t.string :keyword, null: false
+      t.boolean :ignorecase, default: true
+      t.boolean :regexp, default: false
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190910140929_add_name_and_flag_to_keyword_subscribe.rb b/db/migrate/20190910140929_add_name_and_flag_to_keyword_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..30e0a4bdf179a5243fa4159e5a9086ee4a9a90bf
--- /dev/null
+++ b/db/migrate/20190910140929_add_name_and_flag_to_keyword_subscribe.rb
@@ -0,0 +1,8 @@
+class AddNameAndFlagToKeywordSubscribe < ActiveRecord::Migration[5.2]
+  def change
+    add_column :keyword_subscribes, :name, :string, default: '', null: false
+    add_column :keyword_subscribes, :ignore_block, :boolean, default: false
+    add_column :keyword_subscribes, :disabled, :boolean, default: false
+    add_column :keyword_subscribes, :exclude_home, :boolean, default: false
+  end
+end
diff --git a/db/migrate/20190911093445_create_domain_subscribes.rb b/db/migrate/20190911093445_create_domain_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0a007b0a2befb165240dc03b5de26bddf7994e8f
--- /dev/null
+++ b/db/migrate/20190911093445_create_domain_subscribes.rb
@@ -0,0 +1,11 @@
+class CreateDomainSubscribes < ActiveRecord::Migration[5.2]
+  def change
+    create_table :domain_subscribes do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }
+      t.references :list, foreign_key: { on_delete: :cascade }
+      t.string :domain, default: '', null: false
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190914231645_add_exclude_keyword_to_keyword_subscribe.rb b/db/migrate/20190914231645_add_exclude_keyword_to_keyword_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c3c1df3f8071b8ce9f90be7f9c42e2bc01696e8
--- /dev/null
+++ b/db/migrate/20190914231645_add_exclude_keyword_to_keyword_subscribe.rb
@@ -0,0 +1,5 @@
+class AddExcludeKeywordToKeywordSubscribe < ActiveRecord::Migration[5.2]
+  def change
+    add_column :keyword_subscribes, :exclude_keyword, :string, default: '', null: false
+  end
+end
diff --git a/db/migrate/20191022105417_add_exclude_reblog_to_domain_subscribe.rb b/db/migrate/20191022105417_add_exclude_reblog_to_domain_subscribe.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3459b0052458c1c7f99e6188e2ae7832e5ae2003
--- /dev/null
+++ b/db/migrate/20191022105417_add_exclude_reblog_to_domain_subscribe.rb
@@ -0,0 +1,5 @@
+class AddExcludeReblogToDomainSubscribe < ActiveRecord::Migration[5.2]
+  def change
+    add_column :domain_subscribes, :exclude_reblog, :boolean, default: true
+  end
+end
diff --git a/db/migrate/20191025031836_remove_exclude_home_from_keyword_subscribes.rb b/db/migrate/20191025031836_remove_exclude_home_from_keyword_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..66fd3b9d47cfbf7e8ff954f01fa808787a81674a
--- /dev/null
+++ b/db/migrate/20191025031836_remove_exclude_home_from_keyword_subscribes.rb
@@ -0,0 +1,5 @@
+class RemoveExcludeHomeFromKeywordSubscribes < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured { remove_column :keyword_subscribes, :exclude_home, :boolean }
+  end
+end
diff --git a/db/migrate/20191025190919_add_list_to_keyword_subscribes.rb b/db/migrate/20191025190919_add_list_to_keyword_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e7973dd864b9dafd8103e9cc2b2ce942df3741e6
--- /dev/null
+++ b/db/migrate/20191025190919_add_list_to_keyword_subscribes.rb
@@ -0,0 +1,8 @@
+class AddListToKeywordSubscribes < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def change
+    add_reference :keyword_subscribes, :list, foreign_key: { on_delete: :cascade }, index: false
+    add_index :keyword_subscribes, :list_id, algorithm: :concurrently
+  end
+end
diff --git a/db/migrate/20191026110416_add_list_to_account_subscribes.rb b/db/migrate/20191026110416_add_list_to_account_subscribes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7420c8bf63b1c75962a4a7a30bd4285d078e5c8c
--- /dev/null
+++ b/db/migrate/20191026110416_add_list_to_account_subscribes.rb
@@ -0,0 +1,8 @@
+class AddListToAccountSubscribes < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def change
+    add_reference :account_subscribes, :list, foreign_key: { on_delete: :cascade }, index: false
+    add_index :account_subscribes, :list_id, algorithm: :concurrently
+  end
+end
diff --git a/db/migrate/20191026110502_add_list_to_follow_tags.rb b/db/migrate/20191026110502_add_list_to_follow_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ecb0714bc5009168a258ecceb24af17835c88a60
--- /dev/null
+++ b/db/migrate/20191026110502_add_list_to_follow_tags.rb
@@ -0,0 +1,8 @@
+class AddListToFollowTags < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def change
+    add_reference :follow_tags, :list, foreign_key: { on_delete: :cascade }, index: false
+    add_index :follow_tags, :list_id, algorithm: :concurrently
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 65a200371b0aa2d7abf25ce53df8f87ca3c0572d..0ceb16e18d54268e2f1002cc769efdd783a6cfb7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -101,6 +101,17 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
     t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true
   end
 
+  create_table "account_subscribes", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "target_account_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.bigint "list_id"
+    t.index ["account_id"], name: "index_account_subscribes_on_account_id"
+    t.index ["list_id"], name: "index_account_subscribes_on_list_id"
+    t.index ["target_account_id"], name: "index_account_subscribes_on_target_account_id"
+  end
+
   create_table "account_tag_stats", force: :cascade do |t|
     t.bigint "tag_id", null: false
     t.bigint "accounts_count", default: 0, null: false
@@ -295,6 +306,17 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
     t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
   end
 
+  create_table "domain_subscribes", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "list_id"
+    t.string "domain", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.boolean "exclude_reblog", default: true
+    t.index ["account_id"], name: "index_domain_subscribes_on_account_id"
+    t.index ["list_id"], name: "index_domain_subscribes_on_list_id"
+  end
+
   create_table "email_domain_blocks", force: :cascade do |t|
     t.string "domain", default: "", null: false
     t.datetime "created_at", null: false
@@ -342,6 +364,17 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
     t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true
   end
 
+  create_table "follow_tags", force: :cascade do |t|
+    t.bigint "account_id"
+    t.bigint "tag_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.bigint "list_id"
+    t.index ["account_id"], name: "index_follow_tags_on_account_id"
+    t.index ["list_id"], name: "index_follow_tags_on_list_id"
+    t.index ["tag_id"], name: "index_follow_tags_on_tag_id"
+  end
+
   create_table "follows", force: :cascade do |t|
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
@@ -349,6 +382,7 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
     t.bigint "target_account_id", null: false
     t.boolean "show_reblogs", default: true, null: false
     t.string "uri"
+    t.boolean "private", default: true, null: false
     t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true
     t.index ["target_account_id"], name: "index_follows_on_target_account_id"
   end
@@ -389,6 +423,22 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
     t.index ["user_id"], name: "index_invites_on_user_id"
   end
 
+  create_table "keyword_subscribes", force: :cascade do |t|
+    t.bigint "account_id"
+    t.string "keyword", null: false
+    t.boolean "ignorecase", default: true
+    t.boolean "regexp", default: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.string "name", default: "", null: false
+    t.boolean "ignore_block", default: false
+    t.boolean "disabled", default: false
+    t.string "exclude_keyword", default: "", null: false
+    t.bigint "list_id"
+    t.index ["account_id"], name: "index_keyword_subscribes_on_account_id"
+    t.index ["list_id"], name: "index_keyword_subscribes_on_list_id"
+  end
+
   create_table "list_accounts", force: :cascade do |t|
     t.bigint "list_id", null: false
     t.bigint "account_id", null: false
@@ -823,6 +873,9 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
   add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
   add_foreign_key "account_pins", "accounts", on_delete: :cascade
   add_foreign_key "account_stats", "accounts", on_delete: :cascade
+  add_foreign_key "account_subscribes", "accounts", column: "target_account_id", on_delete: :cascade
+  add_foreign_key "account_subscribes", "accounts", on_delete: :cascade
+  add_foreign_key "account_subscribes", "lists", on_delete: :cascade
   add_foreign_key "account_tag_stats", "tags", on_delete: :cascade
   add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade
   add_foreign_key "account_warnings", "accounts", on_delete: :nullify
@@ -836,6 +889,8 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
   add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade
   add_foreign_key "conversation_mutes", "conversations", on_delete: :cascade
   add_foreign_key "custom_filters", "accounts", on_delete: :cascade
+  add_foreign_key "domain_subscribes", "accounts", on_delete: :cascade
+  add_foreign_key "domain_subscribes", "lists", on_delete: :cascade
   add_foreign_key "favourite_tags", "accounts", on_delete: :cascade
   add_foreign_key "favourite_tags", "tags", on_delete: :cascade
   add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
@@ -844,11 +899,16 @@ ActiveRecord::Schema.define(version: 2020_01_19_112504) do
   add_foreign_key "featured_tags", "tags", on_delete: :cascade
   add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade
   add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
+  add_foreign_key "follow_tags", "accounts", on_delete: :cascade
+  add_foreign_key "follow_tags", "lists", on_delete: :cascade
+  add_foreign_key "follow_tags", "tags", on_delete: :cascade
   add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
   add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
   add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
   add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
   add_foreign_key "invites", "users", on_delete: :cascade
+  add_foreign_key "keyword_subscribes", "accounts", on_delete: :cascade
+  add_foreign_key "keyword_subscribes", "lists", on_delete: :cascade
   add_foreign_key "list_accounts", "accounts", on_delete: :cascade
   add_foreign_key "list_accounts", "follows", on_delete: :cascade
   add_foreign_key "list_accounts", "lists", on_delete: :cascade
diff --git a/spec/fabricators/account_subscribe_fabricator.rb b/spec/fabricators/account_subscribe_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c9fbf4706a317327a3d91b5168842330e81e7536
--- /dev/null
+++ b/spec/fabricators/account_subscribe_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:account_subscribe) do
+  account
+  target_account
+end
diff --git a/spec/fabricators/domain_subscribe_fabricator.rb b/spec/fabricators/domain_subscribe_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8dd39b0d6b6be99d5603ab665508e322c30a96c2
--- /dev/null
+++ b/spec/fabricators/domain_subscribe_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:domain_subscribe) do
+  account
+  list
+  domain
+end
diff --git a/spec/fabricators/follow_tag_fabricator.rb b/spec/fabricators/follow_tag_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c39f781b76382741158a0ce191869e1ab4c6f83
--- /dev/null
+++ b/spec/fabricators/follow_tag_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:follow_tag) do
+  account
+  tag
+end
diff --git a/spec/fabricators/keyword_subscribe_fabricator.rb b/spec/fabricators/keyword_subscribe_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3913be0ce6b663496c5b829c052b6338198b53b1
--- /dev/null
+++ b/spec/fabricators/keyword_subscribe_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:keyword_subscribe) do
+  account
+  keyword
+  ignorecase
+  regexp
+end
diff --git a/spec/models/account_subscribe_spec.rb b/spec/models/account_subscribe_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..283f53b2041edeffc89f6a223d8e20ee70145f3b
--- /dev/null
+++ b/spec/models/account_subscribe_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe AccountSubscribe, type: :model do
+end
diff --git a/spec/models/domain_subscribe_spec.rb b/spec/models/domain_subscribe_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..167c574c128a8e39fdc48266dccc93f3350c59ea
--- /dev/null
+++ b/spec/models/domain_subscribe_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe DomainSubscribe, type: :model do
+end
diff --git a/spec/models/follow_tag_spec.rb b/spec/models/follow_tag_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..07c708859e07dcf44ec782cd64c44d89f1cbb204
--- /dev/null
+++ b/spec/models/follow_tag_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe FollowTag, type: :model do
+end
diff --git a/spec/models/keyword_subscribe_spec.rb b/spec/models/keyword_subscribe_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..87fa7935ff08cce9fe331f9a35a6751a28fba7d8
--- /dev/null
+++ b/spec/models/keyword_subscribe_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe KeywordSubscribe, type: :model do
+end