From 0deb9fa6b9b8820fcb0a9ebd221178a8ec82490a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Jun 2018 02:18:18 +0200 Subject: Remove trending hashtags (#7711) * Delete trends_controller.rb * Update routes.rb * Update trending_tags.rb * Update index.js * Update index.js * Update search_results.js * Update async-components.js * Update index.js * Delete trends.js * Delete trends.js * Delete trends_container.js * Delete trends.js * Update search_results.js * Update search_results_container.js --- app/models/trending_tags.rb | 22 ---------------------- 1 file changed, 22 deletions(-) (limited to 'app/models') diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 287de2a8a..c3641d7fd 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class TrendingTags - KEY = 'trending_tags' EXPIRE_HISTORY_AFTER = 7.days.seconds - THRESHOLD = 5 class << self def record_use!(tag, account, at_time = Time.now.utc) @@ -11,30 +9,10 @@ class TrendingTags increment_historical_use!(tag.id, at_time) increment_unique_use!(tag.id, account.id, at_time) - increment_vote!(tag.id, at_time) - end - - def get(limit) - tag_ids = redis.zrevrange(KEY, 0, limit).map(&:to_i) - tags = Tag.where(id: tag_ids).to_a.map { |tag| [tag.id, tag] }.to_h - tag_ids.map { |tag_id| tags[tag_id] }.compact end private - def increment_vote!(tag_id, at_time) - expected = redis.pfcount("activity:tags:#{tag_id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts").to_f - expected = 1.0 if expected.zero? - observed = redis.pfcount("activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}:accounts").to_f - - if expected > observed || observed < THRESHOLD - redis.zrem(KEY, tag_id.to_s) - else - score = ((observed - expected)**2) / expected - redis.zadd(KEY, score, tag_id.to_s) - end - end - def increment_historical_use!(tag_id, at_time) key = "activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}" redis.incrby(key, 1) -- cgit From 5bf500338478f819a65d25636a0af61a482972d3 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 4 Jun 2018 11:46:14 +0900 Subject: Do not mark remote status sensitive even if spoiler text is present (#7395) Old statuses and statuses from Pawoo, which runs a modified version of Mastodon, may not have been marked sensitive even if spoiler text is present. Such statuses are still not marked sensitve if they are local or arrived before version upgrade. Marking recently fetched remote status sensitive contradicts the behavior. Considering what people expected when they authored such statuses, this change removes the sensitivity enforcement. --- app/models/status.rb | 5 ----- app/services/post_status_service.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'app/models') diff --git a/app/models/status.rb b/app/models/status.rb index 08ec36f38..b256069f7 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -187,7 +187,6 @@ class Status < ApplicationRecord before_validation :set_reblog before_validation :set_visibility before_validation :set_conversation - before_validation :set_sensitivity before_validation :set_local class << self @@ -368,10 +367,6 @@ class Status < ApplicationRecord self.sensitive = false if sensitive.nil? end - def set_sensitivity - self.sensitive = sensitive || spoiler_text.present? - end - def set_conversation self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 7a3576e2d..d072e581d 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -28,7 +28,7 @@ class PostStatusService < BaseService status = account.statuses.create!(text: text, media_attachments: media || [], thread: in_reply_to, - sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]), + sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]) || options[:spoiler_text].present?, spoiler_text: options[:spoiler_text] || '', visibility: options[:visibility] || account.user&.setting_default_privacy, language: language_from_option(options[:language]) || LanguageDetector.instance.detect(text, account), -- cgit From dc73241bd9a529edc5573e10b5899cb47ca479ae Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 5 Jun 2018 01:58:36 +0900 Subject: Detect extname from Content-Type (#7733) --- app/models/concerns/remotable.rb | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'app/models') diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index c17f04776..dc2e12b67 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -24,14 +24,16 @@ module Remotable Request.new(:get, url).perform do |response| next if response.code != 200 - matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/) - filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] + content_type = parse_content_type(response.headers['content-type']) + extname = detect_extname_from_content_type(content_type) + + if extname.nil? + matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/) + filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] + extname = filename.nil? ? '' : File.extname(filename) + end + basename = SecureRandom.hex(8) - extname = if filename.nil? - '' - else - File.extname(filename) - end send("#{attachment_name}=", StringIO.new(response.body_with_limit(limit))) send("#{attachment_name}_file_name=", basename + extname) @@ -57,4 +59,22 @@ module Remotable end end end + + private + + def detect_extname_from_content_type(content_type) + return if content_type.nil? + + type = MIME::Types[content_type].first + + return if type.nil? + + type.extensions.first + end + + def parse_content_type(content_type) + return if content_type.nil? + + content_type.split(/\s*;\s*/).first + end end -- cgit From 5fb013878fa7cb82c887d5215b2d6c8294db6b21 Mon Sep 17 00:00:00 2001 From: nightpool Date: Mon, 4 Jun 2018 16:20:12 -0400 Subject: Fix context performance by partially reverting #7083 (#7734) * Fix context performance by partially reverting #7083 * Fix code style issue * fix off-by-1 error in thread limits * code style fix --- app/models/concerns/status_threading_concern.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index 1ba8fc693..fa441469c 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -51,12 +51,16 @@ module StatusThreadingConcern end def descendant_statuses(limit, max_child_id, since_child_id, depth) - Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) + # use limit + 1 and depth + 1 because 'self' is included + depth += 1 if depth.present? + limit += 1 if limit.present? + + descendants_with_self = Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) WITH RECURSIVE search_tree(id, path) AS ( SELECT id, ARRAY[id] FROM statuses - WHERE in_reply_to_id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) + WHERE id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree @@ -68,6 +72,8 @@ module StatusThreadingConcern ORDER BY path LIMIT :limit SQL + + descendants_with_self - [self] end def find_statuses_from_tree_path(ids, account) -- cgit From bd0791d800902cdbdf6666e65d201df3ec22789c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 5 Jun 2018 00:17:38 +0200 Subject: Add redraft function (#7735) * Add redraft function Fix #7010 * Add explicit confirmation * Add explicit confirmation message --- app/javascript/mastodon/actions/statuses.js | 17 +++++++++++- .../mastodon/components/status_action_bar.js | 6 +++++ .../mastodon/containers/status_container.js | 12 +++++---- .../features/status/components/action_bar.js | 6 +++++ app/javascript/mastodon/features/status/index.js | 12 +++++---- app/javascript/mastodon/reducers/compose.js | 31 ++++++++++++++++++++++ app/models/status.rb | 2 +- 7 files changed, 74 insertions(+), 12 deletions(-) (limited to 'app/models') diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 849cb4f5a..3e1e5f270 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -29,6 +29,8 @@ export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; export const STATUS_REVEAL = 'STATUS_REVEAL'; export const STATUS_HIDE = 'STATUS_HIDE'; +export const REDRAFT = 'REDRAFT'; + export function fetchStatusRequest(id, skipLoading) { return { type: STATUS_FETCH_REQUEST, @@ -131,14 +133,27 @@ export function fetchStatusFail(id, error, skipLoading) { }; }; -export function deleteStatus(id) { +export function redraft(status) { + return { + type: REDRAFT, + status, + }; +}; + +export function deleteStatus(id, withRedraft = false) { return (dispatch, getState) => { + const status = getState().getIn(['statuses', id]); + dispatch(deleteStatusRequest(id)); api(getState).delete(`/api/v1/statuses/${id}`).then(() => { evictStatus(id); dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); + + if (withRedraft) { + dispatch(redraft(status)); + } }).catch(error => { dispatch(deleteStatusFail(id, error)); }); diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index d605dbc8a..0ae21e3f0 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -9,6 +9,7 @@ import { me } from '../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, @@ -88,6 +89,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onDelete(this.props.status); } + handleRedraftClick = () => { + this.props.onDelete(this.props.status, true); + } + handlePinClick = () => { this.props.onPin(this.props.status); } @@ -159,6 +164,7 @@ export default class StatusActionBar extends ImmutablePureComponent { } menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); + menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index f22509edf..3e7b5215b 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -33,6 +33,8 @@ import { showAlertForError } from '../actions/alerts'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, + redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, + redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, }); @@ -91,14 +93,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ })); }, - onDelete (status) { + onDelete (status, withRedraft = false) { if (!deleteModal) { - dispatch(deleteStatus(status.get('id'))); + dispatch(deleteStatus(status.get('id'), withRedraft)); } else { dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'))), + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)), })); } }, diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 9162e1326..541499668 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from '../../../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, @@ -67,6 +68,10 @@ export default class ActionBar extends React.PureComponent { this.props.onDelete(this.props.status); } + handleRedraftClick = () => { + this.props.onDelete(this.props.status, true); + } + handleDirectClick = () => { this.props.onDirect(this.props.status.get('account'), this.context.router.history); } @@ -132,6 +137,7 @@ export default class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); menu.push(null); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); + menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 96144b19e..ca792043f 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -47,6 +47,8 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, + redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, + redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, @@ -172,16 +174,16 @@ export default class Status extends ImmutablePureComponent { } } - handleDeleteClick = (status) => { + handleDeleteClick = (status, withRedraft = false) => { const { dispatch, intl } = this.props; if (!deleteModal) { - dispatch(deleteStatus(status.get('id'))); + dispatch(deleteStatus(status.get('id'), withRedraft)); } else { dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'))), + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)), })); } } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 62461d1a7..e9f6a4902 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -32,6 +32,7 @@ import { } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STORE_HYDRATE } from '../actions/store'; +import { REDRAFT } from '../actions/statuses'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import uuid from '../uuid'; import { me } from '../initial_state'; @@ -170,6 +171,18 @@ const hydrate = (state, hydratedState) => { return state; }; +const domParser = new DOMParser(); + +const htmlToText = status => { + const fragment = domParser.parseFromString(status.get('content'), 'text/html').documentElement; + + status.get('mentions').forEach(mention => { + fragment.querySelector(`a[href="${mention.get('url')}"]`).textContent = `@${mention.get('acct')}`; + }); + + return fragment.textContent; +}; + export default function compose(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: @@ -301,6 +314,24 @@ export default function compose(state = initialState, action) { return item; })); + case REDRAFT: + return state.withMutations(map => { + map.set('text', htmlToText(action.status)); + map.set('in_reply_to', action.status.get('in_reply_to_id')); + map.set('privacy', action.status.get('visibility')); + map.set('media_attachments', action.status.get('media_attachments')); + map.set('focusDate', new Date()); + map.set('caretPosition', null); + map.set('idempotencyKey', uuid()); + + if (action.status.get('spoiler_text').length > 0) { + map.set('spoiler', true); + map.set('spoiler_text', action.status.get('spoiler_text')); + } else { + map.set('spoiler', false); + map.set('spoiler_text', ''); + } + }); default: return state; } diff --git a/app/models/status.rb b/app/models/status.rb index b256069f7..5189e173d 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -52,7 +52,7 @@ class Status < ApplicationRecord has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread has_many :mentions, dependent: :destroy - has_many :media_attachments, dependent: :destroy + has_many :media_attachments, dependent: :nullify has_and_belongs_to_many :tags has_and_belongs_to_many :preview_cards -- cgit From a640c322c19013647cbd19ac907dd18b57c2b1b8 Mon Sep 17 00:00:00 2001 From: David Yip Date: Tue, 5 Jun 2018 02:49:28 -0500 Subject: Escape metacharacters in non-whole-word keyword mutes. Fixes #533. Also addresses #463. --- app/models/glitch/keyword_mute.rb | 2 +- spec/models/glitch/keyword_mute_spec.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/glitch/keyword_mute.rb b/app/models/glitch/keyword_mute.rb index e7cbbe617..7d0002afd 100644 --- a/app/models/glitch/keyword_mute.rb +++ b/app/models/glitch/keyword_mute.rb @@ -50,7 +50,7 @@ class Glitch::KeywordMute < ApplicationRecord end def matches?(str) - str =~ (whole_word ? boundary_regex_for_keyword : /#{keyword}/i) + str =~ (whole_word ? boundary_regex_for_keyword : /#{Regexp.escape(keyword)}/i) end end diff --git a/spec/models/glitch/keyword_mute_spec.rb b/spec/models/glitch/keyword_mute_spec.rb index 79225e3b9..443832ac7 100644 --- a/spec/models/glitch/keyword_mute_spec.rb +++ b/spec/models/glitch/keyword_mute_spec.rb @@ -79,12 +79,18 @@ RSpec.describe Glitch::KeywordMute, type: :model do expect(matcher.matches?('(hot take)')).to be_truthy end - it 'escapes metacharacters in keywords' do + it 'escapes metacharacters in whole-word keywords' do Glitch::KeywordMute.create!(account: alice, keyword: '(hot take)') expect(matcher.matches?('(hot take)')).to be_truthy end + it 'escapes metacharacters in non-whole-word keywords' do + Glitch::KeywordMute.create!(account: alice, keyword: '(-', whole_word: false) + + expect(matcher.matches?('bad (-)')).to be_truthy + end + it 'uses case-folding rules appropriate for more than just English' do Glitch::KeywordMute.create!(account: alice, keyword: 'großeltern') -- cgit From b7b331ad0dd061b55435265009af51cdeef29fdc Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 7 Jun 2018 03:49:39 +0900 Subject: Add missing dot for remote image (#7751) --- app/models/concerns/remotable.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app/models') diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index dc2e12b67..90c6894e0 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -69,7 +69,11 @@ module Remotable return if type.nil? - type.extensions.first + extname = type.extensions.first + + return if extname.nil? + + ".#{extname}" end def parse_content_type(content_type) -- cgit From 683707839f6e61df95a4958675883c8f80aa6a84 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Thu, 7 Jun 2018 03:50:07 +0900 Subject: Fix fetch of remote image with multiple Content-Type headers (#7749) --- app/models/concerns/remotable.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'app/models') diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 90c6894e0..c17f19a60 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -24,13 +24,14 @@ module Remotable Request.new(:get, url).perform do |response| next if response.code != 200 - content_type = parse_content_type(response.headers['content-type']) + content_type = parse_content_type(response.headers.get('content-type').last) extname = detect_extname_from_content_type(content_type) if extname.nil? - matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/) - filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] - extname = filename.nil? ? '' : File.extname(filename) + disposition = response.headers.get('content-disposition').last + matches = disposition&.match(/filename="([^"]*)"/) + filename = matches.nil? ? parsed_url.path.split('/').last : matches[1] + extname = filename.nil? ? '' : File.extname(filename) end basename = SecureRandom.hex(8) -- cgit From 59b42188a73b7c7c09d91b1111a31d72f2f6e9b7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 6 Jun 2018 21:13:30 +0200 Subject: Filter out blocked/muted people from profile timelines (#7747) Fix #7741 --- app/models/status.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app/models') diff --git a/app/models/status.rb b/app/models/status.rb index 5189e173d..7fa069083 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -305,7 +305,11 @@ class Status < ApplicationRecord # non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in. visibility.push(:private) if account.following?(target_account) - where(visibility: visibility).or(where(id: account.mentions.select(:status_id))) + scope = left_outer_joins(:reblog) + + scope.where(visibility: visibility) + .or(scope.where(id: account.mentions.select(:status_id))) + .merge(scope.where(reblog_of_id: nil).or(scope.where.not(reblogs_statuses: { account_id: account.excluded_from_timeline_account_ids }))) end end -- cgit