From 442fdbfc5309f46c23a073829e5fe16d10c7c6ca Mon Sep 17 00:00:00 2001 From: Kit Redgrave Date: Sun, 5 Feb 2017 19:51:56 -0600 Subject: Mute button progress so far. WIP, doesn't entirely work correctly. --- app/services/mute_service.rb | 23 +++++++++++++++++++++++ app/services/unmute_service.rb | 9 +++++++++ 2 files changed, 32 insertions(+) create mode 100644 app/services/mute_service.rb create mode 100644 app/services/unmute_service.rb (limited to 'app/services') diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb new file mode 100644 index 000000000..0050cfc8d --- /dev/null +++ b/app/services/mute_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class MuteService < BaseService + def call(account, target_account) + return if account.id == target_account.id + clear_home_timeline(account, target_account) + account.mute!(target_account) + end + + private + + def clear_home_timeline(account, target_account) + home_key = FeedManager.instance.key(:home, account.id) + + target_account.statuses.select('id').find_each do |status| + redis.zrem(home_key, status.id) + end + end + + def redis + Redis.current + end +end diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb new file mode 100644 index 000000000..ed268b7c5 --- /dev/null +++ b/app/services/unmute_service.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class UnmuteService < BaseService + def call(account, target_account) + return unless account.muting?(target_account) + + account.unmute!(target_account) + end +end -- cgit From c64a1c25c4e9a07c694863a38334ed66e368752e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 2 Mar 2017 18:49:32 +0100 Subject: Fix #231 - Muting --- .../features/account/components/action_bar.jsx | 28 ++++++------ .../account_timeline/components/header.jsx | 4 +- .../compose/components/emoji_picker_dropdown.jsx | 2 +- .../javascripts/components/reducers/timelines.jsx | 4 +- app/lib/feed_manager.rb | 53 ++++++++++------------ app/models/mute.rb | 21 --------- app/models/status.rb | 11 ++--- app/services/unmute_service.rb | 2 + streaming/index.js | 2 +- 9 files changed, 50 insertions(+), 77 deletions(-) (limited to 'app/services') diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx index 60947767c..80a32d3e2 100644 --- a/app/assets/javascripts/components/features/account/components/action_bar.jsx +++ b/app/assets/javascripts/components/features/account/components/action_bar.jsx @@ -9,9 +9,9 @@ const messages = defineMessages({ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - unmute: { id: 'account.unmute', defaultMessage: 'Unmute' }, + unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, block: { id: 'account.block', defaultMessage: 'Block @{name}' }, - mute: { id: 'account.mute', defaultMessage: 'Mute' }, + 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.' } @@ -54,15 +54,19 @@ const ActionBar = React.createClass({ if (account.get('id') === me) { menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); - } else if (account.getIn(['relationship', 'blocking'])) { - menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); - } else if (account.getIn(['relationship', 'following'])) { - menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); } else { - menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); - } + if (account.getIn(['relationship', 'muting'])) { + menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); + } else { + menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute }); + } + + if (account.getIn(['relationship', 'blocking'])) { + menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); + } else { + menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); + } - if (account.get('id') !== me) { menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); } @@ -70,12 +74,6 @@ const ActionBar = React.createClass({ extraInfo = *; } - if (account.getIn(['relationship', 'muting'])) { - menu.push({ text: intl.formatMessage(messages.unmute), action: this.props.onMute }); - } else { - menu.push({ text: intl.formatMessage(messages.mute), action: this.props.onMute }); - } - return (
diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx index f436a180b..99a10562e 100644 --- a/app/assets/javascripts/components/features/account_timeline/components/header.jsx +++ b/app/assets/javascripts/components/features/account_timeline/components/header.jsx @@ -15,8 +15,8 @@ const Header = React.createClass({ onFollow: React.PropTypes.func.isRequired, onBlock: React.PropTypes.func.isRequired, onMention: React.PropTypes.func.isRequired, - onReport: React.PropTypes.func.isRequired - onMute: React.PropTypes.func.isRequired, + onReport: React.PropTypes.func.isRequired, + onMute: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx index 6419ff08a..3a454a5fb 100644 --- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx @@ -36,7 +36,7 @@ const EmojiPickerDropdown = React.createClass({ return ( - + diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index 6472ac6a0..c67d05423 100644 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ b/app/assets/javascripts/components/reducers/timelines.jsx @@ -22,7 +22,8 @@ import { ACCOUNT_TIMELINE_EXPAND_REQUEST, ACCOUNT_TIMELINE_EXPAND_SUCCESS, ACCOUNT_TIMELINE_EXPAND_FAIL, - ACCOUNT_BLOCK_SUCCESS + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS @@ -295,6 +296,7 @@ export default function timelines(state = initialState, action) { case ACCOUNT_TIMELINE_EXPAND_SUCCESS: return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: return filterTimelines(state, action.relationship, action.statuses); case TIMELINE_SCROLL_TOP: return updateTop(state, action.timeline, action.top); diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index d52260713..3a26c5c05 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -22,8 +22,18 @@ class FeedManager end def push(timeline_type, account, status) - redis.zadd(key(timeline_type, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id) - trim(timeline_type, account.id) + timeline_key = key(timeline_type, account.id) + + if status.reblog? + # If the original status is within 40 statuses from top, do not re-insert it into the feed + rank = redis.zrevrank(timeline_key, status.reblog_of_id) + return if !rank.nil? && rank < 40 + redis.zadd(timeline_key, status.id, status.reblog_of_id) + else + redis.zadd(timeline_key, status.id, status.id) + trim(timeline_type, account.id) + end + broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status)) end @@ -85,47 +95,34 @@ class FeedManager end def filter_from_home?(status, receiver) - should_filter = receiver.muting?(status.account_id) # Filter if I'm muting this person + return true if receiver.muting?(status.account) + + should_filter = false - if status.reply? && status.in_reply_to_id.nil? # Filter out replies to nobody + if status.reply? && status.in_reply_to_id.nil? should_filter = true - elsif status.reply? && !status.in_reply_to_account_id.nil? # If it's a reply - should_filter = !receiver.following?(status.in_reply_to_account) # filter if I'm not following the person it's a reply to + elsif status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply + should_filter = !receiver.following?(status.in_reply_to_account) # 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 - elsif status.reblog? # If it's a reblog - should_filter = receiver.blocking?(status.reblog.account) # filter if I'm blocking the reblogged person - should_filter ||= receiver.muting?(status.reblog.account) # or if I'm muting the reblogged person + elsif status.reblog? # Filter out a reblog + should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person + should_filter ||= receiver.muting?(status.reblog.account) # or muting that person end - should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # Filter if it mentions someone I blocked + should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # or if it mentions someone I blocked + should_filter end def filter_from_mentions?(status, receiver) - should_filter = receiver.id == status.account_id # Filter out if I'm mentioning myself + should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked should_filter ||= (status.account.silenced? && !receiver.following?(status.account)) # of if the account is silenced and I'm not following them - if status.reply? && !status.in_reply_to_account_id.nil? - should_filter ||= receiver.blocking?(status.in_reply_to_account) # or if it's a reply to a user I blocked - end - - should_filter - end - - def filter_from_public?(status, receiver) - should_filter = receiver.blocking?(status.account) # Filter out if I'm blocking that account - should_filter ||= receiver.muting?(status.account_id) # or if I'm muting this person - should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked - if status.reply? && !status.in_reply_to_account_id.nil? # or it's a reply - should_filter ||= receiver.blocking?(status.in_reply_to_account) # to somebody I've blocked - should_filter ||= receiver.muting?(status.in_reply_to_account) # or to somebody I'm muting - elsif status.reblog? # or if it's a reblog - should_filter ||= receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person - should_filter ||= receiver.muting?(status.reblog.account) # or if I'm muting the reblogged person + should_filter ||= receiver.blocking?(status.in_reply_to_account) # to a user I blocked end should_filter diff --git a/app/models/mute.rb b/app/models/mute.rb index 505196453..a5b334c85 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -2,31 +2,10 @@ class Mute < ApplicationRecord include Paginable - include Streamable belongs_to :account belongs_to :target_account, class_name: 'Account' validates :account, :target_account, presence: true validates :account_id, uniqueness: { scope: :target_account_id } - - def verb - destroyed? ? :unmute : :mute - end - - def target - target_account - end - - def object_type - :person - end - - def hidden? - true - end - - def title - destroyed? ? "#{account.acct} is no longer muting #{target_account.acct}" : "#{account.acct} muted #{target_account.acct}" - end end diff --git a/app/models/status.rb b/app/models/status.rb index b3424f36d..e5e740360 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -103,10 +103,7 @@ class Status < ApplicationRecord class << self def as_home_timeline(account) - muted = Mute.where(account: account).pluck(:target_account_id) - query = where(account: [account] + account.following) - query = query.where('statuses.account_id NOT IN (?)', muted) unless muted.empty? - query + where(account: [account] + account.following) end def as_public_timeline(account = nil, local_only = false) @@ -171,10 +168,8 @@ class Status < ApplicationRecord private def filter_timeline(query, account) - blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id) - muted = Mute.where(account: account).pluck(:target_account_id) - query = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty? # Only give us statuses from people we haven't blocked - query = query.where('statuses.account_id NOT IN (?)', muted) unless muted.empty? # and out of those, only people we haven't muted + blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id) + Mute.where(account: account).pluck(:target_account_id) + query = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty? # Only give us statuses from people we haven't blocked, or muted, or that have blocked us query = query.where('accounts.silenced = TRUE') if account.silenced? # and if we're hellbanned, only people who are also hellbanned query end diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb index ed268b7c5..6aeea358f 100644 --- a/app/services/unmute_service.rb +++ b/app/services/unmute_service.rb @@ -5,5 +5,7 @@ class UnmuteService < BaseService return unless account.muting?(target_account) account.unmute!(target_account) + + MergeWorker.perform_async(target_account.id, account.id) if account.following?(target_account) end end diff --git a/streaming/index.js b/streaming/index.js index 125b35bb4..0f838e411 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -164,7 +164,7 @@ const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false) const unpackedPayload = JSON.parse(payload) const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)).concat(unpackedPayload.reblog ? [unpackedPayload.reblog.account.id] : []) - client.query(`SELECT target_account_id FROM blocks WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)})`, [req.accountId].concat(targetAccountIds), (err, result) => { + client.query(`SELECT target_account_id FROM blocks WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)}) UNION SELECT target_account_id FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)})`, [req.accountId].concat(targetAccountIds), (err, result) => { done() if (err) { -- cgit From a5daa806f2e4f824925aa1558202458bd3af7a0a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 13 Mar 2017 16:34:15 +0100 Subject: Fix casuality of processing remote mentions such that notifications about them would be processed only after the entire status is processed --- app/services/process_feed_service.rb | 20 +++++++++++++++----- app/services/process_mentions_service.rb | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'app/services') diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index 5d952df6f..69911abc5 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -61,12 +61,25 @@ class ProcessFeedService < BaseService status.save! - NotifyService.new.call(status.reblog.account, status) if status.reblog? && status.reblog.account.local? + notify_about_mentions!(status) unless status.reblog? + notify_about_reblog!(status) if status.reblog? && status.reblog.account.local? Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution" DistributionWorker.perform_async(status.id) status end + def notify_about_mentions!(status) + status.mentions.includes(:account).each do |mention| + mentioned_account = mention.account + next unless mentioned_account.local? + NotifyService.new.call(mentioned_account, mention) + end + end + + def notify_about_reblog!(status) + NotifyService.new.call(status.reblog.account, status) + end + def delete_status Rails.logger.debug "Deleting remote status #{id}" status = Status.find_by(uri: id) @@ -159,10 +172,7 @@ class ProcessFeedService < BaseService next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id) - mention = mentioned_account.mentions.where(status: parent).first_or_create(status: parent) - - # Notify local user - NotifyService.new.call(mentioned_account, mention) if mentioned_account.local? + mentioned_account.mentions.where(status: parent).first_or_create(status: parent) # So we can skip duplicate mentions processed_account_ids << mentioned_account.id diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index d3d3af8af..aa0a4d71b 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -27,7 +27,7 @@ class ProcessMentionsService < BaseService mentioned_account.mentions.where(status: status).first_or_create(status: status) end - status.mentions.each do |mention| + status.mentions.includes(:account).each do |mention| mentioned_account = mention.account if mentioned_account.local? -- cgit