Merge branch 'master' into master

This commit is contained in:
Shel R
2017-04-07 22:21:21 -04:00
committed by GitHub
81 changed files with 979 additions and 400 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 209 KiB

View File

@ -1,5 +1,9 @@
@import 'variables';
.app-body{
-ms-overflow-style: -ms-autohiding-scrollbar;
}
.button {
background-color: darken($color4, 3%);
font-family: inherit;

View File

@ -16,7 +16,8 @@ class AccountsController < ApplicationController
end
format.atom do
@entries = @account.stream_entries.order('id desc').where(activity_type: 'Status').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
@entries = @account.stream_entries.order('id desc').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
end
format.activitystreams2

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
include Localized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
@ -14,7 +16,6 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :set_locale
before_action :set_user_activity
before_action :check_suspension, if: :user_signed_in?
@ -28,12 +29,6 @@ class ApplicationController < ActionController::Base
store_location_for(:user, request.url)
end
def set_locale
I18n.locale = current_user.try(:locale) || I18n.default_locale
rescue I18n::InvalidLocale
I18n.locale = I18n.default_locale
end
def require_admin!
redirect_to root_path unless current_user&.admin?
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Localized
extend ActiveSupport::Concern
included do
before_action :set_locale
end
def set_locale
I18n.locale = current_user.try(:locale) || default_locale
rescue I18n::InvalidLocale
I18n.locale = default_locale
end
def default_locale
ENV.fetch('DEFAULT_LOCALE') { I18n.default_locale }
end
end

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include Localized
skip_before_action :authenticate_resource_owner!
before_action :set_locale
before_action :store_current_location
before_action :authenticate_resource_owner!
@ -12,10 +13,4 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
def store_current_location
store_location_for(:user, request.url)
end
def set_locale
I18n.locale = current_user.try(:locale) || I18n.default_locale
rescue I18n::InvalidLocale
I18n.locale = I18n.default_locale
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
include Localized
skip_before_action :authenticate_resource_owner!
before_action :store_current_location
before_action :authenticate_resource_owner!
private
def store_current_location
store_location_for(:user, request.url)
end
end

View File

@ -19,7 +19,9 @@ class StreamEntriesController < ApplicationController
end
end
format.atom
format.atom do
render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true))
end
end
end

View File

@ -34,10 +34,6 @@ module StreamEntriesHelper
user_signed_in? && @favourited.key?(status.id) ? 'favourited' : ''
end
def proper_status(status)
status.reblog? ? status.reblog : status
end
def rtl?(text)
return false if text.empty?

351
app/lib/atom_serializer.rb Normal file
View File

@ -0,0 +1,351 @@
# frozen_string_literal: true
class AtomSerializer
include RoutingHelper
class << self
def render(element)
document = Ox::Document.new(version: '1.0')
document << element
('<?xml version="1.0"?>' + Ox.dump(element)).force_encoding('UTF-8')
end
end
def author(account)
author = Ox::Element.new('author')
uri = TagManager.instance.uri_for(account)
append_element(author, 'id', uri)
append_element(author, 'activity:object-type', TagManager::TYPES[:person])
append_element(author, 'uri', uri)
append_element(author, 'name', account.username)
append_element(author, 'email', account.local? ? "#{account.acct}@#{Rails.configuration.x.local_domain}" : account.acct)
append_element(author, 'summary', account.note)
append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original)))
append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original)))
append_element(author, 'poco:preferredUsername', account.username)
append_element(author, 'poco:displayName', account.display_name) unless account.display_name.blank?
append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) unless account.note.blank?
append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
author
end
def feed(account, stream_entries)
feed = Ox::Element.new('feed')
add_namespaces(feed)
append_element(feed, 'id', account_url(account, format: 'atom'))
append_element(feed, 'title', account.display_name)
append_element(feed, 'subtitle', account.note)
append_element(feed, 'updated', account.updated_at.iso8601)
append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
feed << author(account)
append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20
append_element(feed, 'link', nil, rel: :hub, href: api_push_url)
append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id))
stream_entries.each do |stream_entry|
feed << entry(stream_entry)
end
feed
end
def entry(stream_entry, root = false)
entry = Ox::Element.new('entry')
add_namespaces(entry) if root
append_element(entry, 'id', TagManager.instance.unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type))
append_element(entry, 'published', stream_entry.created_at.iso8601)
append_element(entry, 'updated', stream_entry.updated_at.iso8601)
append_element(entry, 'title', stream_entry&.status&.title)
entry << author(stream_entry.account) if root
append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type])
append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb])
entry << object(stream_entry.target) if stream_entry.targeted?
serialize_status_attributes(entry, stream_entry.status) unless stream_entry.status.nil?
append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: account_stream_entry_url(stream_entry.account, stream_entry))
append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(stream_entry.thread), href: TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded?
entry
end
def object(status)
object = Ox::Element.new('activity:object')
append_element(object, 'id', TagManager.instance.uri_for(status))
append_element(object, 'published', status.created_at.iso8601)
append_element(object, 'updated', status.updated_at.iso8601)
append_element(object, 'title', status.title)
object << author(status.account)
append_element(object, 'activity:object-type', TagManager::TYPES[status.object_type])
append_element(object, 'activity:verb', TagManager::VERBS[status.verb])
serialize_status_attributes(object, status)
append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status))
append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply? && !status.thread.nil?
object
end
def follow_salmon(follow)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{follow.account.acct} started following #{follow.target_account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
append_element(entry, 'title', description)
append_element(entry, 'content', description, type: :html)
entry << author(follow.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:follow])
object = author(follow.target_account)
object.value = 'activity:object'
entry << object
entry
end
def follow_request_salmon(follow_request)
entry = Ox::Element.new('entry')
add_namespaces(entry)
append_element(entry, 'id', TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
entry << author(follow_request.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:request_friend])
object = author(follow_request.target_account)
object.value = 'activity:object'
entry << object
entry
end
def authorize_follow_request_salmon(follow_request)
entry = Ox::Element.new('entry')
add_namespaces(entry)
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
entry << author(follow_request.target_account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:authorize])
object = Ox::Element.new('activity:object')
object << author(follow_request.account)
append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
inner_object = author(follow_request.target_account)
inner_object.value = 'activity:object'
object << inner_object
entry << object
entry
end
def reject_follow_request_salmon(follow_request)
entry = Ox::Element.new('entry')
add_namespaces(entry)
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
entry << author(follow_request.target_account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:reject])
object = Ox::Element.new('activity:object')
object << author(follow_request.account)
append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
inner_object = author(follow_request.target_account)
inner_object.value = 'activity:object'
object << inner_object
entry << object
entry
end
def unfollow_salmon(follow)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
append_element(entry, 'title', description)
append_element(entry, 'content', description, type: :html)
entry << author(follow.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:unfollow])
object = author(follow.target_account)
object.value = 'activity:object'
entry << object
entry
end
def block_salmon(block)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
append_element(entry, 'title', description)
entry << author(block.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:block])
object = author(block.target_account)
object.value = 'activity:object'
entry << object
entry
end
def unblock_salmon(block)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
append_element(entry, 'title', description)
entry << author(block.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:unblock])
object = author(block.target_account)
object.value = 'activity:object'
entry << object
entry
end
def favourite_salmon(favourite)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
append_element(entry, 'title', description)
append_element(entry, 'content', description, type: :html)
entry << author(favourite.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:favorite])
entry << object(favourite.status)
append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
entry
end
def unfavourite_salmon(favourite)
entry = Ox::Element.new('entry')
add_namespaces(entry)
description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
append_element(entry, 'title', description)
append_element(entry, 'content', description, type: :html)
entry << author(favourite.account)
append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
append_element(entry, 'activity:verb', TagManager::VERBS[:unfavorite])
entry << object(favourite.status)
append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
entry
end
private
def append_element(parent, name, content = nil, attributes = {})
element = Ox::Element.new(name)
attributes.each { |k, v| element[k] = v.to_s }
element << content.to_s unless content.nil?
parent << element
end
def add_namespaces(parent)
parent['xmlns'] = TagManager::XMLNS
parent['xmlns:thr'] = TagManager::THR_XMLNS
parent['xmlns:activity'] = TagManager::AS_XMLNS
parent['xmlns:poco'] = TagManager::POCO_XMLNS
parent['xmlns:media'] = TagManager::MEDIA_XMLNS
parent['xmlns:ostatus'] = TagManager::OS_XMLNS
parent['xmlns:mastodon'] = TagManager::MTDN_XMLNS
end
def serialize_status_attributes(entry, status)
append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank?
append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html')
status.mentions.each do |mentioned|
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
end
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:collection], href: TagManager::COLLECTIONS[:public]) if status.public_visibility?
status.tags.each do |tag|
append_element(entry, 'category', nil, term: tag.name)
end
append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive?
status.media_attachments.each do |media|
append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
end
append_element(entry, 'mastodon:scope', status.visibility)
end
end

View File

@ -78,6 +78,8 @@ class TagManager
case target.object_type
when :person
account_url(target)
when :note, :comment, :activity
unique_tag(target.created_at, target.id, 'Status')
else
unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type)
end

View File

@ -125,11 +125,11 @@ class Account < ApplicationRecord
end
def favourited?(status)
(status.reblog? ? status.reblog : status).favourites.where(account: self).count.positive?
status.proper.favourites.where(account: self).count.positive?
end
def reblogged?(status)
(status.reblog? ? status.reblog : status).reblogs.where(account: self).count.positive?
status.proper.reblogs.where(account: self).count.positive?
end
def keypair

View File

@ -62,8 +62,12 @@ class Status < ApplicationRecord
reply? ? :comment : :note
end
def proper
reblog? ? reblog : self
end
def content
reblog? ? reblog.text : text
proper.text
end
def target

View File

@ -5,25 +5,21 @@ class StreamEntry < ApplicationRecord
belongs_to :account, inverse_of: :stream_entries
belongs_to :activity, polymorphic: true
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry
validates :account, :activity, presence: true
STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, mentions: :account], thread: [:stream_entry, :account]].freeze
STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze
default_scope { where(activity_type: 'Status') }
scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) }
def object_type
if orphaned?
:activity
else
targeted? ? :activity : activity.object_type
end
orphaned? || targeted? ? :activity : status.object_type
end
def verb
orphaned? ? :delete : activity.verb
orphaned? ? :delete : status.verb
end
def targeted?
@ -31,15 +27,15 @@ class StreamEntry < ApplicationRecord
end
def target
orphaned? ? nil : activity.target
orphaned? ? nil : status.target
end
def title
orphaned? ? nil : activity.title
orphaned? ? nil : status.title
end
def content
orphaned? ? nil : activity.content
orphaned? ? nil : status.content
end
def threaded?
@ -47,20 +43,16 @@ class StreamEntry < ApplicationRecord
end
def thread
orphaned? ? nil : activity.thread
orphaned? ? nil : status.thread
end
def mentions
activity.respond_to?(:mentions) ? activity.mentions.map(&:account) : []
end
def activity
!new_record? ? send(activity_type.underscore) || super : super
orphaned? ? [] : status.mentions.map(&:account)
end
private
def orphaned?
activity.nil?
status.nil?
end
end

View File

@ -9,20 +9,20 @@ class AfterBlockService < BaseService
private
def clear_timelines(account, target_account)
mentions_key = FeedManager.instance.key(:mentions, account.id)
home_key = FeedManager.instance.key(:home, account.id)
home_key = FeedManager.instance.key(:home, account.id)
target_account.statuses.select('id').find_each do |status|
redis.zrem(mentions_key, status.id)
redis.zrem(home_key, status.id)
redis.pipelined do
target_account.statuses.select('id').find_each do |status|
redis.zrem(home_key, status.id)
end
end
end
def clear_notifications(account, target_account)
Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).destroy_all
Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).destroy_all
Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).destroy_all
Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).destroy_all
Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).delete_all
end
def redis

View File

@ -10,31 +10,6 @@ class AuthorizeFollowService < BaseService
private
def build_xml(follow_request)
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, Time.now.utc, follow_request.id, 'FollowRequest'
title xml, "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}"
author(xml) do
include_author xml, follow_request.target_account
end
object_type xml, :activity
verb xml, :authorize
target(xml) do
author(xml) do
include_author xml, follow_request.account
end
object_type xml, :activity
verb xml, :request_friend
target(xml) do
include_author xml, follow_request.target_account
end
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.authorize_follow_request_salmon(follow_request))
end
end

View File

@ -18,22 +18,6 @@ class BlockService < BaseService
private
def build_xml(block)
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, block.created_at, block.id, 'Block'
title xml, "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
author(xml) do
include_author xml, block.account
end
object_type xml, :activity
verb xml, :block
target(xml) do
include_author xml, block.target_account
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.block_salmon(block))
end
end

View File

@ -2,7 +2,6 @@
module StreamEntryRenderer
def stream_entry_to_xml(stream_entry)
renderer = StreamEntriesController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https)
renderer.render(:show, assigns: { stream_entry: stream_entry }, formats: [:atom])
AtomSerializer.render(AtomSerializer.new.entry(stream_entry, true))
end
end

View File

@ -51,21 +51,22 @@ class FanOutOnWriteService < BaseService
def render_anonymous_payload(status)
@payload = InlineRenderer.render(status, nil, 'api/v1/statuses/show')
@payload = Oj.dump(event: :update, payload: @payload)
end
def deliver_to_hashtags(status)
Rails.logger.debug "Delivering status #{status.id} to hashtags"
status.tags.pluck(:name).each do |hashtag|
Redis.current.publish("hashtag:#{hashtag}", Oj.dump(event: :update, payload: @payload))
Redis.current.publish("hashtag:#{hashtag}:local", Oj.dump(event: :update, payload: @payload)) if status.account.local?
Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
end
end
def deliver_to_public(status)
Rails.logger.debug "Delivering status #{status.id} to public timeline"
Redis.current.publish('public', Oj.dump(event: 'update', payload: @payload))
Redis.current.publish('public:local', Oj.dump(event: 'update', payload: @payload)) if status.account.local?
Redis.current.publish('timeline:public', @payload)
Redis.current.publish('timeline:public:local', @payload) if status.local?
end
end

View File

@ -22,26 +22,6 @@ class FavouriteService < BaseService
private
def build_xml(favourite)
description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, favourite.created_at, favourite.id, 'Favourite'
title xml, description
content xml, description
author(xml) do
include_author xml, favourite.account
end
object_type xml, :activity
verb xml, :favorite
in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status)
target(xml) do
include_target xml, favourite.status
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.favourite_salmon(favourite))
end
end

View File

@ -45,13 +45,13 @@ class FollowRemoteAccountService < BaseService
account.suspended = true if domain_block && domain_block.suspend?
account.silenced = true if domain_block && domain_block.silence?
xml = get_feed(account.remote_url)
hubs = get_hubs(xml)
body, xml = get_feed(account.remote_url)
hubs = get_hubs(xml)
account.uri = get_account_uri(xml)
account.hub_url = hubs.first.attribute('href').value
get_profile(xml, account)
get_profile(body, account)
account.save!
account
@ -61,7 +61,7 @@ class FollowRemoteAccountService < BaseService
def get_feed(url)
response = http_client.get(Addressable::URI.parse(url))
Nokogiri::XML(response)
[response.to_s, Nokogiri::XML(response)]
end
def get_hubs(xml)
@ -82,12 +82,8 @@ class FollowRemoteAccountService < BaseService
author_uri.content
end
def get_profile(xml, account)
update_remote_profile_service.call(xml.at_xpath('/xmlns:feed'), account)
end
def update_remote_profile_service
@update_remote_profile_service ||= UpdateRemoteProfileService.new
def get_profile(body, account)
RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), false)
end
def http_client

View File

@ -10,7 +10,7 @@ class FollowService < BaseService
target_account = FollowRemoteAccountService.new.call(uri)
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)
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
if target_account.locked?
request_follow(source_account, target_account)
@ -55,48 +55,10 @@ class FollowService < BaseService
end
def build_follow_request_xml(follow_request)
description = "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}"
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, follow_request.created_at, follow_request.id, 'FollowRequest'
title xml, description
content xml, description
author(xml) do
include_author xml, follow_request.account
end
object_type xml, :activity
verb xml, :request_friend
target(xml) do
include_author xml, follow_request.target_account
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.follow_request_salmon(follow_request))
end
def build_follow_xml(follow)
description = "#{follow.account.acct} started following #{follow.target_account.acct}"
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, follow.created_at, follow.id, 'Follow'
title xml, description
content xml, description
author(xml) do
include_author xml, follow.account
end
object_type xml, :activity
verb xml, :follow
target(xml) do
include_author xml, follow.target_account
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.follow_salmon(follow))
end
end

View File

@ -50,7 +50,7 @@ class NotifyService < BaseService
def create_notification
@notification.save!
return unless @notification.browserable?
Redis.current.publish(@recipient.id, Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show')))
Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show')))
end
def send_email

View File

@ -37,11 +37,11 @@ class PostStatusService < BaseService
def validate_media!(media_ids)
return if media_ids.nil? || !media_ids.is_a?(Enumerable)
raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4
media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?)
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?)
media
end

View File

@ -5,15 +5,15 @@ class ProcessFeedService < BaseService
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
update_author(xml, account)
update_author(body, xml, account)
process_entries(xml, account)
end
private
def update_author(xml, account)
def update_author(body, xml, account)
return if xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS).nil?
UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS), account, true)
RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
end
def process_entries(xml, account)

View File

@ -24,7 +24,7 @@ class ProcessInteractionService < BaseService
return if account.suspended?
if salmon.verify(envelope, account.keypair)
update_remote_profile_service.call(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS), account, true)
RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
case verb(xml)
when :follow
@ -114,7 +114,7 @@ class ProcessInteractionService < BaseService
return if status.nil?
remove_status_service.call(status) if account.id == status.account_id
RemovalWorker.perform_async(status.id) if account.id == status.account_id
end
def favourite!(xml, from_account)
@ -130,7 +130,7 @@ class ProcessInteractionService < BaseService
end
def add_post!(body, account)
process_feed_service.call(body, account)
ProcessingWorker.perform_async(account.id, body.force_encoding('UTF-8'))
end
def status(xml)
@ -153,10 +153,6 @@ class ProcessInteractionService < BaseService
@process_feed_service ||= ProcessFeedService.new
end
def update_remote_profile_service
@update_remote_profile_service ||= UpdateRemoteProfileService.new
end
def remove_status_service
@remove_status_service ||= RemoveStatusService.new
end

View File

@ -10,31 +10,6 @@ class RejectFollowService < BaseService
private
def build_xml(follow_request)
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, Time.now.utc, follow_request.id, 'FollowRequest'
title xml, "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}"
author(xml) do
include_author xml, follow_request.target_account
end
object_type xml, :activity
verb xml, :reject
target(xml) do
author(xml) do
include_author xml, follow_request.account
end
object_type xml, :activity
verb xml, :request_friend
target(xml) do
include_author xml, follow_request.target_account
end
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.reject_follow_request_salmon(follow_request))
end
end

View File

@ -4,6 +4,8 @@ class RemoveStatusService < BaseService
include StreamEntryRenderer
def call(status)
@payload = Oj.dump(event: :delete, payload: status.id)
remove_from_self(status) if status.account.local?
remove_from_followers(status)
remove_from_mentioned(status)
@ -25,25 +27,23 @@ class RemoveStatusService < BaseService
end
def remove_from_followers(status)
status.account.followers.each do |follower|
next unless follower.local?
status.account.followers.where(domain: nil).each do |follower|
unpush(:home, follower, status)
end
end
def remove_from_mentioned(status)
return unless status.local?
notified_domains = []
status.mentions.each do |mention|
mentioned_account = mention.account
if mentioned_account.local?
unpush(:mentions, mentioned_account, status)
else
next if notified_domains.include?(mentioned_account.domain)
notified_domains << mentioned_account.domain
send_delete_salmon(mentioned_account, status)
end
next if mentioned_account.local?
next if notified_domains.include?(mentioned_account.domain)
notified_domains << mentioned_account.domain
send_delete_salmon(mentioned_account, status)
end
end
@ -65,17 +65,19 @@ class RemoveStatusService < BaseService
redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id)
end
Redis.current.publish(receiver.id, Oj.dump(event: :delete, payload: status.id))
Redis.current.publish("timeline:#{receiver.id}", @payload)
end
def remove_from_hashtags(status)
status.tags.each do |tag|
Redis.current.publish("hashtag:#{tag.name}", Oj.dump(event: :delete, payload: status.id))
status.tags.pluck(:name) do |hashtag|
Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
end
end
def remove_from_public(status)
Redis.current.publish('public', Oj.dump(event: :delete, payload: status.id))
Redis.current.publish('timeline:public', @payload)
Redis.current.publish('timeline:public:local', @payload) if status.local?
end
def redis

View File

@ -11,22 +11,6 @@ class UnblockService < BaseService
private
def build_xml(block)
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, Time.now.utc, block.id, 'Block'
title xml, "#{block.account.acct} no longer blocks #{block.target_account.acct}"
author(xml) do
include_author xml, block.account
end
object_type xml, :activity
verb xml, :unblock
target(xml) do
include_author xml, block.target_account
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.unblock_salmon(block))
end
end

View File

@ -13,26 +13,6 @@ class UnfavouriteService < BaseService
private
def build_xml(favourite)
description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, Time.now.utc, favourite.id, 'Favourite'
title xml, description
content xml, description
author(xml) do
include_author xml, favourite.account
end
object_type xml, :activity
verb xml, :unfavorite
in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status)
target(xml) do
include_target xml, favourite.status
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.unfavourite_salmon(favourite))
end
end

View File

@ -13,25 +13,6 @@ class UnfollowService < BaseService
private
def build_xml(follow)
description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
unique_id xml, Time.now.utc, follow.id, 'Follow'
title xml, description
content xml, description
author(xml) do
include_author xml, follow.account
end
object_type xml, :activity
verb xml, :unfollow
target(xml) do
include_author xml, follow.target_account
end
end
end.to_xml
AtomSerializer.render(AtomSerializer.new.unfollow_salmon(follow))
end
end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
Nokogiri::XML::Builder.new do |xml|
feed(xml) do
simple_id xml, account_url(@account, format: 'atom')
title xml, @account.display_name
subtitle xml, @account.note
updated_at xml, stream_updated_at
logo xml, full_asset_url(@account.avatar.url(:original))
author(xml) do
include_author xml, @account
end
link_alternate xml, TagManager.instance.url_for(@account)
link_self xml, account_url(@account, format: 'atom')
link_next xml, account_url(@account, format: 'atom', max_id: @entries.last.id) if @entries.size == 20
link_hub xml, api_push_url
link_salmon xml, api_salmon_url(@account.id)
@entries.each do |stream_entry|
entry(xml, false) do
include_entry xml, stream_entry
end
end
end
end.to_xml

View File

@ -11,8 +11,10 @@
%meta{:name => "theme-color", :content => "#282c37"}/
%meta{:name => "apple-mobile-web-app-capable", :content => "yes"}/
%title
= "#{yield(:page_title)} - " if content_for?(:page_title)
%title<
- if content_for?(:page_title)
= yield(:page_title)
= ' - '
= Setting.site_title
= stylesheet_link_tag 'application', media: 'all'

View File

@ -16,7 +16,7 @@
%strong= display_name(status.account)
= t('stream_entries.reblogged')
= render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: proper_status(status) }
= render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper }
- if include_threads
= render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true }

View File

@ -1,9 +0,0 @@
Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do
author(xml) do
include_author xml, @stream_entry.account
end
include_entry xml, @stream_entry
end
end.to_xml

View File

@ -0,0 +1,5 @@
<p>Tervetuloa <%= @resource.email %>!</p>
<p>Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä:</p>
<p><%= link_to 'Varmista tilini', confirmation_url(@resource, confirmation_token: @token) %></p>

View File

@ -0,0 +1,5 @@
Tervetuloa <%= @resource.email %>!
Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä:
<%= confirmation_url(@resource, confirmation_token: @token) %>

View File

@ -0,0 +1,3 @@
<p>Hei <%= @resource.email %>!</p>
<p>Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu.</p>

View File

@ -0,0 +1,3 @@
Hei <%= @resource.email %>!
Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu.

View File

@ -0,0 +1,8 @@
<p>Hei <%= @resource.email %>!</p>
<p>Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä.</p>
<p><%= link_to 'Vaihda salasanani', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>Jos et pyytänyt vaihtoa, poista tämä viesti.</p>
<p>Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden.</p>

View File

@ -0,0 +1,8 @@
Hei <%= @resource.email %>!
Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä.
<%= edit_password_url(@resource, reset_password_token: @token) %>
Jos et pyytänyt vaihtoa, poista tämä viesti.
Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden.

View File

@ -3,6 +3,8 @@
class Admin::SuspensionWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull'
def perform(account_id)
SuspendAccountService.new.call(Account.find(account_id))
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationWorker
def info(message)
Rails.logger.info("#{self.class.name} - #{message}")

View File

@ -4,10 +4,7 @@ class DistributionWorker < ApplicationWorker
include Sidekiq::Worker
def perform(status_id)
status = Status.find(status_id)
FanOutOnWriteService.new.call(status)
WarmCacheService.new.call(status)
FanOutOnWriteService.new.call(Status.find(status_id))
rescue ActiveRecord::RecordNotFound
info("Couldn't find the status")
end

View File

@ -46,7 +46,7 @@ class ImportWorker
begin
FollowService.new.call(from_account, row[0])
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
next
end
end

View File

@ -13,6 +13,9 @@ class Pubsubhubbub::DeliveryWorker
def perform(subscription_id, payload)
subscription = Subscription.find(subscription_id)
headers = {}
host = Addressable::URI.parse(subscription.callback_url).host
return if DomainBlock.blocked?(host)
headers['User-Agent'] = 'Mastodon/PubSubHubbub'
headers['Link'] = LinkHeader.new([[api_push_url, [%w(rel hub)]], [account_url(subscription.account, format: :atom), [%w(rel self)]]]).to_s

View File

@ -10,14 +10,10 @@ class Pubsubhubbub::DistributionWorker
return if stream_entry.hidden?
account = stream_entry.account
renderer = AccountsController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https)
payload = renderer.render(:show, assigns: { account: account, entries: [stream_entry] }, formats: [:atom])
# domains = account.followers_domains
account = stream_entry.account
payload = AtomSerializer.render(AtomSerializer.new.feed(account, [stream_entry]))
Subscription.where(account: account).active.select('id, callback_url').find_each do |subscription|
host = Addressable::URI.parse(subscription.callback_url).host
next if DomainBlock.blocked?(host) # || !domains.include?(host)
Pubsubhubbub::DeliveryWorker.perform_async(subscription.id, payload)
end
rescue ActiveRecord::RecordNotFound

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class RemoteProfileUpdateWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull'
def perform(account_id, body, resubscribe)
account = Account.find(account_id)
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
author_container = xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS) || xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)
UpdateRemoteProfileService.new.call(author_container, account, resubscribe)
rescue ActiveRecord::RecordNotFound
true
end
end

View File

@ -7,7 +7,7 @@ class SalmonWorker
def perform(account_id, body)
ProcessInteractionService.new.call(body, Account.find(account_id))
rescue ActiveRecord::RecordNotFound
rescue Nokogiri::XML::XPath::SyntaxError, ActiveRecord::RecordNotFound
true
end
end