Separate PuSH subscriptions from following, add mastodon:push:refresh task,
respect hub.lease_seconds (fix #46)shrike
parent
1245ee42fb
commit
059ebbf48d
|
@ -4,6 +4,7 @@ class Api::SubscriptionsController < ApiController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'], params['hub.verify_token'])
|
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
|
render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200
|
||||||
else
|
else
|
||||||
head 404
|
head 404
|
||||||
|
|
|
@ -38,6 +38,12 @@ class Account < ApplicationRecord
|
||||||
|
|
||||||
has_many :media_attachments, dependent: :destroy
|
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)
|
def follow!(other_account)
|
||||||
self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,22 +3,18 @@ class FollowRemoteAccountService < BaseService
|
||||||
# When creating, look up the user's webfinger and fetch all
|
# When creating, look up the user's webfinger and fetch all
|
||||||
# important information from their feed
|
# important information from their feed
|
||||||
# @param [String] uri User URI in the form of username@domain
|
# @param [String] uri User URI in the form of username@domain
|
||||||
# @param [Boolean] subscribe Whether to initiate a PubSubHubbub subscription
|
|
||||||
# @return [Account]
|
# @return [Account]
|
||||||
def call(uri, subscribe = true)
|
def call(uri)
|
||||||
username, domain = uri.split('@')
|
username, domain = uri.split('@')
|
||||||
|
|
||||||
return Account.find_local(username) if domain == Rails.configuration.x.local_domain || domain.nil?
|
return Account.find_local(username) if domain == Rails.configuration.x.local_domain || domain.nil?
|
||||||
|
|
||||||
account = Account.find_remote(username, domain)
|
account = Account.find_remote(username, domain)
|
||||||
|
|
||||||
if account.nil?
|
return account unless account.nil?
|
||||||
Rails.logger.debug "Creating new remote account for #{uri}"
|
|
||||||
account = Account.new(username: username, domain: domain)
|
Rails.logger.debug "Creating new remote account for #{uri}"
|
||||||
elsif account.subscribed?
|
account = Account.new(username: username, domain: domain)
|
||||||
Rails.logger.debug "Already subscribed to remote account #{uri}"
|
|
||||||
return account
|
|
||||||
end
|
|
||||||
|
|
||||||
data = Goldfinger.finger("acct:#{uri}")
|
data = Goldfinger.finger("acct:#{uri}")
|
||||||
|
|
||||||
|
@ -45,16 +41,6 @@ class FollowRemoteAccountService < BaseService
|
||||||
get_profile(feed, account)
|
get_profile(feed, account)
|
||||||
account.save!
|
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
|
return account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -90,8 +76,3 @@ class FollowRemoteAccountService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NoAuthorFeedError < StandardError
|
|
||||||
end
|
|
||||||
|
|
||||||
class NoHubError < StandardError
|
|
||||||
end
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ class FollowService < BaseService
|
||||||
if target_account.local?
|
if target_account.local?
|
||||||
NotificationMailer.follow(target_account, source_account).deliver_later
|
NotificationMailer.follow(target_account, source_account).deliver_later
|
||||||
else
|
else
|
||||||
|
subscribe_service.(target_account)
|
||||||
NotificationWorker.perform_async(follow.stream_entry.id, target_account.id)
|
NotificationWorker.perform_async(follow.stream_entry.id, target_account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -40,4 +41,8 @@ class FollowService < BaseService
|
||||||
def follow_remote_account_service
|
def follow_remote_account_service
|
||||||
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscribe_service
|
||||||
|
@subscribe_service ||= SubscribeService.new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -106,7 +106,7 @@ class ProcessFeedService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post!(status)
|
def delete_post!(status)
|
||||||
RemoveStatusService.new.(status)
|
remove_status_service.(status)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_original_status(_xml, id)
|
def find_original_status(_xml, id)
|
||||||
|
@ -126,7 +126,7 @@ class ProcessFeedService < BaseService
|
||||||
account = Account.find_by(username: username, domain: domain)
|
account = Account.find_by(username: username, domain: domain)
|
||||||
|
|
||||||
if account.nil?
|
if account.nil?
|
||||||
account = follow_remote_account_service.("#{username}@#{domain}", false)
|
account = follow_remote_account_service.("#{username}@#{domain}")
|
||||||
end
|
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))
|
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
|
def update_remote_profile_service
|
||||||
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_status_service
|
||||||
|
@remove_status_service ||= RemoveStatusService.new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ProcessInteractionService < BaseService
|
||||||
account = Account.find_by(username: username, domain: domain)
|
account = Account.find_by(username: username, domain: domain)
|
||||||
|
|
||||||
if account.nil?
|
if account.nil?
|
||||||
account = follow_remote_account_service.("#{username}@#{domain}", false)
|
account = follow_remote_account_service.("#{username}@#{domain}")
|
||||||
end
|
end
|
||||||
|
|
||||||
if salmon.verify(envelope, account.keypair)
|
if salmon.verify(envelope, account.keypair)
|
||||||
|
@ -71,7 +71,7 @@ class ProcessInteractionService < BaseService
|
||||||
return if status.nil?
|
return if status.nil?
|
||||||
|
|
||||||
if account.id == status.account_id
|
if account.id == status.account_id
|
||||||
RemoveStatusService.new.(status)
|
remove_status_service.(status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,4 +108,8 @@ class ProcessInteractionService < BaseService
|
||||||
def update_remote_profile_service
|
def update_remote_profile_service
|
||||||
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_status_service
|
||||||
|
@remove_status_service ||= RemoveStatusService.new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
create_table "accounts", force: :cascade do |t|
|
create_table "accounts", force: :cascade do |t|
|
||||||
t.string "username", default: "", null: false
|
t.string "username", default: "", null: false
|
||||||
t.string "domain"
|
t.string "domain"
|
||||||
t.string "verify_token", default: "", null: false
|
t.string "verify_token", default: "", null: false
|
||||||
t.string "secret", default: "", null: false
|
t.string "secret", default: "", null: false
|
||||||
t.text "private_key"
|
t.text "private_key"
|
||||||
t.text "public_key", default: "", null: false
|
t.text "public_key", default: "", null: false
|
||||||
t.string "remote_url", default: "", null: false
|
t.string "remote_url", default: "", null: false
|
||||||
t.string "salmon_url", default: "", null: false
|
t.string "salmon_url", default: "", null: false
|
||||||
t.string "hub_url", default: "", null: false
|
t.string "hub_url", default: "", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "note", default: "", null: false
|
t.text "note", default: "", null: false
|
||||||
t.string "display_name", default: "", null: false
|
t.string "display_name", default: "", null: false
|
||||||
t.string "uri", default: "", null: false
|
t.string "uri", default: "", null: false
|
||||||
t.string "url"
|
t.string "url"
|
||||||
t.string "avatar_file_name"
|
t.string "avatar_file_name"
|
||||||
t.string "avatar_content_type"
|
t.string "avatar_content_type"
|
||||||
|
@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20160905150353) do
|
||||||
t.integer "header_file_size"
|
t.integer "header_file_size"
|
||||||
t.datetime "header_updated_at"
|
t.datetime "header_updated_at"
|
||||||
t.string "avatar_remote_url"
|
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
|
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,16 @@ namespace :mastodon do
|
||||||
namespace :push do
|
namespace :push do
|
||||||
desc 'Unsubscribes from PuSH updates of feeds nobody follows locally'
|
desc 'Unsubscribes from PuSH updates of feeds nobody follows locally'
|
||||||
task clear: :environment do
|
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.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
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue