Skip to content

Commit

Permalink
Add ability to manage which websites can credit you in link previews (m…
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron authored Sep 10, 2024
1 parent 3929e3c commit e0c27a5
Show file tree
Hide file tree
Showing 92 changed files with 381 additions and 160 deletions.
20 changes: 18 additions & 2 deletions app/controllers/settings/verifications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@

class Settings::VerificationsController < Settings::BaseController
before_action :set_account
before_action :set_verified_links

def show
@verified_links = @account.fields.select(&:verified?)
def show; end

def update
if UpdateAccountService.new.call(@account, account_params)
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
else
render :show
end
end

private

def account_params
params.require(:account).permit(:attribution_domains_as_text)
end

def set_account
@account = current_account
end

def set_verified_links
@verified_links = @account.fields.select(&:verified?)
end
end
1 change: 1 addition & 0 deletions app/helpers/context_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module ContextHelper
'cipherText' => 'toot:cipherText',
},
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
}.freeze

def full_context
Expand Down
35 changes: 35 additions & 0 deletions app/javascript/styles/mastodon/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,41 @@ code {
margin: 50px auto;
}

.form-section {
border-radius: 8px;
background: var(--surface-background-color);
padding: 24px;
margin-bottom: 24px;
}

.fade-out-top {
position: relative;
overflow: hidden;
height: 160px;

&::after {
content: '';
display: block;
background: linear-gradient(
to bottom,
var(--surface-background-color),
transparent
);
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100px;
pointer-events: none;
}

& > div {
position: absolute;
inset-inline-start: 0;
bottom: 0;
}
}

.indicator-icon {
display: flex;
align-items: center;
Expand Down
2 changes: 2 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
# reviewed_at :datetime
# requested_review_at :datetime
# indexable :boolean default(FALSE), not null
# attribution_domains :string default([]), is an Array
#

class Account < ApplicationRecord
Expand Down Expand Up @@ -88,6 +89,7 @@ class Account < ApplicationRecord
include Account::Merging
include Account::Search
include Account::StatusesSearch
include Account::AttributionDomains
include DomainMaterializable
include DomainNormalizable
include Paginable
Expand Down
25 changes: 25 additions & 0 deletions app/models/concerns/account/attribution_domains.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Account::AttributionDomains
extend ActiveSupport::Concern

included do
validates :attribution_domains_as_text, domain: { multiline: true }, lines: { maximum: 100 }, if: -> { local? && will_save_change_to_attribution_domains? }
end

def attribution_domains_as_text
self[:attribution_domains].join("\n")
end

def attribution_domains_as_text=(str)
self[:attribution_domains] = str.split.filter_map do |line|
line.strip.delete_prefix('*.')
end
end

def can_be_attributed_from?(domain)
segments = domain.split('.')
variants = segments.map.with_index { |_, i| segments[i..].join('.') }.to_set
self[:attribution_domains].to_set.intersect?(variants)
end
end
3 changes: 2 additions & 1 deletion app/serializers/activitypub/actor_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer

context_extensions :manually_approves_followers, :featured, :also_known_as,
:moved_to, :property_value, :discoverable, :olm, :suspended,
:memorial, :indexable
:memorial, :indexable, :attribution_domains

attributes :id, :type, :following, :followers,
:inbox, :outbox, :featured, :featured_tags,
Expand All @@ -25,6 +25,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
attribute :moved_to, if: :moved?
attribute :also_known_as, if: :also_known_as?
attribute :suspended, if: :suspended?
attribute :attribution_domains, if: -> { object.attribution_domains.any? }

class EndpointsSerializer < ActivityPub::Serializer
include RoutingHelper
Expand Down
1 change: 1 addition & 0 deletions app/services/activitypub/process_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def set_immediate_attributes!
@account.discoverable = @json['discoverable'] || false
@account.indexable = @json['indexable'] || false
@account.memorial = @json['memorial'] || false
@account.attribution_domains = as_array(@json['attributionDomains'] || []).map { |item| value_or_id(item) }
end

def set_fetchable_key!
Expand Down
7 changes: 4 additions & 3 deletions app/services/fetch_link_card_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,13 @@ def attempt_opengraph
return if html.nil?

link_details_extractor = LinkDetailsExtractor.new(@url, @html, @html_charset)
provider = PreviewCardProvider.matching_domain(Addressable::URI.parse(link_details_extractor.canonical_url).normalized_host)
linked_account = ResolveAccountService.new.call(link_details_extractor.author_account, suppress_errors: true) if link_details_extractor.author_account.present? && provider&.trendable?
domain = Addressable::URI.parse(link_details_extractor.canonical_url).normalized_host
provider = PreviewCardProvider.matching_domain(domain)
linked_account = ResolveAccountService.new.call(link_details_extractor.author_account, suppress_errors: true) if link_details_extractor.author_account.present?

@card = PreviewCard.find_or_initialize_by(url: link_details_extractor.canonical_url) if link_details_extractor.canonical_url != @card.url
@card.assign_attributes(link_details_extractor.to_preview_card_attributes)
@card.author_account = linked_account
@card.author_account = linked_account if linked_account&.can_be_attributed_from?(domain) || provider&.trendable?
@card.save_with_optional_image! unless @card.title.blank? && @card.html.blank?
end
end
21 changes: 14 additions & 7 deletions app/validators/domain_validator.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
# frozen_string_literal: true

class DomainValidator < ActiveModel::EachValidator
MAX_DOMAIN_LENGTH = 256
MIN_LABEL_LENGTH = 1
MAX_LABEL_LENGTH = 63
ALLOWED_CHARACTERS_RE = /^[a-z0-9\-]+$/i

def validate_each(record, attribute, value)
return if value.blank?

domain = if options[:acct]
value.split('@').last
else
value
end
(options[:multiline] ? value.split : [value]).each do |domain|
_, domain = domain.split('@') if options[:acct]

next if domain.blank?

record.errors.add(attribute, I18n.t('domain_validator.invalid_domain')) unless compliant?(domain)
record.errors.add(attribute, options[:multiline] ? :invalid_domain_on_line : :invalid, value: domain) unless compliant?(domain)
end
end

private

def compliant?(value)
Addressable::URI.new.tap { |uri| uri.host = value }
uri = Addressable::URI.new
uri.host = value
uri.normalized_host.size < MAX_DOMAIN_LENGTH && uri.normalized_host.split('.').all? { |label| label.size.between?(MIN_LABEL_LENGTH, MAX_LABEL_LENGTH) && label =~ ALLOWED_CHARACTERS_RE }
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
false
end
Expand Down
9 changes: 9 additions & 0 deletions app/validators/lines_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class LinesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?

record.errors.add(attribute, :too_many_lines, limit: options[:maximum]) if options[:maximum].present? && value.split.size > options[:maximum]
end
end
34 changes: 33 additions & 1 deletion app/views/settings/verifications/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
%h2= t('settings.profile')
= render partial: 'settings/shared/profile_navigation'

.simple_form
.simple_form.form-section
%h3= t('verification.website_verification')

%p.lead= t('verification.hint_html')

%h4= t('verification.here_is_how')
Expand All @@ -28,3 +30,33 @@
%span.verified-badge
= material_symbol 'check', class: 'verified-badge__mark'
%span= field.value

= simple_form_for @account, url: settings_verification_path, html: { method: :put, class: 'form-section' } do |f|
= render 'shared/error_messages', object: @account

%h3= t('author_attribution.title')

%p.lead= t('author_attribution.hint_html')

.fields-row
.fields-row__column.fields-row__column-6
.fields-group
= f.input :attribution_domains_as_text, as: :text, wrapper: :with_block_label, input_html: { placeholder: "example1.com\nexample2.com\nexample3.com", rows: 4 }
.fields-row__column.fields-row__column-6
.fields-group.fade-out-top
%div
.status-card.expanded.bottomless
.status-card__image
= image_tag frontend_asset_url('images/preview.png'), alt: '', class: 'status-card__image-image'
.status-card__content
%span.status-card__host
%span= t('author_attribution.s_blog', name: @account.username)
·
%time.time-ago{ datetime: 1.year.ago.to_date.iso8601 }
%strong.status-card__title= t('author_attribution.example_title')
.more-from-author
= logo_as_symbol(:icon)
= t('author_attribution.more_from_html', name: link_to(root_url, class: 'story__details__shared__author-link') { image_tag(@account.avatar.url, class: 'account__avatar', width: 16, height: 16, alt: '') + content_tag(:bdi, display_name(@account)) })

.actions
= f.button :button, t('generic.save_changes'), type: :submit
6 changes: 6 additions & 0 deletions config/locales/activerecord.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ en:
user/invite_request:
text: Reason
errors:
attributes:
domain:
invalid: is not a valid domain name
messages:
invalid_domain_on_line: "%{value} is not a valid domain name"
too_many_lines: is over the limit of %{limit} lines
models:
account:
attributes:
Expand Down
2 changes: 0 additions & 2 deletions config/locales/an.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1017,8 +1017,6 @@ an:
your_appeal_approved: S'aprebó la tuya apelación
your_appeal_pending: Has ninviau una apelación
your_appeal_rejected: La tuya apelación ha estau refusada
domain_validator:
invalid_domain: no ye un nombre de dominio valido
errors:
'400': La solicitut que has ninviau no ye valida u yera malformada.
'403': No tiens permiso pa acceder ta esta pachina.
Expand Down
2 changes: 0 additions & 2 deletions config/locales/ar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1239,8 +1239,6 @@ ar:
your_appeal_approved: تمت الموافقة على طعنك
your_appeal_pending: لقد قمت بتقديم طعن
your_appeal_rejected: تم رفض طعنك
domain_validator:
invalid_domain: ليس بإسم نطاق صالح
edit_profile:
basic_information: معلومات أساسية
hint_html: "<strong>قم بتخصيص ما سيراه الناس في ملفك الشخصي العام وبجوار منشوراتك.</strong> من المرجح أن يتابعك أشخاص آخرون ويتفاعلون معك إن كان لديك صفحة شخصية مملوء وصورة."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/be.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1256,8 +1256,6 @@ be:
your_appeal_approved: Ваша абскарджанне было ўхвалена
your_appeal_pending: Вы адправілі апеляцыю
your_appeal_rejected: Ваша абскарджанне было адхілена
domain_validator:
invalid_domain: не з'яўляецца сапраўдным даменным імем
edit_profile:
basic_information: Асноўная інфармацыя
hint_html: "<strong>Наладзьце тое, што людзі будуць бачыць у вашым профілі і побач з вашымі паведамленнямі.</strong> Іншыя людзі з большай верагоднасцю будуць сачыць і ўзаемадзейнічаць з вамі, калі ў вас ёсць запоўнены профіль і фота профілю."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/bg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1179,8 +1179,6 @@ bg:
your_appeal_approved: Вашето обжалване е одобрено
your_appeal_pending: Подадохте обжалване
your_appeal_rejected: Вашето обжалване е отхвърлено
domain_validator:
invalid_domain: не е валидно име на домейн
edit_profile:
basic_information: Основна информация
hint_html: "<strong>Персонализирайте какво хората виждат в обществения ви профил и до публикациите ви.</strong> Другите хора са по-склонни да ви последват и да взаимодействат с вас, когато имате попълнен профил и снимка на профила."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/ca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1216,8 +1216,6 @@ ca:
your_appeal_approved: La teva apel·lació s'ha aprovat
your_appeal_pending: Has enviat una apel·lació
your_appeal_rejected: La teva apel·lació ha estat rebutjada
domain_validator:
invalid_domain: no es un nom de domini vàlid
edit_profile:
basic_information: Informació bàsica
hint_html: "<strong>Personalitza el que la gent veu en el teu perfil públic i a prop dels teus tuts..</strong> És més probable que altres persones et segueixin i interaccionin amb tu quan tens emplenat el teu perfil i amb la teva imatge."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/ckb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,6 @@ ckb:
strikes:
title_actions:
none: ئاگاداری
domain_validator:
invalid_domain: ناوی دۆمەین بڕوادار نییە
errors:
'400': داواکاریەکەی کە پێشکەشت کردووە نادروستە یان نەیپێکا.
'403': تۆ مۆڵەتت نیە بۆ بینینی ئەم لاپەڕەیە.
Expand Down
2 changes: 0 additions & 2 deletions config/locales/co.yml
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,6 @@ co:
more_details_html: Per più di ditagli, videte a <a href="%{terms_path}">pulitica di vita privata</a>.
username_available: U vostru cugnome riduvinterà dispunibule
username_unavailable: U vostru cugnome ùn sarà sempre micca dispunibule
domain_validator:
invalid_domain: ùn hè micca un nome di duminiu currettu
errors:
'400': A richiesta mandata ùn era micca valida o curretta.
'403': Ùn site micca auturizatu·a à vede sta pagina.
Expand Down
2 changes: 0 additions & 2 deletions config/locales/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1213,8 +1213,6 @@ cs:
your_appeal_approved: Vaše odvolání bylo schváleno
your_appeal_pending: Podali jste odvolání
your_appeal_rejected: Vaše odvolání bylo zamítnuto
domain_validator:
invalid_domain: není platné doménové jméno
edit_profile:
basic_information: Základní informace
hint_html: "<strong>Nastavte si, co lidé uvidí na vašem veřejném profilu a vedle vašich příspěvků.</strong> Ostatní lidé vás budou spíše sledovat a komunikovat s vámi, když budete mít vyplněný profil a profilový obrázek."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/cy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1306,8 +1306,6 @@ cy:
your_appeal_approved: Mae eich apêl wedi'i chymeradwyo
your_appeal_pending: Rydych wedi cyflwyno apêl
your_appeal_rejected: Mae eich apêl wedi'i gwrthod
domain_validator:
invalid_domain: ddim yn enw parth dilys
edit_profile:
basic_information: Gwybodaeth Sylfaenol
hint_html: "<strong>Addaswch yr hyn y mae pobl yn ei weld ar eich proffil cyhoeddus ac wrth ymyl eich postiadau.</strong> Mae pobl eraill yn fwy tebygol o'ch dilyn yn ôl a rhyngweithio â chi pan fydd gennych broffil wedi'i lenwi a llun proffil."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/da.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1235,8 +1235,6 @@ da:
your_appeal_approved: Din appel er godkendt
your_appeal_pending: Du har indgivet en appel
your_appeal_rejected: Din appel er afvist
domain_validator:
invalid_domain: er ikke et gyldigt domænenavn
edit_profile:
basic_information: Oplysninger
hint_html: "<strong>Tilpas hvad folk ser på din offentlige profil og ved siden af dine indlæg.</strong> Andre personer vil mere sandsynligt følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1234,8 +1234,6 @@ de:
your_appeal_approved: Dein Einspruch wurde angenommen
your_appeal_pending: Du hast Einspruch erhoben
your_appeal_rejected: Dein Einspruch wurde abgelehnt
domain_validator:
invalid_domain: ist keine gültige Domain
edit_profile:
basic_information: Allgemeine Informationen
hint_html: "<strong>Bestimme, was andere auf deinem öffentlichen Profil und neben deinen Beiträgen sehen können.</strong> Wenn du ein Profilbild festlegst und dein Profil vervollständigst, werden andere eher mit dir interagieren und dir folgen."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/el.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1192,8 +1192,6 @@ el:
your_appeal_approved: Η έφεση σου έχει εγκριθεί
your_appeal_pending: Υπέβαλλες έφεση
your_appeal_rejected: Η έφεση σου απορρίφθηκε
domain_validator:
invalid_domain: δεν είναι έγκυρο όνομα τομέα
edit_profile:
basic_information: Βασικές πληροφορίες
hint_html: "<strong>Τροποποίησε τί βλέπουν άτομα στο δημόσιο προφίλ σου και δίπλα στις αναρτήσεις σου.</strong> Είναι πιο πιθανό κάποιος να σε ακολουθήσει πίσω και να αλληλεπιδράσουν μαζί σου αν έχεις ολοκληρωμένο προφίλ και εικόνα προφίλ."
Expand Down
2 changes: 0 additions & 2 deletions config/locales/en-GB.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1202,8 +1202,6 @@ en-GB:
your_appeal_approved: Your appeal has been approved
your_appeal_pending: You have submitted an appeal
your_appeal_rejected: Your appeal has been rejected
domain_validator:
invalid_domain: is not a valid domain name
edit_profile:
basic_information: Basic information
hint_html: "<strong>Customise what people see on your public profile and next to your posts.</strong> Other people are more likely to follow you back and interact with you when you have a filled out profile and a profile picture."
Expand Down
Loading

0 comments on commit e0c27a5

Please sign in to comment.