Send Salmon interactions
This commit is contained in:
parent
10eb47a33e
commit
fa7868675d
1
Gemfile
1
Gemfile
|
@ -19,6 +19,7 @@ gem 'grape'
|
||||||
gem 'grape-route-helpers'
|
gem 'grape-route-helpers'
|
||||||
gem 'grape-entity'
|
gem 'grape-entity'
|
||||||
gem 'hashie-forbidden_attributes'
|
gem 'hashie-forbidden_attributes'
|
||||||
|
gem 'paranoia', '~> 2.0'
|
||||||
|
|
||||||
gem 'http'
|
gem 'http'
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
|
|
|
@ -152,6 +152,8 @@ GEM
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
http (~> 1.0)
|
http (~> 1.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
|
paranoia (2.1.5)
|
||||||
|
activerecord (~> 4.0)
|
||||||
parser (2.3.0.6)
|
parser (2.3.0.6)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.18.4)
|
pg (0.18.4)
|
||||||
|
@ -305,6 +307,7 @@ DEPENDENCIES
|
||||||
nokogiri
|
nokogiri
|
||||||
nyan-cat-formatter
|
nyan-cat-formatter
|
||||||
ostatus2
|
ostatus2
|
||||||
|
paranoia (~> 2.0)
|
||||||
pg
|
pg
|
||||||
pry-rails
|
pry-rails
|
||||||
puma
|
puma
|
||||||
|
|
|
@ -2,7 +2,7 @@ class Favourite < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :favourites
|
belongs_to :account, inverse_of: :favourites
|
||||||
belongs_to :status, 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
|
def verb
|
||||||
:favorite
|
:favorite
|
||||||
|
|
|
@ -2,13 +2,13 @@ class Follow < ActiveRecord::Base
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :target_account, class_name: '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, :target_account, presence: true
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
|
||||||
def verb
|
def verb
|
||||||
:follow
|
self.destroyed? ? :unfollow : :follow
|
||||||
end
|
end
|
||||||
|
|
||||||
def target
|
def target
|
||||||
|
@ -20,7 +20,7 @@ class Follow < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def content
|
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
|
end
|
||||||
|
|
||||||
def title
|
def title
|
||||||
|
|
|
@ -4,8 +4,11 @@ class Status < ActiveRecord::Base
|
||||||
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
|
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
|
||||||
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
|
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
|
||||||
|
|
||||||
has_one :stream_entry, as: :activity
|
has_one :stream_entry, as: :activity, dependent: :destroy
|
||||||
has_many :favourites, inverse_of: :status
|
|
||||||
|
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 :account, presence: true
|
||||||
validates :uri, uniqueness: true, unless: 'local?'
|
validates :uri, uniqueness: true, unless: 'local?'
|
||||||
|
|
3
app/services/base_service.rb
Normal file
3
app/services/base_service.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
class BaseService
|
||||||
|
include ApplicationHelper
|
||||||
|
end
|
16
app/services/fetch_entry_service.rb
Normal file
16
app/services/fetch_entry_service.rb
Normal 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
|
|
@ -1,4 +1,6 @@
|
||||||
class FetchFeedService
|
class FetchFeedService < BaseService
|
||||||
|
# Fetch an account's feed and process it
|
||||||
|
# @param [Account] account
|
||||||
def call(account)
|
def call(account)
|
||||||
process_service.(http_client.get(account.remote_url), account)
|
process_service.(http_client.get(account.remote_url), account)
|
||||||
end
|
end
|
||||||
|
@ -6,7 +8,7 @@ class FetchFeedService
|
||||||
private
|
private
|
||||||
|
|
||||||
def process_service
|
def process_service
|
||||||
ProcessFeedService.new
|
@process_service ||= ProcessFeedService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def http_client
|
def http_client
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
class FollowRemoteAccountService
|
class FollowRemoteAccountService < BaseService
|
||||||
include ApplicationHelper
|
# 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)
|
def call(uri, subscribe = true)
|
||||||
username, domain = uri.split('@')
|
username, domain = uri.split('@')
|
||||||
account = Account.where(username: username, domain: domain).first
|
account = Account.where(username: username, domain: domain).first
|
||||||
|
|
|
@ -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)
|
def call(source_account, uri)
|
||||||
target_account = follow_remote_account_service.(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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def follow_remote_account_service
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class ProcessFeedService
|
class ProcessFeedService < BaseService
|
||||||
include ApplicationHelper
|
# Create local statuses from an Atom feed
|
||||||
|
# @param [String] body Atom feed
|
||||||
|
# @param [Account] account Account this feed belongs to
|
||||||
def call(body, account)
|
def call(body, account)
|
||||||
xml = Nokogiri::XML(body)
|
xml = Nokogiri::XML(body)
|
||||||
|
|
||||||
|
@ -105,6 +106,6 @@ class ProcessFeedService
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_remote_account_service
|
def follow_remote_account_service
|
||||||
FollowRemoteAccountService.new
|
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class ProcessInteractionService
|
class ProcessInteractionService < BaseService
|
||||||
include ApplicationHelper
|
# 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)
|
def call(envelope, target_account)
|
||||||
body = salmon.unpack(envelope)
|
body = salmon.unpack(envelope)
|
||||||
xml = Nokogiri::XML(body)
|
xml = Nokogiri::XML(body)
|
||||||
|
@ -75,14 +76,14 @@ class ProcessInteractionService
|
||||||
end
|
end
|
||||||
|
|
||||||
def salmon
|
def salmon
|
||||||
OStatus2::Salmon.new
|
@salmon ||= OStatus2::Salmon.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_remote_account_service
|
def follow_remote_account_service
|
||||||
FollowRemoteAccountService.new
|
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_feed_service
|
def process_feed_service
|
||||||
ProcessFeedService.new
|
@process_feed_service ||= ProcessFeedService.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
29
app/services/send_interaction_service.rb
Normal file
29
app/services/send_interaction_service.rb
Normal 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
|
|
@ -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)
|
def call(user, username)
|
||||||
user.build_account
|
user.build_account
|
||||||
|
|
||||||
|
|
15
app/services/unfollow_service.rb
Normal file
15
app/services/unfollow_service.rb
Normal 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
|
Reference in a new issue