Send Salmon interactions
This commit is contained in:
		
							
								
								
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -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?' | ||||
|   | ||||
							
								
								
									
										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) | ||||
|     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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										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) | ||||
|     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 New Issue
	
	Block a user