about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/api/v1/accounts_controller.rb35
-rw-r--r--app/controllers/api/v1/domain_blocks_controller.rb2
-rw-r--r--app/javascript/mastodon/actions/domain_blocks.js117
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js15
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js22
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js16
-rw-r--r--app/javascript/mastodon/locales/ar.json5
-rw-r--r--app/javascript/mastodon/locales/bg.json5
-rw-r--r--app/javascript/mastodon/locales/ca.json5
-rw-r--r--app/javascript/mastodon/locales/de.json5
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json25
-rw-r--r--app/javascript/mastodon/locales/en.json5
-rw-r--r--app/javascript/mastodon/locales/eo.json5
-rw-r--r--app/javascript/mastodon/locales/es.json5
-rw-r--r--app/javascript/mastodon/locales/fa.json5
-rw-r--r--app/javascript/mastodon/locales/fi.json5
-rw-r--r--app/javascript/mastodon/locales/fr.json5
-rw-r--r--app/javascript/mastodon/locales/he.json5
-rw-r--r--app/javascript/mastodon/locales/hr.json5
-rw-r--r--app/javascript/mastodon/locales/hu.json5
-rw-r--r--app/javascript/mastodon/locales/id.json5
-rw-r--r--app/javascript/mastodon/locales/io.json5
-rw-r--r--app/javascript/mastodon/locales/it.json5
-rw-r--r--app/javascript/mastodon/locales/ja.json5
-rw-r--r--app/javascript/mastodon/locales/nl.json5
-rw-r--r--app/javascript/mastodon/locales/no.json5
-rw-r--r--app/javascript/mastodon/locales/oc.json5
-rw-r--r--app/javascript/mastodon/locales/pl.json5
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json5
-rw-r--r--app/javascript/mastodon/locales/pt.json5
-rw-r--r--app/javascript/mastodon/locales/ru.json5
-rw-r--r--app/javascript/mastodon/locales/tr.json5
-rw-r--r--app/javascript/mastodon/locales/uk.json5
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json5
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json5
-rw-r--r--app/javascript/mastodon/reducers/relationships.js8
-rw-r--r--app/models/account_domain_block.rb1
-rw-r--r--app/models/concerns/account_interactions.rb6
-rw-r--r--app/models/status.rb14
-rw-r--r--app/services/block_domain_from_account_service.rb8
-rw-r--r--app/services/notify_service.rb16
-rw-r--r--app/services/process_interaction_service.rb4
-rw-r--r--app/services/send_interaction_service.rb14
-rw-r--r--app/views/api/v1/accounts/relationship.rabl11
-rw-r--r--spec/services/block_domain_from_account_service_spec.rb19
-rw-r--r--spec/services/notify_service_spec.rb6
46 files changed, 436 insertions, 43 deletions
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 5724dbaea..12c7dd2b0 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -71,11 +71,12 @@ class Api::V1::AccountsController < ApiController
   def block
     BlockService.new.call(current_user.account, @account)
 
-    @following   = { @account.id => false }
-    @followed_by = { @account.id => false }
-    @blocking    = { @account.id => true }
-    @requested   = { @account.id => false }
-    @muting      = { @account.id => current_user.account.muting?(@account.id) }
+    @following       = { @account.id => false }
+    @followed_by     = { @account.id => false }
+    @blocking        = { @account.id => true }
+    @requested       = { @account.id => false }
+    @muting          = { @account.id => current_account.muting?(@account.id) }
+    @domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) }
 
     render :relationship
   end
@@ -107,12 +108,13 @@ class Api::V1::AccountsController < ApiController
   def relationships
     ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
 
-    @accounts    = Account.where(id: ids).select('id')
-    @following   = Account.following_map(ids, current_user.account_id)
-    @followed_by = Account.followed_by_map(ids, current_user.account_id)
-    @blocking    = Account.blocking_map(ids, current_user.account_id)
-    @muting      = Account.muting_map(ids, current_user.account_id)
-    @requested   = Account.requested_map(ids, current_user.account_id)
+    @accounts        = Account.where(id: ids).select('id')
+    @following       = Account.following_map(ids, current_user.account_id)
+    @followed_by     = Account.followed_by_map(ids, current_user.account_id)
+    @blocking        = Account.blocking_map(ids, current_user.account_id)
+    @muting          = Account.muting_map(ids, current_user.account_id)
+    @requested       = Account.requested_map(ids, current_user.account_id)
+    @domain_blocking = Account.domain_blocking_map(ids, current_user.account_id)
   end
 
   def search
@@ -128,11 +130,12 @@ class Api::V1::AccountsController < ApiController
   end
 
   def set_relationship
-    @following   = Account.following_map([@account.id], current_user.account_id)
-    @followed_by = Account.followed_by_map([@account.id], current_user.account_id)
-    @blocking    = Account.blocking_map([@account.id], current_user.account_id)
-    @muting      = Account.muting_map([@account.id], current_user.account_id)
-    @requested   = Account.requested_map([@account.id], current_user.account_id)
+    @following       = Account.following_map([@account.id], current_user.account_id)
+    @followed_by     = Account.followed_by_map([@account.id], current_user.account_id)
+    @blocking        = Account.blocking_map([@account.id], current_user.account_id)
+    @muting          = Account.muting_map([@account.id], current_user.account_id)
+    @requested       = Account.requested_map([@account.id], current_user.account_id)
+    @domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
   end
 
   def pagination_params(core_params)
diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb
index e14547911..f223dd16e 100644
--- a/app/controllers/api/v1/domain_blocks_controller.rb
+++ b/app/controllers/api/v1/domain_blocks_controller.rb
@@ -17,7 +17,7 @@ class Api::V1::DomainBlocksController < ApiController
   end
 
   def create
-    current_account.block_domain!(domain_block_params[:domain])
+    BlockDomainFromAccountService.new.call(current_account, domain_block_params[:domain])
     render_empty
   end
 
diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js
new file mode 100644
index 000000000..c88498117
--- /dev/null
+++ b/app/javascript/mastodon/actions/domain_blocks.js
@@ -0,0 +1,117 @@
+import api, { getLinks } from '../api'
+
+export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
+export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
+export const DOMAIN_BLOCK_FAIL    = 'DOMAIN_BLOCK_FAIL';
+
+export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
+export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
+export const DOMAIN_UNBLOCK_FAIL    = 'DOMAIN_UNBLOCK_FAIL';
+
+export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
+export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS';
+export const DOMAIN_BLOCKS_FETCH_FAIL    = 'DOMAIN_BLOCKS_FETCH_FAIL';
+
+export function blockDomain(domain, accountId) {
+  return (dispatch, getState) => {
+    dispatch(blockDomainRequest(domain));
+
+    api(getState).post('/api/v1/domain_blocks', { domain }).then(response => {
+      dispatch(blockDomainSuccess(domain, accountId));
+    }).catch(err => {
+      dispatch(blockDomainFail(domain, err));
+    });
+  };
+};
+
+export function blockDomainRequest(domain) {
+  return {
+    type: DOMAIN_BLOCK_REQUEST,
+    domain
+  };
+};
+
+export function blockDomainSuccess(domain, accountId) {
+  return {
+    type: DOMAIN_BLOCK_SUCCESS,
+    domain,
+    accountId
+  };
+};
+
+export function blockDomainFail(domain, error) {
+  return {
+    type: DOMAIN_BLOCK_FAIL,
+    domain,
+    error
+  };
+};
+
+export function unblockDomain(domain, accountId) {
+  return (dispatch, getState) => {
+    dispatch(unblockDomainRequest(domain));
+
+    api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => {
+      dispatch(unblockDomainSuccess(domain, accountId));
+    }).catch(err => {
+      dispatch(unblockDomainFail(domain, err));
+    });
+  };
+};
+
+export function unblockDomainRequest(domain) {
+  return {
+    type: DOMAIN_UNBLOCK_REQUEST,
+    domain
+  };
+};
+
+export function unblockDomainSuccess(domain, accountId) {
+  return {
+    type: DOMAIN_UNBLOCK_SUCCESS,
+    domain,
+    accountId
+  };
+};
+
+export function unblockDomainFail(domain, error) {
+  return {
+    type: DOMAIN_UNBLOCK_FAIL,
+    domain,
+    error
+  };
+};
+
+export function fetchDomainBlocks() {
+  return (dispatch, getState) => {
+    dispatch(fetchDomainBlocksRequest());
+
+    api(getState).get().then(response => {
+      const next = getLinks(response).refs.find(link => link.rel === 'next');
+      dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
+    }).catch(err => {
+      dispatch(fetchDomainBlocksFail(err));
+    });
+  };
+};
+
+export function fetchDomainBlocksRequest() {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_REQUEST
+  };
+};
+
+export function fetchDomainBlocksSuccess(domains, next) {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_SUCCESS,
+    domains,
+    next
+  };
+};
+
+export function fetchDomainBlocksFail(error) {
+  return {
+    type: DOMAIN_BLOCKS_FETCH_FAIL,
+    error
+  };
+};
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index 44ed8af7d..1997bf7b7 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -15,7 +15,9 @@ const messages = defineMessages({
   mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
   report: { id: 'account.report', defaultMessage: 'Report @{name}' },
-  disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
+  disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' },
+  blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
 });
 
 class ActionBar extends React.PureComponent {
@@ -28,6 +30,8 @@ class ActionBar extends React.PureComponent {
     onMention: PropTypes.func.isRequired,
     onReport: PropTypes.func.isRequired,
     onMute: PropTypes.func.isRequired,
+    onBlockDomain: PropTypes.func.isRequired,
+    onUnblockDomain: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired
   };
 
@@ -59,7 +63,16 @@ class ActionBar extends React.PureComponent {
     }
 
     if (account.get('acct') !== account.get('username')) {
+      const domain = account.get('acct').split('@')[1];
       extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
+
+      menu.push(null);
+
+      if (account.getIn(['relationship', 'domain_blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
+      }
     }
 
     return (
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index d7226d9b2..f1a0e8d77 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -15,7 +15,9 @@ class Header extends ImmutablePureComponent {
     onBlock: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
     onReport: PropTypes.func.isRequired,
-    onMute: PropTypes.func.isRequired
+    onMute: PropTypes.func.isRequired,
+    onBlockDomain: PropTypes.func.isRequired,
+    onUnblockDomain: PropTypes.func.isRequired,
   };
 
   static contextTypes = {
@@ -43,6 +45,22 @@ class Header extends ImmutablePureComponent {
     this.props.onMute(this.props.account);
   }
 
+  handleBlockDomain = () => {
+    const domain = this.props.account.get('acct').split('@')[1];
+
+    if (!domain) return;
+
+    this.props.onBlockDomain(domain, this.props.account.get('id'));
+  }
+
+  handleUnblockDomain = () => {
+    const domain = this.props.account.get('acct').split('@')[1];
+
+    if (!domain) return;
+
+    this.props.onUnblockDomain(domain, this.props.account.get('id'));
+  }
+
   render () {
     const { account, me } = this.props;
 
@@ -65,6 +83,8 @@ class Header extends ImmutablePureComponent {
           onMention={this.handleMention}
           onReport={this.handleReport}
           onMute={this.handleMute}
+          onBlockDomain={this.handleBlockDomain}
+          onUnblockDomain={this.handleUnblockDomain}
         />
       </div>
     );
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 50999d2e0..0964efdcf 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -13,11 +13,13 @@ import {
 import { mentionCompose } from '../../../actions/compose';
 import { initReport } from '../../../actions/reports';
 import { openModal } from '../../../actions/modal';
+import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 
 const messages = defineMessages({
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
-  muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }
+  muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
+  blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
 });
 
 const makeMapStateToProps = () => {
@@ -70,6 +72,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
         onConfirm: () => dispatch(muteAccount(account.get('id')))
       }));
     }
+  },
+
+  onBlockDomain (domain, accountId) {
+    dispatch(openModal('CONFIRM', {
+      message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
+      confirm: intl.formatMessage(messages.blockDomainConfirm),
+      onConfirm: () => dispatch(blockDomain(domain, accountId))
+    }));
+  },
+
+  onUnblockDomain (domain, accountId) {
+    dispatch(unblockDomain(domain, accountId));
   }
 });
 
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index e30b7e84a..2a9d00460 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -1,17 +1,20 @@
 {
   "account.block": "حظر @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.",
   "account.edit_profile": "تعديل الملف الشخصي",
   "account.follow": "تابِع",
   "account.followers": "المتابعون",
   "account.follows": "يتبع",
   "account.follows_you": "يتابعك",
+  "account.media": "Media",
   "account.mention": "أُذكُر @{name}",
   "account.mute": "أكتم @{name}",
   "account.posts": "المشاركات",
   "account.report": "أبلغ عن @{name}",
   "account.requested": "في انتظار الموافقة",
   "account.unblock": "إلغاء الحظر عن @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "إلغاء المتابعة",
   "account.unmute": "إلغاء الكتم عن @{name}",
   "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
   "confirmations.delete.confirm": "حذف",
   "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "أكتم",
   "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
   "emoji_button.activity": "الأنشطة",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 3ca2f7775..a43cd3dab 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Блокирай",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Редактирай профила си",
   "account.follow": "Последвай",
   "account.followers": "Последователи",
   "account.follows": "Следвам",
   "account.follows_you": "Твой последовател",
+  "account.media": "Media",
   "account.mention": "Споменаване",
   "account.mute": "Mute @{name}",
   "account.posts": "Публикации",
   "account.report": "Report @{name}",
   "account.requested": "В очакване на одобрение",
   "account.unblock": "Не блокирай",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Не следвай",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index f239a3d2f..61827220e 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloquejar @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Aquest usuari és d'un altra instància. Aquest número podria ser més gran.",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidors",
   "account.follows": "Seguint",
   "account.follows_you": "et segueix",
+  "account.media": "Media",
   "account.mention": "Esmentar @{name}",
   "account.mute": "Silenciar @{name}",
   "account.posts": "Publicacions",
   "account.report": "Informe @{name}",
   "account.requested": "Esperant aprovació",
   "account.unblock": "Desbloquejar @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Treure silenci de @{name}",
   "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Estàs segur que vols bloquejar {name}?",
   "confirmations.delete.confirm": "Esborrar",
   "confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
   "emoji_button.activity": "Activitat",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 8a5b5e418..a82bee22d 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -1,17 +1,20 @@
 {
   "account.block": "@{name} blocken",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.",
   "account.edit_profile": "Profil bearbeiten",
   "account.follow": "Folgen",
   "account.followers": "Folgende",
   "account.follows": "Folgt",
   "account.follows_you": "Folgt dir",
+  "account.media": "Media",
   "account.mention": "@{name} erwähnen",
   "account.mute": "@{name} stummschalten",
   "account.posts": "Beiträge",
   "account.report": "@{name} melden",
   "account.requested": "Warte auf Erlaubnis",
   "account.unblock": "@{name} entblocken",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Entfolgen",
   "account.unmute": "@{name} nicht mehr stummschalten",
   "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 38d76828d..67c7cc527 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -243,6 +243,15 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Media",
+        "id": "account.media"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/account_gallery/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Block",
         "id": "confirmations.block.confirm"
       },
@@ -251,12 +260,20 @@
         "id": "confirmations.mute.confirm"
       },
       {
+        "defaultMessage": "Hide entire domain",
+        "id": "confirmations.domain_block.confirm"
+      },
+      {
         "defaultMessage": "Are you sure you want to block {name}?",
         "id": "confirmations.block.message"
       },
       {
         "defaultMessage": "Are you sure you want to mute {name}?",
         "id": "confirmations.mute.message"
+      },
+      {
+        "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+        "id": "confirmations.domain_block.message"
       }
     ],
     "path": "app/javascript/mastodon/features/account_timeline/containers/header_container.json"
@@ -304,6 +321,14 @@
         "id": "account.disclaimer"
       },
       {
+        "defaultMessage": "Hide everything from {domain}",
+        "id": "account.block_domain"
+      },
+      {
+        "defaultMessage": "Unhide {domain}",
+        "id": "account.unblock_domain"
+      },
+      {
         "defaultMessage": "Posts",
         "id": "account.posts"
       },
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index f4a6a7512..6864123ae 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Block @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Edit profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
   "account.follows": "Follows",
   "account.follows_you": "Follows you",
+  "account.media": "Media",
   "account.mention": "Mention @{name}",
   "account.mute": "Mute @{name}",
   "account.posts": "Posts",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
   "account.unblock": "Unblock @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 904c08cc9..3437bd9e9 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloki @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Redakti la profilon",
   "account.follow": "Sekvi",
   "account.followers": "Sekvantoj",
   "account.follows": "Sekvatoj",
   "account.follows_you": "Sekvas vin",
+  "account.media": "Media",
   "account.mention": "Mencii @{name}",
   "account.mute": "Mute @{name}",
   "account.posts": "Mesaĝoj",
   "account.report": "Report @{name}",
   "account.requested": "Atendas aprobon",
   "account.unblock": "Malbloki @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Malsekvi",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 605c14a09..14b007a3e 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloquear",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
   "account.follows": "Seguir",
   "account.follows_you": "Te sigue",
+  "account.media": "Media",
   "account.mention": "Mencionar",
   "account.mute": "Silenciar",
   "account.posts": "Publicaciones",
   "account.report": "Report @{name}",
   "account.requested": "Esperando aprobación",
   "account.unblock": "Desbloquear",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Dejar de seguir",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 72e938695..59266dcd9 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -1,17 +1,20 @@
 {
   "account.block": "مسدودسازی @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.",
   "account.edit_profile": "ویرایش نمایه",
   "account.follow": "پی بگیرید",
   "account.followers": "پیگیران",
   "account.follows": "پی می‌گیرد",
   "account.follows_you": "پیگیر شماست",
+  "account.media": "Media",
   "account.mention": "نام‌بردن از @{name}",
   "account.mute": "بی‌صدا کردن @{name}",
   "account.posts": "نوشته‌ها",
   "account.report": "گزارش @{name}",
   "account.requested": "در انتظار پذیرش",
   "account.unblock": "رفع انسداد @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "پایان پیگیری",
   "account.unmute": "باصدا کردن @{name}",
   "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "آیا واقعاً می‌خواهید {name} را مسدود کنید؟",
   "confirmations.delete.confirm": "پاک کن",
   "confirmations.delete.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید؟",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "بی‌صدا کن",
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
   "emoji_button.activity": "فعالیت",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 42b1fbcb0..c544c955c 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Estä @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Muokkaa",
   "account.follow": "Seuraa",
   "account.followers": "Seuraajia",
   "account.follows": "Seuraa",
   "account.follows_you": "Seuraa sinua",
+  "account.media": "Media",
   "account.mention": "Mainitse @{name}",
   "account.mute": "Mute @{name}",
   "account.posts": "Postit",
   "account.report": "Report @{name}",
   "account.requested": "Odottaa hyväksyntää",
   "account.unblock": "Salli @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Lopeta seuraaminen",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index c6665f43c..d3c292707 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloquer",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
   "account.edit_profile": "Modifier le profil",
   "account.follow": "Suivre",
   "account.followers": "Abonné⋅e⋅s",
   "account.follows": "Abonnements",
   "account.follows_you": "Vous suit",
+  "account.media": "Media",
   "account.mention": "Mentionner",
   "account.mute": "Masquer",
   "account.posts": "Statuts",
   "account.report": "Signaler",
   "account.requested": "Invitation envoyée",
   "account.unblock": "Débloquer",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Ne plus suivre",
   "account.unmute": "Ne plus masquer",
   "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Confirmez vous le blocage de {name} ?",
   "confirmations.delete.confirm": "Supprimer",
   "confirmations.delete.message": "Confirmez vous la suppression de ce pouet ?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Silencer",
   "confirmations.mute.message": "Confirmez vous la silenciation {name} ?",
   "emoji_button.activity": "Activités",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 8e4baa0c5..0a38065b5 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -1,17 +1,20 @@
 {
   "account.block": "חסימת @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.",
   "account.edit_profile": "עריכת פרופיל",
   "account.follow": "מעקב",
   "account.followers": "עוקבים",
   "account.follows": "נעקבים",
   "account.follows_you": "במעקב אחריך",
+  "account.media": "Media",
   "account.mention": "אזכור של @{name}",
   "account.mute": "להשתיק את @{name}",
   "account.posts": "הודעות",
   "account.report": "לדווח על @{name}",
   "account.requested": "בהמתנה לאישור",
   "account.unblock": "הסרת חסימה מעל @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "הפסקת מעקב",
   "account.unmute": "הפסקת השתקת @{name}",
   "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "לחסום את {name}?",
   "confirmations.delete.confirm": "למחוק",
   "confirmations.delete.message": "למחוק את ההודעה?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "להשתיק",
   "confirmations.mute.message": "להשתיק את {name}?",
   "emoji_button.activity": "פעילות",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index d263a73b9..6d4b43c00 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokiraj @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
   "account.edit_profile": "Uredi profil",
   "account.follow": "Slijedi",
   "account.followers": "Sljedbenici",
   "account.follows": "Slijedi",
   "account.follows_you": "te slijedi",
+  "account.media": "Media",
   "account.mention": "Spomeni @{name}",
   "account.mute": "Utišaj @{name}",
   "account.posts": "Postovi",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čeka pristanak",
   "account.unblock": "Deblokiraj @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Prestani slijediti",
   "account.unmute": "Poništi utišavanje @{name}",
   "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index c50501d24..ebe2d4171 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokkolás",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "This user is from another instance. This number may be larger.",
   "account.edit_profile": "Profil szerkesztése",
   "account.follow": "Követés",
   "account.followers": "Követők",
   "account.follows": "Követve",
   "account.follows_you": "Követnek téged",
+  "account.media": "Media",
   "account.mention": "Említés",
   "account.mute": "Mute @{name}",
   "account.posts": "Posts",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
   "account.unblock": "Blokkolás levétele",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Követés abbahagyása",
   "account.unmute": "Unmute @{name}",
   "boost_modal.combo": "You can press {combo} to skip this next time",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index ca821a5d5..45eea622b 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokir @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.",
   "account.edit_profile": "Ubah profil",
   "account.follow": "Ikuti",
   "account.followers": "Pengikut",
   "account.follows": "Mengikuti",
   "account.follows_you": "Mengikuti anda",
+  "account.media": "Media",
   "account.mention": "Balasan @{name}",
   "account.mute": "Bisukan @{name}",
   "account.posts": "Postingan",
   "account.report": "Laporkan @{name}",
   "account.requested": "Menunggu persetujuan",
   "account.unblock": "Hapus blokir @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Berhenti mengikuti",
   "account.unmute": "Berhenti membisukan @{name}",
   "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
   "confirmations.delete.confirm": "Hapus",
   "confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Bisukan",
   "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
   "emoji_button.activity": "Aktivitas",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 34d38868f..f6791d180 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokusar @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.",
   "account.edit_profile": "Modifikar profilo",
   "account.follow": "Sequar",
   "account.followers": "Sequanti",
   "account.follows": "Sequas",
   "account.follows_you": "Sequas tu",
+  "account.media": "Media",
   "account.mention": "Mencionar @{name}",
   "account.mute": "Celar @{name}",
   "account.posts": "Mesaji",
   "account.report": "Denuncar @{name}",
   "account.requested": "Vartante aprobo",
   "account.unblock": "Desblokusar @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Ne plus sequar",
   "account.unmute": "Ne plus celar @{name}",
   "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 8b92ce86d..c09b705eb 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blocca @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
   "account.edit_profile": "Modifica profilo",
   "account.follow": "Segui",
   "account.followers": "Seguaci",
   "account.follows": "Segue",
   "account.follows_you": "Ti segue",
+  "account.media": "Media",
   "account.mention": "Menziona @{name}",
   "account.mute": "Silenzia @{name}",
   "account.posts": "Posts",
   "account.report": "Segnala @{name}",
   "account.requested": "In attesa di approvazione",
   "account.unblock": "Sblocca @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Non seguire",
   "account.unmute": "Non silenziare @{name}",
   "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 5f1ac2381..38745e20f 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -1,17 +1,20 @@
 {
   "account.block": "ブロック",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
   "account.edit_profile": "プロフィールを編集",
   "account.follow": "フォロー",
   "account.followers": "フォロワー",
   "account.follows": "フォロー",
   "account.follows_you": "フォローされています",
+  "account.media": "Media",
   "account.mention": "返信",
   "account.mute": "ミュート",
   "account.posts": "投稿",
   "account.report": "通報",
   "account.requested": "承認待ち",
   "account.unblock": "ブロック解除",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "フォロー解除",
   "account.unmute": "ミュート解除",
   "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "本当に{name}をブロックしますか?",
   "confirmations.delete.confirm": "削除",
   "confirmations.delete.message": "本当に削除しますか?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "ミュート",
   "confirmations.mute.message": "本当に{name}をミュートしますか?",
   "emoji_button.activity": "活動",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index e63f527c1..33e51a680 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokkeer @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Deze gebruiker zit op een andere server. Dit getal kan hoger zijn.",
   "account.edit_profile": "Profiel bewerken",
   "account.follow": "Volgen",
   "account.followers": "Volgers",
   "account.follows": "Volgt",
   "account.follows_you": "Volgt jou",
+  "account.media": "Media",
   "account.mention": "Vermeld @{name}",
   "account.mute": "Negeer @{name}",
   "account.posts": "Berichten",
   "account.report": "Rapporteer @{name}",
   "account.requested": "Wacht op goedkeuring",
   "account.unblock": "Deblokkeer @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Ontvolgen",
   "account.unmute": "Negeer @{name} niet meer",
   "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Weet je zeker dat je {name} wilt blokkeren?",
   "confirmations.delete.confirm": "Verwijderen",
   "confirmations.delete.message": "Weet je zeker dat je deze toot wilt verwijderen?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Negeren",
   "confirmations.mute.message": "Weet je zeker dat je {name} wilt negeren?",
   "emoji_button.activity": "Activiteiten",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index d3fac55bd..1fddbaa21 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokkér @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
   "account.edit_profile": "Rediger profil",
   "account.follow": "Følg",
   "account.followers": "Følgere",
   "account.follows": "Følger",
   "account.follows_you": "Følger deg",
+  "account.media": "Media",
   "account.mention": "Nevn @{name}",
   "account.mute": "Demp @{name}",
   "account.posts": "Innlegg",
   "account.report": "Rapportér @{name}",
   "account.requested": "Venter på godkjennelse",
   "account.unblock": "Avblokker @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Avfølg",
   "account.unmute": "Avdemp @{name}",
   "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 8d4911187..506cf42e5 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blocar",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
   "account.edit_profile": "Modificar lo perfil",
   "account.follow": "Sègre",
   "account.followers": "Abonats",
   "account.follows": "Abonaments",
   "account.follows_you": "Vos sèc",
+  "account.media": "Media",
   "account.mention": "Mencionar",
   "account.mute": "Rescondre",
   "account.posts": "Estatuts",
   "account.report": "Senhalar",
   "account.requested": "Invitacion mandada",
   "account.unblock": "Desblocar",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Quitar de sègre",
   "account.unmute": "Quitar de rescondre",
   "boost_modal.combo": "Podètz butar {combo} per passar aquò lo còp que ven",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Sètz segur de voler blocar {name}?",
   "confirmations.delete.confirm": "Suprimir",
   "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Metre en silenci",
   "confirmations.mute.message": "Sètz segur de voler metre en silenci {name}?",
   "emoji_button.activity": "Activitat",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 2a18dff8f..f51d2ee58 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Blokuj @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Ten użytkownik pochodzi z innej instancji. Ta liczba może być większa.",
   "account.edit_profile": "Edytuj profil",
   "account.follow": "Obserwuj",
   "account.followers": "Obserwujący",
   "account.follows": "Obserwacje",
   "account.follows_you": "Obserwują cię",
+  "account.media": "Media",
   "account.mention": "Wspomnij o @{name}",
   "account.mute": "Wycisz @{name}",
   "account.posts": "Posty",
   "account.report": "Zgłoś @{name}",
   "account.requested": "Oczekująca prośba",
   "account.unblock": "Odblokuj @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Przestań obserwować",
   "account.unmute": "Cofnij wyciszenie @{name}",
   "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Czy na pewno chcesz zablokować {name}?",
   "confirmations.delete.confirm": "Usuń",
   "confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Wycisz",
   "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
   "emoji_button.activity": "Aktywność",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 57b31733d..c5af75d9c 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloquear @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
   "account.follows": "Segue",
   "account.follows_you": "É teu seguidor",
+  "account.media": "Media",
   "account.mention": "Mencionar @{name}",
   "account.mute": "Silenciar @{name}",
   "account.posts": "Posts",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
   "account.unblock": "Não bloquear @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 57b31733d..c5af75d9c 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Bloquear @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
   "account.follows": "Segue",
   "account.follows_you": "É teu seguidor",
+  "account.media": "Media",
   "account.mention": "Mencionar @{name}",
   "account.mute": "Silenciar @{name}",
   "account.posts": "Posts",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
   "account.unblock": "Não bloquear @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Are you sure you want to block {name}?",
   "confirmations.delete.confirm": "Delete",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "emoji_button.activity": "Activity",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 83d74c619..ba4642ab9 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Блокировать",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
   "account.edit_profile": "Изменить профиль",
   "account.follow": "Подписаться",
   "account.followers": "Подписаны",
   "account.follows": "Подписки",
   "account.follows_you": "Подписан(а) на Вас",
+  "account.media": "Media",
   "account.mention": "Упомянуть",
   "account.mute": "Заглушить",
   "account.posts": "Посты",
   "account.report": "Пожаловаться",
   "account.requested": "Ожидает подтверждения",
   "account.unblock": "Разблокировать",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Отписаться",
   "account.unmute": "Снять глушение",
   "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Вы уверены, что хотите заблокировать {name}?",
   "confirmations.delete.confirm": "Удалить",
   "confirmations.delete.message": "Вы уверены, что хотите удалить этот статус?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Заглушить",
   "confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
   "emoji_button.activity": "Занятия",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index ef9c43d0b..ebfa95b84 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Engelle @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Bu kullanıcının hesabı farklı sunucuda bulunduğu için bu sayı daha fazla olabilir.",
   "account.edit_profile": "Profili düzenle",
   "account.follow": "Takip et",
   "account.followers": "Takipçiler",
   "account.follows": "Takip ettikleri",
   "account.follows_you": "Seni takip ediyor",
+  "account.media": "Media",
   "account.mention": "Bahset @{name}",
   "account.mute": "Sustur @{name}",
   "account.posts": "Gönderiler",
   "account.report": "Rapor et @{name}",
   "account.requested": "Onay bekleniyor",
   "account.unblock": "Engeli kaldır @{name}",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Takipten vazgeç",
   "account.unmute": "Sesi aç @{name}",
   "boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "{name} kullanıcısını engellemek istiyor musunuz?",
   "confirmations.delete.confirm": "Sil",
   "confirmations.delete.message": "Bu gönderiyi silmek istiyor musunuz?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Sessize al",
   "confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
   "emoji_button.activity": "Aktivite",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 99df3b4f4..cd2fa6b11 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -1,17 +1,20 @@
 {
   "account.block": "Заблокувати",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "Це користувач з іншої інстанції. Число може бути більше.",
   "account.edit_profile": "Налаштування профілю",
   "account.follow": "Підписатися",
   "account.followers": "Підписники",
   "account.follows": "Підписки",
   "account.follows_you": "Підписаний(-а) на Вас",
+  "account.media": "Media",
   "account.mention": "Згадати",
   "account.mute": "Заглушити",
   "account.posts": "Пости",
   "account.report": "Поскаржитися",
   "account.requested": "Очікує підтвердження",
   "account.unblock": "Розблокувати",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "Відписатися",
   "account.unmute": "Зняти глушення",
   "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "Ви впевнені, що хочете заблокувати {name}?",
   "confirmations.delete.confirm": "Видалити",
   "confirmations.delete.message": "Ви впевнені, що хочете видалити цей допис?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "Заглушить",
   "confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
   "emoji_button.activity": "Заняття",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index fed311cf7..45eb663a0 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -1,17 +1,20 @@
 {
   "account.block": "屏蔽 @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "由于这个账户处于另一个服务站上,实际数字会比这个更多。",
   "account.edit_profile": "修改个人资料",
   "account.follow": "关注",
   "account.followers": "关注者",
   "account.follows": "正关注",
   "account.follows_you": "关注你",
+  "account.media": "Media",
   "account.mention": "提及 @{name}",
   "account.mute": "将 @{name} 静音",
   "account.posts": "嘟文",
   "account.report": "举报 @{name}",
   "account.requested": "等候审批",
   "account.unblock": "解除对 @{name} 的屏蔽",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "取消关注",
   "account.unmute": "取消 @{name} 的静音",
   "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "想好了,真的要屏蔽 {name}?",
   "confirmations.delete.confirm": "删除",
   "confirmations.delete.message": "想好了,真的要删除这条嘟文?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "静音",
   "confirmations.mute.message": "想好了,真的要静音 {name}?",
   "emoji_button.activity": "活动",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index f3637bf74..23611ae97 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -1,17 +1,20 @@
 {
   "account.block": "封鎖 @{name}",
+  "account.block_domain": "Hide everything from {domain}",
   "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
   "account.edit_profile": "修改個人資料",
   "account.follow": "關注",
   "account.followers": "關注的人",
   "account.follows": "正在關注",
   "account.follows_you": "關注你",
+  "account.media": "Media",
   "account.mention": "提及 @{name}",
   "account.mute": "將 @{name} 靜音",
   "account.posts": "文章",
   "account.report": "舉報 @{name}",
   "account.requested": "等候審批",
   "account.unblock": "解除對 @{name} 的封鎖",
+  "account.unblock_domain": "Unhide {domain}",
   "account.unfollow": "取消關注",
   "account.unmute": "取消 @{name} 的靜音",
   "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
@@ -40,6 +43,8 @@
   "confirmations.block.message": "你確定要封鎖{name}嗎?",
   "confirmations.delete.confirm": "刪除",
   "confirmations.delete.message": "你確定要刪除{name}嗎?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
   "confirmations.mute.confirm": "靜音",
   "confirmations.mute.message": "你確定要將{name}靜音嗎?",
   "emoji_button.activity": "活動",
diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js
index c65c48b43..f25c0b55a 100644
--- a/app/javascript/mastodon/reducers/relationships.js
+++ b/app/javascript/mastodon/reducers/relationships.js
@@ -7,6 +7,10 @@ import {
   ACCOUNT_UNMUTE_SUCCESS,
   RELATIONSHIPS_FETCH_SUCCESS
 } from '../actions/accounts';
+import {
+  DOMAIN_BLOCK_SUCCESS,
+  DOMAIN_UNBLOCK_SUCCESS
+} from '../actions/domain_blocks';
 import Immutable from 'immutable';
 
 const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship));
@@ -32,6 +36,10 @@ export default function relationships(state = initialState, action) {
     return normalizeRelationship(state, action.relationship);
   case RELATIONSHIPS_FETCH_SUCCESS:
     return normalizeRelationships(state, action.relationships);
+  case DOMAIN_BLOCK_SUCCESS:
+    return state.setIn([action.accountId, 'domain_blocking'], true);
+  case DOMAIN_UNBLOCK_SUCCESS:
+    return state.setIn([action.accountId, 'domain_blocking'], false);
   default:
     return state;
   }
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index 9241d9720..bdd64c01a 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -14,6 +14,7 @@ class AccountDomainBlock < ApplicationRecord
   include Paginable
 
   belongs_to :account, required: true
+  validates :domain, presence: true, uniqueness: { scope: :account_id }
 
   after_create  :remove_blocking_cache
   after_destroy :remove_blocking_cache
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index c8006bd0b..0ef7512e2 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -23,6 +23,12 @@ module AccountInteractions
     def requested_map(target_account_ids, account_id)
       follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
+
+    def domain_blocking_map(target_account_ids, account_id)
+      accounts_map    = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
+      blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain)
+      accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h
+    end
   end
 
   included do
diff --git a/app/models/status.rb b/app/models/status.rb
index 70021eb65..80f33a7c7 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -67,7 +67,8 @@ class Status < ApplicationRecord
   scope :local_only, -> { left_outer_joins(:account).where(accounts: { domain: nil }) }
   scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) }
   scope :including_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: true }) }
-  scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids, accounts: { domain: account.excluded_from_timeline_domains }) }
+  scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
+  scope :not_domain_blocked_by_account, ->(account) { left_outer_joins(:account).where.not(accounts: { domain: account.excluded_from_timeline_domains }) }
 
   cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
 
@@ -152,13 +153,13 @@ class Status < ApplicationRecord
     def as_public_timeline(account = nil, local_only = false)
       query = timeline_scope(local_only).without_replies
 
-      apply_timeline_filters(query, account)
+      apply_timeline_filters(query, account, local_only)
     end
 
     def as_tag_timeline(tag, account = nil, local_only = false)
       query = timeline_scope(local_only).tagged_with(tag)
 
-      apply_timeline_filters(query, account)
+      apply_timeline_filters(query, account, local_only)
     end
 
     def as_outbox_timeline(account)
@@ -222,16 +223,17 @@ class Status < ApplicationRecord
         .without_reblogs
     end
 
-    def apply_timeline_filters(query, account)
+    def apply_timeline_filters(query, account, local_only)
       if account.nil?
         filter_timeline_default(query)
       else
-        filter_timeline_for_account(query, account)
+        filter_timeline_for_account(query, account, local_only)
       end
     end
 
-    def filter_timeline_for_account(query, account)
+    def filter_timeline_for_account(query, account, local_only)
       query = query.not_excluded_by_account(account)
+      query = query.not_domain_blocked_by_account(account) unless local_only
       query = query.in_allowed_languages(account) if account.allowed_languages.present?
       query.merge(account_silencing_filter(account))
     end
diff --git a/app/services/block_domain_from_account_service.rb b/app/services/block_domain_from_account_service.rb
new file mode 100644
index 000000000..cae7abcbd
--- /dev/null
+++ b/app/services/block_domain_from_account_service.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class BlockDomainFromAccountService < BaseService
+  def call(account, domain)
+    account.block_domain!(domain)
+    account.passive_relationships.where(account: Account.where(domain: domain)).delete_all
+  end
+end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 150ffe6b2..ce22b6505 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -37,15 +37,15 @@ class NotifyService < BaseService
   end
 
   def blocked?
-    blocked   = @recipient.suspended?                                                                                              # Skip if the recipient account is suspended anyway
-    blocked ||= @recipient.id == @notification.from_account.id                                                                     # Skip for interactions with self
-    blocked ||= @recipient.domain_blocking?(@notification.from_account.domain)                                                     # Skip for domain blocked accounts
-    blocked ||= @recipient.blocking?(@notification.from_account)                                                                   # Skip for blocked accounts
-    blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account))                       # Hellban
-    blocked ||= (@recipient.user.settings.interactions['must_be_follower']  && !@notification.from_account.following?(@recipient)) # Options
-    blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account)) # Options
+    blocked   = @recipient.suspended?                                                                                                # Skip if the recipient account is suspended anyway
+    blocked ||= @recipient.id == @notification.from_account.id                                                                       # Skip for interactions with self
+    blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) && !@recipient.following?(@notification.from_account) # Skip for domain blocked accounts
+    blocked ||= @recipient.blocking?(@notification.from_account)                                                                     # Skip for blocked accounts
+    blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account))                         # Hellban
+    blocked ||= (@recipient.user.settings.interactions['must_be_follower']  && !@notification.from_account.following?(@recipient))   # Options
+    blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account))   # Options
     blocked ||= conversation_muted?
-    blocked ||= send("blocked_#{@notification.type}?")                                                                             # Type-dependent filters
+    blocked ||= send("blocked_#{@notification.type}?")                                                                               # Type-dependent filters
     blocked
   end
 
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
index bc8361510..e9c01103d 100644
--- a/app/services/process_interaction_service.rb
+++ b/app/services/process_interaction_service.rb
@@ -21,9 +21,9 @@ class ProcessInteractionService < BaseService
 
       case verb(xml)
       when :follow
-        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account)
+        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
       when :request_friend
-        follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account)
+        follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
       when :authorize
         authorize_follow_request!(account, target_account)
       when :reject
diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb
index 99113eeca..504f41c72 100644
--- a/app/services/send_interaction_service.rb
+++ b/app/services/send_interaction_service.rb
@@ -6,12 +6,22 @@ class SendInteractionService < BaseService
   # @param [Account] source_account
   # @param [Account] target_account
   def call(xml, source_account, target_account)
-    envelope = salmon.pack(xml, source_account.keypair)
-    salmon.post(target_account.salmon_url, envelope)
+    @xml            = xml
+    @source_account = source_account
+    @target_account = target_account
+
+    return if block_notification?
+
+    envelope = salmon.pack(@xml, @source_account.keypair)
+    salmon.post(@target_account.salmon_url, envelope)
   end
 
   private
 
+  def block_notification?
+    DomainBlock.blocked?(@target_account.domain)
+  end
+
   def salmon
     @salmon ||= OStatus2::Salmon.new
   end
diff --git a/app/views/api/v1/accounts/relationship.rabl b/app/views/api/v1/accounts/relationship.rabl
index d6f1dd48a..4f7763d9d 100644
--- a/app/views/api/v1/accounts/relationship.rabl
+++ b/app/views/api/v1/accounts/relationship.rabl
@@ -1,8 +1,9 @@
 object @account
 
 attribute :id
-node(:following)   { |account| @following[account.id]   || false }
-node(:followed_by) { |account| @followed_by[account.id] || false }
-node(:blocking)    { |account| @blocking[account.id]    || false }
-node(:muting)      { |account| @muting[account.id]      || false }
-node(:requested)   { |account| @requested[account.id]   || false }
+node(:following)       { |account| @following[account.id]       || false }
+node(:followed_by)     { |account| @followed_by[account.id]     || false }
+node(:blocking)        { |account| @blocking[account.id]        || false }
+node(:muting)          { |account| @muting[account.id]          || false }
+node(:requested)       { |account| @requested[account.id]       || false }
+node(:domain_blocking) { |account| @domain_blocking[account.id] || false }
diff --git a/spec/services/block_domain_from_account_service_spec.rb b/spec/services/block_domain_from_account_service_spec.rb
new file mode 100644
index 000000000..e7ee34372
--- /dev/null
+++ b/spec/services/block_domain_from_account_service_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+RSpec.describe BlockDomainFromAccountService do
+  let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org') }
+  let!(:alice) { Fabricate(:account, username: 'alice') }
+
+  subject { BlockDomainFromAccountService.new }
+
+  it 'creates domain block' do
+    subject.call(alice, 'evil.org')
+    expect(alice.domain_blocking?('evil.org')).to be true
+  end
+
+  it 'purge followers from blocked domain' do
+    wolf.follow!(alice)
+    subject.call(alice, 'evil.org')
+    expect(wolf.following?(alice)).to be false
+  end
+end
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 29bd741aa..7a66bd0fe 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -22,6 +22,12 @@ RSpec.describe NotifyService do
     is_expected.to_not change(Notification, :count)
   end
 
+  it 'does still notify when sender\'s domain is blocked but sender is followed' do
+    recipient.block_domain!(sender.domain)
+    recipient.follow!(sender)
+    is_expected.to change(Notification, :count)
+  end
+
   it 'does not notify when sender is silenced and not followed' do
     sender.update(silenced: true)
     is_expected.to_not change(Notification, :count)