Fix #233, fix #268master
@@ -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) | |||
@@ -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 | |||
}; | |||
}; |
@@ -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> | |||
@@ -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)); | |||
} | |||
} | |||
}; | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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: | |||
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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) | |||
@@ -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 | |||
@@ -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') | |||
@@ -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) | |||
@@ -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) | |||
@@ -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}" | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
@@ -0,0 +1,5 @@ | |||
class AddVisibilityToStatuses < ActiveRecord::Migration[5.0] | |||
def change | |||
add_column :statuses, :visibility, :integer, null: false, default: 0 | |||
end | |||
end |
@@ -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 | |||
@@ -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 | |||