Compare commits

..

No commits in common. "bc3737f0c3c4cc0af500413db53fb5443ad02b69" and "6d1c1fd684d0f20691cba552b209b15d0107dfe4" have entirely different histories.

15 changed files with 17 additions and 102 deletions

View File

@ -76,8 +76,8 @@ export function importFetchedStatuses(statuses) {
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
} }
if (status.card) { if (status.card?.author_account) {
status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); pushUnique(accounts, status.card.author_account);
} }
} }

View File

@ -36,15 +36,8 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.poll = status.poll.id; normalStatus.poll = status.poll.id;
} }
if (status.card) { if (status.card?.author_account) {
normalStatus.card = { normalStatus.card = { ...status.card, author_account: status.card.author_account.id };
...status.card,
authors: status.card.authors.map(author => ({
...author,
accountId: author.account?.id,
account: undefined,
})),
};
} }
if (status.filtered) { if (status.filtered) {

View File

@ -51,7 +51,7 @@ export const fetchTrendingLinks = () => (dispatch) => {
api() api()
.get('/api/v1/trends/links', { params: { limit: 20 } }) .get('/api/v1/trends/links', { params: { limit: 20 } })
.then(({ data }) => { .then(({ data }) => {
dispatch(importFetchedAccounts(data.flatMap(link => link.authors.map(author => author.account)).filter(account => !!account))); dispatch(importFetchedAccounts(data.map(link => link.author_account).filter(account => !!account)));
dispatch(fetchTrendingLinksSuccess(data)); dispatch(fetchTrendingLinksSuccess(data));
}) })
.catch(err => dispatch(fetchTrendingLinksFail(err))); .catch(err => dispatch(fetchTrendingLinksFail(err)));

View File

@ -30,12 +30,6 @@ export interface ApiMentionJSON {
acct: string; acct: string;
} }
export interface ApiPreviewCardAuthorJSON {
name: string;
url: string;
account?: ApiAccountJSON;
}
export interface ApiPreviewCardJSON { export interface ApiPreviewCardJSON {
url: string; url: string;
title: string; title: string;
@ -54,7 +48,6 @@ export interface ApiPreviewCardJSON {
embed_url: string; embed_url: string;
blurhash: string; blurhash: string;
published_at: string; published_at: string;
authors: ApiPreviewCardAuthorJSON[];
} }
export interface ApiStatusJSON { export interface ApiStatusJSON {

View File

@ -8,10 +8,6 @@ import { useAppSelector } from 'mastodon/store';
export const AuthorLink = ({ accountId }) => { export const AuthorLink = ({ accountId }) => {
const account = useAppSelector(state => state.getIn(['accounts', accountId])); const account = useAppSelector(state => state.getIn(['accounts', accountId]));
if (!account) {
return null;
}
return ( return (
<Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link' data-hover-card-account={accountId}> <Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link' data-hover-card-account={accountId}>
<Avatar account={account} size={16} /> <Avatar account={account} size={16} />

View File

@ -75,7 +75,7 @@ class Links extends PureComponent {
publisher={link.get('provider_name')} publisher={link.get('provider_name')}
publishedAt={link.get('published_at')} publishedAt={link.get('published_at')}
author={link.get('author_name')} author={link.get('author_name')}
authorAccount={link.getIn(['authors', 0, 'account', 'id'])} authorAccount={link.getIn(['author_account', 'id'])}
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1} sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
thumbnail={link.get('image')} thumbnail={link.get('image')}
thumbnailDescription={link.get('image_description')} thumbnailDescription={link.get('image_description')}

View File

@ -138,7 +138,7 @@ export default class Card extends PureComponent {
const interactive = card.get('type') === 'video'; const interactive = card.get('type') === 'video';
const language = card.get('language') || ''; const language = card.get('language') || '';
const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive; const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive;
const showAuthor = !!card.getIn(['authors', 0, 'accountId']); const showAuthor = !!card.get('author_account');
const description = ( const description = (
<div className='status-card__content'> <div className='status-card__content'>
@ -244,7 +244,7 @@ export default class Card extends PureComponent {
{description} {description}
</a> </a>
{showAuthor && <MoreFromAuthor accountId={card.getIn(['authors', 0, 'accountId'])} />} {showAuthor && <MoreFromAuthor accountId={card.get('author_account')} />}
</> </>
); );
} }

View File

@ -128,22 +128,6 @@ class PreviewCard < ApplicationRecord
@history ||= Trends::History.new('links', id) @history ||= Trends::History.new('links', id)
end end
def authors
@authors ||= [PreviewCard::Author.new(self)]
end
class Author < ActiveModelSerializers::Model
attributes :name, :url, :account
def initialize(preview_card)
super(
name: preview_card.author_name,
url: preview_card.author_url,
account: preview_card.author_account,
)
end
end
class << self class << self
private private

View File

@ -1,11 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::PreviewCardSerializer < ActiveModel::Serializer class REST::PreviewCardSerializer < ActiveModel::Serializer
class AuthorSerializer < ActiveModel::Serializer
attributes :name, :url
has_one :account, serializer: REST::AccountSerializer
end
include RoutingHelper include RoutingHelper
attributes :url, :title, :description, :language, :type, attributes :url, :title, :description, :language, :type,
@ -13,7 +8,7 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
:provider_url, :html, :width, :height, :provider_url, :html, :width, :height,
:image, :image_description, :embed_url, :blurhash, :published_at :image, :image_description, :embed_url, :blurhash, :published_at
has_many :authors, serializer: AuthorSerializer has_one :author_account, serializer: REST::AccountSerializer, if: -> { object.author_account.present? }
def url def url
object.original_url.presence || object.url object.original_url.presence || object.url

View File

@ -15,9 +15,6 @@ class FetchLinkCardService < BaseService
) )
}iox }iox
# URL size limit to safely store in PosgreSQL's unique indexes
BYTESIZE_LIMIT = 2692
def call(status) def call(status)
@status = status @status = status
@original_url = parse_urls @original_url = parse_urls
@ -32,7 +29,7 @@ class FetchLinkCardService < BaseService
end end
attach_card if @card&.persisted? attach_card if @card&.persisted?
rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError => e rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" } Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" }
nil nil
end end
@ -88,7 +85,7 @@ class FetchLinkCardService < BaseService
def bad_url?(uri) def bad_url?(uri)
# Avoid local instance URLs and invalid URLs # Avoid local instance URLs and invalid URLs
uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) || uri.to_s.bytesize > BYTESIZE_LIMIT uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
end end
def mention_link?(anchor) def mention_link?(anchor)

View File

@ -5,11 +5,7 @@ if Rails.configuration.x.use_vips
require 'vips' require 'vips'
unless Vips.at_least_libvips?(8, 13) abort('Incompatible libvips version, please install libvips >= 8.13') unless Vips.at_least_libvips?(8, 13)
abort <<~ERROR.squish
Incompatible libvips version (#{Vips.version_string}), please install libvips >= 8.13
ERROR
end
Vips.block('VipsForeign', true) Vips.block('VipsForeign', true)
@ -29,11 +25,3 @@ if Rails.configuration.x.use_vips
Vips.block_untrusted(true) Vips.block_untrusted(true)
end end
# In some places of the code, we rescue this exception, but we don't always
# load libvips, so it may be an undefined constant:
unless defined?(Vips)
module Vips
class Error < StandardError; end
end
end

View File

@ -1,5 +0,0 @@
HTTP/1.1 301 Moved Permanently
server: nginx
date: Thu, 27 Jun 2024 11:04:53 GMT
content-type: text/html; charset=UTF-8
location: http://example.com/ärgerliche-umlaute.html

View File

@ -210,14 +210,10 @@ RSpec.describe MediaAttachment, :paperclip_processing do
expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102) expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
expect(media.thumbnail.present?).to be true expect(media.thumbnail.present?).to be true
expect(media.file.meta['colors']['background']).to eq(expected_background_color) # NOTE: Our libvips and ImageMagick implementations currently have different results
expect(media.file.meta['colors']['background']).to eq(ENV['MASTODON_USE_LIBVIPS'] ? '#268cd9' : '#3088d4')
expect(media.file_file_name).to_not eq 'boop.ogg' expect(media.file_file_name).to_not eq 'boop.ogg'
end end
def expected_background_color
# The libvips and ImageMagick implementations produce different results
Rails.configuration.x.use_vips ? '#268cd9' : '#3088d4'
end
end end
describe 'mp3 with large cover art' do describe 'mp3 with large cover art' do

View File

@ -27,7 +27,6 @@ RSpec.describe FetchLinkCardService do
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt')) stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt'))
stub_request(:get, 'http://example.com/aergerliche-umlaute').to_return(request_fixture('redirect_with_utf8_url.txt'))
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
@ -102,14 +101,6 @@ RSpec.describe FetchLinkCardService do
end end
end end
context 'with a redirect URL with faulty encoding' do
let(:status) { Fabricate(:status, text: 'http://example.com/aergerliche-umlaute') }
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with a 404 URL' do context 'with a 404 URL' do
let(:status) { Fabricate(:status, text: 'http://example.com/not-found') } let(:status) { Fabricate(:status, text: 'http://example.com/not-found') }
@ -202,19 +193,6 @@ RSpec.describe FetchLinkCardService do
end end
end end
context 'with an URL too long for PostgreSQL unique indexes' do
let(:url) { "http://example.com/#{'a' * 2674}" }
let(:status) { Fabricate(:status, text: url) }
it 'does not fetch the URL' do
expect(a_request(:get, url)).to_not have_been_made
end
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with a URL of a page with oEmbed support' do context 'with a URL of a page with oEmbed support' do
let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' } let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' }
let(:status) { Fabricate(:status, text: 'http://example.com/html') } let(:status) { Fabricate(:status, text: 'http://example.com/html') }

View File

@ -14099,12 +14099,12 @@ __metadata:
linkType: hard linkType: hard
"prom-client@npm:^15.0.0": "prom-client@npm:^15.0.0":
version: 15.1.3 version: 15.1.2
resolution: "prom-client@npm:15.1.3" resolution: "prom-client@npm:15.1.2"
dependencies: dependencies:
"@opentelemetry/api": "npm:^1.4.0" "@opentelemetry/api": "npm:^1.4.0"
tdigest: "npm:^0.1.1" tdigest: "npm:^0.1.1"
checksum: 10c0/816525572e5799a2d1d45af78512fb47d073c842dc899c446e94d17cfc343d04282a1627c488c7ca1bcd47f766446d3e49365ab7249f6d9c22c7664a5bce7021 checksum: 10c0/a221db148fa64e29dfd4c6cdcaaae14635495a4272b68917e2b44fcfd988bc57027d275b04489ceeea4d0c4d64d058af842c1300966d2c1ffa255f1fa6af1277
languageName: node languageName: node
linkType: hard linkType: hard