Separate PuSH subscriptions from following, add mastodon:push:refresh task,
respect hub.lease_seconds (fix #46)
This commit is contained in:
		| @@ -4,6 +4,7 @@ class Api::SubscriptionsController < ApiController | ||||
|  | ||||
|   def show | ||||
|     if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'], params['hub.verify_token']) | ||||
|       @account.update(subscription_expires_at: Time.now + (params['hub.lease_seconds'].to_i).seconds) | ||||
|       render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200 | ||||
|     else | ||||
|       head 404 | ||||
|   | ||||
| @@ -38,6 +38,12 @@ class Account < ApplicationRecord | ||||
|  | ||||
|   has_many :media_attachments, dependent: :destroy | ||||
|  | ||||
|   scope :remote, -> { where.not(domain: nil) } | ||||
|   scope :local, -> { where(domain: nil) } | ||||
|   scope :without_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) = 0') } | ||||
|   scope :with_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) > 0') } | ||||
|   scope :expiring, -> (time) { where(subscription_expires_at: nil).or(where('subscription_expires_at < ?', time)).remote.with_followers } | ||||
|  | ||||
|   def follow!(other_account) | ||||
|     self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account) | ||||
|   end | ||||
|   | ||||
| @@ -3,22 +3,18 @@ class FollowRemoteAccountService < BaseService | ||||
|   # 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) | ||||
|     username, domain = uri.split('@') | ||||
|  | ||||
|     return Account.find_local(username) if domain == Rails.configuration.x.local_domain || domain.nil? | ||||
|  | ||||
|     account = Account.find_remote(username, domain) | ||||
|  | ||||
|     if account.nil? | ||||
|       Rails.logger.debug "Creating new remote account for #{uri}" | ||||
|       account = Account.new(username: username, domain: domain) | ||||
|     elsif account.subscribed? | ||||
|       Rails.logger.debug "Already subscribed to remote account #{uri}" | ||||
|       return account | ||||
|     end | ||||
|     return account unless account.nil? | ||||
|  | ||||
|     Rails.logger.debug "Creating new remote account for #{uri}" | ||||
|     account = Account.new(username: username, domain: domain) | ||||
|  | ||||
|     data = Goldfinger.finger("acct:#{uri}") | ||||
|  | ||||
| @@ -45,16 +41,6 @@ class FollowRemoteAccountService < BaseService | ||||
|     get_profile(feed, account) | ||||
|     account.save! | ||||
|  | ||||
|     if subscribe | ||||
|       account.secret       = SecureRandom.hex | ||||
|       account.verify_token = SecureRandom.hex | ||||
|  | ||||
|       subscription = account.subscription(api_subscription_url(account.id)) | ||||
|       subscription.subscribe | ||||
|  | ||||
|       account.save! | ||||
|     end | ||||
|  | ||||
|     return account | ||||
|   end | ||||
|  | ||||
| @@ -90,8 +76,3 @@ class FollowRemoteAccountService < BaseService | ||||
|   end | ||||
| end | ||||
|  | ||||
| class NoAuthorFeedError < StandardError | ||||
| end | ||||
|  | ||||
| class NoHubError < StandardError | ||||
| end | ||||
|   | ||||
| @@ -12,6 +12,7 @@ class FollowService < BaseService | ||||
|     if target_account.local? | ||||
|       NotificationMailer.follow(target_account, source_account).deliver_later | ||||
|     else | ||||
|       subscribe_service.(target_account) | ||||
|       NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) | ||||
|     end | ||||
|  | ||||
| @@ -40,4 +41,8 @@ class FollowService < BaseService | ||||
|   def follow_remote_account_service | ||||
|     @follow_remote_account_service ||= FollowRemoteAccountService.new | ||||
|   end | ||||
|  | ||||
|   def subscribe_service | ||||
|     @subscribe_service ||= SubscribeService.new | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -106,7 +106,7 @@ class ProcessFeedService < BaseService | ||||
|   end | ||||
|  | ||||
|   def delete_post!(status) | ||||
|     RemoveStatusService.new.(status) | ||||
|     remove_status_service.(status) | ||||
|   end | ||||
|  | ||||
|   def find_original_status(_xml, id) | ||||
| @@ -126,7 +126,7 @@ class ProcessFeedService < BaseService | ||||
|     account  = Account.find_by(username: username, domain: domain) | ||||
|  | ||||
|     if account.nil? | ||||
|       account = follow_remote_account_service.("#{username}@#{domain}", false) | ||||
|       account = follow_remote_account_service.("#{username}@#{domain}") | ||||
|     end | ||||
|  | ||||
|     status = Status.new(account: account, uri: target_id(xml), text: target_content(xml), url: target_url(xml), created_at: published(xml), updated_at: updated(xml)) | ||||
| @@ -196,4 +196,8 @@ class ProcessFeedService < BaseService | ||||
|   def update_remote_profile_service | ||||
|     @update_remote_profile_service ||= UpdateRemoteProfileService.new | ||||
|   end | ||||
|  | ||||
|   def remove_status_service | ||||
|     @remove_status_service ||= RemoveStatusService.new | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class ProcessInteractionService < BaseService | ||||
|     account  = Account.find_by(username: username, domain: domain) | ||||
|  | ||||
|     if account.nil? | ||||
|       account = follow_remote_account_service.("#{username}@#{domain}", false) | ||||
|       account = follow_remote_account_service.("#{username}@#{domain}") | ||||
|     end | ||||
|  | ||||
|     if salmon.verify(envelope, account.keypair) | ||||
| @@ -71,7 +71,7 @@ class ProcessInteractionService < BaseService | ||||
|     return if status.nil? | ||||
|  | ||||
|     if account.id == status.account_id | ||||
|       RemoveStatusService.new.(status) | ||||
|       remove_status_service.(status) | ||||
|     end | ||||
|   end | ||||
|  | ||||
| @@ -108,4 +108,8 @@ class ProcessInteractionService < BaseService | ||||
|   def update_remote_profile_service | ||||
|     @update_remote_profile_service ||= UpdateRemoteProfileService.new | ||||
|   end | ||||
|  | ||||
|   def remove_status_service | ||||
|     @remove_status_service ||= RemoveStatusService.new | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										20
									
								
								app/services/subscribe_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/services/subscribe_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| class SubscribeService < BaseService | ||||
|   def call(account) | ||||
|     account.secret       = SecureRandom.hex | ||||
|     account.verify_token = SecureRandom.hex | ||||
|  | ||||
|     subscription = account.subscription(api_subscription_url(account.id)) | ||||
|     response = subscription.subscribe | ||||
|  | ||||
|     unless response.successful? | ||||
|       account.secret       = '' | ||||
|       account.verify_token = '' | ||||
|  | ||||
|       Rails.logger.debug "PuSH subscription request for #{account.acct} failed: #{response.message}" | ||||
|     end | ||||
|  | ||||
|     account.save! | ||||
|   rescue HTTP::Error, OpenSSL::SSL::SSLError | ||||
|     Rails.logger.debug "PuSH subscription request for #{account.acct} could not be made due to HTTP or SSL error" | ||||
|   end | ||||
| end | ||||
| @@ -0,0 +1,5 @@ | ||||
| class AddSubscriptionExpiresAtToAccounts < ActiveRecord::Migration[5.0] | ||||
|   def change | ||||
|     add_column :accounts, :subscription_expires_at, :datetime, null: true, default: nil | ||||
|   end | ||||
| end | ||||
							
								
								
									
										27
									
								
								db/schema.rb
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								db/schema.rb
									
									
									
									
									
								
							| @@ -10,26 +10,26 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 20160905150353) do | ||||
| ActiveRecord::Schema.define(version: 20160919221059) do | ||||
|  | ||||
|   # These are extensions that must be enabled in order to support this database | ||||
|   enable_extension "plpgsql" | ||||
|  | ||||
|   create_table "accounts", force: :cascade do |t| | ||||
|     t.string   "username",            default: "", null: false | ||||
|     t.string   "username",                default: "", null: false | ||||
|     t.string   "domain" | ||||
|     t.string   "verify_token",        default: "", null: false | ||||
|     t.string   "secret",              default: "", null: false | ||||
|     t.string   "verify_token",            default: "", null: false | ||||
|     t.string   "secret",                  default: "", null: false | ||||
|     t.text     "private_key" | ||||
|     t.text     "public_key",          default: "", null: false | ||||
|     t.string   "remote_url",          default: "", null: false | ||||
|     t.string   "salmon_url",          default: "", null: false | ||||
|     t.string   "hub_url",             default: "", null: false | ||||
|     t.datetime "created_at",                       null: false | ||||
|     t.datetime "updated_at",                       null: false | ||||
|     t.text     "note",                default: "", null: false | ||||
|     t.string   "display_name",        default: "", null: false | ||||
|     t.string   "uri",                 default: "", null: false | ||||
|     t.text     "public_key",              default: "", null: false | ||||
|     t.string   "remote_url",              default: "", null: false | ||||
|     t.string   "salmon_url",              default: "", null: false | ||||
|     t.string   "hub_url",                 default: "", null: false | ||||
|     t.datetime "created_at",                           null: false | ||||
|     t.datetime "updated_at",                           null: false | ||||
|     t.text     "note",                    default: "", null: false | ||||
|     t.string   "display_name",            default: "", null: false | ||||
|     t.string   "uri",                     default: "", null: false | ||||
|     t.string   "url" | ||||
|     t.string   "avatar_file_name" | ||||
|     t.string   "avatar_content_type" | ||||
| @@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20160905150353) do | ||||
|     t.integer  "header_file_size" | ||||
|     t.datetime "header_updated_at" | ||||
|     t.string   "avatar_remote_url" | ||||
|     t.datetime "subscription_expires_at" | ||||
|     t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -11,9 +11,16 @@ namespace :mastodon do | ||||
|   namespace :push do | ||||
|     desc 'Unsubscribes from PuSH updates of feeds nobody follows locally' | ||||
|     task clear: :environment do | ||||
|       Account.where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) = 0').where.not(domain: nil).find_each do |a| | ||||
|       Account.remote.without_followers.find_each do |a| | ||||
|         a.subscription('').unsubscribe | ||||
|         a.update!(verify_token: '', secret: '') | ||||
|         a.update!(verify_token: '', secret: '', subscription_expires_at: nil) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     desc 'Re-subscribes to soon expiring PuSH subscriptions' | ||||
|     task refresh: :environment do | ||||
|       Account.expiring(1.day.from_now).find_each do |a| | ||||
|         SubscribeService.new.(a) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user