Send Salmon interactions

This commit is contained in:
Eugen Rochko 2016-02-24 12:57:29 +01:00
parent 10eb47a33e
commit fa7868675d
15 changed files with 118 additions and 25 deletions

View File

@ -19,6 +19,7 @@ gem 'grape'
gem 'grape-route-helpers'
gem 'grape-entity'
gem 'hashie-forbidden_attributes'
gem 'paranoia', '~> 2.0'
gem 'http'
gem 'addressable'

View File

@ -152,6 +152,8 @@ GEM
addressable (~> 2.4)
http (~> 1.0)
nokogiri (~> 1.6)
paranoia (2.1.5)
activerecord (~> 4.0)
parser (2.3.0.6)
ast (~> 2.2)
pg (0.18.4)
@ -305,6 +307,7 @@ DEPENDENCIES
nokogiri
nyan-cat-formatter
ostatus2
paranoia (~> 2.0)
pg
pry-rails
puma

View File

@ -2,7 +2,7 @@ class Favourite < ActiveRecord::Base
belongs_to :account, inverse_of: :favourites
belongs_to :status, inverse_of: :favourites
has_one :stream_entry, as: :activity
has_one :stream_entry, as: :activity, dependent: :destroy
def verb
:favorite

View File

@ -2,13 +2,13 @@ class Follow < ActiveRecord::Base
belongs_to :account
belongs_to :target_account, class_name: 'Account'
has_one :stream_entry, as: :activity
has_one :stream_entry, as: :activity, dependent: :destroy
validates :account, :target_account, presence: true
validates :account_id, uniqueness: { scope: :target_account_id }
def verb
:follow
self.destroyed? ? :unfollow : :follow
end
def target
@ -20,7 +20,7 @@ class Follow < ActiveRecord::Base
end
def content
"#{self.account.acct} started following #{self.target_account.acct}"
self.destroyed? ? "#{self.account.acct} is no longer following #{self.target_account.acct}" : "#{self.account.acct} started following #{self.target_account.acct}"
end
def title

View File

@ -4,8 +4,11 @@ class Status < ActiveRecord::Base
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
has_one :stream_entry, as: :activity
has_many :favourites, inverse_of: :status
has_one :stream_entry, as: :activity, dependent: :destroy
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status'
has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status'
validates :account, presence: true
validates :uri, uniqueness: true, unless: 'local?'

View File

@ -0,0 +1,3 @@
class BaseService
include ApplicationHelper
end

View File

@ -0,0 +1,16 @@
class FetchEntryService < BaseService
# Knowing nothing but the URL of a remote status, create a local representation of it and return it
# @param [String] url Atom URL
# @return [Status]
def call(url)
body = http_client.get(url)
xml = Nokogiri::XML(body)
# todo
end
private
def http_client
HTTP
end
end

View File

@ -1,4 +1,6 @@
class FetchFeedService
class FetchFeedService < BaseService
# Fetch an account's feed and process it
# @param [Account] account
def call(account)
process_service.(http_client.get(account.remote_url), account)
end
@ -6,7 +8,7 @@ class FetchFeedService
private
def process_service
ProcessFeedService.new
@process_service ||= ProcessFeedService.new
end
def http_client

View File

@ -1,6 +1,10 @@
class FollowRemoteAccountService
include ApplicationHelper
class FollowRemoteAccountService < BaseService
# Find or create a local account for a remote user.
# When creating, look up the user's webfinger and fetch all
# important information from their feed
# @param [String] uri User URI in the form of username@domain
# @param [Boolean] subscribe Whether to initiate a PubSubHubbub subscription
# @return [Account]
def call(uri, subscribe = true)
username, domain = uri.split('@')
account = Account.where(username: username, domain: domain).first

View File

@ -1,12 +1,23 @@
class FollowService
class FollowService < BaseService
# Follow a remote user, notify remote user about the follow
# @param [Account] source_account From which to follow
# @param [String] uri User URI to follow in the form of username@domain
def call(source_account, uri)
target_account = follow_remote_account_service.(uri)
source_account.follow!(target_account) unless target_account.nil?
return if target_account.nil?
follow = source_account.follow!(target_account)
send_interaction_service.(follow.stream_entry, target_account)
end
private
def follow_remote_account_service
FollowRemoteAccountService.new
@follow_remote_account_service ||= FollowRemoteAccountService.new
end
def send_interaction_service
@send_interaction_service ||= SendInteractionService.new
end
end

View File

@ -1,6 +1,7 @@
class ProcessFeedService
include ApplicationHelper
class ProcessFeedService < BaseService
# Create local statuses from an Atom feed
# @param [String] body Atom feed
# @param [Account] account Account this feed belongs to
def call(body, account)
xml = Nokogiri::XML(body)
@ -105,6 +106,6 @@ class ProcessFeedService
end
def follow_remote_account_service
FollowRemoteAccountService.new
@follow_remote_account_service ||= FollowRemoteAccountService.new
end
end

View File

@ -1,6 +1,7 @@
class ProcessInteractionService
include ApplicationHelper
class ProcessInteractionService < BaseService
# Record locally the remote interaction with our user
# @param [String] envelope Salmon envelope
# @param [Account] target_account Account the Salmon was addressed to
def call(envelope, target_account)
body = salmon.unpack(envelope)
xml = Nokogiri::XML(body)
@ -75,14 +76,14 @@ class ProcessInteractionService
end
def salmon
OStatus2::Salmon.new
@salmon ||= OStatus2::Salmon.new
end
def follow_remote_account_service
FollowRemoteAccountService.new
@follow_remote_account_service ||= FollowRemoteAccountService.new
end
def process_feed_service
ProcessFeedService.new
@process_feed_service ||= ProcessFeedService.new
end
end

View File

@ -0,0 +1,29 @@
class SendInteractionService < BaseService
include AtomHelper
# Send an Atom representation of an interaction to a remote Salmon endpoint
# @param [StreamEntry] stream_entry
# @param [Account] target_account
def call(stream_entry, target_account)
envelope = salmon.pack(entry_xml(stream_entry), target_account.keypair)
salmon.post(target_account.salmon_url, envelope)
end
private
def entry_xml(stream_entry)
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
end
def salmon
@salmon ||= OStatus2::Salmon.new
end
end

View File

@ -1,4 +1,8 @@
class SetupLocalAccountService
class SetupLocalAccountService < BaseService
# Setup an account for a new user instance by generating
# an RSA key pair and a profile
# @param [User] user Unsaved user instance
# @param [String] username
def call(user, username)
user.build_account

View File

@ -0,0 +1,15 @@
class UnfollowService < BaseService
# Unfollow and notify the remote user
# @param [Account] source_account Where to unfollow from
# @param [Account] target_account Which to unfollow
def call(source_account, target_account)
follow = source_account.unfollow!(target_account)
send_interaction_service.(follow.stream_entry, target_account) unless target_account.local?
end
private
def send_interaction_service
@send_interaction_service ||= SendInteractionService.new
end
end