From ffaa814bbe202de7e9f594698f6b1297fd950255 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 23 Aug 2018 19:30:09 +0200 Subject: [PATCH 001/177] Use backend from glitch-soc for instance-only toots --- app/controllers/accounts_controller.rb | 2 +- app/controllers/stream_entries_controller.rb | 4 +- app/models/status.rb | 6 ++- app/models/stream_entry.rb | 2 +- app/policies/status_policy.rb | 6 +++ app/services/post_status_service.rb | 9 ++-- app/services/reblog_service.rb | 7 ++- ...0213213_add_local_only_flag_to_statuses.rb | 5 ++ db/schema.rb | 1 + spec/models/status_spec.rb | 47 +++++++++++++++++++ spec/policies/status_policy_spec.rb | 12 +++++ 11 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20171210213213_add_local_only_flag_to_statuses.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index f788a907893059..52753c1c310d87 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -35,7 +35,7 @@ def show end format.rss do - @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status) + @statuses = cache_collection(default_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE), Status) render xml: RSS::AccountSerializer.render(@account, @statuses) end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 8568b151cfaa4e..24435cf8aa2464 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -19,7 +19,7 @@ def show end format.atom do - unless @stream_entry.hidden? + unless @stream_entry.hidden? || @stream_entry.local_only? skip_session! expires_in 3.minutes, public: true end @@ -53,7 +53,7 @@ def set_stream_entry @type = @stream_entry.activity_type.downcase raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil? - authorize @stream_entry.activity, :show? if @stream_entry.hidden? + authorize @stream_entry.activity, :show? if @stream_entry.hidden? || @stream_entry.local_only? rescue Mastodon::NotPermittedError # Reraise in order to get a 404 raise ActiveRecord::RecordNotFound diff --git a/app/models/status.rb b/app/models/status.rb index 35655bff2377d8..90534bab82b598 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -21,6 +21,7 @@ # account_id :bigint(8) not null # application_id :bigint(8) # in_reply_to_account_id :bigint(8) +# local_only :boolean # class Status < ApplicationRecord @@ -73,6 +74,7 @@ class Status < ApplicationRecord scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } + scope :without_local_only, -> { where(local_only: [false, nil]) } scope :with_public_visibility, -> { where(visibility: :public) } scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) } @@ -336,7 +338,7 @@ def permitted_for(target_account, account) visibility = [:public, :unlisted] if account.nil? - where(visibility: visibility) + where(visibility: visibility).without_local_only elsif target_account.blocking?(account) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff @@ -379,7 +381,7 @@ def filter_timeline_for_account(query, account, local_only) end def filter_timeline_default(query) - query.excluding_silenced_accounts + query.without_local_only.excluding_silenced_accounts end def account_silencing_filter(account) diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index a2f273281b6df0..dd383eb816fab9 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -27,7 +27,7 @@ class StreamEntry < ApplicationRecord scope :recent, -> { reorder(id: :desc) } scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) } - delegate :target, :title, :content, :thread, + delegate :target, :title, :content, :thread, :local_only?, to: :status, allow_nil: true diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 6addc8a8a8ebe7..0961ec3e258c02 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -12,6 +12,8 @@ def index? end def show? + return false if local_only? && (current_account.nil? || !current_account.local?) + if direct? owned? || mention_exists? elsif private? @@ -84,4 +86,8 @@ def following_author? def author record.account end + + def local_only? + record.local_only? + end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 300eae547a0eac..52d49a69eed427 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -39,9 +39,12 @@ def call(account, text, in_reply_to = nil, **options) LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text? DistributionWorker.perform_async(status.id) - Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(status.id) - ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? + + unless status.local_only? + Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) + ActivityPub::DistributionWorker.perform_async(status.id) + ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? + end if options[:idempotency].present? redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id) diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 33ddef8b88d948..03db27406adb74 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -20,8 +20,11 @@ def call(account, reblogged_status) reblog = account.statuses.create!(reblog: reblogged_status, text: '') DistributionWorker.perform_async(reblog.id) - Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(reblog.id) + + unless reblogged_status.local_only? + Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) + ActivityPub::DistributionWorker.perform_async(reblog.id) + end create_notification(reblog) bump_potential_friendship(account, reblog) diff --git a/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb b/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb new file mode 100644 index 00000000000000..af1e29d6a141c9 --- /dev/null +++ b/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb @@ -0,0 +1,5 @@ +class AddLocalOnlyFlagToStatuses < ActiveRecord::Migration[5.1] + def change + add_column :statuses, :local_only, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index f3b06f7c0c6f4e..10633c1b8a49ac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -492,6 +492,7 @@ t.bigint "account_id", null: false t.bigint "application_id" t.bigint "in_reply_to_account_id" + t.boolean "local_only" t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc } t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id" diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 9d8670129d40eb..cf48348f5b9814 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -574,6 +574,32 @@ end end end + + context 'with local-only statuses' do + let(:status) { Fabricate(:status, local_only: true) } + + subject { Status.as_public_timeline(viewer) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'excludes local-only statuses' do + expect(subject).to_not include(status) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'includes local-only statuses' do + expect(subject).to include(status) + end + end + + # TODO: What happens if the viewer is remote? + # Can the viewer be remote? + # What prevents the viewer from being remote? + end end describe '.as_tag_timeline' do @@ -595,6 +621,27 @@ results = Status.as_tag_timeline(tag) expect(results).to include(status) end + + context 'on a local-only status' do + let(:tag) { Fabricate(:tag) } + let(:status) { Fabricate(:status, local_only: true, tags: [tag]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'filters the local-only status out of the result set' do + expect(Status.as_tag_timeline(tag, viewer)).not_to include(status) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer', domain: nil) } + + it 'keeps the local-only status in the result set' do + expect(Status.as_tag_timeline(tag, viewer)).to include(status) + end + end + end end describe '.permitted_for' do diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index bacb8fd9e3fe9e..837fa9cee88aaf 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -71,6 +71,18 @@ expect(subject).to_not permit(viewer, status) end + + it 'denies access when local-only and the viewer is not logged in' do + allow(status).to receive(:local_only?) { true } + + expect(subject).to_not permit(nil, status) + end + + it 'denies access when local-only and the viewer is from another domain' do + viewer = Fabricate(:account, domain: 'remote-domain') + allow(status).to receive(:local_only?) { true } + expect(subject).to_not permit(viewer, status) + end end permissions :reblog? do From 4969ea4f2cb5e48b66797bded55c4256601396f1 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 23 Aug 2018 20:35:29 +0200 Subject: [PATCH 002/177] Base frontend on privacy dropdown --- app/javascript/mastodon/actions/compose.js | 9 + .../compose/components/compose_form.js | 3 + .../compose/components/federation_dropdown.js | 250 ++++++++++++++++++ .../containers/compose_form_container.js | 1 + .../federation_dropdown_container.js | 24 ++ app/javascript/mastodon/reducers/compose.js | 11 + 6 files changed, 298 insertions(+) create mode 100644 app/javascript/mastodon/features/compose/components/federation_dropdown.js create mode 100644 app/javascript/mastodon/features/compose/containers/federation_dropdown_container.js diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 6d975cd1e4fa90..d992f365d9611b 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -40,6 +40,7 @@ export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; +export const COMPOSE_FEDERATION_CHANGE = 'COMPOSE_FEDERATION_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; @@ -125,6 +126,7 @@ export function submitCompose() { sensitive: getState().getIn(['compose', 'sensitive']), spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), visibility: getState().getIn(['compose', 'privacy']), + local_only: !getState().getIn(['compose', 'federation']), }, { headers: { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), @@ -445,6 +447,13 @@ export function changeComposeVisibility(value) { }; }; +export function changeComposeFederation(value) { + return { + type: COMPOSE_FEDERATION_CHANGE, + value, + }; +}; + export function insertEmojiCompose(position, emoji, needsSpace) { return { type: COMPOSE_EMOJI_INSERT, diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 6eb01123e46da9..ec49a994311e46 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -9,6 +9,7 @@ import UploadButtonContainer from '../containers/upload_button_container'; import { defineMessages, injectIntl } from 'react-intl'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; +import FederationDropdownContainer from '../containers/federation_dropdown_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import UploadFormContainer from '../containers/upload_form_container'; @@ -37,6 +38,7 @@ export default class ComposeForm extends ImmutablePureComponent { suggestions: ImmutablePropTypes.list, spoiler: PropTypes.bool, privacy: PropTypes.string, + federation: PropTypes.bool, spoiler_text: PropTypes.string, focusDate: PropTypes.instanceOf(Date), caretPosition: PropTypes.number, @@ -207,6 +209,7 @@ export default class ComposeForm extends ImmutablePureComponent { +
diff --git a/app/javascript/mastodon/features/compose/components/federation_dropdown.js b/app/javascript/mastodon/features/compose/components/federation_dropdown.js new file mode 100644 index 00000000000000..6271c14cc66f47 --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/federation_dropdown.js @@ -0,0 +1,250 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; +import IconButton from '../../../components/icon_button'; +import Overlay from 'react-overlays/lib/Overlay'; +import Motion from '../../ui/util/optional_motion'; +import spring from 'react-motion/lib/spring'; +import detectPassiveEvents from 'detect-passive-events'; +import classNames from 'classnames'; + +const messages = defineMessages({ + federate_short: { id: 'federation.federated.short', defaultMessage: 'Federated' }, + federate_long: { id: 'federation.federated.long', defaultMessage: 'Allow toot to reach other instances' }, + local_only_short: { id: 'federation.local_only.short', defaultMessage: 'Local-only' }, + local_only_long: { id: 'federation.local_only.long', defaultMessage: 'Restrict this toot only to my instance' }, + change_federation: { id: 'federation.change', defaultMessage: 'Adjust status federation' }, +}); + +const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; + +class FederationDropdownMenu extends React.PureComponent { + + static propTypes = { + style: PropTypes.object, + items: PropTypes.array.isRequired, + value: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + }; + + state = { + mounted: false, + }; + + handleDocumentClick = e => { + if (this.node && !this.node.contains(e.target)) { + this.props.onClose(); + } + } + + handleKeyDown = e => { + const { items } = this.props; + const value = Boolean(e.currentTarget.getAttribute('data-index')); + const index = items.findIndex(item => { + return (item.value === value); + }); + let element; + + switch(e.key) { + case 'Escape': + this.props.onClose(); + break; + case 'Enter': + this.handleClick(e); + break; + case 'ArrowDown': + element = this.node.childNodes[index + 1]; + if (element) { + element.focus(); + this.props.onChange(Boolean(element.getAttribute('data-index'))); + } + break; + case 'ArrowUp': + element = this.node.childNodes[index - 1]; + if (element) { + element.focus(); + this.props.onChange(Boolean(element.getAttribute('data-index'))); + } + break; + case 'Home': + element = this.node.firstChild; + if (element) { + element.focus(); + this.props.onChange(Boolean(element.getAttribute('data-index'))); + } + break; + case 'End': + element = this.node.lastChild; + if (element) { + element.focus(); + this.props.onChange(Boolean(element.getAttribute('data-index'))); + } + break; + } + } + + handleClick = e => { + const value = Boolean(e.currentTarget.getAttribute('data-index')); + + e.preventDefault(); + + this.props.onClose(); + this.props.onChange(value); + } + + componentDidMount () { + document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + if (this.focusedItem) this.focusedItem.focus(); + this.setState({ mounted: true }); + } + + componentWillUnmount () { + document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); + } + + setRef = c => { + this.node = c; + } + + setFocusRef = c => { + this.focusedItem = c; + } + + render () { + const { mounted } = this.state; + const { style, items, value } = this.props; + + return ( + + {({ opacity, scaleX, scaleY }) => ( + // It should not be transformed when mounting because the resulting + // size will be used to determine the coordinate of the menu by + // react-overlays +
+ {items.map(item => ( +
+
+ +
+ +
+ {item.text} + {item.meta} +
+
+ ))} +
+ )} +
+ ); + } + +} + +@injectIntl +export default class FederationDropdown extends React.PureComponent { + + static propTypes = { + isUserTouching: PropTypes.func, + isModalOpen: PropTypes.bool.isRequired, + onModalOpen: PropTypes.func, + onModalClose: PropTypes.func, + value: PropTypes.bool.isRequired, + onChange: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + open: false, + placement: null, + }; + + handleToggle = ({ target }) => { + if (this.props.isUserTouching()) { + if (this.state.open) { + this.props.onModalClose(); + } else { + this.props.onModalOpen({ + actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })), + onClick: this.handleModalActionClick, + }); + } + } else { + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); + this.setState({ open: !this.state.open }); + } + } + + handleModalActionClick = (e) => { + e.preventDefault(); + + const { value } = this.options[Boolean(e.currentTarget.getAttribute('data-index'))]; + + this.props.onModalClose(); + this.props.onChange(value); + } + + handleKeyDown = e => { + switch(e.key) { + case 'Escape': + this.handleClose(); + break; + } + } + + handleClose = () => { + this.setState({ open: false }); + } + + handleChange = value => { + this.props.onChange(value); + } + + componentWillMount () { + const { intl: { formatMessage } } = this.props; + + this.options = [ + { icon: 'link', value: true, text: formatMessage(messages.federate_short), meta: formatMessage(messages.federate_long) }, + { icon: 'chain-broken', value: false, text: formatMessage(messages.local_only_short), meta: formatMessage(messages.local_only_long) }, + ]; + } + + render () { + const { value, intl } = this.props; + const { open, placement } = this.state; + + const valueOption = this.options.find(item => item.value === value); + + return ( +
+
+ +
+ + + + +
+ ); + } + +} diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 3822dd711fe32e..cec15dfa3b2102 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -18,6 +18,7 @@ const mapStateToProps = state => ({ spoiler: state.getIn(['compose', 'spoiler']), spoiler_text: state.getIn(['compose', 'spoiler_text']), privacy: state.getIn(['compose', 'privacy']), + federation: state.getIn(['compose', 'federation']), focusDate: state.getIn(['compose', 'focusDate']), caretPosition: state.getIn(['compose', 'caretPosition']), preselectDate: state.getIn(['compose', 'preselectDate']), diff --git a/app/javascript/mastodon/features/compose/containers/federation_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/federation_dropdown_container.js new file mode 100644 index 00000000000000..268f4da2dbb6dd --- /dev/null +++ b/app/javascript/mastodon/features/compose/containers/federation_dropdown_container.js @@ -0,0 +1,24 @@ +import { connect } from 'react-redux'; +import FederationDropdown from '../components/federation_dropdown'; +import { changeComposeFederation } from '../../../actions/compose'; +import { openModal, closeModal } from '../../../actions/modal'; +import { isUserTouching } from '../../../is_mobile'; + +const mapStateToProps = state => ({ + isModalOpen: state.get('modal').modalType === 'ACTIONS', + value: state.getIn(['compose', 'federation']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (value) { + dispatch(changeComposeFederation(value)); + }, + + isUserTouching, + onModalOpen: props => dispatch(openModal('ACTIONS', props)), + onModalClose: () => dispatch(closeModal()), + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(FederationDropdown); diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 67d55f66f0086d..127c99e5c1fdb4 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -23,6 +23,7 @@ import { COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_VISIBILITY_CHANGE, + COMPOSE_FEDERATION_CHANGE, COMPOSE_COMPOSING_CHANGE, COMPOSE_EMOJI_INSERT, COMPOSE_UPLOAD_CHANGE_REQUEST, @@ -44,6 +45,7 @@ const initialState = ImmutableMap({ spoiler: false, spoiler_text: '', privacy: null, + federation: null, text: '', focusDate: null, caretPosition: null, @@ -57,6 +59,7 @@ const initialState = ImmutableMap({ suggestion_token: null, suggestions: ImmutableList(), default_privacy: 'public', + default_federation: true, default_sensitive: false, resetFileKey: Math.floor((Math.random() * 0x10000)), idempotencyKey: null, @@ -81,6 +84,7 @@ function clearAll(state) { map.set('is_submitting', false); map.set('in_reply_to', null); map.set('privacy', state.get('default_privacy')); + map.set('federation', state.get('default_federation')); map.set('sensitive', false); map.update('media_attachments', list => list.clear()); map.set('idempotencyKey', uuid()); @@ -209,6 +213,10 @@ export default function compose(state = initialState, action) { return state .set('spoiler_text', action.text) .set('idempotencyKey', uuid()); + case COMPOSE_FEDERATION_CHANGE: + return state + .set('federation', action.value) + .set('idempotencyKey', uuid()); case COMPOSE_VISIBILITY_CHANGE: return state .set('privacy', action.value) @@ -224,6 +232,7 @@ export default function compose(state = initialState, action) { map.set('in_reply_to', action.status.get('id')); map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); + map.set('federation', !action.status.get('local_only')); map.set('focusDate', new Date()); map.set('caretPosition', null); map.set('preselectDate', new Date()); @@ -245,6 +254,7 @@ export default function compose(state = initialState, action) { map.set('spoiler', false); map.set('spoiler_text', ''); map.set('privacy', state.get('default_privacy')); + map.set('federation', state.get('default_federation')); map.set('idempotencyKey', uuid()); }); case COMPOSE_SUBMIT_REQUEST: @@ -313,6 +323,7 @@ export default function compose(state = initialState, action) { map.set('text', unescapeHTML(expandMentions(action.status))); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility')); + map.set('federation', !action.status.get('local_only')); map.set('media_attachments', action.status.get('media_attachments')); map.set('focusDate', new Date()); map.set('caretPosition', null); From f37ca30ea166ee6a9c0e2fdec58ede0e10a7c6b5 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Sat, 25 Aug 2018 13:27:58 +0200 Subject: [PATCH 003/177] Add backend support for local_only on status create Based off ThibG implementation on glitch #502 --- app/controllers/api/v1/statuses_controller.rb | 5 +++-- app/models/status.rb | 10 ++++++++++ app/serializers/rest/status_serializer.rb | 2 +- app/services/post_status_service.rb | 8 +++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 49a52f7a6c14a3..f377688d78f0ed 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -52,7 +52,8 @@ def create spoiler_text: status_params[:spoiler_text], visibility: status_params[:visibility], application: doorkeeper_token.application, - idempotency: request.headers['Idempotency-Key']) + idempotency: request.headers['Idempotency-Key'], + local_only: status_params[:local_only]) render json: @status, serializer: REST::StatusSerializer end @@ -77,7 +78,7 @@ def set_status end def status_params - params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: []) + params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, :local_only, media_ids: []) end def pagination_params(core_params) diff --git a/app/models/status.rb b/app/models/status.rb index 90534bab82b598..8eaa74a04bb02e 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -130,6 +130,10 @@ def local? attributes['local'] || uri.nil? end + def local_only? + local_only + end + def reblog? !reblog_of_id.nil? end @@ -222,6 +226,8 @@ def decrement_count!(key) around_create Mastodon::Snowflake::Callbacks + before_create :set_locality + before_validation :prepare_contents, if: :local? before_validation :set_reblog before_validation :set_visibility @@ -445,6 +451,10 @@ def set_local self.local = account.local? end + def set_locality + self.local_only = reblog.local_only if reblog? + end + def update_statistics return unless public_visibility? || unlisted_visibility? ActivityTracker.increment('activity:statuses:local') diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 61423f9615193d..fd3fc9a157f152 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitive, :spoiler_text, :visibility, :language, :uri, :content, :url, :replies_count, :reblogs_count, - :favourites_count + :favourites_count, :local_only attribute :favourited, if: :current_user? attribute :reblogged, if: :current_user? diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 52d49a69eed427..cdcb6b0879ef3c 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -31,7 +31,8 @@ def call(account, text, in_reply_to = nil, **options) spoiler_text: options[:spoiler_text] || '', visibility: options[:visibility] || account.user&.setting_default_privacy, language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account), - application: options[:application]) + application: options[:application], + local_only: local_only_option(options[:local_only], in_reply_to)) end process_hashtags_service.call(status) @@ -57,6 +58,11 @@ def call(account, text, in_reply_to = nil, **options) private + def local_only_option(local_only, in_reply_to) + return in_reply_to&.local_only? if local_only.nil? # XXX temporary, just until clients implement to avoid leaking local_only posts + local_only + end + def validate_media!(media_ids) return if media_ids.blank? || !media_ids.is_a?(Enumerable) From c963e1548d750dbb8ce0c46b1df268c43fcdec4d Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Fri, 24 Aug 2018 18:56:11 +0200 Subject: [PATCH 004/177] Add local_only indicator in the status action bar --- app/javascript/mastodon/components/status_action_bar.js | 5 +++++ app/javascript/styles/mastodon/components.scss | 1 + 2 files changed, 6 insertions(+) diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 6d44a4b4511630..4a89b54baefb2e 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -22,6 +22,7 @@ const messages = defineMessages({ reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost to original audience' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, + local_only: { id: 'status.local_only', defaultMessage: 'This post is only visible by other users of your instance' }, favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, open: { id: 'status.open', defaultMessage: 'Expand this status' }, report: { id: 'status.report', defaultMessage: 'Report @{name}' }, @@ -145,6 +146,7 @@ export default class StatusActionBar extends ImmutablePureComponent { const mutingConversation = status.get('muted'); const anonymousAccess = !me; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); + const federated = !status.get('local_only'); let menu = []; let reblogIcon = 'retweet'; @@ -212,6 +214,9 @@ export default class StatusActionBar extends ImmutablePureComponent {
+ { !federated && + + } ); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c2965a5d7e5dde..1a5a3910e49f86 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -959,6 +959,7 @@ .status__action-bar-dropdown { height: 23.15px; width: 23.15px; + margin-right: 18px; } .detailed-status__action-bar-dropdown { From b4ae56e82fe8ca4a8e7b1dfd19f84c0af34b69ce Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Fri, 24 Aug 2018 19:27:12 +0200 Subject: [PATCH 005/177] Add local_only indicator to detailed status --- .../features/status/components/detailed_status.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index b4bbda16152608..1893156f533e27 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -7,11 +7,16 @@ import StatusContent from '../../../components/status_content'; import MediaGallery from '../../../components/media_gallery'; import AttachmentList from '../../../components/attachment_list'; import { Link } from 'react-router-dom'; -import { FormattedDate, FormattedNumber } from 'react-intl'; +import { defineMessages, injectIntl, FormattedDate, FormattedNumber } from 'react-intl'; import CardContainer from '../containers/card_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from '../../video'; +const messages = defineMessages({ + local_only: { id: 'status.local_only', defaultMessage: 'This post is only visible by other users of your instance' }, +}); + +@injectIntl export default class DetailedStatus extends ImmutablePureComponent { static contextTypes = { @@ -44,10 +49,12 @@ export default class DetailedStatus extends ImmutablePureComponent { render () { const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; + const intl = this.props.intl; let media = ''; let applicationLink = ''; let reblogLink = ''; + let localOnly = ''; let reblogIcon = 'retweet'; if (status.get('media_attachments').size > 0) { @@ -104,6 +111,10 @@ export default class DetailedStatus extends ImmutablePureComponent { ); } + if(status.get('local_only')) { + localOnly = · ; + } + return ( ); From e1049a94aa4e1a8ed8ec02b25f5ce5bba4f4665f Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Sun, 26 Aug 2018 12:27:13 +0200 Subject: [PATCH 006/177] Normalize translations ran yarn build:development && i18n-tasks normalize && yarn manage:translations && i18n-tasks remove-unused --- app/javascript/mastodon/locales/ar.json | 6 +++ app/javascript/mastodon/locales/ast.json | 6 +++ app/javascript/mastodon/locales/bg.json | 6 +++ app/javascript/mastodon/locales/ca.json | 6 +++ app/javascript/mastodon/locales/co.json | 6 +++ app/javascript/mastodon/locales/cs.json | 6 +++ app/javascript/mastodon/locales/cy.json | 6 +++ app/javascript/mastodon/locales/da.json | 6 +++ app/javascript/mastodon/locales/de.json | 6 +++ .../mastodon/locales/defaultMessages.json | 38 +++++++++++++++++++ app/javascript/mastodon/locales/el.json | 6 +++ app/javascript/mastodon/locales/en.json | 6 +++ app/javascript/mastodon/locales/eo.json | 6 +++ app/javascript/mastodon/locales/es.json | 6 +++ app/javascript/mastodon/locales/eu.json | 6 +++ app/javascript/mastodon/locales/fa.json | 6 +++ app/javascript/mastodon/locales/fi.json | 6 +++ app/javascript/mastodon/locales/fr.json | 6 +++ app/javascript/mastodon/locales/gl.json | 6 +++ app/javascript/mastodon/locales/he.json | 6 +++ app/javascript/mastodon/locales/hr.json | 6 +++ app/javascript/mastodon/locales/hu.json | 6 +++ app/javascript/mastodon/locales/hy.json | 6 +++ app/javascript/mastodon/locales/id.json | 6 +++ app/javascript/mastodon/locales/io.json | 6 +++ app/javascript/mastodon/locales/it.json | 6 +++ app/javascript/mastodon/locales/ja.json | 6 +++ app/javascript/mastodon/locales/ka.json | 6 +++ app/javascript/mastodon/locales/ko.json | 6 +++ app/javascript/mastodon/locales/nl.json | 6 +++ app/javascript/mastodon/locales/no.json | 6 +++ app/javascript/mastodon/locales/oc.json | 6 +++ app/javascript/mastodon/locales/pl.json | 6 +++ app/javascript/mastodon/locales/pt-BR.json | 6 +++ app/javascript/mastodon/locales/pt.json | 6 +++ app/javascript/mastodon/locales/ro.json | 6 +++ app/javascript/mastodon/locales/ru.json | 6 +++ app/javascript/mastodon/locales/sk.json | 6 +++ app/javascript/mastodon/locales/sl.json | 6 +++ app/javascript/mastodon/locales/sr-Latn.json | 6 +++ app/javascript/mastodon/locales/sr.json | 6 +++ app/javascript/mastodon/locales/sv.json | 6 +++ app/javascript/mastodon/locales/ta.json | 6 +++ app/javascript/mastodon/locales/te.json | 6 +++ app/javascript/mastodon/locales/th.json | 6 +++ app/javascript/mastodon/locales/tr.json | 6 +++ app/javascript/mastodon/locales/uk.json | 6 +++ app/javascript/mastodon/locales/zh-CN.json | 6 +++ app/javascript/mastodon/locales/zh-HK.json | 6 +++ app/javascript/mastodon/locales/zh-TW.json | 6 +++ 50 files changed, 332 insertions(+) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 677911e4ecbd05..bdba4a7935912f 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -123,6 +123,11 @@ "empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.", "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.", "empty_column.public": "لا يوجد أي شيء هنا ! قم بنشر شيء ما للعامة، أو إتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "ترخيص", "follow_request.reject": "رفض", "getting_started.developers": "المُطوِّرون", @@ -286,6 +291,7 @@ "status.favourite": "أضف إلى المفضلة", "status.filtered": "مُصفّى", "status.load_more": "حمّل المزيد", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "الصورة مستترة", "status.mention": "أذكُر @{name}", "status.more": "المزيد", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index da1ab62e60d3e2..edd554e98d3dd7 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 725bbed418a89d..a2eaf35503af82 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Предпочитани", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Споменаване", "status.more": "More", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 3d8f7bae254ab7..2d67cc88166b78 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Encara no has silenciat cap usuari.", "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.", "empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autoritzar", "follow_request.reject": "Rebutjar", "getting_started.developers": "Desenvolupadors", @@ -286,6 +291,7 @@ "status.favourite": "Favorit", "status.filtered": "Filtrat", "status.load_more": "Carrega més", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Multimèdia amagat", "status.mention": "Esmentar @{name}", "status.more": "Més", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index d5aeab9f2ea7ac..e8e1758446d989 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.", "empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.", "empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altre istanze per empie a linea pubblica", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Auturizà", "follow_request.reject": "Righjittà", "getting_started.developers": "Sviluppatori", @@ -286,6 +291,7 @@ "status.favourite": "Aghjunghje à i favuriti", "status.filtered": "Filtratu", "status.load_more": "Vede di più", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media piattata", "status.mention": "Mintuvà @{name}", "status.more": "Più", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index c6c3095a7bbbe5..4806029ff5d14f 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Ještě neignorujete žádné uživatele.", "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.", "empty_column.public": "Tady nic není! Napište něco veřejně, nebo manuálně začněte sledovat uživatele z jiných instancí, aby tu něco přibylo", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorizovat", "follow_request.reject": "Odmítnout", "getting_started.developers": "Vývojáři", @@ -286,6 +291,7 @@ "status.favourite": "Oblíbit", "status.filtered": "Filtrováno", "status.load_more": "Zobrazit více", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Média skryta", "status.mention": "Zmínit uživatele @{name}", "status.more": "Více", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 26af4d8f016793..8f93ecca764b4e 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index f5ce7d0b9dce65..5ed617fd1c7075 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Du har endnu ikke dæmpet nogen som helst bruger.", "empty_column.notifications": "Du har endnu ingen notifikationer. Tag ud og bland dig med folkemængden for at starte samtalen.", "empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre instanser for st udfylde tomrummet", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Godkend", "follow_request.reject": "Afvis", "getting_started.developers": "Udviklere", @@ -286,6 +291,7 @@ "status.favourite": "Favorit", "status.filtered": "Filtreret", "status.load_more": "Indlæs mere", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Medie skjult", "status.mention": "Nævn @{name}", "status.more": "Mere", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0bfdfc46a261fa..13f960f934ce01 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Du hast keine Profile stummgeschaltet.", "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.", "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Erlauben", "follow_request.reject": "Ablehnen", "getting_started.developers": "Entwickler", @@ -286,6 +291,7 @@ "status.favourite": "Favorisieren", "status.filtered": "Gefiltert", "status.load_more": "Weitere laden", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Medien versteckt", "status.mention": "@{name} erwähnen", "status.more": "Mehr", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 2f5242e26c8d08..42aec406bf5fa1 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -271,6 +271,10 @@ "defaultMessage": "This post cannot be boosted", "id": "status.cannot_reblog" }, + { + "defaultMessage": "This post is only visible by other users of your instance", + "id": "status.local_only" + }, { "defaultMessage": "Favourite", "id": "status.favourite" @@ -795,6 +799,31 @@ ], "path": "app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.json" }, + { + "descriptors": [ + { + "defaultMessage": "Federated", + "id": "federation.federated.short" + }, + { + "defaultMessage": "Allow toot to reach other instances", + "id": "federation.federated.long" + }, + { + "defaultMessage": "Local-only", + "id": "federation.local_only.short" + }, + { + "defaultMessage": "Restrict this toot only to my instance", + "id": "federation.local_only.long" + }, + { + "defaultMessage": "Adjust status federation", + "id": "federation.change" + } + ], + "path": "app/javascript/mastodon/features/compose/components/federation_dropdown.json" + }, { "descriptors": [ { @@ -1733,6 +1762,15 @@ ], "path": "app/javascript/mastodon/features/status/components/action_bar.json" }, + { + "descriptors": [ + { + "defaultMessage": "This post is only visible by other users of your instance", + "id": "status.local_only" + } + ], + "path": "app/javascript/mastodon/features/status/components/detailed_status.json" + }, { "descriptors": [ { diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index ad7f040d64c162..ca6fe57f94fb61 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.", "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλα instances για να τη γεμίσεις", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Ενέκρινε", "follow_request.reject": "Απέρριψε", "getting_started.developers": "Ανάπτυξη", @@ -286,6 +291,7 @@ "status.favourite": "Σημείωσε ως αγαπημένο", "status.filtered": "Φιλτραρισμένα", "status.load_more": "Φόρτωσε περισσότερα", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Κρυμμένο πολυμέσο", "status.mention": "Ανέφερε τον/την @{name}", "status.more": "Περισσότερα", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 0a0c987d65d52c..8ecee04017c877 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index ab584840e7aa0d..5b53aa400d21eb 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.", "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Rajtigi", "follow_request.reject": "Rifuzi", "getting_started.developers": "Programistoj", @@ -286,6 +291,7 @@ "status.favourite": "Stelumi", "status.filtered": "Filtrita", "status.load_more": "Ŝargi pli", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Aŭdovidaĵo kaŝita", "status.mention": "Mencii @{name}", "status.more": "Pli", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 07e110048a7f76..1107733564d787 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.", "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorizar", "follow_request.reject": "Rechazar", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favorito", "status.filtered": "Filtered", "status.load_more": "Cargar más", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Contenido multimedia oculto", "status.mention": "Mencionar", "status.more": "Más", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 433815c66826b0..e956bd7b3338bc 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.", "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.", "empty_column.public": "Ez dago ezer hemen! Idatzi zerbait publikoki edo jarraitu eskuz beste instantzia batzuetako erabiltzailean hau betetzeko", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Baimendu", "follow_request.reject": "Ukatu", "getting_started.developers": "Garatzaileak", @@ -286,6 +291,7 @@ "status.favourite": "Gogokoa", "status.filtered": "Iragazita", "status.load_more": "Kargatu gehiago", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Multimedia ezkutatua", "status.mention": "Aipatu @{name}", "status.more": "Gehiago", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 44956aeb3d9ec1..2b718f50cfc1ec 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -123,6 +123,11 @@ "empty_column.mutes": "شما هنوز هیچ کاربری را بی‌صدا نکرده‌اید.", "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.", "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا این‌جا پر شود", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "اجازه دهید", "follow_request.reject": "اجازه ندهید", "getting_started.developers": "برای برنامه‌نویسان", @@ -286,6 +291,7 @@ "status.favourite": "پسندیدن", "status.filtered": "فیلترشده", "status.load_more": "بیشتر نشان بده", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "تصویر پنهان شده", "status.mention": "نام‌بردن از @{name}", "status.more": "بیشتر", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index fd23e7ff509429..c347f80033b3de 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.", "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt manuaalisesti seuraamassa muiden instanssien käyttäjiä", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Valtuuta", "follow_request.reject": "Hylkää", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Tykkää", "status.filtered": "Filtered", "status.load_more": "Lataa lisää", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media piilotettu", "status.mention": "Mainitse @{name}", "status.more": "Lisää", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index e77d0028664c23..2abc3819ccc618 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Vous n'avez pas encore mis des utilisateurs en silence.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Accepter", "follow_request.reject": "Rejeter", "getting_started.developers": "Développeurs", @@ -286,6 +291,7 @@ "status.favourite": "Ajouter aux favoris", "status.filtered": "Filtré", "status.load_more": "Charger plus", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Média caché", "status.mention": "Mentionner", "status.more": "Plus", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index c550306ac0de4d..db25afd187ecd0 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.", "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outras instancias para ir enchéndoa", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorizar", "follow_request.reject": "Rexeitar", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favorita", "status.filtered": "Filtered", "status.load_more": "Cargar máis", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Medios ocultos", "status.mention": "Mencionar @{name}", "status.more": "Máis", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e9a13b02d41051..c64e6c2cc93e5f 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.", "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "קבלה", "follow_request.reject": "דחיה", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "חיבוב", "status.filtered": "Filtered", "status.load_more": "עוד", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "מדיה מוסתרת", "status.mention": "פניה אל @{name}", "status.more": "עוד", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 7da45c3a43e738..bf57bd4f362a0a 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.", "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autoriziraj", "follow_request.reject": "Odbij", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Označi omiljenim", "status.filtered": "Filtered", "status.load_more": "Učitaj više", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Sakriven media sadržaj", "status.mention": "Spomeni @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index c9d32135acdcab..9a26a984561e98 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Jelenleg nincsenek értesítései. Lépj kapcsolatba másokkal, hogy indítsd el a beszélgetést.", "empty_column.public": "Jelenleg semmi nincs itt! Írj valamit publikusan vagy kövess más szervereken levő felhasználókat, hogy megtöltsd", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Engedélyez", "follow_request.reject": "Visszautasít", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Kedvenc", "status.filtered": "Filtered", "status.load_more": "Többet", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Média elrejtve", "status.mention": "Említés", "status.more": "Többet", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 1376f68944669d..cddcb4264fb802 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր մյուսներին՝ խոսակցությունը սկսելու համար։", "empty_column.public": "Այստեղ բան չկա՛։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգույցներից էակների՝ այն լցնելու համար։", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Վավերացնել", "follow_request.reject": "Մերժել", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Հավանել", "status.filtered": "Filtered", "status.load_more": "Բեռնել ավելին", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "մեդիաբովանդակությունը թաքցված է", "status.mention": "Նշել @{name}֊ին", "status.more": "Ավելին", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index e8b087e9d1264e..0d22c187a222d0 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.", "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Izinkan", "follow_request.reject": "Tolak", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Difavoritkan", "status.filtered": "Filtered", "status.load_more": "Tampilkan semua", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media disembunyikan", "status.mention": "Balasan @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index a3ea121e429cd2..b6dbcfad76d0ca 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.", "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Yurizar", "follow_request.reject": "Refuzar", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favorizar", "status.filtered": "Filtered", "status.load_more": "Kargar pluse", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Kontenajo celita", "status.mention": "Mencionar @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index fa0956fe341529..92b52fbe33e912 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.", "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorizza", "follow_request.reject": "Rifiuta", "getting_started.developers": "Sviluppatori", @@ -286,6 +291,7 @@ "status.favourite": "Apprezzato", "status.filtered": "Filtrato", "status.load_more": "Mostra di più", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Allegato nascosto", "status.mention": "Nomina @{name}", "status.more": "Altro", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index d54849362107f4..d2afbd2132b507 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -123,6 +123,11 @@ "empty_column.mutes": "まだ誰もミュートしていません。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "許可", "follow_request.reject": "拒否", "getting_started.developers": "開発", @@ -286,6 +291,7 @@ "status.favourite": "お気に入り", "status.filtered": "フィルターされました", "status.load_more": "もっと見る", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "非表示のメディア", "status.mention": "@{name}さんにトゥート", "status.more": "もっと見る", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index 6ad12925421400..849a777905794f 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.", "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "ავტორიზაცია", "follow_request.reject": "უარყოფა", "getting_started.developers": "დეველოპერები", @@ -286,6 +291,7 @@ "status.favourite": "ფავორიტი", "status.filtered": "ფილტრირებული", "status.load_more": "მეტის ჩატვირთვა", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "მედია დამალულია", "status.mention": "ასახელე @{name}", "status.more": "მეტი", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 50f785c1aea6c2..0f43c5e2e6d0b2 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -123,6 +123,11 @@ "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.", "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요.", "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스의 유저를 팔로우 해서 채워보세요", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "허가", "follow_request.reject": "거부", "getting_started.developers": "개발자", @@ -286,6 +291,7 @@ "status.favourite": "즐겨찾기", "status.filtered": "필터링 됨", "status.load_more": "더 보기", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "미디어 숨겨짐", "status.mention": "답장", "status.more": "자세히", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 5623bc54f583b4..e50cda888e6c7d 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.", "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.", "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Goedkeuren", "follow_request.reject": "Afkeuren", "getting_started.developers": "Ontwikkelaars", @@ -286,6 +291,7 @@ "status.favourite": "Favoriet", "status.filtered": "Gefilterd", "status.load_more": "Meer laden", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media verborgen", "status.mention": "Vermeld @{name}", "status.more": "Meer", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index d533ac3153f10e..ecd0af0afe8e08 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.", "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorisér", "follow_request.reject": "Avvis", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Lik", "status.filtered": "Filtered", "status.load_more": "Last mer", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media skjult", "status.mention": "Nevn @{name}", "status.more": "Mer", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 254e9a2a51dadd..388b7c7133ed15 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Encara avètz pas mes en silenci degun.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Acceptar", "follow_request.reject": "Regetar", "getting_started.developers": "Desvelopaires", @@ -286,6 +291,7 @@ "status.favourite": "Apondre als favorits", "status.filtered": "Filtrat", "status.load_more": "Cargar mai", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Mèdia rescondut", "status.mention": "Mencionar", "status.more": "Mai", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 9fc2de4165dd3c..e901fcc1f23602 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Nie wyciszyłeś(-aś) jeszcze żadnego użytkownika.", "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autoryzuj", "follow_request.reject": "Odrzuć", "getting_started.developers": "Dla programistów", @@ -286,6 +291,7 @@ "status.favourite": "Dodaj do ulubionych", "status.filtered": "Filtrowany(-a)", "status.load_more": "Załaduj więcej", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", "status.more": "Więcej", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index ed6ea3674ec9cb..9970a3f03281b3 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.", "empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias", + "federation.change": "Ajustar federação do toot", + "federation.federated.long": "Permitir que o toot chegue a outras instâncias", + "federation.federated.short": "Federado", + "federation.local_only.long": "Restringir o toot somente à minha instância", + "federation.local_only.short": "Somente local", "follow_request.authorize": "Autorizar", "follow_request.reject": "Rejeitar", "getting_started.developers": "Desenvolvedores", @@ -286,6 +291,7 @@ "status.favourite": "Adicionar aos favoritos", "status.filtered": "Filtrado", "status.load_more": "Carregar mais", + "status.local_only": "Esse post só é visível para outros usuários da sua instância", "status.media_hidden": "Mídia escondida", "status.mention": "Mencionar @{name}", "status.more": "Mais", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 2f601cb32662cf..7c574465191789 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos", + "federation.change": "Ajustar federação do toot", + "federation.federated.long": "Permitir que o toot chegue a outras instâncias", + "federation.federated.short": "Federado", + "federation.local_only.long": "Restringir o toot somente à minha instância", + "federation.local_only.short": "Somente local", "follow_request.authorize": "Autorizar", "follow_request.reject": "Rejeitar", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Adicionar aos favoritos", "status.filtered": "Filtered", "status.load_more": "Carregar mais", + "status.local_only": "Esse post só é visível para outros usuários da sua instância", "status.media_hidden": "Media escondida", "status.mention": "Mencionar @{name}", "status.more": "Mais", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index f3c540f2ead1ac..8fc77d5c18da90 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Nu ai oprit nici un utilizator inca.", "empty_column.notifications": "Nu ai nici o notificare inca. Interactioneaza cu altii pentru a incepe o conversatie.", "empty_column.public": "Nu este nimci aici inca! Scrie ceva public, sau urmareste alti utilizatori din alte medii pentru a porni fluxul", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Autorizeaza", "follow_request.reject": "Respinge", "getting_started.developers": "Dezvolatori", @@ -286,6 +291,7 @@ "status.favourite": "Favorite", "status.filtered": "Sortate", "status.load_more": "Incarca mai multe", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media ascunsa", "status.mention": "Mentioneaza @{name}", "status.more": "Mai mult", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 83cbb5240d49c9..27fe89f4b9a236 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Вы ещё никого не заглушили.", "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.", "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Авторизовать", "follow_request.reject": "Отказать", "getting_started.developers": "Для разработчиков", @@ -286,6 +291,7 @@ "status.favourite": "Нравится", "status.filtered": "Отфильтровано", "status.load_more": "Показать еще", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Медиа скрыто", "status.mention": "Упомянуть @{name}", "status.more": "Больше", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index edfd69a252d4b6..8bdda434e9d8e5 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Nemáš ešte žiadne oznámenia. Zapoj sa s niekym do debaty a komunikuj s ostatnými aby diskusia mohla začať.", "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začnite sledovať užívateľov z iných Mastodon serverov, aby tu tak niečo pribudlo", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Povoľ prístup", "follow_request.reject": "Odmietni", "getting_started.developers": "Vývojári", @@ -286,6 +291,7 @@ "status.favourite": "Páči sa mi", "status.filtered": "Filtrované", "status.load_more": "Ukáž viac", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Skryté médiá", "status.mention": "Spomeň @{name}", "status.more": "Viac", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 8f6c2e3d576f3f..ef443e966f73b1 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Nimate še nobenih obvestil. Poveži se z drugimi, da začnete pogovor.", "empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih vozlišč", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Odobri", "follow_request.reject": "Zavrni", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 0f56f642ab2051..98bfa99c5e42ab 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.", "empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Odobri", "follow_request.reject": "Odbij", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Omiljeno", "status.filtered": "Filtered", "status.load_more": "Učitaj još", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Multimedija sakrivena", "status.mention": "Pomeni korisnika @{name}", "status.more": "Još", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 81a37cc4c68bc0..d6985033d02c82 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -123,6 +123,11 @@ "empty_column.mutes": "Још увек немате ућутканих корисника.", "empty_column.notifications": "Тренутно немате обавештења. Дружите се мало да започнете разговор.", "empty_column.public": "Овде нема ничега! Напишите нешто јавно, или нађите кориснике са других инстанци које ћете запратити да попуните ову празнину", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Одобри", "follow_request.reject": "Одбиј", "getting_started.developers": "Програмери", @@ -286,6 +291,7 @@ "status.favourite": "Омиљено", "status.filtered": "Филтрирано", "status.load_more": "Учитај још", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Мултимедија сакривена", "status.mention": "Помени корисника @{name}", "status.more": "Још", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 498f6b411934a6..85248e2b9d21fd 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.", "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Godkänn", "follow_request.reject": "Avvisa", "getting_started.developers": "Utvecklare", @@ -286,6 +291,7 @@ "status.favourite": "Favorit", "status.filtered": "Filtered", "status.load_more": "Ladda fler", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media dold", "status.mention": "Omnämn @{name}", "status.more": "Mer", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index d80eba2c13be95..067ae60b574d78 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index bbe575fde1600b..24e6c626d3e48d 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "మీకు ఇంకా ఏ నోటిఫికేషన్లు లేవు. సంభాషణను ప్రారంభించడానికి ఇతరులతో ప్రతిస్పందించండి.", "empty_column.public": "ఇక్కడ ఏమీ లేదు! దీన్ని నింపడానికి బహిరంగంగా ఏదైనా వ్రాయండి, లేదా ఇతర దృష్టాంతాల్లోని వినియోగదారులను అనుసరించండి", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "అనుమతించు", "follow_request.reject": "తిరస్కరించు", "getting_started.developers": "డెవలపర్లు", @@ -286,6 +291,7 @@ "status.favourite": "ఇష్టపడు", "status.filtered": "వడకట్టబడిన", "status.load_more": "మరిన్ని లోడ్ చేయి", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "మీడియా దాచబడింది", "status.mention": "@{name}ను ప్రస్తావించు", "status.more": "ఇంకొన్ని", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 5340fda928bbd5..158ca11db1ff82 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index cec07b87e16c8d..0366560680b881 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.", "empty_column.public": "Burada hiçbir gönderi yok! Herkese açık bir şeyler yazın, veya diğer sunucudaki insanları takip ederek bu alanın dolmasını sağlayın", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Yetkilendir", "follow_request.reject": "Reddet", "getting_started.developers": "Developers", @@ -286,6 +291,7 @@ "status.favourite": "Favorilere ekle", "status.filtered": "Filtered", "status.load_more": "Daha fazla", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Gizli görsel", "status.mention": "Bahset @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 9a2c18e367db6b..5f42a2c4bc8ce5 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.", "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Авторизувати", "follow_request.reject": "Відмовити", "getting_started.developers": "Розробникам", @@ -286,6 +291,7 @@ "status.favourite": "Подобається", "status.filtered": "Filtered", "status.load_more": "Завантажити більше", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Медіаконтент приховано", "status.mention": "Згадати", "status.more": "More", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index cf2e1a640ff0b0..c6c833747436f9 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "你还没有收到过任何通知,快向其他用户搭讪吧。", "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户后,这里就会有嘟文出现了哦!", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "同意", "follow_request.reject": "拒绝", "getting_started.developers": "开发", @@ -286,6 +291,7 @@ "status.favourite": "收藏", "status.filtered": "Filtered", "status.load_more": "加载更多", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "隐藏媒体内容", "status.mention": "提及 @{name}", "status.more": "更多", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index dbad69191e8d99..c73797b5d8535f 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -123,6 +123,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。", "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "批准", "follow_request.reject": "拒絕", "getting_started.developers": "開發者", @@ -286,6 +291,7 @@ "status.favourite": "收藏", "status.filtered": "Filtered", "status.load_more": "載入更多", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "隱藏媒體內容", "status.mention": "提及 @{name}", "status.more": "更多", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 4e437f0e025eb0..91d52b0c0010c0 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -123,6 +123,11 @@ "empty_column.mutes": "你還沒有靜音任何使用者。", "empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。", "empty_column.public": "這裡什麼都沒有! 寫一些公開的嘟文,或著關注其他站點的使用者後,這裡就會有嘟文出現了", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "授權", "follow_request.reject": "拒絕", "getting_started.developers": "開發", @@ -286,6 +291,7 @@ "status.favourite": "最愛", "status.filtered": "Filtered", "status.load_more": "載入更多", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "隱藏媒體內容", "status.mention": "提到 @{name}", "status.more": "更多", From 6bef1a8134333efabd9be677273f33e194bd327a Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Sun, 26 Aug 2018 12:42:32 +0200 Subject: [PATCH 007/177] Add local_only to admin screens --- app/views/admin/reports/_status.html.haml | 4 ++++ config/locales/en.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/pt.yml | 1 + 4 files changed, 7 insertions(+) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index 4d557b07152ce1..df16f6bad3410e 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -32,3 +32,7 @@ · = fa_icon('eye-slash fw') = t('stream_entries.sensitive_content') + - if status.proper.local_only + · + = fa_icon('chain-broken fw') + = t('statuses.local_only') diff --git a/config/locales/en.yml b/config/locales/en.yml index 501694eac908ec..1e0b5680da47a4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -760,6 +760,7 @@ en: one: 'contained a disallowed hashtag: %{tags}' other: 'contained the disallowed hashtags: %{tags}' language_detection: Automatically detect language + local_only: Local-only open_in_web: Open in web over_character_limit: character limit of %{max} exceeded pin_errors: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index cc4dddb9c1a31c..55bbd5e3473689 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -758,6 +758,7 @@ pt-BR: one: 'continha a hashtag não permitida: %{tags}' other: 'continha as hashtags não permitidas: %{tags}' language_detection: Detectar idioma automaticamente + local_only: Somente local open_in_web: Abrir na web over_character_limit: limite de caracteres de %{max} excedido pin_errors: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index eebeb498e9a052..fa23914683be26 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -588,6 +588,7 @@ pt: two_factor_authentication: Autenticação em dois passos your_apps: As tuas aplicações statuses: + local_only: Somente local open_in_web: Abrir no browser over_character_limit: limite de caracter excedeu %{max} pin_errors: From 469294d2939669ec8acbfaa10a03993bdb178298 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Mon, 3 Sep 2018 21:47:07 +0200 Subject: [PATCH 008/177] Hide local statuses from user atom and from unlogged users --- app/controllers/accounts_controller.rb | 8 ++++++-- app/models/stream_entry.rb | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 52753c1c310d87..58f4f100dba56c 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -30,7 +30,7 @@ def show end format.atom do - @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]) + @entries = @account.stream_entries.where(hidden: false).with_includes.without_local_only.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]) render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? })) end @@ -63,7 +63,11 @@ def filtered_statuses end def default_statuses - @account.statuses.where(visibility: [:public, :unlisted]) + if current_user.nil? + @account.statuses.without_local_only.where(visibility: [:public, :unlisted]) + else + @account.statuses.where(visibility: [:public, :unlisted]) + end end def only_media_scope diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index dd383eb816fab9..0747300d4f6268 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -26,6 +26,7 @@ class StreamEntry < ApplicationRecord default_scope { where(activity_type: 'Status') } scope :recent, -> { reorder(id: :desc) } scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) } + scope :without_local_only, -> { where(statuses: { local_only: [false, nil] }) } delegate :target, :title, :content, :thread, :local_only?, to: :status, From 5497b5af611a8c5eed14461c3b6fbbd0ead224ec Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 4 Sep 2018 18:27:34 +0200 Subject: [PATCH 009/177] Add local only icon to status page --- app/views/stream_entries/_detailed_status.html.haml | 4 ++++ app/views/stream_entries/_simple_status.html.haml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index d0d9cc5fc5149c..efa5ab2d0dc38e 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -64,6 +64,10 @@ %span.detailed-status__favorites>= number_to_human status.favourites_count, strip_insignificant_zeros: true = " " + - if status.local_only + · + %span.detailed-status__link.modal-button.disabled< + = fa_icon 'chain-broken fw', 'title': t('statuses.local_only') - if user_signed_in? · = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank' diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index fc2cacf30a5e15..447e8663e3f4dd 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -45,3 +45,6 @@ = fa_icon 'envelope fw' = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = fa_icon 'star fw' + - if status.local_only + %span.status__action-bar-button.icon-button.disabled{style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;'}< + = fa_icon 'chain-broken fw', 'title': t('statuses.local_only') From 9d6e005849123baeecc37124a351c1cf75517343 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Mon, 1 Oct 2018 19:14:36 +0200 Subject: [PATCH 010/177] Fix issue with toggle in mobile --- .../mastodon/features/compose/components/federation_dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/federation_dropdown.js b/app/javascript/mastodon/features/compose/components/federation_dropdown.js index 6271c14cc66f47..571cbcb6b59aeb 100644 --- a/app/javascript/mastodon/features/compose/components/federation_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/federation_dropdown.js @@ -182,7 +182,7 @@ export default class FederationDropdown extends React.PureComponent { handleModalActionClick = (e) => { e.preventDefault(); - const { value } = this.options[Boolean(e.currentTarget.getAttribute('data-index'))]; + const { value } = this.options[e.currentTarget.getAttribute('data-index')]; this.props.onModalClose(); this.props.onChange(value); From d42a06fc7404f64ada011554cfd7ea7574c769e9 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Mon, 1 Oct 2018 19:32:47 +0200 Subject: [PATCH 011/177] Add default federation option to settings --- app/controllers/api/v1/accounts/credentials_controller.rb | 1 + app/controllers/settings/preferences_controller.rb | 1 + app/lib/user_settings_decorator.rb | 5 +++++ app/models/user.rb | 2 +- app/serializers/initial_state_serializer.rb | 1 + app/serializers/rest/credential_account_serializer.rb | 1 + app/services/post_status_service.rb | 5 +++-- app/views/settings/preferences/show.html.haml | 2 ++ config/locales/simple_form.en.yml | 1 + config/locales/simple_form.pt-BR.yml | 1 + config/settings.yml | 1 + 11 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index dcd41b35c1b372..994c11bd6c7030 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -33,6 +33,7 @@ def user_settings_params 'setting_default_privacy' => source_params.fetch(:privacy, @account.user.setting_default_privacy), 'setting_default_sensitive' => source_params.fetch(:sensitive, @account.user.setting_default_sensitive), 'setting_default_language' => source_params.fetch(:language, @account.user.setting_default_language), + 'setting_default_federation' => source_params.fetch(:sensitive, @account.user.setting_default_federation), } end end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index b475c722d31d6d..4044eb57975ac6 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -36,6 +36,7 @@ def user_settings_params :setting_default_privacy, :setting_default_sensitive, :setting_default_language, + :setting_default_federation, :setting_unfollow_modal, :setting_boost_modal, :setting_delete_modal, diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 8339593978be0d..4ec8402ebcb6ba 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -20,6 +20,7 @@ def process_update user.settings['default_privacy'] = default_privacy_preference if change?('setting_default_privacy') user.settings['default_sensitive'] = default_sensitive_preference if change?('setting_default_sensitive') user.settings['default_language'] = default_language_preference if change?('setting_default_language') + user.settings['default_federation'] = default_federation_preference if change?('setting_default_federation') user.settings['unfollow_modal'] = unfollow_modal_preference if change?('setting_unfollow_modal') user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal') user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal') @@ -48,6 +49,10 @@ def default_sensitive_preference boolean_cast_setting 'setting_default_sensitive' end + def default_federation_preference + boolean_cast_setting 'setting_default_federation' + end + def unfollow_modal_preference boolean_cast_setting 'setting_unfollow_modal' end diff --git a/app/models/user.rb b/app/models/user.rb index d83df28c253cbf..2b8d182b196307 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -96,7 +96,7 @@ class User < ApplicationRecord delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, :reduce_motion, :system_font_ui, :noindex, :theme, :display_sensitive_media, :hide_network, - :default_language, to: :settings, prefix: :setting, allow_nil: false + :default_language, :default_federation, to: :settings, prefix: :setting, allow_nil: false attr_reader :invite_code diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 78b96298c51294..6062087a86cf87 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -38,6 +38,7 @@ def compose store[:me] = object.current_account.id.to_s store[:default_privacy] = object.current_account.user.setting_default_privacy store[:default_sensitive] = object.current_account.user.setting_default_sensitive + store[:default_federation] = object.current_account.user.setting_default_federation end store[:text] = object.text if object.text diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb index fb195eb07d8bf2..f07f9f05802386 100644 --- a/app/serializers/rest/credential_account_serializer.rb +++ b/app/serializers/rest/credential_account_serializer.rb @@ -10,6 +10,7 @@ def source privacy: user.setting_default_privacy, sensitive: user.setting_default_sensitive, language: user.setting_default_language, + federation: user.setting_default_federation, note: object.note, fields: object.fields.map(&:to_h), } diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index cdcb6b0879ef3c..83d661ae2ef812 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -32,7 +32,7 @@ def call(account, text, in_reply_to = nil, **options) visibility: options[:visibility] || account.user&.setting_default_privacy, language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account), application: options[:application], - local_only: local_only_option(options[:local_only], in_reply_to)) + local_only: local_only_option(options[:local_only], in_reply_to, account.user&.setting_default_federation)) end process_hashtags_service.call(status) @@ -58,8 +58,9 @@ def call(account, text, in_reply_to = nil, **options) private - def local_only_option(local_only, in_reply_to) + def local_only_option(local_only, in_reply_to, federation_setting) return in_reply_to&.local_only? if local_only.nil? # XXX temporary, just until clients implement to avoid leaking local_only posts + return federation_setting if local_only.nil? local_only end diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 43430069f6e224..715ce49edbc3b1 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -23,6 +23,8 @@ = f.input :setting_default_sensitive, as: :boolean, wrapper: :with_label + = f.input :setting_default_federation, as: :boolean, wrapper: :with_label + %h4= t 'preferences.other' .fields-group diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 2e4cb1effd8d10..676fa7b5d9add1 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -65,6 +65,7 @@ en: phrase: Keyword or phrase setting_auto_play_gif: Auto-play animated GIFs setting_boost_modal: Show confirmation dialog before boosting + setting_default_federation: Always allow my toots to reach other instances setting_default_language: Posting language setting_default_privacy: Post privacy setting_default_sensitive: Always mark media as sensitive diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index 013b26066c13dc..b9a3d286460dc0 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -65,6 +65,7 @@ pt-BR: phrase: Palavra-chave ou frase setting_auto_play_gif: Reproduzir GIFs automaticamente setting_boost_modal: Mostrar diálogo de confirmação antes de compartilhar postagem + setting_default_federation: Sempre permitir que meus toots cheguem em outras instâncias setting_default_language: Idioma das postagens setting_default_privacy: Privacidade das postagens setting_default_sensitive: Sempre marcar mídia como sensível diff --git a/config/settings.yml b/config/settings.yml index 78a801406c820b..552ef60f6d2786 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -21,6 +21,7 @@ defaults: &defaults timeline_preview: true show_staff_badge: true default_sensitive: false + default_federation: true hide_network: false unfollow_modal: false boost_modal: false From 3306fad8036a441ede18b112a7dea8d7751d3dc6 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 23 Oct 2018 08:38:13 +0200 Subject: [PATCH 012/177] Fix wrong configuration fetched --- app/controllers/api/v1/accounts/credentials_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 994c11bd6c7030..578f402a25582b 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -33,7 +33,7 @@ def user_settings_params 'setting_default_privacy' => source_params.fetch(:privacy, @account.user.setting_default_privacy), 'setting_default_sensitive' => source_params.fetch(:sensitive, @account.user.setting_default_sensitive), 'setting_default_language' => source_params.fetch(:language, @account.user.setting_default_language), - 'setting_default_federation' => source_params.fetch(:sensitive, @account.user.setting_default_federation), + 'setting_default_federation' => source_params.fetch(:federation, @account.user.setting_default_federation), } end end From b9f0f794db01515c5365ec96150752ba86f5b4f5 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 23 Oct 2018 08:42:13 +0200 Subject: [PATCH 013/177] Change setting wording --- config/locales/simple_form.en.yml | 2 +- config/locales/simple_form.pt-BR.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 0ce026ab6545fd..32ebaa363da9f0 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -71,7 +71,7 @@ en: phrase: Keyword or phrase setting_auto_play_gif: Auto-play animated GIFs setting_boost_modal: Show confirmation dialog before boosting - setting_default_federation: Always allow my toots to reach other instances + setting_default_federation: Allow my toots to reach other instances by default setting_default_language: Posting language setting_default_privacy: Post privacy setting_default_sensitive: Always mark media as sensitive diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index fd118ec81f6ebc..c2be39cf82a558 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -65,7 +65,7 @@ pt-BR: phrase: Palavra-chave ou frase setting_auto_play_gif: Reproduzir GIFs automaticamente setting_boost_modal: Mostrar diálogo de confirmação antes de compartilhar postagem - setting_default_federation: Sempre permitir que meus toots cheguem em outras instâncias + setting_default_federation: Permitir que meus toots cheguem em outras instâncias por padrão setting_default_language: Idioma das postagens setting_default_privacy: Privacidade das postagens setting_default_sensitive: Sempre marcar mídia como sensível From 449e6e451f6185c44ed3b2d60b56b46b55e52281 Mon Sep 17 00:00:00 2001 From: Steven Tappert Date: Mon, 5 Nov 2018 18:51:43 +0100 Subject: [PATCH 014/177] Check for empty "last_status" before sorting DM column (#9207) * Check for empty "last_status" before sorting * Small touchups for codeclimate --- app/javascript/mastodon/reducers/conversations.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js index b13a9fdf4fd253..955a07754de087 100644 --- a/app/javascript/mastodon/reducers/conversations.js +++ b/app/javascript/mastodon/reducers/conversations.js @@ -56,7 +56,13 @@ const expandNormalizedConversations = (state, conversations, next) => { list = list.concat(items); - return list.sortBy(x => x.get('last_status'), (a, b) => compareId(a, b) * -1); + return list.sortBy(x => x.get('last_status'), (a, b) => { + if(a === null || b === null) { + return -1; + } + + return compareId(a, b) * -1; + }); }); } From 430499fbe12057b833897dada6407c55a0dab048 Mon Sep 17 00:00:00 2001 From: "m.b" Date: Mon, 5 Nov 2018 18:54:07 +0100 Subject: [PATCH 015/177] Update resolve_url_service.rb (#9188) --- app/services/resolve_url_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 1db1917e27c2ef..ed0c569230f64f 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -20,7 +20,7 @@ def call(url, on_behalf_of: nil) def process_url if equals_or_includes_any?(type, %w(Application Group Organization Person Service)) FetchRemoteAccountService.new.call(atom_url, body, protocol) - elsif equals_or_includes_any?(type, %w(Note Article Image Video)) + elsif equals_or_includes_any?(type, %w(Note Article Image Video Page)) FetchRemoteStatusService.new.call(atom_url, body, protocol) end end From 5ee4fd46063a2c36d92805ede4b8860065e56dc2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 07:42:17 +0100 Subject: [PATCH 016/177] Increase default column width from 330px to 350px (#9227) --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e5d9f7b9f13046..6e535baedacfab 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1847,7 +1847,7 @@ a.account__display-name { } .column { - width: 330px; + width: 350px; position: relative; box-sizing: border-box; display: flex; From 330401bec0146be9762358c774efe9a58954d8c4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:05:42 +0100 Subject: [PATCH 017/177] Optimize the process of following someone (#9220) * Eliminate extra accounts select query from FollowService * Optimistically update follow state in web UI and hide loading bar Fix #6205 * Asynchronize NotifyService in FollowService And fix failing test * Skip Webfinger resolve routine when called from FollowService if possible If an account is ActivityPub, then webfinger re-resolving is not necessary when called from FollowService. Improve options of ResolveAccountService --- app/controllers/api/v1/accounts_controller.rb | 2 +- app/javascript/mastodon/actions/accounts.js | 18 ++++++++--- .../mastodon/reducers/relationships.js | 12 +++++++ app/services/concerns/author_extractor.rb | 2 +- app/services/follow_service.rb | 8 ++--- app/services/process_mentions_service.rb | 2 +- app/services/resolve_account_service.rb | 32 ++++++++++++------- app/workers/local_notification_worker.rb | 13 ++++++-- .../authorize_interactions_controller_spec.rb | 4 ++- 9 files changed, 67 insertions(+), 26 deletions(-) diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 1d5372a8cdd4f2..f711c467675a9d 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -17,7 +17,7 @@ def show end def follow - FollowService.new.call(current_user.account, @account.acct, reblogs: truthy_param?(:reblogs)) + FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index cbae62a0f4cf1b..d4a824e2c9d646 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -145,12 +145,14 @@ export function fetchAccountFail(id, error) { export function followAccount(id, reblogs = true) { return (dispatch, getState) => { const alreadyFollowing = getState().getIn(['relationships', id, 'following']); - dispatch(followAccountRequest(id)); + const locked = getState().getIn(['accounts', id, 'locked'], false); + + dispatch(followAccountRequest(id, locked)); api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { dispatch(followAccountSuccess(response.data, alreadyFollowing)); }).catch(error => { - dispatch(followAccountFail(error)); + dispatch(followAccountFail(error, locked)); }); }; }; @@ -167,10 +169,12 @@ export function unfollowAccount(id) { }; }; -export function followAccountRequest(id) { +export function followAccountRequest(id, locked) { return { type: ACCOUNT_FOLLOW_REQUEST, id, + locked, + skipLoading: true, }; }; @@ -179,13 +183,16 @@ export function followAccountSuccess(relationship, alreadyFollowing) { type: ACCOUNT_FOLLOW_SUCCESS, relationship, alreadyFollowing, + skipLoading: true, }; }; -export function followAccountFail(error) { +export function followAccountFail(error, locked) { return { type: ACCOUNT_FOLLOW_FAIL, error, + locked, + skipLoading: true, }; }; @@ -193,6 +200,7 @@ export function unfollowAccountRequest(id) { return { type: ACCOUNT_UNFOLLOW_REQUEST, id, + skipLoading: true, }; }; @@ -201,6 +209,7 @@ export function unfollowAccountSuccess(relationship, statuses) { type: ACCOUNT_UNFOLLOW_SUCCESS, relationship, statuses, + skipLoading: true, }; }; @@ -208,6 +217,7 @@ export function unfollowAccountFail(error) { return { type: ACCOUNT_UNFOLLOW_FAIL, error, + skipLoading: true, }; }; diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index f4604929723612..8322780de569d6 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -1,6 +1,10 @@ import { ACCOUNT_FOLLOW_SUCCESS, + ACCOUNT_FOLLOW_REQUEST, + ACCOUNT_FOLLOW_FAIL, ACCOUNT_UNFOLLOW_SUCCESS, + ACCOUNT_UNFOLLOW_REQUEST, + ACCOUNT_UNFOLLOW_FAIL, ACCOUNT_BLOCK_SUCCESS, ACCOUNT_UNBLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, @@ -37,6 +41,14 @@ const initialState = ImmutableMap(); export default function relationships(state = initialState, action) { switch(action.type) { + case ACCOUNT_FOLLOW_REQUEST: + return state.setIn([action.id, action.locked ? 'requested' : 'following'], true); + case ACCOUNT_FOLLOW_FAIL: + return state.setIn([action.id, action.locked ? 'requested' : 'following'], false); + case ACCOUNT_UNFOLLOW_REQUEST: + return state.setIn([action.id, 'following'], false); + case ACCOUNT_UNFOLLOW_FAIL: + return state.setIn([action.id, 'following'], true); case ACCOUNT_FOLLOW_SUCCESS: case ACCOUNT_UNFOLLOW_SUCCESS: case ACCOUNT_BLOCK_SUCCESS: diff --git a/app/services/concerns/author_extractor.rb b/app/services/concerns/author_extractor.rb index 1e00eb803b73a0..c2419e9ecb23d6 100644 --- a/app/services/concerns/author_extractor.rb +++ b/app/services/concerns/author_extractor.rb @@ -18,6 +18,6 @@ def author_from_xml(xml, update_profile = true) acct = "#{username}@#{domain}" end - ResolveAccountService.new.call(acct, update_profile) + ResolveAccountService.new.call(acct, update_profile: update_profile) end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index f6888a68d4f7a1..0020bc9fec7e93 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -7,9 +7,9 @@ class FollowService < BaseService # @param [Account] source_account From which to follow # @param [String, Account] uri User URI to follow in the form of username@domain (or account record) # @param [true, false, nil] reblogs Whether or not to show reblogs, defaults to true - def call(source_account, uri, reblogs: nil) + def call(source_account, target_account, reblogs: nil) reblogs = true if reblogs.nil? - target_account = uri.is_a?(Account) ? uri : ResolveAccountService.new.call(uri) + target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true) raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) @@ -42,7 +42,7 @@ def request_follow(source_account, target_account, reblogs: true) follow_request = FollowRequest.create!(account: source_account, target_account: target_account, show_reblogs: reblogs) if target_account.local? - NotifyService.new.call(target_account, follow_request) + LocalNotificationWorker.perform_async(target_account.id, follow_request.id, follow_request.class.name) elsif target_account.ostatus? NotificationWorker.perform_async(build_follow_request_xml(follow_request), source_account.id, target_account.id) AfterRemoteFollowRequestWorker.perform_async(follow_request.id) @@ -57,7 +57,7 @@ def direct_follow(source_account, target_account, reblogs: true) follow = source_account.follow!(target_account, reblogs: reblogs) if target_account.local? - NotifyService.new.call(target_account, follow) + LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name) else Pubsubhubbub::SubscribeWorker.perform_async(target_account.id) unless target_account.subscribed? NotificationWorker.perform_async(build_follow_xml(follow), source_account.id, target_account.id) diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index b4641c4b4aba3d..ec7d33b1d82755 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -47,7 +47,7 @@ def create_notification(mention) mentioned_account = mention.account if mentioned_account.local? - LocalNotificationWorker.perform_async(mention.id) + LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name) elsif mentioned_account.ostatus? && !@status.stream_entry.hidden? NotificationWorker.perform_async(ostatus_xml, @status.account_id, mentioned_account.id) elsif mentioned_account.activitypub? diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 4323e7f06d57f7..c3064211dcf605 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -9,17 +9,27 @@ class ResolveAccountService < BaseService # Find or create a local account for a remote user. # When creating, look up the user's webfinger and fetch all # important information from their feed - # @param [String] uri User URI in the form of username@domain + # @param [String, Account] uri User URI in the form of username@domain + # @param [Hash] options # @return [Account] - def call(uri, update_profile = true, redirected = nil) - @username, @domain = uri.split('@') - @update_profile = update_profile + def call(uri, options = {}) + @options = options - return Account.find_local(@username) if TagManager.instance.local_domain?(@domain) + if uri.is_a?(Account) + @account = uri + @username = @account.username + @domain = @account.domain + + return @account if @account.local? || !webfinger_update_due? + else + @username, @domain = uri.split('@') - @account = Account.find_remote(@username, @domain) + return Account.find_local(@username) if TagManager.instance.local_domain?(@domain) - return @account unless webfinger_update_due? + @account = Account.find_remote(@username, @domain) + + return @account unless webfinger_update_due? + end Rails.logger.debug "Looking up webfinger for #{uri}" @@ -30,8 +40,8 @@ def call(uri, update_profile = true, redirected = nil) if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? @username = confirmed_username @domain = confirmed_domain - elsif redirected.nil? - return call("#{confirmed_username}@#{confirmed_domain}", update_profile, true) + elsif options[:redirected].nil? + return call("#{confirmed_username}@#{confirmed_domain}", options.merge(redirected: true)) else Rails.logger.debug 'Requested and returned acct URIs do not match' return @@ -76,7 +86,7 @@ def ostatus_ready? end def webfinger_update_due? - @account.nil? || @account.possibly_stale? + @account.nil? || ((!@options[:skip_webfinger] || @account.ostatus?) && @account.possibly_stale?) end def activitypub_ready? @@ -93,7 +103,7 @@ def handle_ostatus end def update_profile? - @update_profile + @options[:update_profile] end def handle_activitypub diff --git a/app/workers/local_notification_worker.rb b/app/workers/local_notification_worker.rb index 748270563c532a..48635e498fffd7 100644 --- a/app/workers/local_notification_worker.rb +++ b/app/workers/local_notification_worker.rb @@ -3,9 +3,16 @@ class LocalNotificationWorker include Sidekiq::Worker - def perform(mention_id) - mention = Mention.find(mention_id) - NotifyService.new.call(mention.account, mention) + def perform(receiver_account_id, activity_id = nil, activity_class_name = nil) + if activity_id.nil? && activity_class_name.nil? + activity = Mention.find(receiver_account_id) + receiver = activity.account + else + receiver = Account.find(receiver_account_id) + activity = activity_class_name.constantize.find(activity_id) + end + + NotifyService.new.call(receiver, activity) rescue ActiveRecord::RecordNotFound true end diff --git a/spec/controllers/authorize_interactions_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb index 81fd9ceb766df4..ce4257b68dcda8 100644 --- a/spec/controllers/authorize_interactions_controller_spec.rb +++ b/spec/controllers/authorize_interactions_controller_spec.rb @@ -99,10 +99,12 @@ allow(ResolveAccountService).to receive(:new).and_return(service) allow(service).to receive(:call).with('user@hostname').and_return(target_account) + allow(service).to receive(:call).with(target_account, skip_webfinger: true).and_return(target_account) + post :create, params: { acct: 'acct:user@hostname' } - expect(service).to have_received(:call).with('user@hostname') + expect(service).to have_received(:call).with(target_account, skip_webfinger: true) expect(account.following?(target_account)).to be true expect(response).to render_template(:success) end From b3c29ece478d2e34525b4edb9b4eaed4904b1cb5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:06:01 +0100 Subject: [PATCH 018/177] Fix follow limit validator reporting lower number past threshold (#9230) * Fix follow limit validator reporting lower number past threshold * Avoid floating point follow limit --- app/validators/follow_limit_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/validators/follow_limit_validator.rb b/app/validators/follow_limit_validator.rb index eb083ed854b74a..409bf01763b45e 100644 --- a/app/validators/follow_limit_validator.rb +++ b/app/validators/follow_limit_validator.rb @@ -14,7 +14,7 @@ def limit_for_account(account) if account.following_count < LIMIT LIMIT else - account.followers_count * RATIO + [(account.followers_count * RATIO).round, LIMIT].max end end end From 4b2f2548061cbbe37a98951c01438e327c915c92 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:06:14 +0100 Subject: [PATCH 019/177] Fix form validation flash message color and input borders (#9235) * Fix form validation flash message color and input borders * Fix typo --- app/javascript/styles/mastodon/forms.scss | 7 +++++-- app/views/shared/_error_messages.html.haml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 8c4c934ea45e04..46ef8577499f8b 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -330,9 +330,12 @@ code { } input[type=text], + input[type=number], input[type=email], - input[type=password] { - border-bottom-color: $valid-value-color; + input[type=password], + textarea, + select { + border-color: lighten($error-red, 12%); } .error { diff --git a/app/views/shared/_error_messages.html.haml b/app/views/shared/_error_messages.html.haml index b73890216f5091..28becd6c4482da 100644 --- a/app/views/shared/_error_messages.html.haml +++ b/app/views/shared/_error_messages.html.haml @@ -1,3 +1,3 @@ - if object.errors.any? - .flash-message#error_explanation + .flash-message.alert#error_explanation %strong= t('generic.validation_errors', count: object.errors.count) From 21fd335dd7722d512962e5f49812b3e9a0cd426f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:06:26 +0100 Subject: [PATCH 020/177] Display amount of freed disk space in tootctl media remove (#9229) * Display amount of freed disk space in tootctl media remove Fix #9213 * Fix code style issue --- lib/mastodon/media_cli.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index 179d1b6b5370d7..affc4cedb256ba 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -6,6 +6,8 @@ module Mastodon class MediaCLI < Thor + include ActionView::Helpers::NumberHelper + def self.exit_on_failure? true end @@ -36,11 +38,13 @@ def remove time_ago = options[:days].days.ago queued = 0 processed = 0 - dry_run = options[:dry_run] ? '(DRY RUN)' : '' + size = 0 + dry_run = options[:dry_run] ? '(DRY RUN)' : '' if options[:background] - MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id).reorder(nil).find_in_batches do |media_attachments| + MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id, :file_file_size).reorder(nil).find_in_batches do |media_attachments| queued += media_attachments.size + size += media_attachments.reduce(0) { |sum, m| sum + (m.file_file_size || 0) } Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id)) unless options[:dry_run] end else @@ -49,6 +53,7 @@ def remove Maintenance::UncacheMediaWorker.new.perform(m) unless options[:dry_run] options[:verbose] ? say(m.id) : say('.', :green, false) processed += 1 + size += m.file_file_size end end end @@ -56,9 +61,9 @@ def remove say if options[:background] - say("Scheduled the deletion of #{queued} media attachments #{dry_run}", :green, true) + say("Scheduled the deletion of #{queued} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true) else - say("Removed #{processed} media attachments #{dry_run}", :green, true) + say("Removed #{processed} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true) end end end From 0f436de035d848ce481a1d21a774031eef41f10d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:08:57 +0100 Subject: [PATCH 021/177] Add "Show thread" link to self-replies (#9228) Fix #4716 --- app/javascript/mastodon/components/status.js | 9 ++++++++- app/javascript/mastodon/components/status_action_bar.js | 5 +---- app/javascript/mastodon/components/status_list.js | 2 ++ .../mastodon/features/status/components/action_bar.js | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 9fa8cc00846820..fd0780025a52e6 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -67,6 +67,7 @@ class Status extends ImmutablePureComponent { unread: PropTypes.bool, onMoveUp: PropTypes.func, onMoveDown: PropTypes.func, + showThread: PropTypes.bool, }; // Avoid checking props that are functions (and whose equality will always @@ -168,7 +169,7 @@ class Status extends ImmutablePureComponent { let media = null; let statusAvatar, prepend, rebloggedByText; - const { intl, hidden, featured, otherAccounts, unread } = this.props; + const { intl, hidden, featured, otherAccounts, unread, showThread } = this.props; let { status, account, ...other } = this.props; @@ -309,6 +310,12 @@ class Status extends ImmutablePureComponent { {media} + {showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && ( + + )} + diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index e7e5b0a6c02b14..68a1fda2445598 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -148,7 +148,6 @@ class StatusActionBar extends ImmutablePureComponent { let menu = []; let reblogIcon = 'retweet'; - let replyIcon; let replyTitle; menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); @@ -191,10 +190,8 @@ class StatusActionBar extends ImmutablePureComponent { } if (status.get('in_reply_to_id', null) === null) { - replyIcon = 'reply'; replyTitle = intl.formatMessage(messages.reply); } else { - replyIcon = 'reply-all'; replyTitle = intl.formatMessage(messages.replyAll); } @@ -204,7 +201,7 @@ class StatusActionBar extends ImmutablePureComponent { return (
-
{obfuscatedCount(status.get('replies_count'))}
+
{obfuscatedCount(status.get('replies_count'))}
{shareButton} diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 37f21fb4406649..f3e304618e49e5 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -104,6 +104,7 @@ export default class StatusList extends ImmutablePureComponent { onMoveUp={this.handleMoveUp} onMoveDown={this.handleMoveDown} contextType={timelineId} + showThread /> )) ) : null; @@ -117,6 +118,7 @@ export default class StatusList extends ImmutablePureComponent { onMoveUp={this.handleMoveUp} onMoveDown={this.handleMoveDown} contextType={timelineId} + showThread /> )).concat(scrollableContent); } diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index fa6fd56e54564f..565009be2f55db 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -159,7 +159,7 @@ class ActionBar extends React.PureComponent { return (
-
+
{shareButton} From 63f168c3bf26f8c336d966b3619307801cab7cab Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 8 Nov 2018 21:55:59 +0100 Subject: [PATCH 022/177] Fix nil error regression from #9229 in tootctl media remove (#9239) Fix #9237 --- lib/mastodon/media_cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index affc4cedb256ba..99660dd1d9af48 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -53,7 +53,7 @@ def remove Maintenance::UncacheMediaWorker.new.perform(m) unless options[:dry_run] options[:verbose] ? say(m.id) : say('.', :green, false) processed += 1 - size += m.file_file_size + size += m.file_file_size || 0 end end end From f73b7e77dacd94c1d0c7c4bc0c0227eb3159ad19 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 9 Nov 2018 09:08:01 +0100 Subject: [PATCH 023/177] Improve ActiveRecord connection in on_worker_boot (#9238) This is how it looks in the example in the Puma README --- config/puma.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/puma.rb b/config/puma.rb index 5ebf5ed192a654..1afdb1c6dfbc09 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -13,7 +13,9 @@ preload_app! on_worker_boot do - ActiveRecord::Base.establish_connection if defined?(ActiveRecord) + ActiveSupport.on_load(:active_record) do + ActiveRecord::Base.establish_connection + end end plugin :tmp_restart From d06a724b1c097b4e8b7f1fa2591b0753c349a5ad Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 10 Nov 2018 20:42:04 +0100 Subject: [PATCH 024/177] Check that twitter:player is valid before using it (#9254) Fixes #9251 --- app/services/fetch_link_card_service.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 3e77579bbac0fa..38c578de29265e 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -136,14 +136,15 @@ def attempt_opengraph detector = CharlockHolmes::EncodingDetector.new detector.strip_tags = true - guess = detector.detect(@html, @html_charset) - page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) + guess = detector.detect(@html, @html_charset) + page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil)) + player_url = meta_property(page, 'twitter:player') - if meta_property(page, 'twitter:player') + if player_url && !bad_url?(Addressable::URI.parse(player_url)) @card.type = :video @card.width = meta_property(page, 'twitter:player:width') || 0 @card.height = meta_property(page, 'twitter:player:height') || 0 - @card.html = content_tag(:iframe, nil, src: meta_property(page, 'twitter:player'), + @card.html = content_tag(:iframe, nil, src: player_url, width: @card.width, height: @card.height, allowtransparency: 'true', From 886ef1cc384f758944407ac0255afe7d71afc513 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 10 Nov 2018 23:59:51 +0100 Subject: [PATCH 025/177] Fix emoji update date processing (#9255) --- app/lib/activitypub/activity/create.rb | 2 +- app/services/activitypub/process_account_service.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 45079e2b3cf674..9d2ddd3f6e5198 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -177,7 +177,7 @@ def process_emoji(tag) updated = tag['updated'] emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) - return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated) + return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && updated >= emoji.updated_at) emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri) emoji.image_remote_url = image_url diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index c77858f1df8130..5c865dae2f4760 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -232,7 +232,7 @@ def process_emoji(tag) updated = tag['updated'] emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) - return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && emoji.updated_at >= updated) + return unless emoji.nil? || image_url != emoji.image_remote_url || (updated && updated >= emoji.updated_at) emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri) emoji.image_remote_url = image_url From 4ce6ed20211b83d36746f61d4fb7dd001339baa1 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 12 Nov 2018 18:17:50 +0100 Subject: [PATCH 026/177] Perform deep comparison for card data when receiving new props (#9270) Fixes #9226 --- app/javascript/mastodon/features/status/components/card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 743fe779a20063..3474a11e51087b 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -73,7 +73,7 @@ export default class Card extends React.PureComponent { }; componentWillReceiveProps (nextProps) { - if (this.props.card !== nextProps.card) { + if (!this.props.card.equals(nextProps.card)) { this.setState({ embedded: false }); } } From cd8575aef671dd44b4384b79b568f367add43537 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 12 Nov 2018 22:07:31 +0100 Subject: [PATCH 027/177] Fix null error introduced in #9270 (#9275) --- app/javascript/mastodon/features/status/components/card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 3474a11e51087b..235d209b886c06 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -73,7 +73,7 @@ export default class Card extends React.PureComponent { }; componentWillReceiveProps (nextProps) { - if (!this.props.card.equals(nextProps.card)) { + if (!Immutable.is(this.props.card, nextProps.card)) { this.setState({ embedded: false }); } } From a3ef0761602481515207c0cf93cae0119dff4b25 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 13 Nov 2018 14:58:14 +0100 Subject: [PATCH 028/177] Fix race condition causing shallow status with only a "favourited" attribute (#9272) Fixes #9231 --- app/javascript/mastodon/reducers/statuses.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 6e3d830dacbc6c..885cc221cd4d29 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -38,11 +38,11 @@ export default function statuses(state = initialState, action) { case FAVOURITE_REQUEST: return state.setIn([action.status.get('id'), 'favourited'], true); case FAVOURITE_FAIL: - return state.setIn([action.status.get('id'), 'favourited'], false); + return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false); case REBLOG_REQUEST: return state.setIn([action.status.get('id'), 'reblogged'], true); case REBLOG_FAIL: - return state.setIn([action.status.get('id'), 'reblogged'], false); + return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false); case STATUS_MUTE_SUCCESS: return state.setIn([action.id, 'muted'], true); case STATUS_UNMUTE_SUCCESS: From 01a8ab921e6e2b23cfea834c63b2cd196d15ff0b Mon Sep 17 00:00:00 2001 From: mayaeh Date: Fri, 16 Nov 2018 17:47:40 +0900 Subject: [PATCH 029/177] Fix "tootctl media remove" can't count the file size (#9288) * Fixed an issue where "tootctl media remove" can not count the file size. * Fixed the problem pointed out by codeclimate. --- lib/mastodon/media_cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index 99660dd1d9af48..6152d5a0950cca 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -50,10 +50,10 @@ def remove else MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).reorder(nil).find_in_batches do |media_attachments| media_attachments.each do |m| + size += m.file_file_size || 0 Maintenance::UncacheMediaWorker.new.perform(m) unless options[:dry_run] options[:verbose] ? say(m.id) : say('.', :green, false) processed += 1 - size += m.file_file_size || 0 end end end From 6d4438a6ae351e2a8a73c7373c22d28f10838f65 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 16 Nov 2018 15:02:18 +0100 Subject: [PATCH 030/177] Remove intermediary arrays when creating hash maps from results (#9291) --- app/controllers/application_controller.rb | 2 +- app/lib/entity_cache.rb | 2 +- app/lib/formatter.rb | 4 ++-- app/lib/settings/scoped_settings.rb | 4 ++-- app/models/concerns/account_interactions.rb | 4 ++-- app/models/notification.rb | 2 +- app/models/setting.rb | 2 +- app/models/status.rb | 10 +++++----- app/models/trending_tags.rb | 2 +- app/services/batched_remove_status_service.rb | 6 +++--- spec/models/notification_spec.rb | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7bb14aa4c1a4bf..b54e7b008eeb06 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -113,7 +113,7 @@ def cache_collection(raw, klass) klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) unless uncached_ids.empty? - uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h + uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item } uncached.each_value do |item| Rails.cache.write(item, item) diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb index 2aa37389ca627e..8fff544a054a35 100644 --- a/app/lib/entity_cache.rb +++ b/app/lib/entity_cache.rb @@ -21,7 +21,7 @@ def emoji(shortcodes, domain) end unless uncached_ids.empty? - uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).map { |item| [item.shortcode, item] }.to_h + uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).each_with_object({}) { |item, h| h[item.shortcode] = item } uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } end diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index d13884ec818681..05fd9eeb1261ab 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -128,9 +128,9 @@ def encode_custom_emojis(html, emojis, animate = false) return html if emojis.empty? emoji_map = if animate - emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h + emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url) } else - emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h + emojis.each_with_object({}) { |e, h| h[e.shortcode] = full_asset_url(e.image.url(:static)) } end i = -1 diff --git a/app/lib/settings/scoped_settings.rb b/app/lib/settings/scoped_settings.rb index 5ee30825d44d0f..3653ab1149d543 100644 --- a/app/lib/settings/scoped_settings.rb +++ b/app/lib/settings/scoped_settings.rb @@ -31,7 +31,7 @@ def respond_to_missing?(*) def all_as_records vars = thing_scoped - records = vars.map { |r| [r.var, r] }.to_h + records = vars.each_with_object({}) { |r, h| h[r.var] = r } Setting.default_settings.each do |key, default_value| next if records.key?(key) || default_value.is_a?(Hash) @@ -65,7 +65,7 @@ def [](key) class << self def default_settings - defaulting = DEFAULTING_TO_UNSCOPED.map { |k| [k, Setting[k]] }.to_h + defaulting = DEFAULTING_TO_UNSCOPED.each_with_object({}) { |k, h| h[k] = Setting[k] } Setting.default_settings.merge!(defaulting) end end diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index f5f833446ab916..ad2909d9183aec 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -45,9 +45,9 @@ def endorsed_map(target_account_ids, 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 + accounts_map = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain } blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id) - accounts_map.map { |id, domain| [id, blocked_domains[domain]] }.to_h + accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) } end def domain_blocking_map_by_domain(target_domains, account_id) diff --git a/app/models/notification.rb b/app/models/notification.rb index 78b180301a5ba2..4233532d0825e4 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -75,7 +75,7 @@ def reload_stale_associations!(cached_items) return if account_ids.empty? - accounts = Account.where(id: account_ids).map { |a| [a.id, a] }.to_h + accounts = Account.where(id: account_ids).each_with_object({}) { |a, h| h[a.id] = a } cached_items.each do |item| item.from_account = accounts[item.from_account_id] diff --git a/app/models/setting.rb b/app/models/setting.rb index 033d09fd58022e..a5878e96a21cc5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -40,7 +40,7 @@ def [](key) def all_as_records vars = thing_scoped - records = vars.map { |r| [r.var, r] }.to_h + records = vars.each_with_object({}) { |r, h| h[r.var] = r } default_settings.each do |key, default_value| next if records.key?(key) || default_value.is_a?(Hash) diff --git a/app/models/status.rb b/app/models/status.rb index 32fedb924aefcc..a7216f910f0094 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -310,19 +310,19 @@ def as_outbox_timeline(account) end def favourites_map(status_ids, account_id) - Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h + Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true } end def reblogs_map(status_ids, account_id) - select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).map { |s| [s.reblog_of_id, true] }.to_h + select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).each_with_object({}) { |s, h| h[s.reblog_of_id] = true } end def mutes_map(conversation_ids, account_id) - ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).map { |m| [m.conversation_id, true] }.to_h + ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true } end def pins_map(status_ids, account_id) - StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |p| [p.status_id, true] }.to_h + StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true } end def reload_stale_associations!(cached_items) @@ -337,7 +337,7 @@ def reload_stale_associations!(cached_items) return if account_ids.empty? - accounts = Account.where(id: account_ids).map { |a| [a.id, a] }.to_h + accounts = Account.where(id: account_ids).each_with_object({}) { |a, h| h[a.id] = a } cached_items.each do |item| item.account = accounts[item.account_id] diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index c559651c666862..3a8be21649b936 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -18,7 +18,7 @@ def record_use!(tag, account, at_time = Time.now.utc) def get(limit) key = "#{KEY}:#{Time.now.utc.beginning_of_day.to_i}" tag_ids = redis.zrevrange(key, 0, limit - 1).map(&:to_i) - tags = Tag.where(id: tag_ids).to_a.map { |tag| [tag.id, tag] }.to_h + tags = Tag.where(id: tag_ids).to_a.each_with_object({}) { |tag, h| h[tag.id] = tag } tag_ids.map { |tag_id| tags[tag_id] }.compact end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index b8ab58938de8ab..75d75649505b58 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -12,12 +12,12 @@ class BatchedRemoveStatusService < BaseService def call(statuses) statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a } - @mentions = statuses.map { |s| [s.id, s.active_mentions.includes(:account).to_a] }.to_h - @tags = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h + @mentions = statuses.each_with_object({}) { |s, h| h[s.id] = s.active_mentions.includes(:account).to_a } + @tags = statuses.each_with_object({}) { |s, h| h[s.id] = s.tags.pluck(:name) } @stream_entry_batches = [] @salmon_batches = [] - @json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h + @json_payloads = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) } @activity_xml = {} # Ensure that rendered XML reflects destroyed state diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index c781f2a294b584..be5545646f6495 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -101,7 +101,7 @@ before do allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1) allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2) - allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids) + allow(Account).to receive_message_chain(:where, :each_with_object).and_return(accounts_with_ids) end let(:cached_items) do From ecc58c0f2358ea764c4a4ebd7f9daf4c9143ec7a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 16 Nov 2018 19:46:23 +0100 Subject: [PATCH 031/177] Prevent multiple handlers for Delete of Actor from running (#9292) --- app/lib/activitypub/activity.rb | 6 ++++++ app/lib/activitypub/activity/delete.rb | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 999954cb5bc994..0a729011f22560 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -129,4 +129,10 @@ def fetch_remote_original_status ::FetchRemoteStatusService.new.call(@object['url']) end end + + def lock_or_return(key, expire_after = 7.days.seconds) + yield if redis.set(key, true, nx: true, ex: expire_after) + ensure + redis.del(key) + end end diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 457047ac060fb2..8270fed1b20a6b 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -12,8 +12,10 @@ def perform private def delete_person - SuspendAccountService.new.call(@account) - @account.destroy! + lock_or_return("delete_in_progress:#{@account.id}") do + SuspendAccountService.new.call(@account) + @account.destroy! + end end def delete_note From fa02f878fc6fdbc1aae8d3f45e71b4aeb589e7ea Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Nov 2018 10:37:57 +0100 Subject: [PATCH 032/177] Fix filter ID not being a string in REST API (#9303) --- app/serializers/rest/filter_serializer.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/serializers/rest/filter_serializer.rb b/app/serializers/rest/filter_serializer.rb index 3134be371b066b..57205630bbbf84 100644 --- a/app/serializers/rest/filter_serializer.rb +++ b/app/serializers/rest/filter_serializer.rb @@ -3,4 +3,8 @@ class REST::FilterSerializer < ActiveModel::Serializer attributes :id, :phrase, :context, :whole_word, :expires_at, :irreversible + + def id + object.id.to_s + end end From c0736c466c33473b4db55bf59ed6edc0a0020b27 Mon Sep 17 00:00:00 2001 From: Dan Hunsaker Date: Tue, 20 Nov 2018 14:24:35 -0700 Subject: [PATCH 033/177] Update Nginx config for Nanobox apps (#9310) The Nanobox files have gotten out of sync, a touch, with what Masto needs for Nginx settings. This PR updates them accordingly. --- nanobox/nginx-local.conf | 2 +- nanobox/nginx-stream.conf.erb | 2 +- nanobox/nginx-web.conf.erb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nanobox/nginx-local.conf b/nanobox/nginx-local.conf index f56339cac9b5fa..c0e883603d2b00 100644 --- a/nanobox/nginx-local.conf +++ b/nanobox/nginx-local.conf @@ -38,7 +38,7 @@ http { root /app/public; - client_max_body_size 8M; + client_max_body_size 80M; location / { try_files $uri @rails; diff --git a/nanobox/nginx-stream.conf.erb b/nanobox/nginx-stream.conf.erb index 2a047dd9f6dd51..12bcc8ca53ab5b 100644 --- a/nanobox/nginx-stream.conf.erb +++ b/nanobox/nginx-stream.conf.erb @@ -32,7 +32,7 @@ http { listen 8080; add_header Strict-Transport-Security "max-age=31536000"; - add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; + # add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; root /app/public; diff --git a/nanobox/nginx-web.conf.erb b/nanobox/nginx-web.conf.erb index 797201eabf8356..d96f1bfc7a740c 100644 --- a/nanobox/nginx-web.conf.erb +++ b/nanobox/nginx-web.conf.erb @@ -32,11 +32,11 @@ http { listen 8080; add_header Strict-Transport-Security "max-age=31536000"; - add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; + # add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://<%= ENV["LOCAL_DOMAIN"] %>; upgrade-insecure-requests"; root /app/public; - client_max_body_size 8M; + client_max_body_size 80M; location / { try_files $uri @rails; From 2c36d357848c7d7cb64da6fd3464306ea6729da7 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 20 Nov 2018 22:25:04 +0100 Subject: [PATCH 034/177] WebSub: ATOM before RSS (#9302) Hello, The ATOM feed contains the hub declaration for WebSub, but the RSS version does not. RSS/ATOM readers will typically pick whichever version comes first, and will thus not see the WebSub feature. I therefore suggest putting the ATOM version first, as it is more feature-rich than its RSS counterpart is. Clients not compatible with ATOM would not pick it anyway due to the different type attribute. A more complicated alternative would be to declare the WebSub feature in the RSS version as well, using something like the following code, and ensuring that clients subscribed to the RSS version would receive PuSH updates just like those subscribed to the ATOM version. ````xml ``` --- app/views/accounts/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index b825b82cb11899..0ee9dd7deda4dc 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -8,8 +8,8 @@ %meta{ name: 'robots', content: 'noindex' }/ %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/ - %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ + %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ - if @older_url From 15dcb414bf2faaf21a686aa467015d244743c04e Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 20 Nov 2018 22:25:32 +0100 Subject: [PATCH 035/177] Touch account on successful response, change char shown when culled (#9293) Just the color is not enough change since not everyone uses colored terminals. Touching the account makes it so that the account is not in the threshold window in case of running again --- lib/mastodon/accounts_cli.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 142436c19a72e8..9f7870bcd33126 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -242,8 +242,9 @@ def cull end culled += 1 - say('.', :green, false) + say('+', :green, false) else + account.touch # Touch account even during dry run to avoid getting the account into the window again say('.', nil, false) end end From 12bdd7dc5f05e1b9eecf3b56dbcc24cf77bee884 Mon Sep 17 00:00:00 2001 From: valerauko Date: Thu, 22 Nov 2018 20:49:07 +0900 Subject: [PATCH 036/177] Ignore JSON-LD profile in mime type comparison (#9179) Ignore JSON-LD profile in mime type comparison --- app/services/fetch_atom_service.rb | 4 ++-- spec/services/fetch_atom_service_spec.rb | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 550e75f3344b17..d6508a9888a69f 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -29,7 +29,7 @@ def process(url, terminal = false) def perform_request(&block) accept = 'text/html' - accept = 'application/activity+json, application/ld+json, application/atom+xml, ' + accept unless @unsupported_activity + accept = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/atom+xml, ' + accept unless @unsupported_activity Request.new(:get, @url).add_headers('Accept' => accept).perform(&block) end @@ -39,7 +39,7 @@ def process_response(response, terminal = false) if response.mime_type == 'application/atom+xml' [@url, { prefetched_body: response.body_with_limit }, :ostatus] - elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) + elsif ['application/activity+json', 'application/ld+json'].include?(response.mime_type) body = response.body_with_limit json = body_to_json(body) if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present? diff --git a/spec/services/fetch_atom_service_spec.rb b/spec/services/fetch_atom_service_spec.rb index 30e5b0935a3763..495540004e9504 100644 --- a/spec/services/fetch_atom_service_spec.rb +++ b/spec/services/fetch_atom_service_spec.rb @@ -60,8 +60,15 @@ it { is_expected.to eq [url, { :prefetched_body => "" }, :ostatus] } end - context 'content_type is json' do - let(:content_type) { 'application/activity+json' } + context 'content_type is activity+json' do + let(:content_type) { 'application/activity+json; charset=utf-8' } + let(:body) { json } + + it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] } + end + + context 'content_type is ld+json with profile' do + let(:content_type) { 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' } let(:body) { json } it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] } From a2cda74ba3cf6690f257ae612f28e890b7df2237 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Nov 2018 20:12:04 +0100 Subject: [PATCH 037/177] Fix connect timeout not being enforced (#9329) * Fix connect timeout not being enforced The loop was catching the timeout exception that should stop execution, so the next IP would no longer be within a timed block, which led to requests taking much longer than 10 seconds. * Use timeout on each IP attempt, but limit to 2 attempts * Fix code style issue * Do not break Request#perform if no block given * Update method stub in spec for Request * Move timeout inside the begin/rescue block * Use Resolv::DNS with timeout of 1 to get IP addresses * Update Request spec to stub Resolv::DNS instead of Addrinfo * Fix Resolve::DNS stubs in Request spec --- app/lib/request.rb | 32 +++++++++++++++++++++++--------- spec/lib/request_spec.rb | 17 +++++++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 36c211dbfe5010..bb6ef4661a45a9 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -2,6 +2,7 @@ require 'ipaddr' require 'socket' +require 'resolv' class Request REQUEST_TARGET = '(request-target)' @@ -45,7 +46,7 @@ def perform end begin - yield response.extend(ClientLimit) + yield response.extend(ClientLimit) if block_given? ensure http_client.close end @@ -94,7 +95,7 @@ def key_id end def timeout - { write: 10, connect: 10, read: 10 } + { connect: nil, read: 10, write: 10 } end def http_client @@ -139,16 +140,29 @@ def body_with_limit(limit = 1.megabyte) class Socket < TCPSocket class << self def open(host, *args) - return super host, *args if thru_hidden_service? host + return super(host, *args) if thru_hidden_service?(host) + outer_e = nil - Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address| - begin - raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address) - return super address.ip_address, *args - rescue => e - outer_e = e + + Resolv::DNS.open do |dns| + dns.timeouts = 1 + + addresses = dns.getaddresses(host).take(2) + time_slot = 10.0 / addresses.size + + addresses.each do |address| + begin + raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s)) + + ::Timeout.timeout(time_slot, HTTP::TimeoutError) do + return super(address.to_s, *args) + end + rescue => e + outer_e = e + end end end + raise outer_e if outer_e end diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index 8cc5d90ce31db2..2d300f18d6c714 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -48,9 +48,11 @@ end it 'executes a HTTP request when the first address is private' do - allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) - .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) - .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:4860:4860::8844"], :PF_INET6, :SOCK_STREAM)) + resolver = double + + allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844)) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) expect { |block| subject.perform &block }.to yield_control expect(a_request(:get, 'http://example.com')).to have_been_made.once @@ -81,9 +83,12 @@ end it 'raises Mastodon::ValidationError' do - allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM) - .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM)) - .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:db8::face"], :PF_INET6, :SOCK_STREAM)) + resolver = double + + allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face)) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) + expect { subject.perform }.to raise_error Mastodon::ValidationError end end From 404dc97fb013b7f835df65dfc22d07f68e482e23 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Nov 2018 22:32:20 +0100 Subject: [PATCH 038/177] Bump version to 2.6.2 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8bf3272be4e2..e5eba30d54a736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,40 @@ Changelog All notable changes to this project will be documented in this file. +## [2.6.2] - 2018-11-23 +### Added + +- Add Page to whitelisted ActivityPub types (#9188) +- Add 20px to column width in web UI (#9227) +- Add amount of freed disk space in `tootctl media remove` (#9229, #9239, #9288) +- Add "Show thread" link to self-replies (#9228) + +### Changed + +- Change order of Atom and RSS links so Atom is first (#9302) +- Change Nginx configuration for Nanobox apps (#9310) +- Change the follow action to appear instant in web UI (#9220) +- Change how the ActiveRecord connection is instantiated in on_worker_boot (#9238) +- Change `tootctl accounts cull` to always touch accounts so they can be skipped (#9293) +- Change mime type comparison to ignore JSON-LD profile (#9179) + +### Fixed + +- Fix web UI crash when conversation has no last status (#9207) +- Fix follow limit validator reporting lower number past threshold (#9230) +- Fix form validation flash message color and input borders (#9235) +- Fix invalid twitter:player cards being displayed (#9254) +- Fix emoji update date being processed incorrectly (#9255) +- Fix playing embed resetting if status is reloaded in web UI (#9270, #9275) +- Fix web UI crash when favouriting a deleted status (#9272) +- Fix intermediary arrays being created for hash maps (#9291) +- Fix filter ID not being a string in REST API (#9303) + +### Security + +- Fix multiple remote account deletions being able to deadlock the database (#9292) +- Fix HTTP connection timeout of 10s not being enforced (#9329) + ## [2.6.1] - 2018-10-30 ### Fixed diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 2e39ad01ed5191..4a7987203fc7eb 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ def minor end def patch - 1 + 2 end def pre From ec20a5d53aa5d234498d0140ce772cd9f027adfb Mon Sep 17 00:00:00 2001 From: Hugo Gameiro Date: Tue, 27 Nov 2018 11:19:12 +0000 Subject: [PATCH 039/177] add loglevel to ffmpeg in gif upload (#9368) --- app/models/media_attachment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 1bfe02fd68a037..62a11185a369c3 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -59,6 +59,7 @@ class MediaAttachment < ApplicationRecord format: 'mp4', convert_options: { output: { + 'loglevel' => 'fatal', 'movflags' => 'faststart', 'pix_fmt' => 'yuv420p', 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', From 49f49cf367b6fb8413b1967870a709a5e31c9b71 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 27 Nov 2018 12:28:01 +0100 Subject: [PATCH 040/177] Allow hyphens in the middle of remote user names (#9345) Fixes #9309 This only allows hyphens in the middle of a username, much like dots, although I don't have a compelling reason to do so other than keeping the changes minimal. --- app/models/account.rb | 2 +- spec/models/account_spec.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 44963f3e636905..acba6770b29593 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -49,7 +49,7 @@ # class Account < ApplicationRecord - USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i + USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i include AccountAvatar diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 60d13d32e90446..b25b66d2dc6384 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -618,9 +618,15 @@ expect(account).not_to model_have_error_on_field(:username) end - it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do + it 'is valid even if the username contains hyphens' do account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor') account.valid? + expect(account).to_not model_have_error_on_field(:username) + end + + it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do + account = Fabricate.build(:account, domain: 'domain', username: 'the doctor') + account.valid? expect(account).to model_have_error_on_field(:username) end From cc0c1674f03cfbbe3ee28208429f216db1678731 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Nov 2018 18:13:36 +0100 Subject: [PATCH 041/177] Fix nil error when no DNS addresses are found for host (#9379) --- app/lib/request.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index bb6ef4661a45a9..024fce88a6cde3 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -163,7 +163,11 @@ def open(host, *args) end end - raise outer_e if outer_e + if outer_e + raise outer_e + else + raise SocketError, "No address for #{host}" + end end alias new open From 58108b448159a8796500f2d3441cfe7b1ca99a67 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Nov 2018 18:49:37 +0100 Subject: [PATCH 042/177] Don't count suspended users in user count (#9380) Fix #7637 --- app/presenters/instance_presenter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index 9a9157f1bad645..bf33c7287c7749 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -18,7 +18,7 @@ def contact_account end def user_count - Rails.cache.fetch('user_count') { User.confirmed.count } + Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count } end def status_count From 442f335504129f99bc405539967df628d4701761 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Nov 2018 19:15:08 +0100 Subject: [PATCH 043/177] Skip deliveries to inboxes that have already been marked as unavailable (#9358) --- app/workers/activitypub/delivery_worker.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index adbb496d9b3442..f9c385ea3e20ab 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -11,6 +11,8 @@ class ActivityPub::DeliveryWorker HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze def perform(json, source_account_id, inbox_url, options = {}) + return if DeliveryFailureTracker.unavailable?(inbox_url) + @options = options.with_indifferent_access @json = json @source_account = Account.find(source_account_id) From 34de90c486176992d8bc3d0f5f9f1156509d448c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Nov 2018 19:46:05 +0100 Subject: [PATCH 044/177] Fix TLS handshake timeout not being enforced (#9381) Follow-up to #9329 --- app/lib/request.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index 024fce88a6cde3..4a81773e354484 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -4,6 +4,16 @@ require 'socket' require 'resolv' +# Monkey-patch the HTTP.rb timeout class to avoid using a timeout block +# around the Socket#open method, since we use our own timeout blocks inside +# that method +class HTTP::Timeout::PerOperation + def connect(socket_class, host, port, nodelay = false) + @socket = socket_class.open(host, port) + @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay + end +end + class Request REQUEST_TARGET = '(request-target)' @@ -95,7 +105,11 @@ def key_id end def timeout - { connect: nil, read: 10, write: 10 } + # We enforce a 1s timeout on DNS resolving, 10s timeout on socket opening + # and 5s timeout on the TLS handshake, meaning the worst case should take + # about 16s in total + + { connect: 5, read: 10, write: 10 } end def http_client From a1216e631537b1fbf07f2c8724ac05e757800be6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 30 Nov 2018 03:08:37 +0100 Subject: [PATCH 045/177] Bump version to 2.6.3 --- CHANGELOG.md | 19 +++++++++++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5eba30d54a736..47fa6a25df0127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ Changelog All notable changes to this project will be documented in this file. +## [2.6.3] - 2018-11-30 +### Added + +- Add hyphen to characters allowed in remote usernames (#9345) + +### Changed + +- Change server user count to exclude suspended accounts (#9380) + +### Fixed + +- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer (#9368) +- Fix missing DNS records raising the wrong kind of exception (#9379) +- Fix already queued deliveries still trying to reach inboxes marked as unavailable (#9358) + +### Security + +- Fix TLS handshake timeout not being enforced (#9381) + ## [2.6.2] - 2018-11-23 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 4a7987203fc7eb..7157ae90e8a06c 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ def minor end def patch - 2 + 3 end def pre From 82570019ba01ec11b93f62921b3fc92f369ec53c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 30 Nov 2018 19:16:32 +0100 Subject: [PATCH 046/177] Remove npm-run-all dependency (#9401) Fix #9359 --- package.json | 3 +- yarn.lock | 141 ++------------------------------------------------- 2 files changed, 6 insertions(+), 138 deletions(-) diff --git a/package.json b/package.json index 7b162e57684ec8..ee138c374c35fc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack", "manage:translations": "node ./config/webpack/translationRunner.js", "start": "node ./streaming/index.js", - "test": "npm-run-all test:lint test:jest", + "test": "npm run test:lint && npm run test:jest", "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ streaming/", "test:jest": "cross-env NODE_ENV=test jest --coverage" }, @@ -76,7 +76,6 @@ "mini-css-extract-plugin": "^0.4.2", "mkdirp": "^0.5.1", "node-sass": "^4.9.2", - "npm-run-all": "^4.1.2", "npmlog": "^4.1.2", "object-assign": "^4.1.1", "object-fit-images": "^3.2.3", diff --git a/yarn.lock b/yarn.lock index 38a91d10bb5b15..eb7a6c2c29c41e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1109,11 +1109,6 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -1137,16 +1132,6 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= - array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -2411,7 +2396,7 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^6.0.0, cross-spawn@^6.0.4, cross-spawn@^6.0.5: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -2920,7 +2905,7 @@ double-ended-queue@^2.1.0-0: resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= -duplexer@^0.1.1, duplexer@~0.1.1: +duplexer@^0.1.1: version "0.1.1" resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= @@ -3079,7 +3064,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.10.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: +es-abstract@^1.10.0, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.12.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== @@ -3329,20 +3314,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-stream@~3.3.0: - version "3.3.6" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" - integrity sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g== - dependencies: - duplexer "^0.1.1" - flatmap-stream "^0.1.0" - from "^0.1.7" - map-stream "0.0.7" - pause-stream "^0.0.11" - split "^1.0.1" - stream-combiner "^0.2.2" - through "^2.3.8" - eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -3744,11 +3715,6 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" -flatmap-stream@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.0.tgz#ed54e01422cd29281800914fcb968d58b685d5f1" - integrity sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA== - flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -3846,11 +3812,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -from@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= - fs-extra@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6" @@ -5642,16 +5603,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" @@ -5841,11 +5792,6 @@ map-obj@^1.0.0, map-obj@^1.0.1: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= -map-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" - integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg= - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -5905,11 +5851,6 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= - meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -6462,21 +6403,6 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" -npm-run-all@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.3.tgz#49f15b55a66bb4101664ce270cb18e7103f8f185" - integrity sha512-aOG0N3Eo/WW+q6sUIdzcV2COS8VnTZCmdji0VQIAZF3b+a3YWb0AD0vFIyjKec18A7beLGbaQ5jFTNI2bPt9Cg== - dependencies: - ansi-styles "^3.2.0" - chalk "^2.1.0" - cross-spawn "^6.0.4" - memorystream "^0.3.1" - minimatch "^3.0.4" - ps-tree "^1.1.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6979,20 +6905,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -pause-stream@^0.0.11: - version "0.0.11" - resolved "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= - dependencies: - through "~2.3" - pbkdf2@^3.0.3: version "3.0.16" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" @@ -7663,13 +7575,6 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -ps-tree@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" - integrity sha1-tCGyQUDWID8e08dplrRCewjowBQ= - dependencies: - event-stream "~3.3.0" - pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -8115,15 +8020,6 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -8827,16 +8723,6 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -9039,7 +8925,7 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@^1.0.0, split@^1.0.1: +split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== @@ -9124,14 +9010,6 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-combiner@^0.2.2: - version "0.2.2" - resolved "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" - integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg= - dependencies: - duplexer "~0.1.1" - through "~2.3.4" - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -9181,15 +9059,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string.prototype.padend@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" - integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= - dependencies: - define-properties "^1.1.2" - es-abstract "^1.4.3" - function-bind "^1.0.2" - string.prototype.trim@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" @@ -9407,7 +9276,7 @@ through2@^2.0.0: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: +through@2, through@^2.3.6: version "2.3.8" resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= From 13979a84f93ab07dc002111f9a86eb358260dd00 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 30 Nov 2018 19:54:24 +0100 Subject: [PATCH 047/177] Bump version to 2.6.4 --- CHANGELOG.md | 5 +++++ lib/mastodon/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47fa6a25df0127..d09dc452a0052d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Changelog All notable changes to this project will be documented in this file. +## [2.6.4] - 2018-11-30 +### Fixed + +- Fix yarn dependencies not installing due to yanked event-stream package (#9401) + ## [2.6.3] - 2018-11-30 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 7157ae90e8a06c..9c2542ac7eb981 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ def minor end def patch - 3 + 4 end def pre From f13d08314e1d683fd40b3cb48c667aced222ce28 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 2 Dec 2018 16:46:13 +0100 Subject: [PATCH 048/177] Preload common JSON-LD contexts (#9412) Fixes #9411 --- Gemfile | 1 + Gemfile.lock | 5 ++++ config/initializers/json_ld.rb | 3 ++ lib/json_ld/security.rb | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 config/initializers/json_ld.rb create mode 100644 lib/json_ld/security.rb diff --git a/Gemfile b/Gemfile index bf23015e636126..6e1c9403a5ce3d 100644 --- a/Gemfile +++ b/Gemfile @@ -90,6 +90,7 @@ gem 'webpacker', '~> 3.5' gem 'webpush' gem 'json-ld', '~> 2.2' +gem 'json-ld-preloaded', '~> 2.2' gem 'rdf-normalize', '~> 0.3' group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 91a2e828114f32..d83ad6d8b2e239 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -291,6 +291,10 @@ GEM json-ld (2.2.1) multi_json (~> 1.12) rdf (>= 2.2.8, < 4.0) + json-ld-preloaded (2.2.3) + json-ld (>= 2.2, < 4.0) + multi_json (~> 1.12) + rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -696,6 +700,7 @@ DEPENDENCIES idn-ruby iso-639 json-ld (~> 2.2) + json-ld-preloaded (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) diff --git a/config/initializers/json_ld.rb b/config/initializers/json_ld.rb new file mode 100644 index 00000000000000..d5575d135464f1 --- /dev/null +++ b/config/initializers/json_ld.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative '../../lib/json_ld/security' diff --git a/lib/json_ld/security.rb b/lib/json_ld/security.rb new file mode 100644 index 00000000000000..1230206f035d1e --- /dev/null +++ b/lib/json_ld/security.rb @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true +# This file generated automatically from https://w3id.org/security/v1 +require 'json/ld' +class JSON::LD::Context + add_preloaded("https://w3id.org/security/v1") do + new(processingMode: "json-ld-1.0", term_definitions: { + "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true), + "EcdsaKoblitzSignature2016" => TermDefinition.new("EcdsaKoblitzSignature2016", id: "https://w3id.org/security#EcdsaKoblitzSignature2016", simple: true), + "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true), + "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true), + "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true), + "LinkedDataSignature2016" => TermDefinition.new("LinkedDataSignature2016", id: "https://w3id.org/security#LinkedDataSignature2016", simple: true), + "authenticationTag" => TermDefinition.new("authenticationTag", id: "https://w3id.org/security#authenticationTag", simple: true), + "canonicalizationAlgorithm" => TermDefinition.new("canonicalizationAlgorithm", id: "https://w3id.org/security#canonicalizationAlgorithm", simple: true), + "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true), + "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true), + "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true), + "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), + "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"), + "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true), + "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true), + "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true), + "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true), + "encryptionKey" => TermDefinition.new("encryptionKey", id: "https://w3id.org/security#encryptionKey", simple: true), + "expiration" => TermDefinition.new("expiration", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), + "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), + "id" => TermDefinition.new("id", id: "@id", simple: true), + "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true), + "iterationCount" => TermDefinition.new("iterationCount", id: "https://w3id.org/security#iterationCount", simple: true), + "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true), + "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true), + "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"), + "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true), + "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"), + "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true), + "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"), + "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true), + "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"), + "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), + "salt" => TermDefinition.new("salt", id: "https://w3id.org/security#salt", simple: true), + "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true), + "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true), + "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signingAlgorithm", simple: true), + "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true), + "type" => TermDefinition.new("type", id: "@type", simple: true), + "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true) + }) + end +end From e625425c8feb611e037c62855845b38ceb4b35c1 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 21 Nov 2018 17:02:58 +0100 Subject: [PATCH 049/177] Include replies to list owner and replies to list members in list statuses (#9324) --- app/lib/feed_manager.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 3d7db27211a63d..31ff53860b4cb4 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -40,7 +40,11 @@ def unpush_from_home(account, status) end def push_to_list(list, status) - return false if status.reply? && status.in_reply_to_account_id != status.account_id + if status.reply? && status.in_reply_to_account_id != status.account_id + should_filter = status.in_reply_to_account_id != list.account_id + should_filter &&= !ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists? + return false if should_filter + end return false unless add_to_feed(:list, list.id, status) trim(:list, list.id) PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}") From 887f9de6dc12ef405f92b94eeaa775df74ebb1ef Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 2 Dec 2018 16:52:40 +0100 Subject: [PATCH 050/177] Bump version to 2.6.5 --- CHANGELOG.md | 9 +++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d09dc452a0052d..1e24df451b2b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ Changelog All notable changes to this project will be documented in this file. +## [2.6.5] - 2018-12-01 +### Changed + +- Change lists to display replies to others on the list and list owner (#9324) + +### Fixed + +- Fix failures caused by commonly-used JSON-LD contexts being unavailable (#9412) + ## [2.6.4] - 2018-11-30 ### Fixed diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 9c2542ac7eb981..cb5c2440cb03e8 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ def minor end def patch - 4 + 5 end def pre From 237952c10d0989ec7510a9ec190baf87cacc40c3 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 13 Nov 2018 17:30:15 +0100 Subject: [PATCH 051/177] Only stream local-only toots to logged-in users --- streaming/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/streaming/index.js b/streaming/index.js index b4d09d0ad2c7d6..406ee09e1d86dd 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -334,6 +334,12 @@ const startWorker = (workerId) => { return; } + // Only send local-only statuses to logged-in users + if (payload.local_only && !req.accountId) { + log.silly(req.requestId, `Message ${payload.id} filtered because it was local-only`); + return; + } + // Only messages that may require filtering are statuses, since notifications // are already personalized and deletes do not matter if (!needsFiltering || event !== 'update') { From e8161a11fc7a9d9413fe4d9aa794ef734522ded0 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Wed, 9 Jan 2019 11:12:49 +0100 Subject: [PATCH 052/177] Normalize translations --- app/javascript/mastodon/locales/lv.json | 6 ++++++ app/javascript/mastodon/locales/ms.json | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 0d510d011fd988..70df099706c7f2 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -129,6 +129,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -304,6 +309,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 0d510d011fd988..70df099706c7f2 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -129,6 +129,11 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "federation.change": "Adjust status federation", + "federation.federated.long": "Allow toot to reach other instances", + "federation.federated.short": "Federated", + "federation.local_only.long": "Restrict this toot only to my instance", + "federation.local_only.short": "Local-only", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -304,6 +309,7 @@ "status.favourite": "Favourite", "status.filtered": "Filtered", "status.load_more": "Load more", + "status.local_only": "This post is only visible by other users of your instance", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", "status.more": "More", From 88a1d0cdb4df422594c3ffb335ad794df4e55ea7 Mon Sep 17 00:00:00 2001 From: Sam Schlinkert Date: Mon, 28 Jan 2019 16:57:42 -0500 Subject: [PATCH 053/177] Bumps copyright year in README.md to 2019 (#9939) This is so incredibly small, but assuming this is a needed change. Might want to check year in other files. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ad6894ca7332a..03c8472b9b17ab 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ You can open issues for bugs you've found or features you think are missing. You ## License -Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) +Copyright (C) 2016-2019 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. From e31970b924a7eaa1279708919b2743a15fb099f0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 1 Feb 2019 00:15:38 +0100 Subject: [PATCH 054/177] Fix link color in high-contrast theme, add underlines (#9949) Improve sorting of default themes in the dropdown --- app/javascript/styles/contrast/diff.scss | 51 ++++++++++++++++++++++++ config/locales/en.yml | 6 +-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index eee9ecc3ef7f27..7d8993a50802a1 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -12,3 +12,54 @@ } } } + +.status__content a, +.reply-indicator__content a { + color: lighten($ui-highlight-color, 12%); + text-decoration: underline; + + &.mention { + text-decoration: none; + } + + &.mention span { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + + &.status__content__spoiler-link { + color: $secondary-text-color; + text-decoration: none; + } +} + +.status__content__read-more-button { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } +} + +.getting-started__footer a { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 10749b21b4d870..ba413f8fc2e6de 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -929,9 +929,9 @@ en:

Originally adapted from the Discourse privacy policy.

title: "%{instance} Terms of Service and Privacy Policy" themes: - contrast: High contrast - default: Mastodon - mastodon-light: Mastodon (light) + contrast: Mastodon (High contrast) + default: Mastodon (Dark) + mastodon-light: Mastodon (Light) time: formats: default: "%b %d, %Y, %H:%M" From 687a0cbcb036255d02aa1fab5132c51da2adc888 Mon Sep 17 00:00:00 2001 From: Clar Charr Date: Thu, 31 Jan 2019 07:45:15 -0500 Subject: [PATCH 055/177] Replace unlock-alt icon with unlock (#9952) --- app/helpers/stream_entries_helper.rb | 2 +- app/javascript/mastodon/components/account.js | 2 +- app/javascript/mastodon/components/domain.js | 2 +- app/javascript/mastodon/features/account/components/header.js | 2 +- .../mastodon/features/compose/components/privacy_dropdown.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 033d435c497809..7a74c0b7d058f1 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -170,7 +170,7 @@ def fa_visibility_icon(status) when 'public' fa_icon 'globe fw' when 'unlisted' - fa_icon 'unlock-alt fw' + fa_icon 'unlock fw' when 'private' fa_icon 'lock fw' when 'direct' diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 206030c006bb34..2705a6001341bf 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -88,7 +88,7 @@ class Account extends ImmutablePureComponent { if (requested) { buttons = ; } else if (blocking) { - buttons = ; + buttons = ; } else if (muting) { let hidingNotificationsButton; if (account.getIn(['relationship', 'muting_notifications'])) { diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js index 24f80e7888a61c..85729ca9438ea2 100644 --- a/app/javascript/mastodon/components/domain.js +++ b/app/javascript/mastodon/components/domain.js @@ -32,7 +32,7 @@ class Account extends ImmutablePureComponent {
- +
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 2ab25cde44777c..4f6d0712d11054 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -132,7 +132,7 @@ class Header extends ImmutablePureComponent { } else if (account.getIn(['relationship', 'blocking'])) { actionBtn = (
- +
); } diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index 5698765d94f2b5..e8f57a4661b201 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -214,7 +214,7 @@ class PrivacyDropdown extends React.PureComponent { this.options = [ { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, - { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, + { icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, ]; From fdf819b83e820576164074b6726cb6ffdb4a47f6 Mon Sep 17 00:00:00 2001 From: Jakub Mendyk Date: Sat, 2 Feb 2019 19:01:18 +0100 Subject: [PATCH 056/177] Allow most kinds of characters in URL query (fixes #8408) (#8447) * Allow unicode characters in URL query strings Fixes #8408 * Alternative approach to unicode support in urls Adds PoC/idea to approch this problem. --- app/lib/formatter.rb | 39 +++++++++++++++++++++++++++++++++++++- spec/lib/formatter_spec.rb | 32 ++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 05fd9eeb1261ab..2e358716968a4b 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -99,7 +99,7 @@ def encode(html) end def encode_and_link_urls(html, accounts = nil, options = {}) - entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false) + entities = utf8_friendly_extractor(html, extract_url_without_protocol: false) if accounts.is_a?(Hash) options = accounts @@ -199,6 +199,43 @@ def rewrite(text, entities) result.flatten.join end + def utf8_friendly_extractor(text, options = {}) + old_to_new_index = [0] + + escaped = text.chars.map do |c| + output = c.ord.to_s(16).length > 2 ? CGI.escape(c) : c + old_to_new_index << old_to_new_index.last + output.length + output + end.join + + # Note: I couldn't obtain list_slug with @user/list-name format + # for mention so this requires additional check + special = Extractor.extract_entities_with_indices(escaped, options).map do |extract| + # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present + key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first + + new_indices = [ + old_to_new_index.find_index(extract[:indices].first), + old_to_new_index.find_index(extract[:indices].last), + ] + + has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key) + value_indices = [ + new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $ + new_indices.last - 1, + ] + + next extract.merge( + :indices => new_indices, + key => text[value_indices.first..value_indices.last] + ) + end + + standard = Extractor.extract_entities_with_indices(text, options) + + Extractor.remove_overlapping_entities(special + standard) + end + def link_to_url(entity, options = {}) url = Addressable::URI.parse(entity[:url]) html_attrs = { target: '_blank', rel: 'nofollow noopener' } diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 0c1efe7c3cc763..9872d375679dc0 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -74,10 +74,36 @@ end context 'given a URL with a query string' do - let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } + context 'with escaped unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } - it 'matches the full URL' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + end + end + + context 'with unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' } + + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"' + end + end + + context 'with unicode character at the end' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' } + + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"' + end + end + + context 'with escaped and not escaped unicode characters' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' } + + it 'preserves escaped unicode characters' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"' + end end end From a742a09530b6bdca78713e14dbab51bc3a56d222 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Tue, 5 Feb 2019 06:25:42 +0900 Subject: [PATCH 057/177] Fix authorized applications list page design (#9969) --- app/controllers/oauth/authorized_applications_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 0c28d194bc82c8..f3d2353669464c 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :store_current_location before_action :authenticate_resource_owner! + before_action :set_body_classes include Localized @@ -15,6 +16,10 @@ def destroy private + def set_body_classes + @body_classes = 'admin' + end + def store_current_location store_location_for(:user, request.url) end From 5e7c75cfd328af81a557985e1b5b2c70e6c68645 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Tue, 5 Feb 2019 07:14:57 +0900 Subject: [PATCH 058/177] Fix not showing custom emojis in share page emoji picker (#9970) --- app/javascript/mastodon/containers/compose_container.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js index 5ee1d2f141ff6f..7bc7bbaa4dc29d 100644 --- a/app/javascript/mastodon/containers/compose_container.js +++ b/app/javascript/mastodon/containers/compose_container.js @@ -7,6 +7,7 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import Compose from '../features/standalone/compose'; import initialState from '../initial_state'; +import { fetchCustomEmojis } from '../actions/custom_emojis'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -17,6 +18,8 @@ if (initialState) { store.dispatch(hydrateStore(initialState)); } +store.dispatch(fetchCustomEmojis()); + export default class TimelineContainer extends React.PureComponent { static propTypes = { From cd36ff43fd214b6b1447e88457b5a3f56461c1dd Mon Sep 17 00:00:00 2001 From: trwnh Date: Mon, 4 Feb 2019 21:46:18 -0600 Subject: [PATCH 059/177] [UI] Fix whitespace being applied to div instead of p (#9968) * fix large line breaks * fix ascii art posts --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 10e094648197e6..32fd773859e758 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -638,7 +638,6 @@ font-weight: 400; overflow: hidden; text-overflow: ellipsis; - white-space: pre-wrap; padding-top: 2px; color: $primary-text-color; @@ -662,6 +661,7 @@ p { margin-bottom: 20px; + white-space: pre-wrap; &:last-child { margin-bottom: 0; From edde07f5ab235d6ceca95db66bb53161d372f830 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 5 Feb 2019 15:11:35 +0100 Subject: [PATCH 060/177] =?UTF-8?q?Hide=20misleading=20=E2=80=9CYou=20will?= =?UTF-8?q?=20be=20sent=20a=20confirmation=20e-mail=E2=80=9D=20hint=20from?= =?UTF-8?q?=20admin=20view=20(#9973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks @wryk for noticing this issue. --- app/views/admin/change_emails/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/change_emails/show.html.haml b/app/views/admin/change_emails/show.html.haml index 6febef9b1a811a..6ff0d785ed21c9 100644 --- a/app/views/admin/change_emails/show.html.haml +++ b/app/views/admin/change_emails/show.html.haml @@ -3,7 +3,7 @@ = simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f| .fields-group - = f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email') + = f.input :email, wrapper: :with_label, hint: false, disabled: true, label: t('admin.accounts.change_email.current_email') .fields-group = f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email') From e2afe5fdfba352b35731acf028bad54f32223282 Mon Sep 17 00:00:00 2001 From: abcang Date: Wed, 6 Feb 2019 10:50:52 +0900 Subject: [PATCH 061/177] Fix Tombstone.delete_all ArgumentError (#9978) --- app/services/activitypub/process_account_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 487456f3afcec2..5e33084282d93b 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -212,7 +212,7 @@ def key_changed? end def clear_tombstones! - Tombstone.delete_all(account_id: @account.id) + Tombstone.where(account_id: @account.id).delete_all end def protocol_changed? From 2a7c091eae68b06ae4ad7c566878a04f9926ac92 Mon Sep 17 00:00:00 2001 From: Hinaloe Date: Sat, 9 Feb 2019 11:39:38 +0900 Subject: [PATCH 062/177] Only URLs extract with pre-escaped text (#9991) * [test] add japanese hashtag testcase * Only URLs extract with pre-escaped text ( https://github.com/tootsuite/mastodon/issues/9989 ) --- app/lib/formatter.rb | 2 +- spec/lib/formatter_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 2e358716968a4b..6603b8df178c91 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -210,7 +210,7 @@ def utf8_friendly_extractor(text, options = {}) # Note: I couldn't obtain list_slug with @user/list-name format # for mention so this requires additional check - special = Extractor.extract_entities_with_indices(escaped, options).map do |extract| + special = Extractor.extract_urls_with_indices(escaped, options).map do |extract| # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 9872d375679dc0..8fb6695a9bf12c 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -194,6 +194,14 @@ is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag' end end + + context 'given text containing a hashtag with Unicode chars' do + let(:text) { '#hashtagタグ' } + + it 'creates a hashtag link' do + is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ' + end + end end describe '#format_spoiler' do From 6ea4cd5b86b0f96bfa9cfc904ee2c1adb48079a4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 9 Feb 2019 20:13:11 +0100 Subject: [PATCH 063/177] Fix URL linkifier grabbing full-width spaces and quotations (#9997) Fix #9993 Fix #5654 --- app/lib/formatter.rb | 12 +++++++- config/initializers/twitter_regex.rb | 4 +-- spec/lib/formatter_spec.rb | 44 ++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 6603b8df178c91..0653214f53d693 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -199,12 +199,22 @@ def rewrite(text, entities) result.flatten.join end + UNICODE_ESCAPE_BLACKLIST_RE = /\p{Z}|\p{P}/ + def utf8_friendly_extractor(text, options = {}) old_to_new_index = [0] escaped = text.chars.map do |c| - output = c.ord.to_s(16).length > 2 ? CGI.escape(c) : c + output = begin + if c.ord.to_s(16).length > 2 && UNICODE_ESCAPE_BLACKLIST_RE.match(c).nil? + CGI.escape(c) + else + c + end + end + old_to_new_index << old_to_new_index.last + output.length + output end.join diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb index 0e8f5bfeb47353..0ddbbee9828bfd 100644 --- a/config/initializers/twitter_regex.rb +++ b/config/initializers/twitter_regex.rb @@ -1,7 +1,7 @@ module Twitter class Regex - REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}\(\)\?]/iou - REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*';:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou + REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}<>\(\)\?]/iou + REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*"'「」<>;:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou REGEXEN[:valid_url_balanced_parens] = / \( (?: diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 8fb6695a9bf12c..96d2fc7e06c247 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -115,6 +115,22 @@ end end + context 'given a URL in quotation marks' do + let(:text) { '"https://example.com/"' } + + it 'does not match the quotation marks' do + is_expected.to include 'href="https://example.com/"' + end + end + + context 'given a URL in angle brackets' do + let(:text) { '' } + + it 'does not match the angle brackets' do + is_expected.to include 'href="https://example.com/"' + end + end + context 'given a URL with Japanese path string' do let(:text) { 'https://ja.wikipedia.org/wiki/日本' } @@ -131,6 +147,22 @@ end end + context 'given a URL with a full-width space' do + let(:text) { 'https://example.com/ abc123' } + + it 'does not match the full-width space' do + is_expected.to include 'href="https://example.com/"' + end + end + + context 'given a URL in Japanese quotation marks' do + let(:text) { '「[https://example.org/」' } + + it 'does not match the quotation marks' do + is_expected.to include 'href="https://example.org/"' + end + end + context 'given a URL with Simplified Chinese path string' do let(:text) { 'https://baike.baidu.com/item/中华人民共和国' } @@ -150,7 +182,11 @@ context 'given a URL containing unsafe code (XSS attack, visible part)' do let(:text) { %q{http://example.com/bb} } - it 'escapes the HTML in the URL' do + it 'does not include the HTML in the URL' do + is_expected.to include '"http://example.com/b"' + end + + it 'escapes the HTML' do is_expected.to include '<del>b</del>' end end @@ -158,7 +194,11 @@ context 'given a URL containing unsafe code (XSS attack, invisible part)' do let(:text) { %q{http://example.com/blahblahblahblah/a} } - it 'escapes the HTML in the URL' do + it 'does not include the HTML in the URL' do + is_expected.to include '"http://example.com/blahblahblahblah/a"' + end + + it 'escapes the HTML' do is_expected.to include '<script>alert("Hello")</script>' end end From d9f0c7fb841bebfa942ddc5b7aae2857eb1381e3 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 6 Feb 2019 23:36:43 +0100 Subject: [PATCH 064/177] Fix IntersectionObserverArticle not hiding some out-of-view items (#9982) IntersectionObserverArticle is made to save on RAM by avoiding fully rendering items that are far out of view. However, it did not work for items spawned outside the intersection observer. --- .../mastodon/components/intersection_observer_article.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js index de2203a4bc4c69..e453730ba4fccd 100644 --- a/app/javascript/mastodon/components/intersection_observer_article.js +++ b/app/javascript/mastodon/components/intersection_observer_article.js @@ -65,7 +65,7 @@ export default class IntersectionObserverArticle extends React.Component { } updateStateAfterIntersection = (prevState) => { - if (prevState.isIntersecting && !this.entry.isIntersecting) { + if (prevState.isIntersecting !== false && !this.entry.isIntersecting) { scheduleIdleTask(this.hideIfNotIntersecting); } return { From e1dbdf7377f20ace894ee92cf681542cc4b5eddb Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 11 Feb 2019 13:19:59 +0100 Subject: [PATCH 065/177] Fix timeline jumps (#10001) * Avoid two-step rendering of statuses as much as possible Cache width shared by Video player, MediaGallery and Cards at the ScrollableList level, pass it down through StatusList and Notifications. * Adjust scroll when new preview cards appear * Adjust scroll when statuses above the current scroll position are deleted --- .../mastodon/components/media_gallery.js | 10 ++- .../mastodon/components/scrollable_list.js | 28 +++++++- app/javascript/mastodon/components/status.js | 67 +++++++++++++++++-- .../notifications/components/notification.js | 32 ++++++++- .../features/status/components/card.js | 5 +- .../mastodon/features/video/index.js | 4 +- 6 files changed, 134 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index c507920d0ee34a..a2bc9525564b4c 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -194,6 +194,8 @@ class MediaGallery extends React.PureComponent { height: PropTypes.number.isRequired, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + defaultWidth: PropTypes.number, + cacheWidth: PropTypes.func, }; static defaultProps = { @@ -202,6 +204,7 @@ class MediaGallery extends React.PureComponent { state = { visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all', + width: this.props.defaultWidth, }; componentWillReceiveProps (nextProps) { @@ -221,6 +224,7 @@ class MediaGallery extends React.PureComponent { handleRef = (node) => { if (node /*&& this.isStandaloneEligible()*/) { // offsetWidth triggers a layout, so only calculate when we need to + if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth); this.setState({ width: node.offsetWidth, }); @@ -233,8 +237,10 @@ class MediaGallery extends React.PureComponent { } render () { - const { media, intl, sensitive, height } = this.props; - const { width, visible } = this.state; + const { media, intl, sensitive, height, defaultWidth } = this.props; + const { visible } = this.state; + + const width = this.state.width || defaultWidth; let children; diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index fec06e26373357..0376cf85accef9 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -40,6 +40,7 @@ export default class ScrollableList extends PureComponent { state = { fullscreen: null, + cachedMediaWidth: 250, // Default media/card width using default Mastodon theme }; intersectionObserverWrapper = new IntersectionObserverWrapper(); @@ -130,6 +131,20 @@ export default class ScrollableList extends PureComponent { this.handleScroll(); } + getScrollPosition = () => { + if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { + return { height: this.node.scrollHeight, top: this.node.scrollTop }; + } else { + return null; + } + } + + updateScrollBottom = (snapshot) => { + const newScrollTop = this.node.scrollHeight - snapshot; + + this.setScrollTop(newScrollTop); + } + getSnapshotBeforeUpdate (prevProps) { const someItemInserted = React.Children.count(prevProps.children) > 0 && React.Children.count(prevProps.children) < React.Children.count(this.props.children) && @@ -150,6 +165,12 @@ export default class ScrollableList extends PureComponent { } } + cacheMediaWidth = (width) => { + if (width && this.state.cachedMediaWidth !== width) { + this.setState({ cachedMediaWidth: width }); + } + } + componentWillUnmount () { this.clearMouseIdleTimer(); this.detachScrollListener(); @@ -239,7 +260,12 @@ export default class ScrollableList extends PureComponent { intersectionObserverWrapper={this.intersectionObserverWrapper} saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null} > - {child} + {React.cloneElement(child, { + getScrollPosition: this.getScrollPosition, + updateScrollBottom: this.updateScrollBottom, + cachedMediaWidth: this.state.cachedMediaWidth, + cacheMediaWidth: this.cacheMediaWidth, + })} ))} diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 20d838500967f3..fde8e60a423cbc 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -68,6 +68,10 @@ class Status extends ImmutablePureComponent { onMoveUp: PropTypes.func, onMoveDown: PropTypes.func, showThread: PropTypes.bool, + getScrollPosition: PropTypes.func, + updateScrollBottom: PropTypes.func, + cacheMediaWidth: PropTypes.func, + cachedMediaWidth: PropTypes.number, }; // Avoid checking props that are functions (and whose equality will always @@ -79,6 +83,43 @@ class Status extends ImmutablePureComponent { 'hidden', ]; + // Track height changes we know about to compensate scrolling + componentDidMount () { + this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status.get('card'); + } + + getSnapshotBeforeUpdate () { + if (this.props.getScrollPosition) { + return this.props.getScrollPosition(); + } else { + return null; + } + } + + // Compensate height changes + componentDidUpdate (prevProps, prevState, snapshot) { + const doShowCard = !this.props.muted && !this.props.hidden && this.props.status.get('card'); + if (doShowCard && !this.didShowCard) { + this.didShowCard = true; + if (snapshot !== null && this.props.updateScrollBottom) { + if (this.node && this.node.offsetTop < snapshot.top) { + this.props.updateScrollBottom(snapshot.height - snapshot.top); + } + } + } + } + + componentWillUnmount() { + if (this.node && this.props.getScrollPosition) { + const position = this.props.getScrollPosition(); + if (position !== null && this.node.offsetTop < position.top) { + requestAnimationFrame(() => { + this.props.updateScrollBottom(position.height - position.top); + }); + } + } + } + handleClick = () => { if (this.props.onClick) { this.props.onClick(); @@ -165,6 +206,10 @@ class Status extends ImmutablePureComponent { } } + handleRef = c => { + this.node = c; + } + render () { let media = null; let statusAvatar, prepend, rebloggedByText; @@ -179,7 +224,7 @@ class Status extends ImmutablePureComponent { if (hidden) { return ( -
+
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} {status.get('content')}
@@ -194,7 +239,7 @@ class Status extends ImmutablePureComponent { return ( -
+
@@ -242,11 +287,12 @@ class Status extends ImmutablePureComponent { preview={video.get('preview_url')} src={video.get('url')} alt={video.get('description')} - width={239} + width={this.props.cachedMediaWidth} height={110} inline sensitive={status.get('sensitive')} onOpenVideo={this.handleOpenVideo} + cacheWidth={this.props.cacheMediaWidth} /> )} @@ -254,7 +300,16 @@ class Status extends ImmutablePureComponent { } else { media = ( - {Component => } + {Component => ( + + )} ); } @@ -264,6 +319,8 @@ class Status extends ImmutablePureComponent { onOpenMedia={this.props.onOpenMedia} card={status.get('card')} compact + cacheWidth={this.props.cacheMediaWidth} + defaultWidth={this.props.cachedMediaWidth} /> ); } @@ -290,7 +347,7 @@ class Status extends ImmutablePureComponent { return ( -
+
{prepend}
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 97efff69c85208..24843a2b603f0c 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -34,6 +34,10 @@ class Notification extends ImmutablePureComponent { onToggleHidden: PropTypes.func.isRequired, status: PropTypes.option, intl: PropTypes.object.isRequired, + getScrollPosition: PropTypes.func, + updateScrollBottom: PropTypes.func, + cacheMediaWidth: PropTypes.func, + cachedMediaWidth: PropTypes.number, }; handleMoveUp = () => { @@ -128,6 +132,10 @@ class Notification extends ImmutablePureComponent { onMoveDown={this.handleMoveDown} onMoveUp={this.handleMoveUp} contextType='notifications' + getScrollPosition={this.props.getScrollPosition} + updateScrollBottom={this.props.updateScrollBottom} + cachedMediaWidth={this.props.cachedMediaWidth} + cacheMediaWidth={this.props.cacheMediaWidth} /> ); } @@ -148,7 +156,17 @@ class Notification extends ImmutablePureComponent {
-
); @@ -170,7 +188,17 @@ class Notification extends ImmutablePureComponent {
-
); diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 8491299ef49c68..30ed3fc2103839 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -60,6 +60,8 @@ export default class Card extends React.PureComponent { maxDescription: PropTypes.number, onOpenMedia: PropTypes.func.isRequired, compact: PropTypes.bool, + defaultWidth: PropTypes.number, + cacheWidth: PropTypes.func, }; static defaultProps = { @@ -68,7 +70,7 @@ export default class Card extends React.PureComponent { }; state = { - width: 280, + width: this.props.defaultWidth || 280, embedded: false, }; @@ -111,6 +113,7 @@ export default class Card extends React.PureComponent { setRef = c => { if (c) { + if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth); this.setState({ width: c.offsetWidth }); } } diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js index 0d0c24d7194ee8..823bc25ab710ab 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.js @@ -99,6 +99,7 @@ class Video extends React.PureComponent { onCloseVideo: PropTypes.func, detailed: PropTypes.bool, inline: PropTypes.bool, + cacheWidth: PropTypes.func, intl: PropTypes.object.isRequired, }; @@ -108,7 +109,7 @@ class Video extends React.PureComponent { volume: 0.5, paused: true, dragging: false, - containerWidth: false, + containerWidth: this.props.width, fullscreen: false, hovered: false, muted: false, @@ -128,6 +129,7 @@ class Video extends React.PureComponent { this.player = c; if (c) { + if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth); this.setState({ containerWidth: c.offsetWidth, }); From 41ecf80645d465d67dfee54d3cbb14a825ed8953 Mon Sep 17 00:00:00 2001 From: Hinaloe Date: Tue, 12 Feb 2019 13:10:31 +0900 Subject: [PATCH 066/177] Don't focus spiler input when disabled spoiler (#10017) --- .../mastodon/features/compose/components/compose_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index ac458fd2592a79..294dcb5c18b610 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -179,7 +179,7 @@ class ComposeForm extends ImmutablePureComponent {
From d66267508ac316ffc2756a6677fcbae5a44605c2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 12 Feb 2019 05:10:43 +0100 Subject: [PATCH 067/177] =?UTF-8?q?Move=20sending=20account=20Delete=20to?= =?UTF-8?q?=20anyone=20but=20the=20account's=20followers=20to=20the=20pull?= =?UTF-8?q?=CC=80=20queue=20(#10016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/suspend_account_service.rb | 10 +++++++++- .../activitypub/low_priority_delivery_worker.rb | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 app/workers/activitypub/low_priority_delivery_worker.rb diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 1bc2314de7156e..fc3bc03a532db3 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -102,6 +102,10 @@ def distribute_delete_actor! ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url| [delete_actor_json, @account.id, inbox_url] end + + ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url| + [delete_actor_json, @account.id, inbox_url] + end end def delete_actor_json @@ -117,7 +121,11 @@ def delete_actor_json end def delivery_inboxes - Account.inboxes + Relay.enabled.pluck(:inbox_url) + @delivery_inboxes ||= @account.followers.inboxes + Relay.enabled.pluck(:inbox_url) + end + + def low_priority_delivery_inboxes + Account.inboxes - delivery_inboxes end def associations_for_destruction diff --git a/app/workers/activitypub/low_priority_delivery_worker.rb b/app/workers/activitypub/low_priority_delivery_worker.rb new file mode 100644 index 00000000000000..a141b8f7809da1 --- /dev/null +++ b/app/workers/activitypub/low_priority_delivery_worker.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ActivityPub::LowPriorityDeliveryWorker < ActivityPub::DeliveryWorker + sidekiq_options queue: 'pull', retry: 8, dead: false +end From 27310a84a43623da8f08832e30e078ce936f6a1d Mon Sep 17 00:00:00 2001 From: Franck Zoccolo Date: Tue, 12 Feb 2019 14:48:04 +0100 Subject: [PATCH 068/177] Add support for IPv6 only MXes in Email validation (#10009) * Add support for IPv6 only MXes * Fixed email validator tests --- app/validators/email_mx_validator.rb | 1 + spec/validators/email_mx_validator_spec.rb | 38 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/app/validators/email_mx_validator.rb b/app/validators/email_mx_validator.rb index 5b4c684b2c4b44..96fbedcfcf9b81 100644 --- a/app/validators/email_mx_validator.rb +++ b/app/validators/email_mx_validator.rb @@ -24,6 +24,7 @@ def invalid_mx?(value) ([domain] + hostnames).uniq.each do |hostname| ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s }) + ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s }) end end diff --git a/spec/validators/email_mx_validator_spec.rb b/spec/validators/email_mx_validator_spec.rb index bc68f63cfbc16f..48e17a4f1086ef 100644 --- a/spec/validators/email_mx_validator_spec.rb +++ b/spec/validators/email_mx_validator_spec.rb @@ -11,6 +11,7 @@ allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) @@ -23,7 +24,9 @@ allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) @@ -37,6 +40,21 @@ allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([double(address: '1.2.3.4')]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) + + subject.validate(user) + expect(user.errors).to have_received(:add) + end + + it 'adds an error if the AAAA record is blacklisted' do + EmailDomainBlock.create!(domain: 'fd00::1') + resolver = double + + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([double(address: 'fd00::1')]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) @@ -50,7 +68,25 @@ allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([double(address: '2.3.4.5')]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) + + subject.validate(user) + expect(user.errors).to have_received(:add) + end + + it 'adds an error if the MX IPv6 record is blacklisted' do + EmailDomainBlock.create!(domain: 'fd00::2') + resolver = double + + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([double(address: 'fd00::2')]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) @@ -64,7 +100,9 @@ allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([double(address: '2.3.4.5')]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([double(address: 'fd00::2')]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) From 1186b9abebc0b92acecebcd020d2f9c17de5d0b3 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 12 Feb 2019 22:24:14 +0100 Subject: [PATCH 069/177] Save IP address used for sign-up, not only sign-in (#10026) Fixes #9995 --- app/controllers/auth/registrations_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index f2a832542fe548..ad7b1859f68247 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -28,6 +28,7 @@ def build_resource(hash = nil) resource.invite_code = params[:invite_code] if resource.invite_code.blank? resource.agreement = true + resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil? resource.build_account if resource.account.nil? end From f0f657e77c65923e6d77b5f62f7ee8544b4d9e00 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 13 Feb 2019 05:30:49 +0100 Subject: [PATCH 070/177] Fix color of static page links in high contrast theme (#10028) --- app/javascript/styles/contrast/diff.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index 7d8993a50802a1..8429103b8aff1e 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -13,6 +13,10 @@ } } +.rich-formatting a, +.rich-formatting p a, +.rich-formatting li a, +.landing-page__short-description p a, .status__content a, .reply-indicator__content a { color: lighten($ui-highlight-color, 12%); From a46487e895fbba23922888820c6b88ad07ebe56b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 13 Feb 2019 18:34:58 +0100 Subject: [PATCH 071/177] Fix hashtags select styling in default and high contrast themes (#10029) --- .../components/column_settings.js | 55 ++++++----- app/javascript/styles/mastodon/_mixins.scss | 31 ++++++ .../styles/mastodon/components.scss | 95 +++++++++++++------ 3 files changed, 128 insertions(+), 53 deletions(-) diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js index 9c9f62d8211ebc..cdc138c8bf3a44 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js @@ -1,10 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Toggle from 'react-toggle'; import AsyncSelect from 'react-select/lib/Async'; +const messages = defineMessages({ + placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' }, + noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' }, +}); + export default @injectIntl class ColumnSettings extends React.PureComponent { @@ -25,6 +30,7 @@ class ColumnSettings extends React.PureComponent { tags (mode) { let tags = this.props.settings.getIn(['tags', mode]) || []; + if (tags.toJSON) { return tags.toJSON(); } else { @@ -32,33 +38,36 @@ class ColumnSettings extends React.PureComponent { } }; - onSelect = (mode) => { - return (value) => { - this.props.onChange(['tags', mode], value); - }; - }; + onSelect = mode => value => this.props.onChange(['tags', mode], value); onToggle = () => { if (this.state.open && this.hasTags()) { this.props.onChange('tags', {}); } + this.setState({ open: !this.state.open }); }; + noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions); + modeSelect (mode) { return ( -
- {this.modeLabel(mode)} +
+ + {this.modeLabel(mode)} + +
); @@ -66,11 +75,15 @@ class ColumnSettings extends React.PureComponent { modeLabel (mode) { switch(mode) { - case 'any': return ; - case 'all': return ; - case 'none': return ; + case 'any': + return ; + case 'all': + return ; + case 'none': + return ; + default: + return ''; } - return ''; }; render () { @@ -78,23 +91,21 @@ class ColumnSettings extends React.PureComponent {
- + +
- {this.state.open && + + {this.state.open && (
{this.modeSelect('any')} {this.modeSelect('all')} {this.modeSelect('none')}
- } + )}
); } diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index d5bafe6b6a2e7a..08806599e8f498 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -41,3 +41,34 @@ font-size: 16px; } } + +@mixin search-popout() { + background: $simple-background-color; + border-radius: 4px; + padding: 10px 14px; + padding-bottom: 14px; + margin-top: 10px; + color: $light-text-color; + box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); + + h4 { + text-transform: uppercase; + color: $light-text-color; + font-size: 13px; + font-weight: 500; + margin-bottom: 10px; + } + + li { + padding: 4px 0; + } + + ul { + margin-bottom: 10px; + } + + em { + font-weight: 500; + color: $inverted-text-color; + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 32fd773859e758..02bbd345a7b87c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3056,14 +3056,41 @@ a.status-card.compact:hover { display: block; font-weight: 500; margin-bottom: 10px; +} + +.column-settings__hashtags { + .column-settings__row { + margin-bottom: 15px; + } - .column-settings__hashtag-select { + .column-select { &__control { @include search-input(); } + &__placeholder { + color: $dark-text-color; + padding-left: 2px; + font-size: 12px; + } + + &__value-container { + padding-left: 6px; + } + &__multi-value { background: lighten($ui-base-color, 8%); + + &__remove { + cursor: pointer; + + &:hover, + &:active, + &:focus { + background: lighten($ui-base-color, 12%); + color: lighten($darker-text-color, 4%); + } + } } &__multi-value__label, @@ -3071,9 +3098,42 @@ a.status-card.compact:hover { color: $darker-text-color; } - &__indicator-separator, + &__clear-indicator, &__dropdown-indicator { - display: none; + cursor: pointer; + transition: none; + color: $dark-text-color; + + &:hover, + &:active, + &:focus { + color: lighten($dark-text-color, 4%); + } + } + + &__indicator-separator { + background-color: lighten($ui-base-color, 8%); + } + + &__menu { + @include search-popout(); + padding: 0; + background: $ui-secondary-color; + } + + &__menu-list { + padding: 6px; + } + + &__option { + color: $inverted-text-color; + border-radius: 4px; + font-size: 14px; + + &--is-focused, + &--is-selected { + background: darken($ui-secondary-color, 10%); + } } } } @@ -4867,34 +4927,7 @@ a.status-card.compact:hover { } .search-popout { - background: $simple-background-color; - border-radius: 4px; - padding: 10px 14px; - padding-bottom: 14px; - margin-top: 10px; - color: $light-text-color; - box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); - - h4 { - text-transform: uppercase; - color: $light-text-color; - font-size: 13px; - font-weight: 500; - margin-bottom: 10px; - } - - li { - padding: 4px 0; - } - - ul { - margin-bottom: 10px; - } - - em { - font-weight: 500; - color: $inverted-text-color; - } + @include search-popout(); } noscript { From 2a1adab7d7824df9fa148a9431e942d6677c1d71 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 13 Feb 2019 18:36:40 +0100 Subject: [PATCH 072/177] Fix style regressions on landing page (#10030) --- app/javascript/styles/mastodon/about.scss | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index b6c92a09e7dac6..b078d4d24d5def 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -49,15 +49,9 @@ $small-breakpoint: 960px; } } + strong, em { - display: inline; - margin: 0; - padding: 0; font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; color: lighten($darker-text-color, 10%); } @@ -796,7 +790,7 @@ $small-breakpoint: 960px; width: 100%; display: flex; flex-direction: row-reverse; - flex-wrap: wrap; + flex-wrap: nowrap; justify-content: space-between; align-items: center; } @@ -846,14 +840,7 @@ $small-breakpoint: 960px; } strong { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; + font-weight: 500; color: lighten($darker-text-color, 10%); } From 5a04861c7f2e98c80f315a19d7eadade044d8aae Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 14 Feb 2019 06:27:54 +0100 Subject: [PATCH 073/177] Add tight rate-limit for API deletions (#10042) Deletions take a lot of resources to execute and cause a lot of federation traffic, so it makes sense to decrease the number someone can queue up through the API. 30 per 30 minutes --- config/initializers/rack_attack.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 35302e37b1be76..28201cc64c803d 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -46,14 +46,14 @@ def web_request? end throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req| - req.api_request? && req.authenticated_user_id + req.authenticated_user_id if req.api_request? end throttle('throttle_unauthenticated_api', limit: 7_500, period: 5.minutes) do |req| req.ip if req.api_request? end - throttle('throttle_media', limit: 30, period: 30.minutes) do |req| + throttle('throttle_api_media', limit: 30, period: 30.minutes) do |req| req.authenticated_user_id if req.post? && req.path.start_with?('/api/v1/media') end @@ -61,6 +61,13 @@ def web_request? req.ip if req.post? && req.path == '/api/v1/accounts' end + API_DELETE_REBLOG_REGEX = /\A\/api\/v1\/statuses\/[\d]+\/unreblog/.freeze + API_DELETE_STATUS_REGEX = /\A\/api\/v1\/statuses\/[\d]+/.freeze + + throttle('throttle_api_delete', limit: 30, period: 30.minutes) do |req| + req.authenticated_user_id if (req.post? && req.path =~ API_DELETE_REBLOG_REGEX) || (req.delete? && req.path =~ API_DELETE_STATUS_REGEX) + end + throttle('protected_paths', limit: 25, period: 5.minutes) do |req| req.ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX end From 17a41e1f779874a270f531086f5576affcdb6cb1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 14 Feb 2019 15:46:42 +0100 Subject: [PATCH 074/177] Fix hashtag column not subscribing to stream on mount (#10040) Fix #9895 --- .../mastodon/features/hashtag_timeline/index.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index c2e026d13611d9..0d3c97a6489cc9 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -41,15 +41,19 @@ class HashtagTimeline extends React.PureComponent { title = () => { let title = [this.props.params.id]; + if (this.additionalFor('any')) { - title.push(' ', ); + title.push(' ', ); } + if (this.additionalFor('all')) { - title.push(' ', ); + title.push(' ', ); } + if (this.additionalFor('none')) { - title.push(' ', ); + title.push(' ', ); } + return title; } @@ -77,9 +81,10 @@ class HashtagTimeline extends React.PureComponent { let all = (tags.all || []).map(tag => tag.value); let none = (tags.none || []).map(tag => tag.value); - [id, ...any].map((tag) => { - this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => { + [id, ...any].map(tag => { + this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => { let tags = status.tags.map(tag => tag.name); + return all.filter(tag => tags.includes(tag)).length === all.length && none.filter(tag => tags.includes(tag)).length === 0; }))); @@ -95,12 +100,14 @@ class HashtagTimeline extends React.PureComponent { const { dispatch } = this.props; const { id, tags } = this.props.params; + this._subscribe(dispatch, id, tags); dispatch(expandHashtagTimeline(id, { tags })); } componentWillReceiveProps (nextProps) { const { dispatch, params } = this.props; const { id, tags } = nextProps.params; + if (id !== params.id || !isEqual(tags, params.tags)) { this._unsubscribe(); this._subscribe(dispatch, id, tags); From 737ac4b59df77e43bed71abc6de80bc89f893de1 Mon Sep 17 00:00:00 2001 From: ysksn Date: Sun, 3 Feb 2019 03:11:38 +0900 Subject: [PATCH 075/177] Create Redisable#redis (#9633) * Create Redisable * Use #redis instead of Redis.current --- app/lib/activity_tracker.rb | 6 ++---- app/lib/activitypub/activity.rb | 5 +---- app/lib/feed_manager.rb | 9 +++------ app/lib/ostatus/activity/base.rb | 6 ++---- app/lib/potential_friendship_tracker.rb | 8 ++------ app/models/concerns/redisable.rb | 11 +++++++++++ app/models/feed.rb | 6 ++---- app/models/trending_tags.rb | 6 ++---- app/services/batched_remove_status_service.rb | 5 +---- app/services/follow_service.rb | 6 ++---- app/services/post_status_service.rb | 6 ++---- app/services/remove_status_service.rb | 19 ++++++++----------- .../scheduler/feed_cleanup_scheduler.rb | 5 +---- 13 files changed, 39 insertions(+), 59 deletions(-) create mode 100644 app/models/concerns/redisable.rb diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb index 5b497267477aff..ae3c11b6af5c99 100644 --- a/app/lib/activity_tracker.rb +++ b/app/lib/activity_tracker.rb @@ -4,6 +4,8 @@ class ActivityTracker EXPIRE_AFTER = 90.days.seconds class << self + include Redisable + def increment(prefix) key = [prefix, current_week].join(':') @@ -20,10 +22,6 @@ def record(prefix, value) private - def redis - Redis.current - end - def current_week Time.zone.today.cweek end diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 87318fb1c5c33b..919678618a3168 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -2,6 +2,7 @@ class ActivityPub::Activity include JsonLdHelper + include Redisable def initialize(json, account, **options) @json = json @@ -70,10 +71,6 @@ def object_uri @object_uri ||= value_or_id(@object) end - def redis - Redis.current - end - def distribute(status) crawl_links(status) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index f99df33e54f0d2..d77cdb3a3cc206 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -4,6 +4,7 @@ class FeedManager include Singleton + include Redisable MAX_ITEMS = 400 @@ -35,7 +36,7 @@ def push_to_home(account, status) def unpush_from_home(account, status) return false unless remove_from_feed(:home, account.id, status) - Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s)) + redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -53,7 +54,7 @@ def push_to_list(list, status) def unpush_from_list(list, status) return false unless remove_from_feed(:list, list.id, status) - Redis.current.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s)) + redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -142,10 +143,6 @@ def populate_feed(account) private - def redis - Redis.current - end - def push_update_required?(timeline_id) redis.exists("subscribed:#{timeline_id}") end diff --git a/app/lib/ostatus/activity/base.rb b/app/lib/ostatus/activity/base.rb index c5933f3adfecfe..db70f19980cd58 100644 --- a/app/lib/ostatus/activity/base.rb +++ b/app/lib/ostatus/activity/base.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class OStatus::Activity::Base + include Redisable + def initialize(xml, account = nil, **options) @xml = xml @account = account @@ -66,8 +68,4 @@ def find_activitypub_status(uri, href) Status.find_by(uri: uri) end end - - def redis - Redis.current - end end diff --git a/app/lib/potential_friendship_tracker.rb b/app/lib/potential_friendship_tracker.rb index 017a9748d5f979..188aa4a275657e 100644 --- a/app/lib/potential_friendship_tracker.rb +++ b/app/lib/potential_friendship_tracker.rb @@ -11,6 +11,8 @@ class PotentialFriendshipTracker }.freeze class << self + include Redisable + def record(account_id, target_account_id, action) return if account_id == target_account_id @@ -31,11 +33,5 @@ def get(account_id, limit: 20, offset: 0) return [] if account_ids.empty? Account.searchable.where(id: account_ids) end - - private - - def redis - Redis.current - end end end diff --git a/app/models/concerns/redisable.rb b/app/models/concerns/redisable.rb new file mode 100644 index 00000000000000..c6cf9735960c1c --- /dev/null +++ b/app/models/concerns/redisable.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Redisable + extend ActiveSupport::Concern + + private + + def redis + Redis.current + end +end diff --git a/app/models/feed.rb b/app/models/feed.rb index 5bce88f25522df..0e8943ff863bde 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Feed + include Redisable + def initialize(type, id) @type = type @id = id @@ -27,8 +29,4 @@ def from_redis(limit, max_id, since_id, min_id) def key FeedManager.instance.key(@type, @id) end - - def redis - Redis.current - end end diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 3a8be21649b936..148535c2121e0a 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -7,6 +7,8 @@ class TrendingTags THRESHOLD = 5 class << self + include Redisable + def record_use!(tag, account, at_time = Time.now.utc) return if disallowed_hashtags.include?(tag.name) || account.silenced? || account.bot? @@ -59,9 +61,5 @@ def disallowed_hashtags @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String @disallowed_hashtags = @disallowed_hashtags.map(&:downcase) end - - def redis - Redis.current - end end end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 2e61904fc770f6..cd3b14e08e34ef 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -2,6 +2,7 @@ class BatchedRemoveStatusService < BaseService include StreamEntryRenderer + include Redisable # Delete given statuses and reblogs of them # Dispatch PuSH updates of the deleted statuses, but only local ones @@ -109,10 +110,6 @@ def batch_salmon_slaps(status) end end - def redis - Redis.current - end - def build_xml(stream_entry) return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id) diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 9d36a1449dff37..92d8c864a7de1e 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FollowService < BaseService + include Redisable + # Follow a remote user, notify remote user about the follow # @param [Account] source_account From which to follow # @param [String, Account] uri User URI to follow in the form of username@domain (or account record) @@ -67,10 +69,6 @@ def direct_follow(source_account, target_account, reblogs: true) follow end - def redis - Redis.current - end - def build_follow_request_xml(follow_request) OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_request_salmon(follow_request)) end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 9959bb1fbf7d02..686b10c5817c3b 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class PostStatusService < BaseService + include Redisable + MIN_SCHEDULE_OFFSET = 5.minutes.freeze # Post a text status update, fetch and notify remote users mentioned @@ -110,10 +112,6 @@ def process_hashtags_service ProcessHashtagsService.new end - def redis - Redis.current - end - def scheduled? @scheduled_at.present? end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 11d28e783d04c8..28c5224b01184f 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -2,6 +2,7 @@ class RemoveStatusService < BaseService include StreamEntryRenderer + include Redisable def call(status, **options) @payload = Oj.dump(event: :delete, payload: status.id.to_s) @@ -55,7 +56,7 @@ def remove_from_lists def remove_from_affected @mentions.map(&:account).select(&:local?).each do |account| - Redis.current.publish("timeline:#{account.id}", @payload) + redis.publish("timeline:#{account.id}", @payload) end end @@ -133,26 +134,22 @@ def remove_from_hashtags return unless @status.public_visibility? @tags.each do |hashtag| - Redis.current.publish("timeline:hashtag:#{hashtag}", @payload) - Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if @status.local? + redis.publish("timeline:hashtag:#{hashtag}", @payload) + redis.publish("timeline:hashtag:#{hashtag}:local", @payload) if @status.local? end end def remove_from_public return unless @status.public_visibility? - Redis.current.publish('timeline:public', @payload) - Redis.current.publish('timeline:public:local', @payload) if @status.local? + redis.publish('timeline:public', @payload) + redis.publish('timeline:public:local', @payload) if @status.local? end def remove_from_media return unless @status.public_visibility? - Redis.current.publish('timeline:public:media', @payload) - Redis.current.publish('timeline:public:local:media', @payload) if @status.local? - end - - def redis - Redis.current + redis.publish('timeline:public:media', @payload) + redis.publish('timeline:public:local:media', @payload) if @status.local? end end diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb index cd22734180572c..bf5e20757007d8 100644 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ b/app/workers/scheduler/feed_cleanup_scheduler.rb @@ -2,6 +2,7 @@ class Scheduler::FeedCleanupScheduler include Sidekiq::Worker + include Redisable sidekiq_options unique: :until_executed, retry: 0 @@ -57,8 +58,4 @@ def inactive_list_ids def feed_manager FeedManager.instance end - - def redis - Redis.current - end end From 6c11f0f8cf91aa4d0b83a0a9c5a92cfa99dcbfdd Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 13 Feb 2019 18:36:23 +0100 Subject: [PATCH 076/177] Alternative handling of private self-boosts (#9998) * When self-boosting, embed original toot into Announce serialization * Process unknown self-boosts from Announce object if it is more than an URI * Add some self-boost specs * Only serialize private toots in self-Announces --- app/lib/activitypub/activity.rb | 32 +++++++++++ app/lib/activitypub/activity/announce.rb | 4 +- app/lib/activitypub/activity/create.rb | 15 ------ .../activitypub/activity_serializer.rb | 8 ++- .../lib/activitypub/activity/announce_spec.rb | 53 ++++++++++++++++--- 5 files changed, 86 insertions(+), 26 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 919678618a3168..7e4e195313a655 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -4,6 +4,9 @@ class ActivityPub::Activity include JsonLdHelper include Redisable + SUPPORTED_TYPES = %w(Note).freeze + CONVERTED_TYPES = %w(Image Video Article Page).freeze + def initialize(json, account, **options) @json = json @account = account @@ -71,6 +74,18 @@ def object_uri @object_uri ||= value_or_id(@object) end + def unsupported_object_type? + @object.is_a?(String) || !(supported_object_type? || converted_object_type?) + end + + def supported_object_type? + equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) + end + + def converted_object_type? + equals_or_includes_any?(@object['type'], CONVERTED_TYPES) + end + def distribute(status) crawl_links(status) @@ -120,6 +135,23 @@ def delete_later!(uri) redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri) end + def status_from_object + # If the status is already known, return it + status = status_from_uri(object_uri) + return status unless status.nil? + + # If the boosted toot is embedded and it is a self-boost, handle it like a Create + unless unsupported_object_type? + actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri + if actor_id == @account.uri + return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform + end + end + + # If the status is not from the actor, try to fetch it + return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri + end + def fetch_remote_original_status if object_uri.start_with?('http') return if ActivityPub::TagManager.instance.local_uri?(object_uri) diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 34d1b7cbd0fe1f..04afeea202c34f 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -2,9 +2,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def perform - original_status = status_from_uri(object_uri) - original_status ||= fetch_remote_original_status - + original_status = status_from_object return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) status = Status.find_by(account: @account, reblog: original_status) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b49657d4b14625..9a3db51dd9e6bd 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ActivityPub::Activity::Create < ActivityPub::Activity - SUPPORTED_TYPES = %w(Note).freeze - CONVERTED_TYPES = %w(Image Video Article Page).freeze - def perform return if unsupported_object_type? || invalid_origin?(@object['id']) return if Tombstone.exists?(uri: @object['id']) @@ -318,22 +315,10 @@ def name_language_map? @object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty? end - def unsupported_object_type? - @object.is_a?(String) || !(supported_object_type? || converted_object_type?) - end - def unsupported_media_type?(mime_type) mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type) end - def supported_object_type? - equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) - end - - def converted_object_type? - equals_or_includes_any?(@object['type'], CONVERTED_TYPES) - end - def skip_download? return @skip_download if defined?(@skip_download) @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media? diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb index 50c4f6a049ac43..b51e8c54429bb6 100644 --- a/app/serializers/activitypub/activity_serializer.rb +++ b/app/serializers/activitypub/activity_serializer.rb @@ -3,8 +3,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer attributes :id, :type, :actor, :published, :to, :cc - has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce? - attribute :proper_uri, key: :object, if: :announce? + has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce? + attribute :proper_uri, key: :object, if: :owned_announce? attribute :atom_uri, if: :announce? def id @@ -42,4 +42,8 @@ def atom_uri def announce? object.reblog? end + + def owned_announce? + announce? && object.account == object.proper.account && object.proper.private_visibility? + end end diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index 54dd52a60490a1..1725c2843bb7ea 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe ActivityPub::Activity::Announce do - let(:sender) { Fabricate(:account) } + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') } let(:recipient) { Fabricate(:account) } let(:status) { Fabricate(:status, account: recipient) } @@ -11,19 +11,60 @@ id: 'foo', type: 'Announce', actor: ActivityPub::TagManager.instance.uri_for(sender), - object: ActivityPub::TagManager.instance.uri_for(status), + object: object_json, }.with_indifferent_access end - describe '#perform' do - subject { described_class.new(json, sender) } + subject { described_class.new(json, sender) } + + before do + sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) + end + describe '#perform' do before do subject.perform end - it 'creates a reblog by sender of status' do - expect(sender.reblogged?(status)).to be true + context 'a known status' do + let(:object_json) do + ActivityPub::TagManager.instance.uri_for(status) + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(status)).to be true + end + end + + context 'self-boost of a previously unknown status with missing attributedTo' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(sender.statuses.first)).to be true + end + end + + context 'self-boost of a previously unknown status with correct attributedTo' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + to: 'http://example.com/followers', + } + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(sender.statuses.first)).to be true + end end end end From ef45411c537d37f06b23914135460544d348bfd6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 13 Feb 2019 18:42:47 +0100 Subject: [PATCH 077/177] Filter incoming Create activities by relation to local activity (#10005) Reject those from accounts with no local followers, from relays that are not enabled, which do not address local accounts and are not replies to accounts that do have local followers --- app/lib/activitypub/activity/create.rb | 34 +++++++++++++++++-- .../activitypub/process_collection_service.rb | 1 + app/workers/activitypub/processing_worker.rb | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 9a3db51dd9e6bd..1b31768d964a61 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -2,8 +2,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def perform - return if unsupported_object_type? || invalid_origin?(@object['id']) - return if Tombstone.exists?(uri: @object['id']) + return if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity? RedisLock.acquire(lock_options) do |lock| if lock.acquired? @@ -337,6 +336,37 @@ def reply_to_local? !replied_to_status.nil? && replied_to_status.account.local? end + def related_to_local_activity? + fetch? || followed_by_local_accounts? || requested_through_relay? || + responds_to_followed_account? || addresses_local_accounts? + end + + def fetch? + !@options[:delivery] + end + + def followed_by_local_accounts? + @account.passive_relationships.exists? + end + + def requested_through_relay? + @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? + end + + def responds_to_followed_account? + !replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?) + end + + def addresses_local_accounts? + return true if @options[:delivered_to_account_id] + + local_usernames = (as_array(@object['to']) + as_array(@object['cc'])).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) } + + return false if local_usernames.empty? + + Account.local.where(username: local_usernames).exists? + end + def forward_for_reply return unless @json['signature'].present? && reply_to_local? ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url]) diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb index 5c54aad89f1840..881df478bf53c7 100644 --- a/app/services/activitypub/process_collection_service.rb +++ b/app/services/activitypub/process_collection_service.rb @@ -44,6 +44,7 @@ def process_item(item) end def verify_account! + @options[:relayed_through_account] = @account @account = ActivityPub::LinkedDataSignature.new(@json).verify_account! rescue JSON::LD::JsonLdError => e Rails.logger.debug "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}" diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb index a8a3ebf0f56987..a3abe72cf66eed 100644 --- a/app/workers/activitypub/processing_worker.rb +++ b/app/workers/activitypub/processing_worker.rb @@ -6,6 +6,6 @@ class ActivityPub::ProcessingWorker sidekiq_options backtrace: true def perform(account_id, body, delivered_to_account_id = nil) - ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id) + ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true) end end From e84c7618192b1ba1538e3c0af79acdf604aea5fc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 15 Feb 2019 18:19:45 +0100 Subject: [PATCH 078/177] Filter incoming Announce activities by relation to local activity (#10041) * Filter incoming Announce activities by relation to local activity Reject if announcer is not followed by local accounts, and is not from an enabled relay, and the object is not a local status Follow-up to #10005 * Fix tests --- app/lib/activitypub/activity.rb | 14 ++++++++++++++ app/lib/activitypub/activity/announce.rb | 11 ++++++++++- app/lib/activitypub/activity/create.rb | 12 ------------ spec/lib/activitypub/activity/announce_spec.rb | 1 + 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 7e4e195313a655..3cf38764a985cc 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -138,11 +138,13 @@ def delete_later!(uri) def status_from_object # If the status is already known, return it status = status_from_uri(object_uri) + return status unless status.nil? # If the boosted toot is embedded and it is a self-boost, handle it like a Create unless unsupported_object_type? actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri + if actor_id == @account.uri return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform end @@ -166,4 +168,16 @@ def lock_or_return(key, expire_after = 7.days.seconds) ensure redis.del(key) end + + def fetch? + !@options[:delivery] + end + + def followed_by_local_accounts? + @account.passive_relationships.exists? + end + + def requested_through_relay? + @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? + end end diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 04afeea202c34f..28a1cda024da6a 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -3,7 +3,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def perform original_status = status_from_object - return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) + + return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) || !related_to_local_activity? status = Status.find_by(account: @account, reblog: original_status) @@ -39,4 +40,12 @@ def visibility_from_audience def announceable?(status) status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility? end + + def related_to_local_activity? + followed_by_local_accounts? || requested_through_relay? || reblog_of_local_status? + end + + def reblog_of_local_status? + status_from_uri(object_uri)&.account&.local? + end end diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 1b31768d964a61..4fc37fb4b5ee7b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -341,18 +341,6 @@ def related_to_local_activity? responds_to_followed_account? || addresses_local_accounts? end - def fetch? - !@options[:delivery] - end - - def followed_by_local_accounts? - @account.passive_relationships.exists? - end - - def requested_through_relay? - @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? - end - def responds_to_followed_account? !replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?) end diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index 1725c2843bb7ea..5e6f008ec0b4c7 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -18,6 +18,7 @@ subject { described_class.new(json, sender) } before do + Fabricate(:account).follow!(sender) sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) end From 71b831601db2fdc41aaf4ddbe9fd60db109a3153 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 17 Feb 2019 03:38:25 +0100 Subject: [PATCH 079/177] Add logging for rejected ActivityPub payloads and add tests (#10062) --- app/lib/activitypub/activity.rb | 5 + app/lib/activitypub/activity/announce.rb | 4 +- app/lib/activitypub/activity/create.rb | 2 +- .../lib/activitypub/activity/announce_spec.rb | 117 ++- spec/lib/activitypub/activity/create_spec.rb | 710 ++++++++++-------- 5 files changed, 525 insertions(+), 313 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 3cf38764a985cc..8265810a0011a5 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -180,4 +180,9 @@ def followed_by_local_accounts? def requested_through_relay? @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? end + + def reject_payload! + Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}") + nil + end end diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 28a1cda024da6a..9f8ffd9fb77f7a 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -2,9 +2,11 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def perform + return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity? + original_status = status_from_object - return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) || !related_to_local_activity? + return reject_payload! if original_status.nil? || !announceable?(original_status) status = Status.find_by(account: @account, reblog: original_status) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 4fc37fb4b5ee7b..d7bd65c80668b6 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -2,7 +2,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def perform - return if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity? + return reject_payload! if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity? RedisLock.acquire(lock_options) do |lock| if lock.acquired? diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index 5e6f008ec0b4c7..94b9d348d01abb 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -18,16 +18,63 @@ subject { described_class.new(json, sender) } before do - Fabricate(:account).follow!(sender) sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) end describe '#perform' do - before do - subject.perform + context 'when sender is followed by a local account' do + before do + Fabricate(:account).follow!(sender) + subject.perform + end + + context 'a known status' do + let(:object_json) do + ActivityPub::TagManager.instance.uri_for(status) + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(status)).to be true + end + end + + context 'self-boost of a previously unknown status with missing attributedTo' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(sender.statuses.first)).to be true + end + end + + context 'self-boost of a previously unknown status with correct attributedTo' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + to: 'http://example.com/followers', + } + end + + it 'creates a reblog by sender of status' do + expect(sender.reblogged?(sender.statuses.first)).to be true + end + end end - context 'a known status' do + context 'when the status belongs to a local user' do + before do + subject.perform + end + let(:object_json) do ActivityPub::TagManager.instance.uri_for(status) end @@ -37,34 +84,68 @@ end end - context 'self-boost of a previously unknown status with missing attributedTo' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - } + context 'when the sender is relayed' do + let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') } + let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') } + + subject { described_class.new(json, sender, relayed_through_account: relay_account) } + + context 'and the relay is enabled' do + before do + relay.update(state: :accepted) + subject.perform + end + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'creates a reblog by sender of status' do + expect(sender.statuses.count).to eq 2 + end end - it 'creates a reblog by sender of status' do - expect(sender.reblogged?(sender.statuses.first)).to be true + context 'and the relay is disabled' do + before do + subject.perform + end + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'does not create anything' do + expect(sender.statuses.count).to eq 0 + end end end - context 'self-boost of a previously unknown status with correct attributedTo' do + context 'when the sender has no relevance to local activity' do + before do + subject.perform + end + let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - attributedTo: ActivityPub::TagManager.instance.uri_for(sender), to: 'http://example.com/followers', } end - it 'creates a reblog by sender of status' do - expect(sender.reblogged?(sender.statuses.first)).to be true + it 'does not create anything' do + expect(sender.statuses.count).to eq 0 end end end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index cd20b7c7cb0069..26cb84871c33dd 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -13,8 +13,6 @@ }.with_indifferent_access end - subject { described_class.new(json, sender) } - before do sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) @@ -23,59 +21,407 @@ end describe '#perform' do - before do - subject.perform - end - - context 'standalone' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - } - end - - it 'creates status' do - status = sender.statuses.first - - expect(status).to_not be_nil - expect(status.text).to eq 'Lorem ipsum' - end - - it 'missing to/cc defaults to direct privacy' do - status = sender.statuses.first + context 'when fetching' do + subject { described_class.new(json, sender) } + + before do + subject.perform + end + + context 'standalone' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.text).to eq 'Lorem ipsum' + end + + it 'missing to/cc defaults to direct privacy' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'direct' + end + end + + context 'public' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'https://www.w3.org/ns/activitystreams#Public', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'public' + end + end + + context 'unlisted' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + cc: 'https://www.w3.org/ns/activitystreams#Public', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'unlisted' + end + end + + context 'private' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: 'http://example.com/followers', + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'private' + end + end + + context 'limited' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: ActivityPub::TagManager.instance.uri_for(recipient), + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'limited' + end + + it 'creates silent mention' do + status = sender.statuses.first + expect(status.mentions.first).to be_silent + end + end + + context 'direct' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: ActivityPub::TagManager.instance.uri_for(recipient), + tag: { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(recipient), + }, + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'direct' + end + end + + context 'as a reply' do + let(:original_status) { Fabricate(:status) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.thread).to eq original_status + expect(status.reply?).to be true + expect(status.in_reply_to_account).to eq original_status.account + expect(status.conversation).to eq original_status.conversation + end + end + + context 'with mentions' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(recipient), + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.mentions.map(&:account)).to include(recipient) + end + end + + context 'with mentions missing href' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Mention', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + end + end + + context 'with media attachments' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + attachment: [ + { + type: 'Document', + mediaType: 'image/png', + url: 'http://example.com/attachment.png', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png') + end + end + + context 'with media attachments with focal points' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + attachment: [ + { + type: 'Document', + mediaType: 'image/png', + url: 'http://example.com/attachment.png', + focalPoint: [0.5, -0.7], + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.media_attachments.map(&:focus)).to include('0.5,-0.7') + end + end + + context 'with media attachments missing url' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + attachment: [ + { + type: 'Document', + mediaType: 'image/png', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + end + end + + context 'with hashtags' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Hashtag', + href: 'http://example.com/blah', + name: '#test', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.tags.map(&:name)).to include('test') + end + end + + context 'with hashtags missing name' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + tag: [ + { + type: 'Hashtag', + href: 'http://example.com/blah', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + end + end + + context 'with emojis' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum :tinking:', + tag: [ + { + type: 'Emoji', + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.emojis.map(&:shortcode)).to include('tinking') + end + end + + context 'with emojis missing name' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum :tinking:', + tag: [ + { + type: 'Emoji', + icon: { + url: 'http://example.com/emoji.png', + }, + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + end + end + + context 'with emojis missing icon' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum :tinking:', + tag: [ + { + type: 'Emoji', + name: 'tinking', + }, + ], + } + end - expect(status).to_not be_nil - expect(status.visibility).to eq 'direct' + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + end end end - context 'public' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'https://www.w3.org/ns/activitystreams#Public', - } - end - - it 'creates status' do - status = sender.statuses.first + context 'when sender is followed by local users' do + subject { described_class.new(json, sender, delivery: true) } - expect(status).to_not be_nil - expect(status.visibility).to eq 'public' + before do + Fabricate(:account).follow!(sender) + subject.perform end - end - context 'unlisted' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - cc: 'https://www.w3.org/ns/activitystreams#Public', } end @@ -83,66 +429,25 @@ status = sender.statuses.first expect(status).to_not be_nil - expect(status.visibility).to eq 'unlisted' - end - end - - context 'private' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: 'http://example.com/followers', - } - end - - it 'creates status' do - status = sender.statuses.first - - expect(status).to_not be_nil - expect(status.visibility).to eq 'private' + expect(status.text).to eq 'Lorem ipsum' end end - context 'limited' do - let(:recipient) { Fabricate(:account) } + context 'when sender replies to local status' do + let!(:local_status) { Fabricate(:status) } - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(recipient), - } - end - - it 'creates status' do - status = sender.statuses.first - - expect(status).to_not be_nil - expect(status.visibility).to eq 'limited' - end + subject { described_class.new(json, sender, delivery: true) } - it 'creates silent mention' do - status = sender.statuses.first - expect(status.mentions.first).to be_silent + before do + subject.perform end - end - - context 'direct' do - let(:recipient) { Fabricate(:account) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - to: ActivityPub::TagManager.instance.uri_for(recipient), - tag: { - type: 'Mention', - href: ActivityPub::TagManager.instance.uri_for(recipient), - }, + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), } end @@ -150,47 +455,25 @@ status = sender.statuses.first expect(status).to_not be_nil - expect(status.visibility).to eq 'direct' + expect(status.text).to eq 'Lorem ipsum' end end - context 'as a reply' do - let(:original_status) { Fabricate(:status) } + context 'when sender targets a local user' do + let!(:local_account) { Fabricate(:account) } - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status), - } - end - - it 'creates status' do - status = sender.statuses.first + subject { described_class.new(json, sender, delivery: true) } - expect(status).to_not be_nil - expect(status.thread).to eq original_status - expect(status.reply?).to be true - expect(status.in_reply_to_account).to eq original_status.account - expect(status.conversation).to eq original_status.conversation + before do + subject.perform end - end - - context 'with mentions' do - let(:recipient) { Fabricate(:account) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - tag: [ - { - type: 'Mention', - href: ActivityPub::TagManager.instance.uri_for(recipient), - }, - ], + to: ActivityPub::TagManager.instance.uri_for(local_account), } end @@ -198,68 +481,25 @@ status = sender.statuses.first expect(status).to_not be_nil - expect(status.mentions.map(&:account)).to include(recipient) - end - end - - context 'with mentions missing href' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - tag: [ - { - type: 'Mention', - }, - ], - } - end - - it 'creates status' do - status = sender.statuses.first - expect(status).to_not be_nil + expect(status.text).to eq 'Lorem ipsum' end end - context 'with media attachments' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - attachment: [ - { - type: 'Document', - mediaType: 'image/png', - url: 'http://example.com/attachment.png', - }, - ], - } - end + context 'when sender cc\'s a local user' do + let!(:local_account) { Fabricate(:account) } - it 'creates status' do - status = sender.statuses.first + subject { described_class.new(json, sender, delivery: true) } - expect(status).to_not be_nil - expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png') + before do + subject.perform end - end - context 'with media attachments with focal points' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - attachment: [ - { - type: 'Document', - mediaType: 'image/png', - url: 'http://example.com/attachment.png', - focalPoint: [0.5, -0.7], - }, - ], + cc: ActivityPub::TagManager.instance.uri_for(local_account), } end @@ -267,143 +507,27 @@ status = sender.statuses.first expect(status).to_not be_nil - expect(status.media_attachments.map(&:focus)).to include('0.5,-0.7') + expect(status.text).to eq 'Lorem ipsum' end end - context 'with media attachments missing url' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - attachment: [ - { - type: 'Document', - mediaType: 'image/png', - }, - ], - } - end + context 'when the sender has no relevance to local activity' do + subject { described_class.new(json, sender, delivery: true) } - it 'creates status' do - status = sender.statuses.first - expect(status).to_not be_nil + before do + subject.perform end - end - context 'with hashtags' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', - tag: [ - { - type: 'Hashtag', - href: 'http://example.com/blah', - name: '#test', - }, - ], } end - it 'creates status' do - status = sender.statuses.first - - expect(status).to_not be_nil - expect(status.tags.map(&:name)).to include('test') - end - end - - context 'with hashtags missing name' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum', - tag: [ - { - type: 'Hashtag', - href: 'http://example.com/blah', - }, - ], - } - end - - it 'creates status' do - status = sender.statuses.first - expect(status).to_not be_nil - end - end - - context 'with emojis' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum :tinking:', - tag: [ - { - type: 'Emoji', - icon: { - url: 'http://example.com/emoji.png', - }, - name: 'tinking', - }, - ], - } - end - - it 'creates status' do - status = sender.statuses.first - - expect(status).to_not be_nil - expect(status.emojis.map(&:shortcode)).to include('tinking') - end - end - - context 'with emojis missing name' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum :tinking:', - tag: [ - { - type: 'Emoji', - icon: { - url: 'http://example.com/emoji.png', - }, - }, - ], - } - end - - it 'creates status' do - status = sender.statuses.first - expect(status).to_not be_nil - end - end - - context 'with emojis missing icon' do - let(:object_json) do - { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, - type: 'Note', - content: 'Lorem ipsum :tinking:', - tag: [ - { - type: 'Emoji', - name: 'tinking', - }, - ], - } - end - - it 'creates status' do - status = sender.statuses.first - expect(status).to_not be_nil + it 'does not create anything' do + expect(sender.statuses.count).to eq 0 end end end From b163368c3e3e3a22c4ef98c0d0cd6c07a2ad13e6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 17 Feb 2019 15:16:36 +0100 Subject: [PATCH 080/177] Fix Announce activities of unknown statuses not fetching those statuses (#10065) Regression from #9998 --- app/lib/activitypub/activity.rb | 3 +- .../lib/activitypub/activity/announce_spec.rb | 43 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 8265810a0011a5..11fa3363aff5e6 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -150,8 +150,7 @@ def status_from_object end end - # If the status is not from the actor, try to fetch it - return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri + fetch_remote_original_status end def fetch_remote_original_status diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index 94b9d348d01abb..aa58d9e2348848 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe ActivityPub::Activity::Announce do - let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') } + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor') } let(:recipient) { Fabricate(:account) } let(:status) { Fabricate(:status, account: recipient) } @@ -10,21 +10,29 @@ '@context': 'https://www.w3.org/ns/activitystreams', id: 'foo', type: 'Announce', - actor: ActivityPub::TagManager.instance.uri_for(sender), + actor: 'https://example.com/actor', object: object_json, }.with_indifferent_access end - subject { described_class.new(json, sender) } - - before do - sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender)) + let(:unknown_object_json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'https://example.com/actor/hello-world', + type: 'Note', + attributedTo: 'https://example.com/actor', + content: 'Hello world', + to: 'http://example.com/followers', + } end + subject { described_class.new(json, sender) } + describe '#perform' do context 'when sender is followed by a local account' do before do Fabricate(:account).follow!(sender) + stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json)) subject.perform end @@ -38,10 +46,21 @@ end end + context 'an unknown status' do + let(:object_json) { 'https://example.com/actor/hello-world' } + + it 'creates a reblog by sender of status' do + reblog = sender.statuses.first + + expect(reblog).to_not be_nil + expect(reblog.reblog.text).to eq 'Hello world' + end + end + context 'self-boost of a previously unknown status with missing attributedTo' do let(:object_json) do { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + id: 'https://example.com/actor#bar', type: 'Note', content: 'Lorem ipsum', to: 'http://example.com/followers', @@ -56,10 +75,10 @@ context 'self-boost of a previously unknown status with correct attributedTo' do let(:object_json) do { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + id: 'https://example.com/actor#bar', type: 'Note', content: 'Lorem ipsum', - attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + attributedTo: 'https://example.com/actor', to: 'http://example.com/followers', } end @@ -98,7 +117,7 @@ let(:object_json) do { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + id: 'https://example.com/actor#bar', type: 'Note', content: 'Lorem ipsum', to: 'http://example.com/followers', @@ -117,7 +136,7 @@ let(:object_json) do { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + id: 'https://example.com/actor#bar', type: 'Note', content: 'Lorem ipsum', to: 'http://example.com/followers', @@ -137,7 +156,7 @@ let(:object_json) do { - id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + id: 'https://example.com/actor#bar', type: 'Note', content: 'Lorem ipsum', to: 'http://example.com/followers', From 8ad75eea62117f8635de8897c74297a5a8b3bd5c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 15 Feb 2019 16:08:59 +0100 Subject: [PATCH 081/177] Fix relay enabling/disabling not resetting inbox availability status (#10048) Fix #10033 --- app/models/relay.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/relay.rb b/app/models/relay.rb index 7478c110d45575..6934a5c628760f 100644 --- a/app/models/relay.rb +++ b/app/models/relay.rb @@ -29,6 +29,7 @@ def enable! payload = Oj.dump(follow_activity(activity_id)) update!(state: :pending, follow_activity_id: activity_id) + DeliveryFailureTracker.new(inbox_url).track_success! ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url) end @@ -37,6 +38,7 @@ def disable! payload = Oj.dump(unfollow_activity(activity_id)) update!(state: :idle, follow_activity_id: nil) + DeliveryFailureTracker.new(inbox_url).track_success! ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url) end From 637f0007b9535ed988cbe94c9b9b40a8f8e8a24f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 13 Feb 2019 21:28:18 +0100 Subject: [PATCH 082/177] Change robots.txt to exclude some URLs (#10037) - Exclude static assets - Exclude uploaded files - Exclude alternate versions of the profile page - Exclude media proxy URLs --- public/robots.txt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/public/robots.txt b/public/robots.txt index 3c9c7c01f30b90..36afc85eff582b 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,5 +1,13 @@ -# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file -# -# To ban all spiders from the entire site uncomment the next two lines: -# User-agent: * -# Disallow: / +User-Agent: * +Disallow: /users/*/followers +Disallow: /users/*/following +Disallow: /@*/media +Disallow: /@*/with_replies +Disallow: /@*/tagged/* +Disallow: /media_proxy/* +Disallow: /emoji/* +Disallow: /packs/* +Disallow: /sounds/* +Disallow: /system/* +Disallow: /avatars/* +Disallow: /headers/* From 45b2bb464b5ff5b5b5805004a5dc856b495dfc54 Mon Sep 17 00:00:00 2001 From: nightpool Date: Wed, 13 Feb 2019 21:11:47 -0500 Subject: [PATCH 083/177] Change robots.txt to exclude only media proxy URLs (#10038) * Revert "Change robots.txt to exclude some URLs (#10037)" This reverts commit 80161f43510ad9316c60c9b50dd5c09c2dae4d54. * Let's block media_proxy /media_proxy/ is a dynamic route used for requesting uncached media, so it's probably bad to let crawlers use it * misleading comment --- public/robots.txt | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/public/robots.txt b/public/robots.txt index 36afc85eff582b..d93648beee28c9 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,13 +1,4 @@ -User-Agent: * -Disallow: /users/*/followers -Disallow: /users/*/following -Disallow: /@*/media -Disallow: /@*/with_replies -Disallow: /@*/tagged/* -Disallow: /media_proxy/* -Disallow: /emoji/* -Disallow: /packs/* -Disallow: /sounds/* -Disallow: /system/* -Disallow: /avatars/* -Disallow: /headers/* +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file + +User-agent: * +Disallow: /media_proxy/ From 1ad0d232b3ed1c2005d64f1746ecd2d476379852 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 13 Feb 2019 18:04:43 -0600 Subject: [PATCH 084/177] Improve image description user experience (#10036) * Add image descriptions to searchable post content. * Allow multi-line image descriptions. * Request image descriptions in the same query as posts when creating the search index. (see https://github.com/tootsuite/mastodon/pull/10036#discussion_r256551624) --- app/chewy/statuses_index.rb | 4 ++-- app/javascript/mastodon/features/compose/components/upload.js | 3 +-- app/javascript/styles/mastodon/components.scss | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index d3104172c485d1..eafc1818b8f02f 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -31,7 +31,7 @@ class StatusesIndex < Chewy::Index }, } - define_type ::Status.unscoped.without_reblogs do + define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do crutch :mentions do |collection| data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id) data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } @@ -50,7 +50,7 @@ class StatusesIndex < Chewy::Index root date_detection: false do field :account_id, type: 'long' - field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do + field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do field :stemmed, type: 'text', analyzer: 'content' end diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index a1e99dcbbdbca3..947c3acc9ae60c 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -107,9 +107,8 @@ class Upload extends ImmutablePureComponent {