diff options
-rw-r--r-- | .env.production.sample | 8 | ||||
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | app/assets/javascripts/components/locales/en.jsx | 6 | ||||
-rw-r--r-- | app/models/account.rb | 6 | ||||
-rw-r--r-- | app/models/status.rb | 15 | ||||
-rw-r--r-- | app/services/fan_out_on_write_service.rb | 5 | ||||
-rw-r--r-- | app/services/follow_remote_account_service.rb | 3 | ||||
-rw-r--r-- | app/services/process_feed_service.rb | 2 | ||||
-rw-r--r-- | app/services/process_hashtags_service.rb | 2 | ||||
-rw-r--r-- | app/services/process_interaction_service.rb | 2 | ||||
-rw-r--r-- | app/services/search_service.rb | 4 | ||||
-rw-r--r-- | app/services/update_remote_profile_service.rb | 26 | ||||
-rw-r--r-- | config/environments/production.rb | 2 | ||||
-rw-r--r-- | config/initializers/rack-attack.rb | 2 | ||||
-rw-r--r-- | spec/controllers/api/salmon_controller_spec.rb | 1 | ||||
-rw-r--r-- | spec/controllers/api/subscriptions_controller_spec.rb | 3 | ||||
-rw-r--r-- | spec/services/process_feed_service_spec.rb | 1 | ||||
-rw-r--r-- | spec/services/update_remote_profile_service_spec.rb | 8 |
19 files changed, 51 insertions, 50 deletions
diff --git a/.env.production.sample b/.env.production.sample index a3da10b97..e75bf9671 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -6,16 +6,13 @@ DB_USER=postgres DB_NAME=postgres DB_PASS= DB_PORT=5432 -NEO4J_HOST=neo4j -NEO4J_PORT=7474 # Federation LOCAL_DOMAIN=example.com LOCAL_HTTPS=true # Application secrets -# These are arbitrary strings. They should be long and cryptographically secure. -# For Docker, `docker-compose run --rm web rake secret` will generate them. +# Generate each with the `rake secret` task PAPERCLIP_SECRET= SECRET_KEY_BASE= @@ -25,3 +22,6 @@ SMTP_PORT=587 SMTP_LOGIN= SMTP_PASSWORD= SMTP_FROM_ADDRESS=notifications@example.com + +# Optional asset host for multi-server setups +# CDN_HOST=assets.example.com diff --git a/.travis.yml b/.travis.yml index f6841779d..fe4549edd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ env: - LOCAL_DOMAIN=cb6e6126.ngrok.io - LOCAL_HTTPS=true - RAILS_ENV=test - - NEO4J_HOST=localhost - - NEO4J_PORT=7575 addons: postgresql: 9.4 diff --git a/README.md b/README.md index 25d179d86..304e37935 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,6 @@ Consult the example configuration file, `.env.production.sample` for the full li - PostgreSQL - Redis -- Neo4J (optional) - - GraphAware NodeRank ## Running with Docker and Docker-Compose @@ -91,7 +89,6 @@ The container has two volumes, for the assets and for user uploads. The default - `rake mastodon:push:clear` unsubscribes from PuSH notifications for remote users that have no local followers. You may not want to actually do that, to keep a fuller footprint of the fediverse or in case your users will soon re-follow - `rake mastodon:push:refresh` re-subscribes PuSH for expiring remote users, this should be run periodically from a cronjob and quite often as the expiration time depends on the particular hub of the remote user - `rake mastodon:feeds:clear` removes all timelines, which forces them to be re-built on the fly next time a user tries to fetch their home/mentions timeline. Only for troubleshooting -- `rake mastodon:graphs:sync` re-imports all follow relationships into Neo4J. Only for troubleshooting Running any of these tasks via docker-compose would look like this: diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx index e6ae25453..41a44e3dc 100644 --- a/app/assets/javascripts/components/locales/en.jsx +++ b/app/assets/javascripts/components/locales/en.jsx @@ -5,9 +5,9 @@ const en = { "status.mention": "Mention", "status.delete": "Delete", "status.reply": "Reply", - "status.reblog": "Reblog", + "status.reblog": "Boost", "status.favourite": "Favourite", - "status.reblogged_by": "{name} reblogged", + "status.reblogged_by": "{name} boosted", "status.sensitive_warning": "Sensitive content", "status.sensitive_toggle": "Click to view", "video_player.toggle_sound": "Toggle sound", @@ -49,7 +49,7 @@ const en = { "upload_form.undo": "Undo", "notification.follow": "{name} followed you", "notification.favourite": "{name} favourited your status", - "notification.reblog": "{name} reblogged your status", + "notification.reblog": "{name} boosted your status", "notification.mention": "{name} mentioned you" }; diff --git a/app/models/account.rb b/app/models/account.rb index 870de8b7c..65fad2f47 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -116,7 +116,11 @@ class Account < ApplicationRecord end def avatar_remote_url=(url) - self.avatar = URI.parse(url) unless self[:avatar_remote_url] == url + parsed_url = URI.parse(url) + + return if !%w(http https).include?(parsed_url.scheme) || self[:avatar_remote_url] == url + + self.avatar = parsed_url self[:avatar_remote_url] = url rescue OpenURI::HTTPError => e Rails.logger.debug "Error fetching remote avatar: #{e}" diff --git a/app/models/status.rb b/app/models/status.rb index 3402929bf..f9dcd97e4 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -97,7 +97,10 @@ class Status < ApplicationRecord end def as_public_timeline(account = nil) - query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id').where('accounts.silenced = FALSE') + query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') + .where('accounts.silenced = FALSE') + .where('statuses.in_reply_to_id IS NULL') + .where('statuses.reblog_of_id IS NULL') query = filter_timeline(query, account) unless account.nil? query end @@ -106,6 +109,8 @@ class Status < ApplicationRecord query = tag.statuses .joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') .where('accounts.silenced = FALSE') + .where('statuses.in_reply_to_id IS NULL') + .where('statuses.reblog_of_id IS NULL') query = filter_timeline(query, account) unless account.nil? query end @@ -123,13 +128,7 @@ class Status < ApplicationRecord def filter_timeline(query, account) blocked = Block.where(account: account).pluck(:target_account_id) return query if blocked.empty? - - query - .joins('LEFT OUTER JOIN statuses AS parents ON statuses.in_reply_to_id = parents.id') - .joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id') - .where('statuses.account_id NOT IN (?)', blocked) - .where('(parents.id IS NULL OR parents.account_id NOT IN (?))', blocked) - .where('(reblogs.id IS NULL OR reblogs.account_id NOT IN (?))', blocked) + query.where('statuses.account_id NOT IN (?)', blocked) end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 78301c6ca..40d8a0fee 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -41,14 +41,17 @@ class FanOutOnWriteService < BaseService end def deliver_to_hashtags(status) - Rails.logger.debug "Delivering status #{status.id} to hashtags" + return if status.reblog? || status.reply? + Rails.logger.debug "Delivering status #{status.id} to hashtags" status.tags.find_each do |tag| FeedManager.instance.broadcast("hashtag:#{tag.name}", type: 'update', id: status.id) end end def deliver_to_public(status) + return if status.reblog? || status.reply? + Rails.logger.debug "Delivering status #{status.id} to public timeline" FeedManager.instance.broadcast(:public, type: 'update', id: status.id) end diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb index 37339d8ed..f640222b0 100644 --- a/app/services/follow_remote_account_service.rb +++ b/app/services/follow_remote_account_service.rb @@ -80,8 +80,7 @@ class FollowRemoteAccountService < BaseService end def get_profile(xml, account) - author = xml.at_xpath('/xmlns:feed/xmlns:author') || xml.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS) - update_remote_profile_service.call(author, account) + update_remote_profile_service.call(xml.at_xpath('/xmlns:feed'), account) end def update_remote_profile_service diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index 1cd801b80..a7a4cb2b0 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -16,7 +16,7 @@ class ProcessFeedService < BaseService def update_author(xml, account) return if xml.at_xpath('/xmlns:feed').nil? - UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed/xmlns:author'), account) + UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed'), account, true) end def process_entries(xml, account) diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 3bf3471ec..fa14c44da 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -4,7 +4,7 @@ class ProcessHashtagsService < BaseService def call(status, tags = []) tags = status.text.scan(Tag::HASHTAG_RE).map(&:first) if status.local? - tags.map(&:downcase).uniq.each do |tag| + tags.map { |str| str.mb_chars.downcase }.uniq.each do |tag| status.tags << Tag.where(name: tag).first_or_initialize(name: tag) end end diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index e7bb3c73b..3159a4ded 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -26,7 +26,7 @@ class ProcessInteractionService < BaseService end if salmon.verify(envelope, account.keypair) - update_remote_profile_service.call(xml.at_xpath('/xmlns:entry/xmlns:author'), account) + update_remote_profile_service.call(xml.at_xpath('/xmlns:entry'), account, true) case verb(xml) when :follow diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 598c7d02c..1ae1d5a80 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -2,9 +2,9 @@ class SearchService < BaseService def call(query, limit, resolve = false) - return if query.blank? + return if query.blank? || query.start_with?('#') - username, domain = query.split('@') + username, domain = query.gsub(/\A@/, '').split('@') results = if domain.nil? Account.search_for(username) diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index 2909ae12a..26ab84d75 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -2,24 +2,22 @@ class UpdateRemoteProfileService < BaseService POCO_NS = 'http://portablecontacts.net/spec/1.0' + DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0' - def call(author_xml, account) - return if author_xml.nil? + def call(xml, account, resubscribe = false) + author_xml = xml.at_xpath('./xmlns:author') || xml.at_xpath('./dfrn:owner', dfrn: DFRN_NS) + hub_link = xml.at_xpath('./xmlns:link[@rel="hub"]') - account.display_name = if author_xml.at_xpath('./poco:displayName', poco: POCO_NS).nil? - account.username - else - author_xml.at_xpath('./poco:displayName', poco: POCO_NS).content - end - - unless author_xml.at_xpath('./poco:note').nil? - account.note = author_xml.at_xpath('./poco:note', poco: POCO_NS).content - end - - unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]').nil? - account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]').attribute('href').value + unless author_xml.nil? + account.display_name = author_xml.at_xpath('./poco:displayName', poco: POCO_NS).content unless author_xml.at_xpath('./poco:displayName', poco: POCO_NS).nil? + account.note = author_xml.at_xpath('./poco:note', poco: POCO_NS).content unless author_xml.at_xpath('./poco:note').nil? + account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]').nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'].blank? end + old_hub_url = account.hub_url + account.hub_url = hub_link['href'] if !hub_link.nil? && !hub_link['href'].blank? && (hub_link['href'] != old_hub_url) account.save! + + SubscribeService.new.call(account) if resubscribe && (account.hub_url != old_hub_url) end end diff --git a/config/environments/production.rb b/config/environments/production.rb index dcb659d6c..0672cd587 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -13,7 +13,7 @@ Rails.application.configure do # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - config.action_controller.asset_host = ENV['CDN_HOST'] + config.action_controller.asset_host = ENV['CDN_HOST'] if ENV.key?('CDN_HOST') # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack-attack.rb index 6d9286e66..0e96f5381 100644 --- a/config/initializers/rack-attack.rb +++ b/config/initializers/rack-attack.rb @@ -11,7 +11,7 @@ class Rack::Attack headers = { 'X-RateLimit-Limit' => match_data[:limit].to_s, 'X-RateLimit-Remaining' => '0', - 'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).to_s + 'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).iso8601(6) } [429, headers, [{ error: 'Throttled' }.to_json]] diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb index 6897caeeb..3d3a973d2 100644 --- a/spec/controllers/api/salmon_controller_spec.rb +++ b/spec/controllers/api/salmon_controller_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Api::SalmonController, type: :controller do let(:account) { Fabricate(:user, account: Fabricate(:account, username: 'catsrgr8')).account } before do + stub_request(:post, "https://pubsubhubbub.superfeedr.com/").to_return(:status => 200, :body => "", :headers => {}) stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt')) stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt')) stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt')) diff --git a/spec/controllers/api/subscriptions_controller_spec.rb b/spec/controllers/api/subscriptions_controller_spec.rb index 2af6cb725..44841176a 100644 --- a/spec/controllers/api/subscriptions_controller_spec.rb +++ b/spec/controllers/api/subscriptions_controller_spec.rb @@ -23,6 +23,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do let(:feed) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom')) } before do + stub_request(:post, "https://quitter.no/main/push/hub").to_return(:status => 200, :body => "", :headers => {}) stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt')) stub_request(:head, "https://quitter.no/notice/1269244").to_return(status: 404) stub_request(:head, "https://quitter.no/notice/1265331").to_return(status: 404) @@ -37,7 +38,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do stub_request(:head, "https://social.umeahackerspace.se/user/2").to_return(status: 404) stub_request(:head, "https://gs.kawa-kun.com/user/2").to_return(status: 404) stub_request(:head, "https://mastodon.social/users/Gargron").to_return(status: 404) - + request.env['HTTP_X_HUB_SIGNATURE'] = "sha1=#{OpenSSL::HMAC.hexdigest('sha1', 'abc', feed)}" request.env['RAW_POST_DATA'] = feed diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb index e4e5858ea..5e57d823b 100644 --- a/spec/services/process_feed_service_spec.rb +++ b/spec/services/process_feed_service_spec.rb @@ -7,6 +7,7 @@ RSpec.describe ProcessFeedService do subject { ProcessFeedService.new } before do + stub_request(:post, "https://pubsubhubbub.superfeedr.com/").to_return(:status => 200, :body => "", :headers => {}) stub_request(:get, "http://kickass.zone/system/accounts/avatars/000/000/001/large/eris.png").to_return(request_fixture('avatar.txt')) stub_request(:get, "http://kickass.zone/system/media_attachments/files/000/000/002/original/morpheus_linux.jpg?1476059910").to_return(request_fixture('attachment1.txt')) stub_request(:get, "http://kickass.zone/system/media_attachments/files/000/000/003/original/gizmo.jpg?1476060065").to_return(request_fixture('attachment2.txt')) diff --git a/spec/services/update_remote_profile_service_spec.rb b/spec/services/update_remote_profile_service_spec.rb index 1ffcfbfac..c3d76c653 100644 --- a/spec/services/update_remote_profile_service_spec.rb +++ b/spec/services/update_remote_profile_service_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe UpdateRemoteProfileService do - let(:xml) { Nokogiri::XML(File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom'))).at_xpath('//xmlns:author') } + let(:xml) { Nokogiri::XML(File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom'))).at_xpath('//xmlns:feed') } subject { UpdateRemoteProfileService.new } @@ -13,7 +13,7 @@ RSpec.describe UpdateRemoteProfileService do let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com') } before do - subject.(xml, remote_account) + subject.call(xml, remote_account) end it 'downloads new avatar' do @@ -34,10 +34,10 @@ RSpec.describe UpdateRemoteProfileService do end context 'with unchanged details' do - let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com',display_name: 'DIGITAL CAT', note: 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes', avatar_remote_url: 'https://quitter.no/avatar/7477-300-20160211190340.png') } + let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com', display_name: 'DIGITAL CAT', note: 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes', avatar_remote_url: 'https://quitter.no/avatar/7477-300-20160211190340.png') } before do - subject.(xml, remote_account) + subject.call(xml, remote_account) end it 'does not re-download avatar' do |