diff options
38 files changed, 240 insertions, 82 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index aa9f59da8..cc7751ae1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails.git - revision: ecb394a31420f6fd1d0ab692c79f2dd44176e2c9 + revision: c7a716aa5a692cae301d56b345faa5d79cbfc320 branch: 5-0-stable specs: actioncable (5.0.0.1) @@ -110,7 +110,7 @@ GEM coffee-script-source (1.10.0) colorize (0.8.1) concurrent-ruby (1.0.2) - connection_pool (2.2.0) + connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) debug_inspector (0.0.2) @@ -287,7 +287,7 @@ GEM execjs railties (>= 3.2) tilt - redis (3.3.1) + redis (3.3.2) redis-actionpack (5.0.0) actionpack (>= 4.0.0, < 6) redis-rack (~> 2.0.0.pre) @@ -348,10 +348,10 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - sidekiq (4.2.1) + sidekiq (4.2.7) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) - rack-protection (~> 1.5) + rack-protection (>= 1.5.0) redis (~> 3.2, >= 3.2.1) simple_form (3.2.1) actionpack (> 4, < 5.1) @@ -374,7 +374,7 @@ GEM tins (~> 1.0) terminal-table (1.7.0) unicode-display_width (~> 1.1) - thor (0.19.1) + thor (0.19.4) thread (0.2.2) thread_safe (0.3.5) tilt (2.0.5) diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx index b97cb7b12..c2a7909f6 100644 --- a/app/assets/javascripts/components/actions/compose.jsx +++ b/app/assets/javascripts/components/actions/compose.jsx @@ -23,6 +23,7 @@ export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; +export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export function changeCompose(text) { return { @@ -65,7 +66,8 @@ export function submitCompose() { status: getState().getIn(['compose', 'text'], ''), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), - sensitive: getState().getIn(['compose', 'sensitive']) + sensitive: getState().getIn(['compose', 'sensitive']), + unlisted: getState().getIn(['compose', 'unlisted']) }).then(function (response) { dispatch(submitComposeSuccess(response.data)); dispatch(updateTimeline('home', response.data)); @@ -207,3 +209,10 @@ export function changeComposeSensitivity(checked) { checked }; }; + +export function changeComposeVisibility(checked) { + return { + type: COMPOSE_VISIBILITY_CHANGE, + checked + }; +}; diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index b16731c05..4688f39d3 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -70,6 +70,7 @@ const ComposeForm = React.createClass({ suggestion_token: React.PropTypes.string, suggestions: React.PropTypes.array, sensitive: React.PropTypes.bool, + unlisted: React.PropTypes.bool, is_submitting: React.PropTypes.bool, is_uploading: React.PropTypes.bool, in_reply_to: ImmutablePropTypes.map, @@ -79,7 +80,8 @@ const ComposeForm = React.createClass({ onClearSuggestions: React.PropTypes.func.isRequired, onFetchSuggestions: React.PropTypes.func.isRequired, onSuggestionSelected: React.PropTypes.func.isRequired, - onChangeSensitivity: React.PropTypes.func.isRequired + onChangeSensitivity: React.PropTypes.func.isRequired, + onChangeVisibility: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], @@ -147,6 +149,10 @@ const ComposeForm = React.createClass({ this.props.onChangeSensitivity(e.target.checked); }, + handleChangeVisibility (e) { + this.props.onChangeVisibility(e.target.checked); + }, + render () { const { intl } = this.props; let replyArea = ''; @@ -187,7 +193,12 @@ const ComposeForm = React.createClass({ <UploadButtonContainer style={{ paddingTop: '4px' }} /> </div> - <label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', marginTop: '10px', borderTop: '1px solid #616b86', paddingTop: '10px' }}> + <label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', marginTop: '10px', borderTop: '1px solid #282c37', paddingTop: '10px' }}> + <Toggle checked={this.props.unlisted} onChange={this.handleChangeVisibility} /> + <span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.unlisted' defaultMessage='Unlisted mode' /></span> + </label> + + <label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle' }}> <Toggle checked={this.props.sensitive} onChange={this.handleChangeSensitivity} /> <span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark content as sensitive' /></span> </label> diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx index 9897f6505..8aa719476 100644 --- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx +++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx @@ -7,7 +7,8 @@ import { clearComposeSuggestions, fetchComposeSuggestions, selectComposeSuggestion, - changeComposeSensitivity + changeComposeSensitivity, + changeComposeVisibility } from '../../../actions/compose'; import { makeGetStatus } from '../../../selectors'; @@ -20,6 +21,7 @@ const makeMapStateToProps = () => { suggestion_token: state.getIn(['compose', 'suggestion_token']), suggestions: state.getIn(['compose', 'suggestions']).toJS(), sensitive: state.getIn(['compose', 'sensitive']), + unlisted: state.getIn(['compose', 'unlisted']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])) @@ -57,6 +59,10 @@ const mapDispatchToProps = function (dispatch) { onChangeSensitivity (checked) { dispatch(changeComposeSensitivity(checked)); + }, + + onChangeVisibility (checked) { + dispatch(changeComposeVisibility(checked)); } } }; diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx index 4e2a70edb..17b74e15d 100644 --- a/app/assets/javascripts/components/locales/de.jsx +++ b/app/assets/javascripts/components/locales/de.jsx @@ -34,6 +34,8 @@ const en = { "tabs_bar.notifications": "Mitteilungen", "compose_form.placeholder": "Worüber möchstest du schreiben?", "compose_form.publish": "Veröffentlichen", + "compose_form.sensitive": "Medien als sensitiv markieren", + "compose_form.unlisted": "Öffentlich nicht auflisten", "navigation_bar.settings": "Einstellungen", "navigation_bar.public_timeline": "Öffentlich", "navigation_bar.logout": "Abmelden", diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx index 41a44e3dc..b17e623cb 100644 --- a/app/assets/javascripts/components/locales/en.jsx +++ b/app/assets/javascripts/components/locales/en.jsx @@ -38,6 +38,7 @@ const en = { "compose_form.placeholder": "What is on your mind?", "compose_form.publish": "Toot", "compose_form.sensitive": "Mark content as sensitive", + "compose_form.unlisted": "Unlisted mode", "navigation_bar.settings": "Settings", "navigation_bar.public_timeline": "Public timeline", "navigation_bar.logout": "Logout", diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx index d4434bba7..d6e61fd9e 100644 --- a/app/assets/javascripts/components/locales/es.jsx +++ b/app/assets/javascripts/components/locales/es.jsx @@ -35,6 +35,8 @@ const es = { "tabs_bar.notifications": "Notificaciones", "compose_form.placeholder": "¿En qué estás pensando?", "compose_form.publish": "Publicar", + "compose_form.sensitive": null, + "compose_form.unlisted": "No listado", "navigation_bar.settings": "Ajustes", "navigation_bar.public_timeline": "Público", "navigation_bar.logout": "Cerrar sesión", diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index c4458a145..968c3f8c3 100644 --- a/app/assets/javascripts/components/locales/fr.jsx +++ b/app/assets/javascripts/components/locales/fr.jsx @@ -36,7 +36,8 @@ const fr = { "tabs_bar.notifications": "Notifications", "compose_form.placeholder": "Qu’avez-vous en tête ?", "compose_form.publish": "Pouet", - "compose_form.sensitive": "Marquer le contenu comme délicat", + "compose_form.sensitive": "Marquer le contenu comme délicat", + "compose_form.unlisted": "Ne pas apparaître dans le fil public", "navigation_bar.settings": "Paramètres", "navigation_bar.public_timeline": "Public", "navigation_bar.logout": "Déconnexion", diff --git a/app/assets/javascripts/components/locales/hu.jsx b/app/assets/javascripts/components/locales/hu.jsx index 4a446965c..606fc830f 100644 --- a/app/assets/javascripts/components/locales/hu.jsx +++ b/app/assets/javascripts/components/locales/hu.jsx @@ -37,6 +37,7 @@ const hu = { "compose_form.placeholder": "Mire gondolsz?", "compose_form.publish": "Tülk!", "compose_form.sensitive": "Tartalom érzékenynek jelölése", + "compose_form.unlisted": "Listázatlan mód", "navigation_bar.settings": "Beállítások", "navigation_bar.public_timeline": "Nyilvános időfolyam", "navigation_bar.logout": "Kijelentkezés", diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx index e67bd80ac..0fba15f2c 100644 --- a/app/assets/javascripts/components/locales/pt.jsx +++ b/app/assets/javascripts/components/locales/pt.jsx @@ -33,6 +33,8 @@ const pt = { "tabs_bar.public": "Público", "compose_form.placeholder": "Que estás pensando?", "compose_form.publish": "Publicar", + "compose_form.sensitive": null, + "compose_form.unlisted": null, "navigation_bar.settings": "Configurações", "navigation_bar.public_timeline": "Timeline Pública", "navigation_bar.logout": "Logout", diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx index 4abc3e6aa..9d1d53083 100644 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ b/app/assets/javascripts/components/reducers/compose.jsx @@ -16,7 +16,8 @@ import { COMPOSE_SUGGESTIONS_CLEAR, COMPOSE_SUGGESTIONS_READY, COMPOSE_SUGGESTION_SELECT, - COMPOSE_SENSITIVITY_CHANGE + COMPOSE_SENSITIVITY_CHANGE, + COMPOSE_VISIBILITY_CHANGE } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; import { ACCOUNT_SET_SELF } from '../actions/accounts'; @@ -25,6 +26,7 @@ import Immutable from 'immutable'; const initialState = Immutable.Map({ mounted: false, sensitive: false, + unlisted: false, text: '', in_reply_to: null, is_submitting: false, @@ -91,6 +93,8 @@ export default function compose(state = initialState, action) { return state.set('mounted', false); case COMPOSE_SENSITIVITY_CHANGE: return state.set('sensitive', action.checked); + case COMPOSE_VISIBILITY_CHANGE: + return state.set('unlisted', action.checked); case COMPOSE_CHANGE: return state.set('text', action.text); case COMPOSE_REPLY: diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb new file mode 100644 index 000000000..4a591dc22 --- /dev/null +++ b/app/controllers/api/oembed_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Api::OembedController < ApiController + respond_to :json + + def show + @stream_entry = stream_entry_from_url(params[:url]) + @width = [300, params[:maxwidth].to_i].min + @height = [200, params[:maxheight].to_i].min + end + + private + + def stream_entry_from_url(url) + params = Rails.application.routes.recognize_path(url) + + raise ActiveRecord::NotFound unless params[:controller] == 'stream_entries' && params[:action] == 'show' + + StreamEntry.find(params[:id]) + end +end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index a0b15cfbc..8b7690850 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -52,7 +52,7 @@ class Api::V1::StatusesController < ApiController end def create - @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive]) + @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], unlisted: params[:unlisted]) render action: :show end @@ -73,7 +73,7 @@ class Api::V1::StatusesController < ApiController @reblogged_map = { @status.id => false } RemovalWorker.perform_async(reblog.id) - + render action: :show end diff --git a/app/models/concerns/obfuscate_filename.rb b/app/controllers/concerns/obfuscate_filename.rb index dc25cdbc2..9f12cb7e9 100644 --- a/app/models/concerns/obfuscate_filename.rb +++ b/app/controllers/concerns/obfuscate_filename.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module ObfuscateFilename extend ActiveSupport::Concern @@ -11,6 +12,6 @@ module ObfuscateFilename file = params.dig(*path) return if file.nil? - file.original_filename = "media" + File.extname(file.original_filename) + file.original_filename = 'media' + File.extname(file.original_filename) end end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 21fbba2af..0276f5fed 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -24,7 +24,7 @@ class Settings::ProfilesController < ApplicationController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :silenced) + params.require(:account).permit(:display_name, :note, :avatar, :header) end def set_account diff --git a/app/helpers/api/oembed_helper.rb b/app/helpers/api/oembed_helper.rb new file mode 100644 index 000000000..05d5ca216 --- /dev/null +++ b/app/helpers/api/oembed_helper.rb @@ -0,0 +1,2 @@ +module Api::OembedHelper +end diff --git a/app/helpers/atom_builder_helper.rb b/app/helpers/atom_builder_helper.rb index 13faaa261..40bbe0491 100644 --- a/app/helpers/atom_builder_helper.rb +++ b/app/helpers/atom_builder_helper.rb @@ -38,7 +38,7 @@ module AtomBuilderHelper end def verb(xml, verb) - xml['activity'].send('verb', "http://activitystrea.ms/schema/1.0/#{verb}") + xml['activity'].send('verb', TagManager::VERBS[verb]) end def content(xml, content) @@ -62,7 +62,7 @@ module AtomBuilderHelper end def object_type(xml, type) - xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{type}") + xml['activity'].send('object-type', TagManager::TYPES[type]) end def uri(xml, uri) @@ -108,7 +108,7 @@ module AtomBuilderHelper end def link_mention(xml, account) - xml.link(rel: 'mentioned', href: TagManager.instance.uri_for(account)) + xml.link(:rel => 'mentioned', :href => TagManager.instance.uri_for(account), 'ostatus:object-type' => TagManager::TYPES[:person]) end def link_enclosure(xml, media) @@ -139,6 +139,11 @@ module AtomBuilderHelper end end + def link_visibility(xml, item) + return unless item.respond_to?(:visibility) && item.public_visibility? + xml.link(:rel => 'mentioned', :href => TagManager::COLLECTIONS[:public], 'ostatus:object-type' => TagManager::TYPES[:collection]) + end + def include_author(xml, account) object_type xml, :person uri xml, TagManager.instance.uri_for(account) @@ -189,6 +194,8 @@ module AtomBuilderHelper include_author xml, stream_entry.target.account end + link_visibility xml, stream_entry.target + stream_entry.target.mentions.each do |mention| link_mention xml, mention.account end @@ -204,25 +211,34 @@ module AtomBuilderHelper end end + link_visibility xml, stream_entry.activity + stream_entry.mentions.each do |mentioned| link_mention xml, mentioned end - if stream_entry.activity.is_a?(Status) - stream_entry.activity.media_attachments.each do |media| - link_enclosure xml, media - end + return unless stream_entry.activity.is_a?(Status) - stream_entry.activity.tags.each do |tag| - category xml, tag - end + stream_entry.activity.media_attachments.each do |media| + link_enclosure xml, media + end + + stream_entry.activity.tags.each do |tag| + category xml, tag end end private def root_tag(xml, tag, &block) - xml.send(tag, { :xmlns => 'http://www.w3.org/2005/Atom', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0', 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/', 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:media' => 'http://purl.org/syndication/atommedia' }, &block) + xml.send(tag, { + 'xmlns' => TagManager::XMLNS, + 'xmlns:thr' => TagManager::THR_XMLNS, + 'xmlns:activity' => TagManager::AS_XMLNS, + 'xmlns:poco' => TagManager::POCO_XMLNS, + 'xmlns:media' => TagManager::MEDIA_XMLNS, + 'xmlns:ostatus' => TagManager::OS_XMLNS, + }, &block) end def single_link_avatar(xml, account, size, px) diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index fcc4b3259..bd4cbbbb0 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -6,6 +6,37 @@ class TagManager include Singleton include RoutingHelper + VERBS = { + post: 'http://activitystrea.ms/schema/1.0/post', + share: 'http://activitystrea.ms/schema/1.0/share', + favorite: 'http://activitystrea.ms/schema/1.0/favorite', + unfavorite: 'http://activitystrea.ms/schema/1.0/unfavorite', + delete: 'delete', + follow: 'http://activitystrea.ms/schema/1.0/follow', + unfollow: 'http://ostatus.org/schema/1.0/unfollow', + }.freeze + + TYPES = { + activity: 'http://activitystrea.ms/schema/1.0/activity', + note: 'http://activitystrea.ms/schema/1.0/note', + comment: 'http://activitystrea.ms/schema/1.0/comment', + person: 'http://activitystrea.ms/schema/1.0/person', + collection: 'http://activitystrea.ms/schema/1.0/collection', + group: 'http://activitystrea.ms/schema/1.0/group', + }.freeze + + COLLECTIONS = { + public: 'http://activityschema.org/collection/public', + }.freeze + + XMLNS = 'http://www.w3.org/2005/Atom' + MEDIA_XMLNS = 'http://purl.org/syndication/atommedia' + AS_XMLNS = 'http://activitystrea.ms/spec/1.0/' + THR_XMLNS = 'http://purl.org/syndication/thread/1.0' + POCO_XMLNS = 'http://portablecontacts.net/spec/1.0' + DFRN_XMLNS = 'http://purl.org/macgirvin/dfrn/1.0' + OS_XMLNS = 'http://ostatus.org/schema/1.0' + def unique_tag(date, id, type) "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" end diff --git a/app/models/status.rb b/app/models/status.rb index 8f65a3ecc..87d8249b1 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -5,6 +5,8 @@ class Status < ApplicationRecord include Streamable include Cacheable + enum visibility: [:public, :unlisted], _suffix: :visibility + belongs_to :account, inverse_of: :statuses belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies @@ -100,6 +102,7 @@ class Status < ApplicationRecord def as_public_timeline(account = nil) query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') + .where(visibility: :public) .where('accounts.silenced = FALSE') .where('statuses.in_reply_to_id IS NULL') .where('statuses.reblog_of_id IS NULL') @@ -110,6 +113,7 @@ class Status < ApplicationRecord def as_tag_timeline(tag, account = nil) query = tag.statuses .joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') + .where(visibility: :public) .where('accounts.silenced = FALSE') .where('statuses.in_reply_to_id IS NULL') .where('statuses.reblog_of_id IS NULL') diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 40d8a0fee..ea9588bec 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -8,7 +8,7 @@ class FanOutOnWriteService < BaseService deliver_to_followers(status) deliver_to_mentioned(status) - return if status.account.silenced? + return if status.account.silenced? || !status.public_visibility? deliver_to_hashtags(status) deliver_to_public(status) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 979a157e9..9e0ced129 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -10,7 +10,7 @@ class PostStatusService < BaseService # @option [Enumerable] :media_ids Optional array of media IDs to attach # @return [Status] def call(account, text, in_reply_to = nil, options = {}) - status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive]) + status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:unlisted] ? :unlisted : :public) attach_media(status, options[:media_ids]) process_mentions_service.call(status) process_hashtags_service.call(status) diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index a7a4cb2b0..3aa0ceff8 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ProcessFeedService < BaseService - ACTIVITY_NS = 'http://activitystrea.ms/spec/1.0/' - THREAD_NS = 'http://purl.org/syndication/thread/1.0' - def call(body, account) xml = Nokogiri::XML(body) xml.encoding = 'utf-8' @@ -15,12 +12,12 @@ class ProcessFeedService < BaseService private def update_author(xml, account) - return if xml.at_xpath('/xmlns:feed').nil? - UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed'), account, true) + return if xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS).nil? + UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS), account, true) end def process_entries(xml, account) - xml.xpath('//xmlns:entry').reverse_each.map { |entry| ProcessEntry.new.call(entry, account) }.compact + xml.xpath('//xmlns:entry', xmlns: TagManager::XMLNS).reverse_each.map { |entry| ProcessEntry.new.call(entry, account) }.compact end class ProcessEntry @@ -48,7 +45,7 @@ class ProcessFeedService < BaseService status = status_from_xml(@xml) if verb == :share - original_status = status_from_xml(@xml.at_xpath('.//activity:object', activity: ACTIVITY_NS)) + original_status = status_from_xml(@xml.at_xpath('.//activity:object', activity: TagManager::AS_XMLNS)) status.reblog = original_status if original_status.nil? @@ -138,9 +135,15 @@ class ProcessFeedService < BaseService def mentions_from_xml(parent, xml) processed_account_ids = [] + public_visibility = false - xml.xpath('./xmlns:link[@rel="mentioned"]').each do |link| - next if link['href'] == 'http://activityschema.org/collection/public' + xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each do |link| + if link['ostatus:object-type'] == TagManager::TYPES[:collection] && link['href'] == TagManager::COLLECTIONS[:public] + public_visibility = true + next + elsif link['ostatus:object-type'] == TagManager::TYPES[:group] + next + end url = Addressable::URI.parse(link['href']) @@ -160,15 +163,18 @@ class ProcessFeedService < BaseService # So we can skip duplicate mentions processed_account_ids << mentioned_account.id end + + parent.visibility = public_visibility ? :public : :unlisted + parent.save! end def hashtags_from_xml(parent, xml) - tags = xml.xpath('./xmlns:category').map { |category| category['term'] }.select { |t| !t.blank? } + tags = xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select { |t| !t.blank? } ProcessHashtagsService.new.call(parent, tags) end def media_from_xml(parent, xml) - xml.xpath('./xmlns:link[@rel="enclosure"]').each do |link| + xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: TagManager::XMLNS).each do |link| next unless link['href'] media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) @@ -183,52 +189,52 @@ class ProcessFeedService < BaseService end def id(xml = @xml) - xml.at_xpath('./xmlns:id').content + xml.at_xpath('./xmlns:id', xmlns: TagManager::XMLNS).content end def verb(xml = @xml) - raw = xml.at_xpath('./activity:verb', activity: ACTIVITY_NS).content - raw.gsub('http://activitystrea.ms/schema/1.0/', '').gsub('http://ostatus.org/schema/1.0/', '').to_sym + raw = xml.at_xpath('./activity:verb', activity: TagManager::AS_XMLNS).content + TagManager::VERBS.key(raw) rescue :post end def type(xml = @xml) - raw = xml.at_xpath('./activity:object-type', activity: ACTIVITY_NS).content - raw.gsub('http://activitystrea.ms/schema/1.0/', '').gsub('http://ostatus.org/schema/1.0/', '').to_sym + raw = xml.at_xpath('./activity:object-type', activity: TagManager::AS_XMLNS).content + TagManager::TYPES.key(raw) rescue :activity end def url(xml = @xml) - link = xml.at_xpath('./xmlns:link[@rel="alternate"]') + link = xml.at_xpath('./xmlns:link[@rel="alternate"]', xmlns: TagManager::XMLNS) link.nil? ? nil : link['href'] end def content(xml = @xml) - xml.at_xpath('./xmlns:content').content + xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS).content end def published(xml = @xml) - xml.at_xpath('./xmlns:published').content + xml.at_xpath('./xmlns:published', xmlns: TagManager::XMLNS).content end def thread?(xml = @xml) - !xml.at_xpath('./thr:in-reply-to', thr: THREAD_NS).nil? + !xml.at_xpath('./thr:in-reply-to', thr: TagManager::THR_XMLNS).nil? end def thread(xml = @xml) - thr = xml.at_xpath('./thr:in-reply-to', thr: THREAD_NS) + thr = xml.at_xpath('./thr:in-reply-to', thr: TagManager::THR_XMLNS) [thr['ref'], thr['href']] end def account?(xml = @xml) - !xml.at_xpath('./xmlns:author').nil? + !xml.at_xpath('./xmlns:author', xmlns: TagManager::XMLNS).nil? end def acct(xml = @xml) - username = xml.at_xpath('./xmlns:author/xmlns:name').content - url = xml.at_xpath('./xmlns:author/xmlns:uri').content + username = xml.at_xpath('./xmlns:author/xmlns:name', xmlns: TagManager::XMLNS).content + url = xml.at_xpath('./xmlns:author/xmlns:uri', xmlns: TagManager::XMLNS).content domain = Addressable::URI.parse(url).host "#{username}@#{domain}" diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index 6b2f6e2d2..129b2a2be 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class ProcessInteractionService < BaseService - ACTIVITY_NS = 'http://activitystrea.ms/spec/1.0/' - # Record locally the remote interaction with our user # @param [String] envelope Salmon envelope # @param [Account] target_account Account the Salmon was addressed to @@ -14,8 +12,8 @@ class ProcessInteractionService < BaseService return unless contains_author?(xml) - username = xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:name').content - url = xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:uri').content + username = xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:name', xmlns: TagManager::XMLNS).content + url = xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:uri', xmlns: TagManager::XMLNS).content domain = Addressable::URI.parse(url).host account = Account.find_by(username: username, domain: domain) @@ -26,7 +24,7 @@ class ProcessInteractionService < BaseService end if salmon.verify(envelope, account.keypair) - update_remote_profile_service.call(xml.at_xpath('/xmlns:entry'), account, true) + update_remote_profile_service.call(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS), account, true) case verb(xml) when :follow @@ -50,16 +48,17 @@ class ProcessInteractionService < BaseService private def contains_author?(xml) - !(xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:name').nil? || xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:uri').nil?) + !(xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:name', xmlns: TagManager::XMLNS).nil? || xml.at_xpath('/xmlns:entry/xmlns:author/xmlns:uri', xmlns: TagManager::XMLNS).nil?) end def mentions_account?(xml, account) - xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]').each { |mention_link| return true if mention_link.attribute('href').value == TagManager.instance.url_for(account) } + xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each { |mention_link| return true if mention_link.attribute('href').value == TagManager.instance.url_for(account) } false end def verb(xml) - xml.at_xpath('//activity:verb', activity: ACTIVITY_NS).content.gsub('http://activitystrea.ms/schema/1.0/', '').gsub('http://ostatus.org/schema/1.0/', '').to_sym + raw = xml.at_xpath('//activity:verb', activity: TagManager::AS_XMLNS).content + TagManager::VERBS.key(raw) rescue :post end @@ -74,7 +73,7 @@ class ProcessInteractionService < BaseService end def delete_post!(xml, account) - status = Status.find(xml.at_xpath('//xmlns:id').content) + status = Status.find(xml.at_xpath('//xmlns:id', xmlns: TagManager::XMLNS).content) return if status.nil? @@ -96,7 +95,7 @@ class ProcessInteractionService < BaseService end def activity_id(xml) - xml.at_xpath('//activity:object', activity: ACTIVITY_NS).at_xpath('./xmlns:id').content + xml.at_xpath('//activity:object', activity: TagManager::AS_XMLNS).at_xpath('./xmlns:id', xmlns: TagManager::XMLNS).content end def salmon diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index 56b25816f..66d25dfeb 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -1,19 +1,16 @@ # frozen_string_literal: true class UpdateRemoteProfileService < BaseService - POCO_NS = 'http://portablecontacts.net/spec/1.0' - DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0' - def call(xml, account, resubscribe = false) return if xml.nil? - author_xml = xml.at_xpath('./xmlns:author') || xml.at_xpath('./dfrn:owner', dfrn: DFRN_NS) - hub_link = xml.at_xpath('./xmlns:link[@rel="hub"]') + author_xml = xml.at_xpath('./xmlns:author', xmlns: TagManager::XMLNS) || xml.at_xpath('./dfrn:owner', dfrn: TagManager::DFRN_XMLNS) + hub_link = xml.at_xpath('./xmlns:link[@rel="hub"]', xmlns: TagManager::XMLNS) unless author_xml.nil? - account.display_name = author_xml.at_xpath('./poco:displayName', poco: POCO_NS).content unless author_xml.at_xpath('./poco:displayName', poco: POCO_NS).nil? - account.note = author_xml.at_xpath('./poco:note', poco: POCO_NS).content unless author_xml.at_xpath('./poco:note').nil? - account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]').nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'].blank? + account.display_name = author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).nil? + account.note = author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).nil? + account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank? end old_hub_url = account.hub_url diff --git a/app/views/api/oembed/show.json.rabl b/app/views/api/oembed/show.json.rabl new file mode 100644 index 000000000..e035bc13c --- /dev/null +++ b/app/views/api/oembed/show.json.rabl @@ -0,0 +1,14 @@ +# frozen_string_literal: true +object @stream_entry + +node(:type) { 'rich' } +node(:version) { '1.0' } +node(:title, &:title) +node(:author_name) { |entry| entry.account.display_name.blank? ? entry.account.username : entry.account.display_name } +node(:author_url) { |entry| account_url(entry.account) } +node(:provider_name) { Rails.configuration.x.local_domain } +node(:provider_url) { root_url } +node(:cache_age) { 86_400 } +node(:html, &:content) +node(:width) { @width } +node(:height) { @height } diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index e5f8a46c4..c2f1adb12 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -8,7 +8,6 @@ = f.input :note, placeholder: t('simple_form.labels.defaults.note') = f.input :avatar, wrapper: :with_label = f.input :header, wrapper: :with_label - = f.input :silenced, as: :boolean, wrapper: :with_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 2c6de32d9..e628dd99d 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -1,8 +1,13 @@ - content_for :header_tags do %link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/ + %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/ + %meta{ name: 'og:site_name', content: 'Mastodon' }/ %meta{ name: 'og:type', content: 'article' }/ + %meta{ name: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/ %meta{ name: 'og:article:author', content: @account.username }/ + %meta{ name: 'og:description', content: @stream_entry.activity.content }/ + %meta{ name: 'og:image', content: @stream_entry.activity.is_a?(Status) && @stream_entry.activity.media_attachments.size > 0 ? full_asset_url(@stream_entry.activity.media_attachments.first.file.url(:preview)) : full_asset_url(@account.avatar.url(:large)) }/ .activity-stream.activity-stream-headless = render partial: @type, locals: { @type.to_sym => @stream_entry.activity, include_threads: true } diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 47e30ccb4..d0aed9d0e 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -14,7 +14,6 @@ de: new_password: Neues Passwort note: Über mich password: Passwort - silenced: Öffentliche Beiträge nicht auflisten username: Nutzername interactions: must_be_follower: Benachrichtigungen von nicht-Folgern blockieren diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 1e975af14..516d79b08 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -14,7 +14,6 @@ en: new_password: New password note: Bio password: Password - silenced: Unlisted mode username: Username interactions: must_be_follower: Block notifications from non-followers diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index bfe000b33..b6a8fe3d2 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -14,7 +14,6 @@ es: new_password: Nueva contraseña note: Biografía password: Contraseña - silenced: No listado username: Nombre de usuario notification_emails: favourite: Enviar correo electrónico cuando alguien de a favorito en su publicación diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index fd20056d7..0fcf89140 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -14,7 +14,6 @@ fr: new_password: Nouveau mot de passe note: Présentation password: Mot de passe - silenced: Ne pas apparaître dans le fil public username: Identifiant interactions: must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 39c450087..89eb70767 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -14,7 +14,6 @@ hu: new_password: Új jelszó note: Önéletrajz password: Jelszó - silenced: Listázatlan mód username: Felhasználónév notification_emails: favourite: E-mail küldése amikor valaki kedvencnek jelöli az állapotod diff --git a/config/routes.rb b/config/routes.rb index f956e99be..35e5c269a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,6 +60,9 @@ Rails.application.routes.draw do # Salmon post '/salmon/:id', to: 'salmon#update', as: :salmon + # OEmbed + get '/oembed', to: 'oembed#show', as: :oembed + # JSON / REST API namespace :v1 do resources :statuses, only: [:create, :show, :destroy] do diff --git a/db/migrate/20161130185319_add_visibility_to_statuses.rb b/db/migrate/20161130185319_add_visibility_to_statuses.rb new file mode 100644 index 000000000..3ad2abe01 --- /dev/null +++ b/db/migrate/20161130185319_add_visibility_to_statuses.rb @@ -0,0 +1,5 @@ +class AddVisibilityToStatuses < ActiveRecord::Migration[5.0] + def change + add_column :statuses, :visibility, :integer, null: false, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 9ba638bfc..7da83914c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161130142058) do +ActiveRecord::Schema.define(version: 20161130185319) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -176,6 +176,7 @@ ActiveRecord::Schema.define(version: 20161130142058) do t.integer "reblog_of_id" t.string "url" t.boolean "sensitive", default: false + t.integer "visibility", default: 0, null: false t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb new file mode 100644 index 000000000..758bfd1da --- /dev/null +++ b/spec/controllers/api/oembed_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Api::OembedController, type: :controller do + +end diff --git a/spec/helpers/api/oembed_helper_spec.rb b/spec/helpers/api/oembed_helper_spec.rb new file mode 100644 index 000000000..4f64cb84f --- /dev/null +++ b/spec/helpers/api/oembed_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Api::OembedHelper. For example: +# +# describe Api::OembedHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Api::OembedHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/atom_builder_helper_spec.rb b/spec/helpers/atom_builder_helper_spec.rb index 7e11a8a77..8a161cab3 100644 --- a/spec/helpers/atom_builder_helper_spec.rb +++ b/spec/helpers/atom_builder_helper_spec.rb @@ -13,7 +13,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do describe '#feed' do it 'creates a feed' do - expect(used_in_builder { |xml| helper.feed(xml) }).to match '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia"/>' + expect(used_in_builder { |xml| helper.feed(xml) }).to match '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0"/>' end end @@ -46,7 +46,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do describe '#verb' do it 'creates an entry' do - expect(used_with_namespaces { |xml| helper.verb(xml, 'verb') }).to match '<activity:verb>http://activitystrea.ms/schema/1.0/verb</activity:verb>' + expect(used_with_namespaces { |xml| helper.verb(xml, :post) }).to match '<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>' end end @@ -76,7 +76,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do describe '#object_type' do it 'creates an object type' do - expect(used_with_namespaces { |xml| helper.object_type(xml, 'test') }).to match '<activity:object-type>http://activitystrea.ms/schema/1.0/test</activity:object-type>' + expect(used_with_namespaces { |xml| helper.object_type(xml, :person) }).to match '<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>' end end @@ -146,7 +146,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do let(:account) { Fabricate(:account, username: 'alice') } it 'creates a link' do - expect(used_in_builder { |xml| helper.link_mention(xml, account) }).to match '<link rel="mentioned" href="https://cb6e6126.ngrok.io/users/alice"/>' + expect(used_in_builder { |xml| helper.link_mention(xml, account) }).to match '<link rel="mentioned" href="https://cb6e6126.ngrok.io/users/alice" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"/>' end end |