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
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
# 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}")
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
end
end
-class NoAuthorFeedError < StandardError
-end
-
-class NoHubError < StandardError
-end
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
def follow_remote_account_service
@follow_remote_account_service ||= FollowRemoteAccountService.new
end
+
+ def subscribe_service
+ @subscribe_service ||= SubscribeService.new
+ end
end
end
def delete_post!(status)
- RemoveStatusService.new.(status)
+ remove_status_service.(status)
end
def find_original_status(_xml, id)
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))
def update_remote_profile_service
@update_remote_profile_service ||= UpdateRemoteProfileService.new
end
+
+ def remove_status_service
+ @remove_status_service ||= RemoveStatusService.new
+ end
end
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)
return if status.nil?
if account.id == status.account_id
- RemoveStatusService.new.(status)
+ remove_status_service.(status)
end
end
def update_remote_profile_service
@update_remote_profile_service ||= UpdateRemoteProfileService.new
end
+
+ def remove_status_service
+ @remove_status_service ||= RemoveStatusService.new
+ end
end
--- /dev/null
+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
--- /dev/null
+class AddSubscriptionExpiresAtToAccounts < ActiveRecord::Migration[5.0]
+ def change
+ add_column :accounts, :subscription_expires_at, :datetime, null: true, default: nil
+ end
+end
#
# 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"
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
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