diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb index 4c8e958e6..6fda75a03 100644 --- a/app/controllers/xrd_controller.rb +++ b/app/controllers/xrd_controller.rb @@ -7,7 +7,7 @@ class XrdController < ApplicationController def webfinger @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) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 29e444a32..ed7b59165 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,12 +1,12 @@ module ApplicationHelper - include GrapeRouteHelpers::NamedRouteMatcher + include RoutingHelper def unique_tag(date, id, type) "tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" end 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 def salmon_url(account) @@ -14,6 +14,6 @@ module ApplicationHelper end def add_base_url_prefix(suffix) - "#{root_url}api#{suffix}" + File.join(root_url, "api", suffix) end end diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb new file mode 100644 index 000000000..655e6bc26 --- /dev/null +++ b/app/helpers/routing_helper.rb @@ -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 diff --git a/app/models/account.rb b/app/models/account.rb index 90e8d7610..fac835168 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -29,6 +29,18 @@ class Account < ActiveRecord::Base self.domain.nil? 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 self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key) end diff --git a/app/models/follow.rb b/app/models/follow.rb index eec01b9ba..203215947 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -2,6 +2,28 @@ class Follow < ActiveRecord::Base belongs_to :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 self.account.stream_entries.create!(activity: self) end diff --git a/app/models/status.rb b/app/models/status.rb index d98297643..c0b0ca9d9 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -1,6 +1,24 @@ class Status < ActiveRecord::Base 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 self.account.stream_entries.create!(activity: self) end diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index cee151a07..7a182bb5d 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -2,32 +2,29 @@ class StreamEntry < ActiveRecord::Base belongs_to :account, inverse_of: :stream_entries belongs_to :activity, polymorphic: true + validates :account, :activity, presence: true + def object_type - case self.activity_type - when 'Status' - :note - when 'Follow' - :person - end + self.activity.object_type end def verb - case self.activity_type - when 'Status' - :post - when 'Follow' - :follow - end + self.activity.verb + end + + def targeted? + [:follow].include? self.verb end def target - case self.activity_type - when 'Follow' - self.activity.target_account - end + self.activity.target + end + + def title + self.activity.title end def content - self.activity.text if self.activity_type == 'Status' + self.activity.content end end diff --git a/app/models/user.rb b/app/models/user.rb index ccfa54e4f..d23f5d608 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,5 @@ class User < ActiveRecord::Base belongs_to :account, inverse_of: :user + + validates :account, presence: true end diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb index 41f8fa4a0..bd3c760d7 100644 --- a/app/services/follow_remote_account_service.rb +++ b/app/services/follow_remote_account_service.rb @@ -5,10 +5,13 @@ class FollowRemoteAccountService username, domain = uri.split('@') 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.salmon_url = data.link('salmon').href @@ -21,16 +24,20 @@ class FollowRemoteAccountService feed = get_feed(account.remote_url) 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 + + get_profile(feed, account) account.save! subscription = account.subscription(subscription_url(account)) subscription.subscribe + + return account rescue Goldfinger::Error, HTTP::Error => e - false + nil end private @@ -40,6 +47,20 @@ class FollowRemoteAccountService Nokogiri::XML(response) 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) _, modulus, exponent = magic_key.split('.') modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } } diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index fc606730b..ea868b544 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -1,7 +1,7 @@ class FollowService def call(source_account, uri) target_account = follow_remote_account_service.(uri) - source_account.follow!(target_account) + source_account.follow!(target_account) unless target_account.nil? end private diff --git a/app/views/atom/user_stream.xml.ruby b/app/views/atom/user_stream.xml.ruby index d418ea0ec..d058d5cb4 100644 --- a/app/views/atom/user_stream.xml.ruby +++ b/app/views/atom/user_stream.xml.ruby @@ -15,20 +15,29 @@ Nokogiri::XML::Builder.new do |xml| end 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: 'self', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id)) @account.stream_entries.each do |stream_entry| xml.entry do 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.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('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 diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/xrd/webfinger.xml.ruby index 7a1e9a1d3..ce21f148a 100644 --- a/app/views/xrd/webfinger.xml.ruby +++ b/app/views/xrd/webfinger.xml.ruby @@ -1,6 +1,8 @@ Nokogiri::XML::Builder.new do |xml| xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do 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: 'salmon', href: salmon_url(@account)) xml.Link(rel: 'magic-public-key', href: @magic_key) diff --git a/config/application.rb b/config/application.rb index a2f778dc3..dddf4905b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,8 @@ require 'rails/all' # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) +Dotenv::Railtie.load + module Mastodon class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. diff --git a/config/environments/development.rb b/config/environments/development.rb index c3377aac8..b55e2144b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -38,6 +38,4 @@ Rails.application.configure do # Raises error for missing translations # config.action_view.raise_on_missing_translations = true - - config.action_mailer.default_url_options = { host: ENV['NGROK_HOST'] } end diff --git a/config/initializers/ostatus.rb b/config/initializers/ostatus.rb index 64204870b..6f00ba7ed 100644 --- a/config/initializers/ostatus.rb +++ b/config/initializers/ostatus.rb @@ -1 +1,6 @@ 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