Fixes and general progress
parent
709c6685a9
commit
1dad72bf13
|
@ -7,7 +7,7 @@ class XrdController < ApplicationController
|
||||||
|
|
||||||
def webfinger
|
def webfinger
|
||||||
@account = Account.find_by!(username: username_from_resource, domain: nil)
|
@account = Account.find_by!(username: username_from_resource, domain: nil)
|
||||||
@canonical_account_uri = "acct:#{@account.username}#{LOCAL_DOMAIN}"
|
@canonical_account_uri = "acct:#{@account.username}@#{LOCAL_DOMAIN}"
|
||||||
@magic_key = pem_to_magic_key(@account.keypair.public_key)
|
@magic_key = pem_to_magic_key(@account.keypair.public_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
include GrapeRouteHelpers::NamedRouteMatcher
|
include RoutingHelper
|
||||||
|
|
||||||
def unique_tag(date, id, type)
|
def unique_tag(date, id, type)
|
||||||
"tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
|
"tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscription_url(account)
|
def subscription_url(account)
|
||||||
add_base_url_prefix subscription_path(id: account.id, format: '')
|
add_base_url_prefix subscriptions_path(id: account.id, format: '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def salmon_url(account)
|
def salmon_url(account)
|
||||||
|
@ -14,6 +14,6 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_base_url_prefix(suffix)
|
def add_base_url_prefix(suffix)
|
||||||
"#{root_url}api#{suffix}"
|
File.join(root_url, "api", suffix)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module RoutingHelper
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include Rails.application.routes.url_helpers
|
||||||
|
include GrapeRouteHelpers::NamedRouteMatcher
|
||||||
|
|
||||||
|
included do
|
||||||
|
def default_url_options
|
||||||
|
ActionMailer::Base.default_url_options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,6 +29,18 @@ class Account < ActiveRecord::Base
|
||||||
self.domain.nil?
|
self.domain.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def acct
|
||||||
|
local? ? self.username : "#{self.username}@#{self.domain}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:person
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribed?
|
||||||
|
!(self.secret.blank? || self.verify_token.blank?)
|
||||||
|
end
|
||||||
|
|
||||||
def keypair
|
def keypair
|
||||||
self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key)
|
self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,28 @@ class Follow < ActiveRecord::Base
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :target_account, class_name: 'Account'
|
belongs_to :target_account, class_name: 'Account'
|
||||||
|
|
||||||
|
validates :account, :target_account, presence: true
|
||||||
|
|
||||||
|
def verb
|
||||||
|
:follow
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:person
|
||||||
|
end
|
||||||
|
|
||||||
|
def target
|
||||||
|
self.target_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def content
|
||||||
|
"#{self.account.acct} started following #{self.target_account.acct}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
content
|
||||||
|
end
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
self.account.stream_entries.create!(activity: self)
|
self.account.stream_entries.create!(activity: self)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,24 @@
|
||||||
class Status < ActiveRecord::Base
|
class Status < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :statuses
|
belongs_to :account, inverse_of: :statuses
|
||||||
|
|
||||||
|
validates :account, presence: true
|
||||||
|
|
||||||
|
def verb
|
||||||
|
:post
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:note
|
||||||
|
end
|
||||||
|
|
||||||
|
def content
|
||||||
|
self.text
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
content.truncate(80, omission: "...")
|
||||||
|
end
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
self.account.stream_entries.create!(activity: self)
|
self.account.stream_entries.create!(activity: self)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,32 +2,29 @@ class StreamEntry < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :stream_entries
|
belongs_to :account, inverse_of: :stream_entries
|
||||||
belongs_to :activity, polymorphic: true
|
belongs_to :activity, polymorphic: true
|
||||||
|
|
||||||
|
validates :account, :activity, presence: true
|
||||||
|
|
||||||
def object_type
|
def object_type
|
||||||
case self.activity_type
|
self.activity.object_type
|
||||||
when 'Status'
|
|
||||||
:note
|
|
||||||
when 'Follow'
|
|
||||||
:person
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def verb
|
def verb
|
||||||
case self.activity_type
|
self.activity.verb
|
||||||
when 'Status'
|
end
|
||||||
:post
|
|
||||||
when 'Follow'
|
def targeted?
|
||||||
:follow
|
[:follow].include? self.verb
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def target
|
def target
|
||||||
case self.activity_type
|
self.activity.target
|
||||||
when 'Follow'
|
end
|
||||||
self.activity.target_account
|
|
||||||
end
|
def title
|
||||||
|
self.activity.title
|
||||||
end
|
end
|
||||||
|
|
||||||
def content
|
def content
|
||||||
self.activity.text if self.activity_type == 'Status'
|
self.activity.content
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
class User < ActiveRecord::Base
|
class User < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :user
|
belongs_to :account, inverse_of: :user
|
||||||
|
|
||||||
|
validates :account, presence: true
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,13 @@ class FollowRemoteAccountService
|
||||||
username, domain = uri.split('@')
|
username, domain = uri.split('@')
|
||||||
account = Account.where(username: username, domain: domain).first
|
account = Account.where(username: username, domain: domain).first
|
||||||
|
|
||||||
return account unless account.nil?
|
if account.nil?
|
||||||
|
account = Account.new(username: username, domain: domain)
|
||||||
|
elsif account.subscribed?
|
||||||
|
return account
|
||||||
|
end
|
||||||
|
|
||||||
account = Account.new(username: username, domain: domain)
|
data = Goldfinger.finger("acct:#{uri}")
|
||||||
data = Goldfinger.finger("acct:#{uri}")
|
|
||||||
|
|
||||||
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
|
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
|
||||||
account.salmon_url = data.link('salmon').href
|
account.salmon_url = data.link('salmon').href
|
||||||
|
@ -21,16 +24,20 @@ class FollowRemoteAccountService
|
||||||
feed = get_feed(account.remote_url)
|
feed = get_feed(account.remote_url)
|
||||||
hubs = feed.xpath('//xmlns:link[@rel="hub"]')
|
hubs = feed.xpath('//xmlns:link[@rel="hub"]')
|
||||||
|
|
||||||
return false if hubs.empty? || hubs.first.attribute('href').nil? || feed.at_xpath('/xmlns:author/xmlns:uri').nil?
|
return nil if hubs.empty? || hubs.first.attribute('href').nil? || feed.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri').nil?
|
||||||
|
|
||||||
account.uri = feed.at_xpath('/xmlns:author/xmlns:uri').content
|
account.uri = feed.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri').content
|
||||||
account.hub_url = hubs.first.attribute('href').value
|
account.hub_url = hubs.first.attribute('href').value
|
||||||
|
|
||||||
|
get_profile(feed, account)
|
||||||
account.save!
|
account.save!
|
||||||
|
|
||||||
subscription = account.subscription(subscription_url(account))
|
subscription = account.subscription(subscription_url(account))
|
||||||
subscription.subscribe
|
subscription.subscribe
|
||||||
|
|
||||||
|
return account
|
||||||
rescue Goldfinger::Error, HTTP::Error => e
|
rescue Goldfinger::Error, HTTP::Error => e
|
||||||
false
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -40,6 +47,20 @@ class FollowRemoteAccountService
|
||||||
Nokogiri::XML(response)
|
Nokogiri::XML(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_profile(xml, account)
|
||||||
|
author = xml.at_xpath('/xmlns:feed/xmlns:author')
|
||||||
|
|
||||||
|
if author.at_xpath('./poco:displayName').nil?
|
||||||
|
account.display_name = account.username
|
||||||
|
else
|
||||||
|
account.display_name = author.at_xpath('./poco:displayName').content
|
||||||
|
end
|
||||||
|
|
||||||
|
unless author.at_xpath('./poco:note').nil?
|
||||||
|
account.note = author.at_xpath('./poco:note').content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def magic_key_to_pem(magic_key)
|
def magic_key_to_pem(magic_key)
|
||||||
_, modulus, exponent = magic_key.split('.')
|
_, modulus, exponent = magic_key.split('.')
|
||||||
modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } }
|
modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class FollowService
|
class FollowService
|
||||||
def call(source_account, uri)
|
def call(source_account, uri)
|
||||||
target_account = follow_remote_account_service.(uri)
|
target_account = follow_remote_account_service.(uri)
|
||||||
source_account.follow!(target_account)
|
source_account.follow!(target_account) unless target_account.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -15,20 +15,29 @@ Nokogiri::XML::Builder.new do |xml|
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
|
xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
|
||||||
xml.link(rel: 'hub', href: '')
|
xml.link(rel: 'hub', href: HUB_URL)
|
||||||
xml.link(rel: 'salmon', href: salmon_url(@account))
|
xml.link(rel: 'salmon', href: salmon_url(@account))
|
||||||
xml.link(rel: 'self', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
|
xml.link(rel: 'self', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
|
||||||
|
|
||||||
@account.stream_entries.each do |stream_entry|
|
@account.stream_entries.each do |stream_entry|
|
||||||
xml.entry do
|
xml.entry do
|
||||||
xml.id_ unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type)
|
xml.id_ unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type)
|
||||||
|
|
||||||
xml.published stream_entry.activity.created_at.iso8601
|
xml.published stream_entry.activity.created_at.iso8601
|
||||||
xml.updated stream_entry.activity.updated_at.iso8601
|
xml.updated stream_entry.activity.updated_at.iso8601
|
||||||
xml.content({ type: 'html' }, stream_entry.content)
|
|
||||||
xml.title
|
|
||||||
|
|
||||||
|
xml.title stream_entry.title
|
||||||
|
xml.content({ type: 'html' }, stream_entry.content)
|
||||||
xml['activity'].send('verb', "http://activitystrea.ms/schema/1.0/#{stream_entry.verb}")
|
xml['activity'].send('verb', "http://activitystrea.ms/schema/1.0/#{stream_entry.verb}")
|
||||||
xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{stream_entry.object_type}")
|
|
||||||
|
if stream_entry.targeted?
|
||||||
|
xml['activity'].send('object') do
|
||||||
|
xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{stream_entry.target.object_type}")
|
||||||
|
xml.id_ stream_entry.target.uri
|
||||||
|
end
|
||||||
|
else
|
||||||
|
xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{stream_entry.object_type}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
Nokogiri::XML::Builder.new do |xml|
|
Nokogiri::XML::Builder.new do |xml|
|
||||||
xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
|
xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
|
||||||
xml.Subject @canonical_account_uri
|
xml.Subject @canonical_account_uri
|
||||||
|
xml.Alias profile_url(name: @account.username)
|
||||||
|
xml.Link(rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: profile_url(name: @account.username))
|
||||||
xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
|
xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
|
||||||
xml.Link(rel: 'salmon', href: salmon_url(@account))
|
xml.Link(rel: 'salmon', href: salmon_url(@account))
|
||||||
xml.Link(rel: 'magic-public-key', href: @magic_key)
|
xml.Link(rel: 'magic-public-key', href: @magic_key)
|
||||||
|
|
|
@ -6,6 +6,8 @@ require 'rails/all'
|
||||||
# you've limited to :test, :development, or :production.
|
# you've limited to :test, :development, or :production.
|
||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
|
|
||||||
|
Dotenv::Railtie.load
|
||||||
|
|
||||||
module Mastodon
|
module Mastodon
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
# Settings in config/environments/* take precedence over those specified here.
|
# Settings in config/environments/* take precedence over those specified here.
|
||||||
|
|
|
@ -38,6 +38,4 @@ Rails.application.configure do
|
||||||
|
|
||||||
# Raises error for missing translations
|
# Raises error for missing translations
|
||||||
# config.action_view.raise_on_missing_translations = true
|
# config.action_view.raise_on_missing_translations = true
|
||||||
|
|
||||||
config.action_mailer.default_url_options = { host: ENV['NGROK_HOST'] }
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1 +1,6 @@
|
||||||
LOCAL_DOMAIN = ENV['LOCAL_DOMAIN'] || 'localhost'
|
LOCAL_DOMAIN = ENV['LOCAL_DOMAIN'] || 'localhost'
|
||||||
|
HUB_URL = ENV['HUB_URL'] || 'https://pubsubhubbub.superfeedr.com'
|
||||||
|
|
||||||
|
Rails.application.configure do
|
||||||
|
config.action_mailer.default_url_options = { host: LOCAL_DOMAIN }
|
||||||
|
end
|
||||||
|
|
Reference in New Issue