Skip to content
Snippets Groups Projects
Commit fa786867 authored by Eugen Rochko's avatar Eugen Rochko
Browse files

Send Salmon interactions

parent 10eb47a3
No related branches found
No related tags found
No related merge requests found
Showing with 118 additions and 25 deletions
......@@ -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'
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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?'
......
class BaseService
include ApplicationHelper
end
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
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
......
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
......
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
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
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
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
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
......
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment