diff --git a/CHANGELOG.md b/CHANGELOG.md index 0696f0b31c..02a12cfe5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,48 @@ All notable changes to this project will be documented in this file. +## [4.3.2] - 2024-12-03 + +### Added + +- Add `tootctl feeds vacuum` (#33065 by @ClearlyClaire) +- Add error message when user tries to follow their own account (#31910 by @lenikadali) +- Add client_secret_expires_at to OAuth Applications (#30317 by @ThisIsMissEm) + +### Changed + +- Change design of Content Warnings and filters (#32543 by @ClearlyClaire) + +### Fixed + +- Fix processing incoming post edits with mentions to unresolvable accounts (#33129 by @ClearlyClaire) +- Fix error when including multiple instances of `embed.js` (#33107 by @YKWeyer) +- Fix inactive users' timelines being backfilled on follow and unsuspend (#33094 by @ClearlyClaire) +- Fix direct inbox delivery pushing posts into inactive followers' timelines (#33067 by @ClearlyClaire) +- Fix `TagFollow` records not being correctly handled in account operations (#33063 by @ClearlyClaire) +- Fix pushing hashtag-followed posts to feeds of inactive users (#33018 by @Gargron) +- Fix duplicate notifications in notification groups when using slow mode (#33014 by @ClearlyClaire) +- Fix posts made in the future being allowed to trend (#32996 by @ClearlyClaire) +- Fix uploading higher-than-wide GIF profile picture with libvips enabled (#32911 by @ClearlyClaire) +- Fix domain attribution field having autocorrect and autocapitalize enabled (#32903 by @ClearlyClaire) +- Fix titles being escaped twice (#32889 by @ClearlyClaire) +- Fix list creation limit check (#32869 by @ClearlyClaire) +- Fix error in `tootctl email_domain_blocks` when supplying `--with-dns-records` (#32863 by @mjankowski) +- Fix `min_id` and `max_id` causing error in search API (#32857 by @Gargron) +- Fix inefficiencies when processing removal of posts that use featured tags (#32787 by @ClearlyClaire) +- Fix alt-text pop-in not using the translated description (#32766 by @ClearlyClaire) +- Fix preview cards with long titles erroneously causing layout changes (#32678 by @ClearlyClaire) +- Fix embed modal layout on mobile (#32641 by @DismalShadowX) +- Fix and improve batch attachment deletion handling when using OpenStack Swift (#32637 by @hugogameiro) +- Fix blocks not being applied on link timeline (#32625 by @tribela) +- Fix follow counters being incorrectly changed (#32622 by @oneiros) +- Fix 'unknown' media attachment type rendering (#32613 and #32713 by @ThisIsMissEm and @renatolond) +- Fix tl language native name (#32606 by @seav) + +### Security + +- Update dependencies + ## [4.3.1] - 2024-10-21 ### Added diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb index 75c88d964e..256d72acb9 100644 --- a/app/lib/activitypub/activity/follow.rb +++ b/app/lib/activitypub/activity/follow.rb @@ -60,19 +60,13 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity already_accepted = friend.accepted? friend.update!(passive_state: :pending, active_state: :idle, passive_follow_activity_id: @json['id']) else - @friend = FriendDomain.new(domain: @account.domain, passive_state: :pending, passive_follow_activity_id: @json['id']) - @friend.inbox_url = @json['inboxUrl'].presence || @friend.default_inbox_url - @friend.save! + @friend = FriendDomain.create!(domain: @account.domain, passive_state: :pending, passive_follow_activity_id: @json['id'], inbox_url: @account.preferred_inbox_url) end - if already_accepted || Setting.unlocked_friend - friend.accept! + friend.accept! if already_accepted || Setting.unlocked_friend - # Notify for admin even if unlocked - notify_staff_about_pending_friend_server! unless already_accepted - else - notify_staff_about_pending_friend_server! - end + # Notify for admin + notify_staff_about_pending_friend_server! unless already_accepted end def friend diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 279b31e98e..10598395ed 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -125,6 +125,8 @@ class FeedManager # @param [Account] into_account # @return [void] def merge_into_home(from_account, into_account) + return unless into_account.user&.signed_in_recently? + timeline_key = key(:home, into_account.id) aggregate = into_account.user&.aggregates_reblogs? query = from_account.statuses.list_eligible_visibility.includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) @@ -151,6 +153,8 @@ class FeedManager # @param [List] list # @return [void] def merge_into_list(from_account, list) + return unless list.account.user&.signed_in_recently? + timeline_key = key(:list, list.id) aggregate = list.account.user&.aggregates_reblogs? query = from_account.statuses.list_eligible_visibility.includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4) diff --git a/app/models/friend_domain.rb b/app/models/friend_domain.rb index 88ebd9338b..2ca501e21d 100644 --- a/app/models/friend_domain.rb +++ b/app/models/friend_domain.rb @@ -116,6 +116,7 @@ class FriendDomain < ApplicationRecord object: ActivityPub::TagManager::COLLECTIONS[:public], # Cannot use inbox_url method because this model also has inbox_url column + # This is deprecated property. Newer version's kmyblue will ignore it. inboxUrl: "https://#{Rails.configuration.x.web_domain}/inbox", } end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 5a8fa2c53f..3b69cadfce 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -261,6 +261,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService def update_mentions! previous_mentions = @status.active_mentions.includes(:account).to_a current_mentions = [] + unresolved_mentions = [] @raw_mentions.each do |href| next if href.blank? @@ -274,6 +275,12 @@ class ActivityPub::ProcessStatusUpdateService < BaseService mention ||= account.mentions.new(status: @status) current_mentions << mention + rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS + # Since previous mentions are about already-known accounts, + # they don't try to resolve again and won't fall into this case. + # In other words, this failure case is only for new mentions and won't + # affect `removed_mentions` so they can safely be retried asynchronously + unresolved_mentions << href end current_mentions.each do |mention| @@ -286,6 +293,11 @@ class ActivityPub::ProcessStatusUpdateService < BaseService removed_mentions = previous_mentions - current_mentions Mention.where(id: removed_mentions.map(&:id)).update_all(silent: true) unless removed_mentions.empty? + + # Queue unresolved mentions for later + unresolved_mentions.uniq.each do |uri| + MentionResolveWorker.perform_in(rand(30...600).seconds, @status.id, uri, { 'request_id' => @request_id }) + end end def update_emojis! diff --git a/docker-compose.yml b/docker-compose.yml index d7958cedfd..4698c292a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes build: . - image: kmyblue:16.0-dev + image: kmyblue:16.1 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: build: dockerfile: ./streaming/Dockerfile context: . - image: kmyblue-streaming:16.0-dev + image: kmyblue-streaming:16.1 restart: always env_file: .env.production command: node ./streaming/index.js @@ -101,7 +101,7 @@ services: sidekiq: build: . - image: kmyblue:16.0-dev + image: kmyblue:16.1 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/cli/feeds.rb b/lib/mastodon/cli/feeds.rb index 9d25cd7859..c0635bbcde 100644 --- a/lib/mastodon/cli/feeds.rb +++ b/lib/mastodon/cli/feeds.rb @@ -5,6 +5,7 @@ require_relative 'base' module Mastodon::CLI class Feeds < Base include Redisable + include DatabaseHelper option :all, type: :boolean, default: false option :concurrency, type: :numeric, default: 5, aliases: [:c] @@ -59,6 +60,38 @@ module Mastodon::CLI say('OK', :green) end + desc 'vacuum', 'Remove home feeds of inactive users from Redis' + long_desc <<-LONG_DESC + Running this task should not be needed in most cases, as Mastodon will + automatically clean up feeds from inactive accounts every day. + + However, this task is more aggressive in order to clean up feeds that + may have been missed because of bugs or database mishaps. + LONG_DESC + def vacuum + with_read_replica do + say('Deleting orphaned home feeds…') + redis.scan_each(match: 'feed:home:*').each_slice(1000) do |keys| + ids = keys.map { |key| key.split(':')[2] }.compact_blank + + known_ids = User.confirmed.signed_in_recently.where(account_id: ids).pluck(:account_id) + + keys_to_delete = keys.filter { |key| known_ids.exclude?(key.split(':')[2]&.to_i) } + redis.del(keys_to_delete) + end + + say('Deleting orphaned list feeds…') + redis.scan_each(match: 'feed:list:*').each_slice(1000) do |keys| + ids = keys.map { |key| key.split(':')[2] }.compact_blank + + known_ids = List.where(account_id: User.confirmed.signed_in_recently.select(:account_id)).where(id: ids).pluck(:id) + + keys_to_delete = keys.filter { |key| known_ids.exclude?(key.split(':')[2]&.to_i) } + redis.del(keys_to_delete) + end + end + end + private def active_user_accounts diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index b57856e472..88f4e20dbb 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,13 +13,13 @@ module Mastodon end def kmyblue_minor - 0 + 1 end def kmyblue_flag # 'LTS' - 'dev' - # nil + # 'dev' + nil end def major diff --git a/public/embed.js b/public/embed.js index 53372a3890..bc1fac3864 100644 --- a/public/embed.js +++ b/public/embed.js @@ -1,8 +1,5 @@ // @ts-check - -const allowedPrefixes = (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.dataset.allowedPrefixes) ? document.currentScript.dataset.allowedPrefixes.split(' ') : []; - -(function () { +(function (allowedPrefixes) { 'use strict'; /** @@ -127,4 +124,4 @@ const allowedPrefixes = (document.currentScript && document.currentScript.tagNam container.appendChild(iframe); }); }); -})(); +})((document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.dataset.allowedPrefixes) ? document.currentScript.dataset.allowedPrefixes.split(' ') : []); diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb index ec0e5e4eef..12c30feb28 100644 --- a/spec/lib/activitypub/activity/follow_spec.rb +++ b/spec/lib/activitypub/activity/follow_spec.rb @@ -380,11 +380,10 @@ RSpec.describe ActivityPub::Activity::Follow do context 'when given a friend server' do subject { described_class.new(json, sender) } - let(:sender) { Fabricate(:account, domain: 'abc.com', url: 'https://abc.com/#actor') } + let(:sender) { Fabricate(:account, domain: 'abc.com', url: 'https://abc.com/#actor', shared_inbox_url: 'https://abc.com/shared_inbox') } let!(:friend) { Fabricate(:friend_domain, domain: 'abc.com', inbox_url: 'https://example.com/inbox', passive_state: :idle) } let!(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) } let!(:patch_user) { Fabricate(:user, role: Fabricate(:user_role, name: 'OhagiOps', permissions: UserRole::FLAGS[:manage_federation])) } - let(:inbox_url) { nil } let(:json) do { @@ -393,7 +392,6 @@ RSpec.describe ActivityPub::Activity::Follow do type: 'Follow', actor: ActivityPub::TagManager.instance.uri_for(sender), object: 'https://www.w3.org/ns/activitystreams#Public', - inboxUrl: inbox_url, }.with_indifferent_access end @@ -415,25 +413,34 @@ RSpec.describe ActivityPub::Activity::Follow do expect(friend).to_not be_nil expect(friend.they_are_pending?).to be true expect(friend.passive_follow_activity_id).to eq 'foo' - expect(friend.inbox_url).to eq 'https://abc.com/inbox' + expect(friend.inbox_url).to eq 'https://abc.com/shared_inbox' end end - context 'when no record and inbox_url is specified' do - let(:inbox_url) { 'https://ohagi.com/inbox' } + context 'when old spec which no record and inbox_url is specified' do + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Follow', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: 'https://www.w3.org/ns/activitystreams#Public', + inboxUrl: 'https://evil.org/bad_inbox', + }.with_indifferent_access + end before do friend.destroy! end - it 'marks the friend as pending' do + it 'marks the friend as pending but inboxUrl is not working' do subject.perform friend = FriendDomain.find_by(domain: 'abc.com') expect(friend).to_not be_nil expect(friend.they_are_pending?).to be true expect(friend.passive_follow_activity_id).to eq 'foo' - expect(friend.inbox_url).to eq 'https://ohagi.com/inbox' + expect(friend.inbox_url).to eq 'https://abc.com/shared_inbox' end end diff --git a/spec/models/friend_domain_spec.rb b/spec/models/friend_domain_spec.rb index 336f921ebd..d3992ed149 100644 --- a/spec/models/friend_domain_spec.rb +++ b/spec/models/friend_domain_spec.rb @@ -21,7 +21,6 @@ RSpec.describe FriendDomain do type: 'Follow', actor: 'https://cb6e6126.ngrok.io/actor', object: 'https://www.w3.org/ns/activitystreams#Public', - inboxUrl: 'https://cb6e6126.ngrok.io/inbox', }))).to have_been_made.once end end