Merge branch 'main' into feature/require-mfa-by-admin

This commit is contained in:
FredysFonseca 2025-07-28 16:02:11 -04:00 committed by GitHub
commit bdf102df4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 530 additions and 222 deletions

View File

@ -6,10 +6,6 @@
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/json_ld_helper.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 82

View File

@ -468,7 +468,7 @@ GEM
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-cas (3.0.1)
omniauth-cas (3.0.2)
addressable (~> 2.8)
nokogiri (~> 1.12)
omniauth (~> 2.1)
@ -860,7 +860,7 @@ GEM
stoplight (4.1.1)
redlock (~> 1.0)
stringio (3.1.7)
strong_migrations (2.4.0)
strong_migrations (2.5.0)
activerecord (>= 7.1)
swd (2.0.3)
activesupport (>= 3)

View File

@ -22,7 +22,7 @@ class Settings::Migration::RedirectsController < Settings::BaseController
end
def destroy
if current_account.moved_to_account_id.present?
if current_account.moved?
current_account.update!(moved_to_account: nil)
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
end

View File

@ -65,12 +65,12 @@ module FormattingHelper
end
def rss_content_preroll(status)
if status.spoiler_text?
safe_join [
tag.p { spoiler_with_warning(status) },
tag.hr,
]
end
return unless status.spoiler_text?
safe_join [
tag.p { spoiler_with_warning(status) },
tag.hr,
]
end
def spoiler_with_warning(status)
@ -81,10 +81,10 @@ module FormattingHelper
end
def rss_content_postroll(status)
if status.preloadable_poll
tag.p do
poll_option_tags(status)
end
return unless status.preloadable_poll
tag.p do
poll_option_tags(status)
end
end

View File

@ -134,7 +134,7 @@ module JsonLdHelper
patch_for_forwarding!(value, compacted_value)
elsif value.is_a?(Array)
compacted_value = [compacted_value] unless compacted_value.is_a?(Array)
return if value.size != compacted_value.size
return nil if value.size != compacted_value.size
compacted[key] = value.zip(compacted_value).map do |v, vc|
if v.is_a?(Hash) && vc.is_a?(Hash)

View File

@ -24,24 +24,24 @@ module ThemeHelper
end
def custom_stylesheet
if active_custom_stylesheet.present?
stylesheet_link_tag(
custom_css_path(active_custom_stylesheet),
host: root_url,
media: :all,
skip_pipeline: true
)
end
return if active_custom_stylesheet.blank?
stylesheet_link_tag(
custom_css_path(active_custom_stylesheet),
host: root_url,
media: :all,
skip_pipeline: true
)
end
private
def active_custom_stylesheet
if cached_custom_css_digest.present?
[:custom, cached_custom_css_digest.to_s.first(8)]
.compact_blank
.join('-')
end
return if cached_custom_css_digest.blank?
[:custom, cached_custom_css_digest.to_s.first(8)]
.compact_blank
.join('-')
end
def cached_custom_css_digest

View File

@ -48,13 +48,13 @@ class TranslateButton extends PureComponent {
return (
<div className='translate-button'>
<div className='translate-button__meta'>
<FormattedMessage id='status.translated_from_with' defaultMessage='Translated from {lang} using {provider}' values={{ lang: languageName, provider }} />
</div>
<button className='link-button' onClick={onClick}>
<FormattedMessage id='status.show_original' defaultMessage='Show original' />
</button>
<div className='translate-button__meta'>
<FormattedMessage id='status.translated_from_with' defaultMessage='Translated from {lang} using {provider}' values={{ lang: languageName, provider }} />
</div>
</div>
);
}

View File

@ -110,7 +110,7 @@
"announcement.announcement": "إعلان",
"annual_report.summary.archetype.booster": "The cool-hunter",
"annual_report.summary.archetype.lurker": "المتصفح الصامت",
"annual_report.summary.archetype.oracle": "حكيم",
"annual_report.summary.archetype.oracle": "الحكيم",
"annual_report.summary.archetype.pollster": "مستطلع للرأي",
"annual_report.summary.archetype.replier": "الفراشة الاجتماعية",
"annual_report.summary.followers.followers": "المُتابِعُون",
@ -845,6 +845,7 @@
"status.bookmark": "أضفه إلى الفواصل المرجعية",
"status.cancel_reblog_private": "إلغاء إعادة النشر",
"status.cannot_reblog": "لا يمكن إعادة نشر هذا المنشور",
"status.context.load_new_replies": "الردود الجديدة المتاحة",
"status.continued_thread": "تكملة للخيط",
"status.copy": "انسخ رابط الرسالة",
"status.delete": "احذف",

View File

@ -324,7 +324,7 @@
"empty_column.follow_requests": "Du har endnu ingen følgeanmodninger. Når du modtager én, vil den dukke op her.",
"empty_column.followed_tags": "Ingen hashtags følges endnu. Når det sker, vil de fremgå her.",
"empty_column.hashtag": "Der er intet med dette hashtag endnu.",
"empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at fylde den op.",
"empty_column.home": "Din hjem-tidslinje er tom! Følg nogle personer, for at fylde den op.",
"empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af denne liste udgiver nye indlæg, vil de blive vist her.",
"empty_column.mutes": "Du har endnu ikke skjult nogle brugere.",
"empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jævnfør dine indstillinger.",
@ -476,7 +476,7 @@
"keyboard_shortcuts.favourites": "Åbn favoritlisten",
"keyboard_shortcuts.federated": "Åbn fødereret tidslinje",
"keyboard_shortcuts.heading": "Tastaturgenveje",
"keyboard_shortcuts.home": "Åbn hjemmetidslinje",
"keyboard_shortcuts.home": "Åbn hjem-tidslinje",
"keyboard_shortcuts.hotkey": "Hurtigtast",
"keyboard_shortcuts.legend": "Vis dette symbol",
"keyboard_shortcuts.local": "Åbn lokal tidslinje",
@ -518,7 +518,7 @@
"lists.done": "Færdig",
"lists.edit": "Redigér liste",
"lists.exclusive": "Skjul medlemmer i Hjem",
"lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.",
"lists.exclusive_hint": "Hvis nogen er på denne liste, så skjul dem i hjem-feed for at undgå at se deres indlæg to gange.",
"lists.find_users_to_add": "Find brugere at tilføje",
"lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}",
"lists.list_name": "Listetitel",
@ -792,7 +792,7 @@
"report.thanks.title": "Ønsker ikke at se dette?",
"report.thanks.title_actionable": "Tak for anmeldelsen, der vil blive set nærmere på dette.",
"report.unfollow": "Følg ikke længere @{name}",
"report.unfollow_explanation": "Du følger denne konto. For ikke længere at se vedkommendes indlæg i din hjemmestrøm, kan du stoppe med at følge dem.",
"report.unfollow_explanation": "Du følger denne konto. Hvis du ikke længere vil se vedkommendes indlæg i dit hjem-feed, så stop med at følge dem.",
"report_notification.attached_statuses": "{count, plural, one {{count} indlæg} other {{count} indlæg}} vedhæftet",
"report_notification.categories.legal": "Juridisk",
"report_notification.categories.legal_sentence": "ikke-tilladt indhold",

View File

@ -43,7 +43,7 @@
"account.followers": "Follower",
"account.followers.empty": "Diesem Profil folgt noch niemand.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}",
"account.followers_you_know_counter": "{counter} Follower kennst Du",
"account.followers_you_know_counter": "{counter} bekannt",
"account.following": "Folge ich",
"account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}",
"account.follows.empty": "Dieses Profil folgt noch niemandem.",

View File

@ -1,7 +1,7 @@
{
"about.blocks": "Moderated servers",
"about.contact": "Contact:",
"about.default_locale": "Default",
"about.default_locale": "Varsayılan",
"about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Reason not available",
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.",
@ -9,11 +9,11 @@
"about.domain_blocks.silenced.title": "Limited",
"about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.",
"about.domain_blocks.suspended.title": "Suspended",
"about.language_label": "Language",
"about.language_label": "Dil",
"about.not_available": "This information has not been made available on this server.",
"about.powered_by": "Decentralised social media powered by {mastodon}",
"about.rules": "Server rules",
"account.account_note_header": "Personal note",
"account.account_note_header": "Kişisel not",
"account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.bot": "Automated",
"account.badges.group": "Group",
@ -845,6 +845,8 @@
"status.bookmark": "Bookmark",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"status.context.load_new_replies": "Yeni yanıtlar geldi.",
"status.context.loading": "Daha fazla yanıt kontrol ediliyor",
"status.continued_thread": "Continued thread",
"status.copy": "Copy link to status",
"status.delete": "Delete",

View File

@ -292,6 +292,7 @@
"emoji_button.search_results": "Paieškos rezultatai",
"emoji_button.symbols": "Simboliai",
"emoji_button.travel": "Kelionės ir vietos",
"empty_column.account_featured_other.unknown": "Ši paskyra dar nieko neparodė.",
"empty_column.account_hides_collections": "Šis (-i) naudotojas (-a) pasirinko nepadaryti šią informaciją prieinamą.",
"empty_column.account_suspended": "Paskyra pristabdyta.",
"empty_column.account_timeline": "Nėra čia įrašų.",
@ -794,6 +795,8 @@
"status.bookmark": "Pridėti į žymės",
"status.cancel_reblog_private": "Nebepasidalinti",
"status.cannot_reblog": "Šis įrašas negali būti pakeltas.",
"status.context.load_new_replies": "Yra naujų atsakymų",
"status.context.loading": "Tikrinama dėl daugiau atsakymų",
"status.continued_thread": "Tęsiama gijoje",
"status.copy": "Kopijuoti nuorodą į įrašą",
"status.delete": "Ištrinti",

View File

@ -845,6 +845,8 @@
"status.bookmark": "冊籤",
"status.cancel_reblog_private": "取消轉送",
"status.cannot_reblog": "Tsit篇PO文bē當轉送",
"status.context.load_new_replies": "有新ê回應",
"status.context.loading": "Leh檢查其他ê回應",
"status.continued_thread": "接續ê討論線",
"status.copy": "Khóo-pih PO文ê連結",
"status.delete": "Thâi掉",

View File

@ -27,6 +27,8 @@
"account.edit_profile": "Upraviť profil",
"account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}",
"account.endorse": "Zobraziť na vlastnom profile",
"account.familiar_followers_one": "Nasledovanie od {name1}",
"account.familiar_followers_two": "Nasledovanie od {name1} a {name2}",
"account.featured": "Zviditeľnené",
"account.featured.accounts": "Profily",
"account.featured.hashtags": "Hashtagy",

View File

@ -845,6 +845,8 @@
"status.bookmark": "Yer işareti ekle",
"status.cancel_reblog_private": "Yeniden paylaşımı geri al",
"status.cannot_reblog": "Bu gönderi yeniden paylaşılamaz",
"status.context.load_new_replies": "Yeni yanıtlar mevcut",
"status.context.loading": "Daha fazla yanıt için kontrol ediliyor",
"status.continued_thread": "Devam eden akış",
"status.copy": "Gönderi bağlantısını kopyala",
"status.delete": "Sil",

View File

@ -301,6 +301,9 @@
"emoji_button.search_results": "搜索结果",
"emoji_button.symbols": "符号",
"emoji_button.travel": "旅行与地点",
"empty_column.account_featured.me": "你尚未设置任何精选。你知道吗?你也可以将自己最常使用的话题标签,甚至是好友的帐号,在你的个人主页上设为精选。",
"empty_column.account_featured.other": "{acct} 尚未设置任何精选。你知道吗?你也可以将自己最常使用的话题标签,甚至是好友的帐号,在你的个人主页上设为精选。",
"empty_column.account_featured_other.unknown": "该用户尚未设置任何精选。",
"empty_column.account_hides_collections": "该用户选择不公开此信息",
"empty_column.account_suspended": "账号已被停用",
"empty_column.account_timeline": "这里没有嘟文!",
@ -402,8 +405,10 @@
"hashtag.counter_by_accounts": "{count, plural,other {{counter} 人讨论}}",
"hashtag.counter_by_uses": "{count, plural, other {{counter} 条嘟文}}",
"hashtag.counter_by_uses_today": "今日 {count, plural, other {{counter} 条嘟文}}",
"hashtag.feature": "设为精选",
"hashtag.follow": "关注话题",
"hashtag.mute": "停止提醒 #{hashtag}",
"hashtag.unfeature": "取消精选",
"hashtag.unfollow": "取消关注话题",
"hashtags.and_other": "… 和另外 {count, plural, other {# 个话题}}",
"hints.profiles.followers_may_be_missing": "该账号的关注者列表可能没有完全显示。",
@ -558,6 +563,7 @@
"navigation_bar.preferences": "偏好设置",
"navigation_bar.privacy_and_reach": "隐私与可达性",
"navigation_bar.search": "搜索",
"navigation_bar.search_trends": "搜索/热门趋势",
"navigation_panel.collapse_lists": "收起菜单列表",
"navigation_panel.expand_lists": "展开菜单列表",
"not_signed_in_indicator.not_signed_in": "你需要登录才能访问此资源。",
@ -786,6 +792,7 @@
"report_notification.categories.violation": "违反规则",
"report_notification.categories.violation_sentence": "违反规则",
"report_notification.open": "打开举报",
"search.clear": "清空搜索内容",
"search.no_recent_searches": "无最近搜索",
"search.placeholder": "搜索",
"search.quick_action.account_search": "包含 {x} 的账号",
@ -827,6 +834,8 @@
"status.bookmark": "添加到书签",
"status.cancel_reblog_private": "取消转嘟",
"status.cannot_reblog": "不能转嘟这条嘟文",
"status.context.load_new_replies": "有新回复",
"status.context.loading": "正在检查更多回复",
"status.continued_thread": "上接嘟文串",
"status.copy": "复制嘟文链接",
"status.delete": "删除",
@ -854,8 +863,10 @@
"status.pin": "在个人资料页面置顶",
"status.quote_error.filtered": "已根据你的筛选器过滤",
"status.quote_error.not_found": "无法显示这篇贴文。",
"status.quote_error.pending_approval": "此嘟文正在等待原作者批准。",
"status.quote_error.rejected": "由于原作者不允许引用转发,无法显示这篇贴文。",
"status.quote_error.removed": "该帖子已被作者删除。",
"status.quote_error.unauthorized": "你无权查看此嘟文,因此无法显示。",
"status.quote_post_author": "{name} 的嘟文",
"status.read_more": "查看更多",
"status.reblog": "转嘟",

View File

@ -65,4 +65,16 @@ class Admin::Metrics::Dimension::BaseDimension
def canonicalized_params
params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
end
def earliest_status_id
snowflake_id(@start_at.beginning_of_day)
end
def latest_status_id
snowflake_id(@end_at.end_of_day)
end
def snowflake_id(datetime)
Mastodon::Snowflake.id_at(datetime, with_random: false)
end
end

View File

@ -19,7 +19,7 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di
end
def sql_array
[sql_query_string, { domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
[sql_query_string, { domain: params[:domain], earliest_status_id:, latest_status_id:, limit: @limit }]
end
def sql_query_string
@ -36,14 +36,6 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di
SQL
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
def params
@params.permit(:domain)
end

View File

@ -14,7 +14,7 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B
end
def sql_array
[sql_query_string, { earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
[sql_query_string, { earliest_status_id:, latest_status_id:, limit: @limit }]
end
def sql_query_string
@ -28,12 +28,4 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B
LIMIT :limit
SQL
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
end

View File

@ -19,7 +19,7 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
end
def sql_array
[sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
[sql_query_string, { tag_id: tag_id, earliest_status_id:, latest_status_id:, limit: @limit }]
end
def sql_query_string
@ -39,14 +39,6 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
params[:id]
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
def params
@params.permit(:id)
end

View File

@ -18,7 +18,7 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
end
def sql_array
[sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
[sql_query_string, { tag_id: tag_id, earliest_status_id:, latest_status_id:, limit: @limit }]
end
def sql_query_string
@ -39,14 +39,6 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
params[:id]
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
def params
@params.permit(:id)
end

View File

@ -104,4 +104,16 @@ class Admin::Metrics::Measure::BaseMeasure
def canonicalized_params
params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';')
end
def earliest_status_id
snowflake_id(@start_at.beginning_of_day)
end
def latest_status_id
snowflake_id(@end_at.end_of_day)
end
def snowflake_id(datetime)
Mastodon::Snowflake.id_at(datetime, with_random: false)
end
end

View File

@ -28,7 +28,7 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id:, latest_status_id: }]
end
def sql_query_string
@ -50,14 +50,6 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure
SQL
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
def params
@params.permit(:domain, :include_subdomains)
end

View File

@ -22,7 +22,7 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
[sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id:, latest_status_id: }]
end
def sql_query_string
@ -45,14 +45,6 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
SQL
end
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end
def latest_status_id
Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end
def tag
@tag ||= Tag.find(params[:id])
end

View File

@ -84,22 +84,18 @@ class Webfinger
def body_from_host_meta
host_meta_request.perform do |res|
if res.code == 200
body_from_webfinger(url_from_template(res.body_with_limit), use_fallback: false)
else
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
end
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}" unless res.code == 200
body_from_webfinger(url_from_template(res.body_with_limit), use_fallback: false)
end
end
def url_from_template(str)
link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]')
if link.present?
link['template'].gsub('{uri}', @uri)
else
raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger"
end
raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger" if link.blank?
link['template'].gsub('{uri}', @uri)
rescue Nokogiri::XML::XPath::SyntaxError
raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}"
end

View File

@ -54,11 +54,9 @@ class WebfingerResource
end
def username_from_acct
if domain_matches_local?
local_username
else
raise ActiveRecord::RecordNotFound
end
raise ActiveRecord::RecordNotFound unless domain_matches_local?
local_username
end
def split_acct

View File

@ -74,7 +74,7 @@ class AccountMigration < ApplicationRecord
errors.add(:acct, I18n.t('migrations.errors.not_found'))
else
errors.add(:acct, I18n.t('migrations.errors.missing_also_known_as')) unless target_account.also_known_as.include?(ActivityPub::TagManager.instance.uri_for(account))
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
end
end

View File

@ -14,7 +14,7 @@
#
class AnnouncementReaction < ApplicationRecord
before_validation :set_custom_emoji
before_validation :set_custom_emoji, if: :name?
after_commit :queue_publish
belongs_to :account
@ -27,7 +27,7 @@ class AnnouncementReaction < ApplicationRecord
private
def set_custom_emoji
self.custom_emoji = CustomEmoji.local.enabled.find_by(shortcode: name) if name.present?
self.custom_emoji = CustomEmoji.local.enabled.find_by(shortcode: name)
end
def queue_publish

View File

@ -3,12 +3,8 @@
module RateLimitable
extend ActiveSupport::Concern
def rate_limit=(value)
@rate_limit = value
end
def rate_limit?
@rate_limit
included do
attribute :rate_limit, :boolean, default: false
end
def rate_limiter(by, options = {})

View File

@ -37,7 +37,7 @@ class FollowRequest < ApplicationRecord
if account.local?
ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id)
MergeWorker.perform_async(target_account.id, account.id, 'home')
MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
MergeWorker.push_bulk(account.owned_lists.with_list_account(target_account).pluck(:id)) do |list_id|
[target_account.id, list_id, 'list']
end
end

View File

@ -40,7 +40,7 @@ class Form::Redirect
if target_account.nil?
errors.add(:acct, I18n.t('migrations.errors.not_found'))
else
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
end
end

View File

@ -32,6 +32,8 @@ class List < ApplicationRecord
before_destroy :clean_feed_manager
scope :with_list_account, ->(account) { joins(:list_accounts).where(list_accounts: { account: }) }
private
def validate_account_lists_limit

View File

@ -170,10 +170,9 @@ class PreviewCard < ApplicationRecord
private
def serialized_authors
if author_name? || author_url? || author_account_id?
PreviewCard::Author
.new(self)
end
return unless author_name? || author_url? || author_account_id?
PreviewCard::Author.new(self)
end
def extract_dimensions

View File

@ -164,9 +164,10 @@ class Tag < ApplicationRecord
end
def validate_display_name_change
unless HashtagNormalizer.new.normalize(display_name).casecmp(name).zero?
errors.add(:display_name,
I18n.t('tags.does_not_match_previous_name'))
end
errors.add(:display_name, I18n.t('tags.does_not_match_previous_name')) unless display_name_matches_name?
end
def display_name_matches_name?
HashtagNormalizer.new.normalize(display_name).casecmp(name).zero?
end
end

View File

@ -466,16 +466,17 @@ class User < ApplicationRecord
yield
if new_user
# Avoid extremely unlikely race condition when approving and confirming
# the user at the same time
reload unless approved?
after_confirmation_tasks if new_user
end
if approved?
prepare_new_user!
else
notify_staff_about_pending_account!
end
def after_confirmation_tasks
# Handle condition when approving and confirming a user at the same time
reload unless approved?
if approved?
prepare_new_user!
else
notify_staff_about_pending_account!
end
end
@ -539,10 +540,10 @@ class User < ApplicationRecord
def regenerate_feed!
home_feed = HomeFeed.new(account)
unless home_feed.regenerating?
home_feed.regeneration_in_progress!
RegenerationWorker.perform_async(account_id)
end
return if home_feed.regenerating?
home_feed.regeneration_in_progress!
RegenerationWorker.perform_async(account_id)
end
def needs_feed_update?

View File

@ -19,17 +19,22 @@ class WorkerBatch
redis.hset(key, { 'async_refresh_key' => async_refresh_key, 'threshold' => threshold })
end
def within
raise NoBlockGivenError unless block_given?
begin
Thread.current[:batch] = self
yield
ensure
Thread.current[:batch] = nil
end
end
# Add jobs to the batch. Usually when the batch is created.
# @param [Array<String>] jids
def add_jobs(jids)
if jids.blank?
async_refresh_key = redis.hget(key, 'async_refresh_key')
if async_refresh_key.present?
async_refresh = AsyncRefresh.new(async_refresh_key)
async_refresh.finish!
end
finish!
return
end
@ -55,8 +60,23 @@ class WorkerBatch
if async_refresh_key.present?
async_refresh = AsyncRefresh.new(async_refresh_key)
async_refresh.increment_result_count(by: 1)
async_refresh.finish! if pending.zero? || processed >= threshold.to_f * (processed + pending)
end
if pending.zero? || processed >= (threshold || 1.0).to_f * (processed + pending)
async_refresh&.finish!
cleanup
end
end
def finish!
async_refresh_key = redis.hget(key, 'async_refresh_key')
if async_refresh_key.present?
async_refresh = AsyncRefresh.new(async_refresh_key)
async_refresh.finish!
end
cleanup
end
# Get pending jobs.
@ -76,4 +96,8 @@ class WorkerBatch
def key(suffix = nil)
"worker_batch:#{@id}#{":#{suffix}" if suffix}"
end
def cleanup
redis.del(key, key('jobs'))
end
end

View File

@ -17,7 +17,12 @@ class ActivityPub::FetchRepliesService < BaseService
batch = WorkerBatch.new
batch.connect(async_refresh_key) if async_refresh_key.present?
batch.add_jobs(FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }] })
batch.finish! if @items.empty?
batch.within do
FetchReplyWorker.push_bulk(@items) do |reply_uri|
[reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }]
end
end
[@items, n_pages]
end

View File

@ -82,7 +82,7 @@ class FollowService < BaseService
LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow')
MergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
MergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:id)) do |list_id|
MergeWorker.push_bulk(@source_account.owned_lists.with_list_account(@target_account).pluck(:id)) do |list_id|
[@target_account.id, list_id, 'list']
end

View File

@ -34,7 +34,7 @@ class UnfollowService < BaseService
unless @options[:skip_unmerge]
UnmergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
UnmergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:list_id)) do |list_id|
UnmergeWorker.push_bulk(@source_account.owned_lists.with_list_account(@target_account).pluck(:list_id)) do |list_id|
[@target_account.id, list_id, 'list']
end
end

View File

@ -9,7 +9,7 @@ class UnmuteService < BaseService
if account.following?(target_account)
MergeWorker.perform_async(target_account.id, account.id, 'home')
MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
MergeWorker.push_bulk(account.owned_lists.with_list_account(target_account).pluck(:id)) do |list_id|
[target_account.id, list_id, 'list']
end
end

View File

@ -30,9 +30,9 @@
= t('admin.accounts.suspended')
- elsif account.silenced?
= t('admin.accounts.silenced')
- elsif account.local? && account.user&.disabled?
- elsif account.local? && account.user_disabled?
= t('admin.accounts.disabled')
- elsif account.local? && !account.user&.confirmed?
- elsif account.local? && !account.user_confirmed?
= t('admin.accounts.confirming')
- elsif account.local? && !account.user_approved?
= t('admin.accounts.pending')

View File

@ -34,7 +34,7 @@
%tr
%th= t('admin.accounts.email_status')
%td
- if account.user&.confirmed?
- if account.user_confirmed?
= t('admin.accounts.confirmed')
- else
= t('admin.accounts.confirming')

View File

@ -42,7 +42,7 @@
%span.red= t('admin.accounts.suspended')
- elsif target_account.silenced?
%span.red= t('admin.accounts.silenced')
- elsif target_account.user&.disabled?
- elsif target_account.user_disabled?
%span.red= t('admin.accounts.disabled')
- else
%span.neutral= t('admin.accounts.no_limits_imposed')

View File

@ -81,7 +81,7 @@ class MoveWorker
def copy_account_notes!
AccountNote.where(target_account: @source_account).find_each do |note|
text = I18n.with_locale(note.account.user&.locale.presence || I18n.default_locale) do
text = I18n.with_locale(note.account.user_locale.presence || I18n.default_locale) do
I18n.t('move_handler.copy_account_note_text', acct: @source_account.acct)
end
@ -104,7 +104,7 @@ class MoveWorker
def carry_blocks_over!
@source_account.blocked_by_relationships.where(account: Account.local).find_each do |block|
unless block.account.blocking?(@target_account) || block.account.following?(@target_account)
unless skip_block_move?(block)
BlockService.new.call(block.account, @target_account)
add_account_note_if_needed!(block.account, 'move_handler.carry_blocks_over_text')
end
@ -115,7 +115,7 @@ class MoveWorker
def carry_mutes_over!
@source_account.muted_by_relationships.where(account: Account.local).find_each do |mute|
MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) unless mute.account.muting?(@target_account) || mute.account.following?(@target_account)
MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) unless skip_mute_move?(mute)
add_account_note_if_needed!(mute.account, 'move_handler.carry_mutes_over_text')
rescue => e
@deferred_error = e
@ -123,11 +123,19 @@ class MoveWorker
end
def add_account_note_if_needed!(account, id)
unless AccountNote.exists?(account: account, target_account: @target_account)
text = I18n.with_locale(account.user&.locale.presence || I18n.default_locale) do
I18n.t(id, acct: @source_account.acct)
end
AccountNote.create!(account: account, target_account: @target_account, comment: text)
return if AccountNote.exists?(account: account, target_account: @target_account)
text = I18n.with_locale(account.user_locale.presence || I18n.default_locale) do
I18n.t(id, acct: @source_account.acct)
end
AccountNote.create!(account: account, target_account: @target_account, comment: text)
end
def skip_mute_move?(mute)
mute.account.muting?(@target_account) || mute.account.following?(@target_account)
end
def skip_block_move?(block)
block.account.blocking?(@target_account) || block.account.following?(@target_account)
end
end

View File

@ -9,7 +9,7 @@ class PublishScheduledStatusWorker
scheduled_status = ScheduledStatus.find(scheduled_status_id)
scheduled_status.destroy!
return true if scheduled_status.account.user.disabled?
return true if scheduled_status.account.user_disabled?
PostStatusService.new.call(
scheduled_status.account,

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../../lib/mastodon/sidekiq_middleware'
require_relative '../../lib/mastodon/worker_batch_middleware'
Sidekiq.configure_server do |config|
config.redis = REDIS_CONFIGURATION.sidekiq
@ -72,14 +73,12 @@ Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Mastodon::SidekiqMiddleware
end
config.server_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Server
end
config.client_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Client
chain.add Mastodon::WorkerBatchMiddleware
end
config.on(:startup) do
@ -105,6 +104,7 @@ Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Client
chain.add Mastodon::WorkerBatchMiddleware
end
config.logger.level = Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s)

View File

@ -560,6 +560,8 @@ br:
one: "%{count} skeudenn"
other: "%{count} skeudenn"
two: "%{count} skeudenn"
errors:
quoted_status_not_found: War a seblant, n'eus ket eus an embannadenn emaoc'h o klask menegiñ.
pin_errors:
ownership: N'hallit ket spilhennañ embannadurioù ar re all
quote_policies:

View File

@ -1870,6 +1870,7 @@ ca:
edited_at_html: Editat %{date}
errors:
in_reply_not_found: El tut al qual intentes respondre sembla que no existeix.
quoted_status_not_found: Sembla que la publicació que vols citar no existeix.
over_character_limit: Límit de caràcters de %{max} superat
pin_errors:
direct: Els tuts que només són visibles per als usuaris mencionats no poden ser fixats

View File

@ -1958,6 +1958,7 @@ cs:
edited_at_html: Upraven %{date}
errors:
in_reply_not_found: Příspěvek, na který se pokoušíte odpovědět, neexistuje.
quoted_status_not_found: Zdá se, že příspěvek, který se pokoušíte citovat neexistuje.
over_character_limit: byl překročen limit %{max} znaků
pin_errors:
direct: Příspěvky viditelné pouze zmíněným uživatelům nelze připnout

View File

@ -2043,6 +2043,7 @@ cy:
edited_at_html: Wedi'i olygu %{date}
errors:
in_reply_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio ei ateb yn bodoli.
quoted_status_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio'i ddyfynnu yn bodoli.
over_character_limit: wedi mynd y tu hwnt i'r terfyn nodau o %{max}
pin_errors:
direct: Nid oes modd pinio postiadau sy'n weladwy i ddefnyddwyr a grybwyllwyd yn unig

View File

@ -1394,7 +1394,7 @@ da:
filters:
contexts:
account: Profiler
home: Hjemmetidslinje
home: Hjem og lister
notifications: Notifikationer
public: Offentlig tidslinje
thread: Samtaler
@ -1872,6 +1872,7 @@ da:
edited_at_html: Redigeret %{date}
errors:
in_reply_not_found: Indlægget, der forsøges besvaret, ser ikke ud til at eksistere.
quoted_status_not_found: Indlægget, du forsøger at citere, ser ikke ud til at eksistere.
over_character_limit: grænsen på %{max} tegn overskredet
pin_errors:
direct: Indlæg, som kun kan ses af omtalte brugere, kan ikke fastgøres

View File

@ -1872,6 +1872,7 @@ de:
edited_at_html: 'Bearbeitet: %{date}'
errors:
in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
quoted_status_not_found: Der Beitrag, den du zitieren möchtest, scheint nicht zu existieren.
over_character_limit: Begrenzung von %{max} Zeichen überschritten
pin_errors:
direct: Beiträge, die nur für erwähnte Profile sichtbar sind, können nicht angeheftet werden

View File

@ -1872,6 +1872,7 @@ el:
edited_at_html: Επεξεργάστηκε στις %{date}
errors:
in_reply_not_found: Η ανάρτηση στην οποία προσπαθείς να απαντήσεις δεν φαίνεται να υπάρχει.
quoted_status_not_found: Η ανάρτηση την οποία προσπαθείς να παραθέσεις δεν φαίνεται να υπάρχει.
over_character_limit: υπέρβαση μέγιστου ορίου %{max} χαρακτήρων
pin_errors:
direct: Αναρτήσεις που είναι ορατές μόνο στους αναφερόμενους χρήστες δεν μπορούν να καρφιτσωθούν

View File

@ -1872,6 +1872,7 @@ es-AR:
edited_at_html: Editado el %{date}
errors:
in_reply_not_found: El mensaje al que intentás responder no existe.
quoted_status_not_found: El mensaje al que intentás citar parece que no existe.
over_character_limit: se excedió el límite de %{max} caracteres
pin_errors:
direct: Los mensajes que sólo son visibles para los usuarios mencionados no pueden ser fijados

View File

@ -1872,6 +1872,7 @@ es-MX:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: La publicación a la que estás intentando responder no existe.
quoted_status_not_found: La publicación que intentas citar no parece existir.
over_character_limit: Límite de caracteres de %{max} superado
pin_errors:
direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse

View File

@ -1872,6 +1872,7 @@ es:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: La publicación a la que intentas responder no existe.
quoted_status_not_found: La publicación que estás intentando citar no existe.
over_character_limit: Límite de caracteres de %{max} superado
pin_errors:
direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse

View File

@ -1872,6 +1872,7 @@ fo:
edited_at_html: Rættað %{date}
errors:
in_reply_not_found: Posturin, sum tú roynir at svara, sýnist ikki at finnast.
quoted_status_not_found: Posturin, sum tú roynir at sitera, sýnist ikki at finnast.
over_character_limit: mesta tal av teknum, %{max}, rokkið
pin_errors:
direct: Postar, sum einans eru sjónligir hjá nevndum brúkarum, kunnu ikki festast

View File

@ -1872,6 +1872,7 @@ gl:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: A publicación á que tentas responder semella que non existe.
quoted_status_not_found: Parece que a publicación que intentas citar non existe.
over_character_limit: Excedeu o límite de caracteres %{max}
pin_errors:
direct: As publicacións que só son visibles para as usuarias mencionadas non se poden fixar

View File

@ -1957,7 +1957,8 @@ he:
two: 'מכיל את התגיות האסורות: %{tags}'
edited_at_html: נערך ב-%{date}
errors:
in_reply_not_found: נראה שההודעה שאת/ה מנסה להגיב לה לא קיימת.
in_reply_not_found: נראה שההודעה שנסית להגיב לה לא קיימת.
quoted_status_not_found: נראה שההודעה שנסית לצטט לא קיימת.
over_character_limit: חריגה מגבול התווים של %{max}
pin_errors:
direct: לא ניתן לקבע הודעות שנראותן מוגבלת למכותבים בלבד

View File

@ -1872,6 +1872,7 @@ hu:
edited_at_html: 'Szerkesztve: %{date}'
errors:
in_reply_not_found: Már nem létezik az a bejegyzés, melyre válaszolni szeretnél.
quoted_status_not_found: Már nem létezik az a bejegyzés, amelyből idézni szeretnél.
over_character_limit: túllépted a maximális %{max} karakteres keretet
pin_errors:
direct: A csak a megemlített felhasználók számára látható bejegyzések nem tűzhetők ki

View File

@ -1876,6 +1876,7 @@ is:
edited_at_html: Breytt %{date}
errors:
in_reply_not_found: Færslan sem þú ert að reyna að svara að er líklega ekki til.
quoted_status_not_found: Færslan sem þú ert að reyna að vitna í virðist ekki vera til.
over_character_limit: hámarksfjölda stafa (%{max}) náð
pin_errors:
direct: Ekki er hægt að festa færslur sem einungis eru sýnilegar þeim notendum sem minnst er á

View File

@ -912,6 +912,10 @@ lt:
your_appeal_rejected: Tavo apeliacija buvo atmesta
edit_profile:
hint_html: "<strong>Tinkink tai, ką žmonės mato tavo viešame profilyje ir šalia įrašų.</strong> Kiti žmonės labiau linkę sekti atgal ir bendrauti su tavimi, jei tavo profilis yra užpildytas ir turi profilio nuotrauką."
emoji_styles:
auto: Automatinis
native: Vietiniai
twemoji: Tvejaustukai
errors:
'403': Jūs neturie prieigos matyti šiam puslapiui.
'404': Puslapis nerastas.
@ -1183,6 +1187,8 @@ lt:
other: "%{count} vaizdų"
boosted_from_html: Pakelta iš %{acct_link}
content_warning: 'Turinio įspėjimas: %{warning}'
errors:
quoted_status_not_found: Įrašas, kurį bandote cituoti, atrodo, neegzistuoja.
over_character_limit: pasiektas %{max} simbolių limitas
pin_errors:
limit: Jūs jau prisegėte maksimalų toot'ų skaičų

View File

@ -97,7 +97,7 @@ nan:
silenced: 受限制
suspended: 權限中止ah
title: 管理
moderation_notes: 管理ê註釋
moderation_notes: 管理ê筆記
most_recent_activity: 最近ê活動時間
most_recent_ip: 最近ê IP
no_account_selected: 因為無揀任何口座所以lóng無改變
@ -245,7 +245,7 @@ nan:
create_announcement_html: "%{name} kā公告 %{target} 建立ah"
create_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue封鎖ah"
create_custom_emoji_html: "%{name} kā 新ê emoji %{target} 傳上去ah"
create_domain_allow_html: "%{name} 允准 %{target} 域名加入聯邦宇宙"
create_domain_allow_html: "%{name} 允准 %{target} 域名加入聯邦"
create_domain_block_html: "%{name} 封鎖域名 %{target}"
create_email_domain_block_html: "%{name} kā 電子phue域名 %{target} 封鎖ah"
create_ip_block_html: "%{name} 建立 IP %{target} ê規則"
@ -256,7 +256,7 @@ nan:
destroy_announcement_html: "%{name} kā公告 %{target} thâi掉ah"
destroy_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue取消封鎖ah"
destroy_custom_emoji_html: "%{name} kā 新ê emoji %{target} thâi掉ah"
destroy_domain_allow_html: "%{name} 無允准 %{target} 域名加入聯邦宇宙"
destroy_domain_allow_html: "%{name} 無允准 %{target} 域名加入聯邦"
destroy_domain_block_html: "%{name} 取消封鎖域名 %{target}"
destroy_email_domain_block_html: "%{name} kā 電子phue域名 %{target} 取消封鎖ah"
destroy_instance_html: "%{name} 清除域名 %{target}"
@ -531,16 +531,111 @@ nan:
content_policies:
comment: 內部ê筆記
description_html: Lí ē當定義用tī所有tuì tsit ê域名kap伊ê子域名來ê口座ê內容政策。
limited_federation_mode_description_html: Lí通選擇kám beh允准tsit ê域名加入聯邦。
policies:
reject_media: 拒絕媒體
reject_reports: 拒絕檢舉
silence: 限制
suspend: 中止權限
policy: 政策
reason: 公開ê理由
title: 內容政策
dashboard:
instance_accounts_dimension: 上tsē lâng跟tuè ê口座
instance_accounts_measure: 儲存ê口座
instance_followers_measure: lán tī hia ê跟tuè者
instance_follows_measure: in tī tsia ê跟tuè者
instance_languages_dimension: Tsia̍p用ê語言
instance_media_attachments_measure: 儲存ê媒體附件
instance_reports_measure: 關係in ê檢舉
instance_statuses_measure: 儲存ê PO文
delivery:
all: 全部
clear: 清寄送ê錯誤
failing: 失敗
restart: 重頭啟動寄送
stop: 停止寄送
unavailable: Bē當用
delivery_available: 通寄送
delivery_error_days: 寄送錯誤ê日數
delivery_error_hint: Nā連續 %{count} kang bē當寄送就ē自動標做bē當寄送。
destroyed_msg: Tuì %{domain} 來ê資料teh排隊beh thâi掉。
empty: Tshuē無域名。
known_accounts:
other: "%{count} ê知影ê口座"
moderation:
all: 全部
limited: 受限制
title: 管理
moderation_notes:
create: 加添管理筆記
created_msg: 站臺ê管理記錄成功建立!
description_html: 檢視á是替別ê管理者kap未來ê家己留筆記
destroyed_msg: 站臺ê管理記錄成功thâi掉
placeholder: 關係本站、行ê行動á是其他通幫tsān lí未來管本站ê資訊。
title: 管理ê筆記
private_comment: 私人評論
public_comment: 公開ê評論
purge: 清除
purge_description_html: Nā lí想講tsit ê域名ē永永斷線ē當tuì儲存內底thâi掉uì tsit ê域名來ê所有口座記錄kap相關資料。Huân-sè ē開點á時間。
title: 聯邦
total_blocked_by_us: Hōo lán封鎖
total_followed_by_them: Hōo in跟tuè
total_followed_by_us: Hōo lán跟tuè
total_reported: 關係in ê檢舉
total_storage: 媒體ê附件
totals_time_period_hint_html: 下kha顯示ê總計包含ta̍k時ê資料。
unknown_instance: 佇本服務器現tsú時iáu無tsit ê域名ê記錄。
invites:
deactivate_all: Lóng停用
filter:
all: 全部
available: 通用ê
expired: 過期ê
title: 過濾器
title: 邀請
ip_blocks:
add_new: 建立規則
created_msg: 成功加添新ê IP規則
delete: Thâi掉
expires_in:
'1209600': 2 禮拜
'15778476': 6個月
'2629746': 1 個月
'31556952': 1
'86400': 1 kang
'94670856': 3
new:
title: 建立新ê IP規則
no_ip_block_selected: 因為無揀任何IP規則所以lóng無改變
title: IP規則
relationships:
title: "%{acct} ê關係"
relays:
add_new: 加添新ê中繼
delete: Thâi掉
description_html: "<strong>聯邦ê中繼站</strong> 是中lâng ê服侍器,ē tī訂koh公開kàu hit ê中繼站ê服侍器之間交換tsē-tsē ê 公開PO文。<strong>中繼站通幫tsān小型kap中型服侍器tuì聯邦宇宙發現內容</strong>本地ê用者免手動跟tuè遠距離ê服侍器ê別lâng。"
disable: 停止使用
disabled: 停止使用ê
enable: 啟用
enable_hint: Lí ê服侍器tsi̍t-ē啟動ē訂tuì tsit ê中繼逐ê公開PO文mā ē開始送tsit ê服侍器ê公開PO文kàu hia。
enabled: 啟用ê
inbox_url: 中繼 URL
pending: Teh等中繼站允准
save_and_enable: 儲存koh啟用
setup: 設定中繼ê連結
signatures_not_enabled: Nā啟用安全模式á是受限ê聯邦模式中繼可能buē-tàng正常運作
status: 狀態
title: 中繼
report_notes:
created_msg: 檢舉記錄成功建立!
destroyed_msg: 檢舉記錄成功thâi掉
reports:
account:
notes:
other: "%{count} 篇筆記"
action_log: 審查日誌
action_taken_by: 操作由
statuses:
language: 語言
trends:

View File

@ -1872,6 +1872,7 @@ nl:
edited_at_html: Bewerkt op %{date}
errors:
in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan.
quoted_status_not_found: Het bericht die je probeert te citeren lijkt niet te bestaan.
over_character_limit: Limiet van %{max} tekens overschreden
pin_errors:
direct: Berichten die alleen zichtbaar zijn voor vermelde gebruikers, kunnen niet worden vastgezet

View File

@ -1872,6 +1872,7 @@ pt-PT:
edited_at_html: Editado em %{date}
errors:
in_reply_not_found: A publicação a que estás a tentar responder parece não existir.
quoted_status_not_found: A publicação que está a tentar citar parece não existir.
over_character_limit: limite de caracteres %{max} excedido
pin_errors:
direct: As publicações que só são visíveis para os utilizadores mencionados não podem ser fixadas

View File

@ -1352,7 +1352,7 @@ ru:
edit_profile:
basic_information: Основные данные
hint_html: "<strong>Здесь вы можете изменить всё то, что будет отображаться в вашем публичном профиле и рядом с вашими постами.</strong> На вас будут чаще подписываться и с вами будут чаще взаимодействовать, если у вас будет заполнен профиль и добавлено фото профиля."
other: Прочее
other: Разное
emoji_styles:
auto: Автоматически
native: Как в системе
@ -1630,19 +1630,19 @@ ru:
media_attachments:
validations:
images_and_video: Нельзя добавить видео к посту с изображениями
not_found: Медиа %{ids} не найдено или уже прикреплено к другому сообщению
not_ready: Не удаётся прикрепить файлы, обработка которых не завершена. Повторите попытку чуть позже!
too_many: Нельзя добавить более 4 файлов
not_found: Медиа %{ids} не найдены или уже прикреплены к другому посту
not_ready: Обработка некоторых прикреплённых файлов ещё не окончена. Подождите немного и попробуйте снова!
too_many: Можно прикрепить не более 4 файлов
migrations:
acct: имя@домен новой учётной записи
acct: Куда
cancel: Отменить переезд
cancel_explanation: Отмена перенаправления повторно активирует текущую учётную запись, но не вернёт обратно подписчиков, которые были перемещены на другую.
cancelled_msg: Переезд был успешно отменён.
cancel_explanation: После отмены перенаправления ваша текущая учётная запись снова станет активна, но ранее перенесённые подписчики не будут возвращены.
cancelled_msg: Переезд отменён.
errors:
already_moved: это та же учётная запись, на которую вы мигрировали
missing_also_known_as: не ссылается на эту учетную запись
already_moved: не может быть той учётной записью, куда уже настроен переезд
missing_also_known_as: должна быть связанной учётной записью
move_to_self: не может быть текущей учётной записью
not_found: не удалось найти
not_found: не найдена
on_cooldown: Вы пока не можете переезжать
followers_count: Подписчиков на момент переезда
incoming_migrations: Переезд со старой учётной записи
@ -1650,25 +1650,25 @@ ru:
moved_msg: Теперь ваша учётная запись перенаправляет к %{acct}, туда же перемещаются подписчики.
not_redirecting: Для вашей учётной записи пока не настроено перенаправление.
on_cooldown: Вы уже недавно переносили свою учётную запись. Эта возможность будет снова доступна через %{count} дн.
past_migrations: Прошлые переезды
past_migrations: История переездов
proceed_with_move: Перенести подписчиков
redirected_msg: Ваша учётная запись теперь перенаправляется на %{acct}.
redirecting_to: Ваша учётная запись перенаправляет к %{acct}.
set_redirect: Настроить перенаправление
warning:
backreference_required: Новая учётная запись должна быть сначала настроена так, чтоб ссылаться на текущую
before: 'Прежде чем продолжить, внимательно прочитайте следующую информацию:'
cooldown: После переезда наступает период, в течение которого вы не сможете ещё раз переехать
backreference_required: Текущая учётная запись сначала должна быть добавлена как связанная в настройках новой учётной записи
before: 'Внимательно ознакомьтесь со следующими замечаниями перед тем как продолжить:'
cooldown: После переезда наступит период ожидания, в течение которого переезд будет невозможен
disabled_account: Вашу текущую учётная запись впоследствии нельзя будет больше использовать. При этом, у вас будет доступ к экспорту данных, а также к повторной активации учётной записи.
followers: Это действие перенесёт всех ваших подписчиков с текущей учётной записи на новую
only_redirect_html: Или же вы можете <a href="%{path}">просто настроить перенаправление в ваш профиль</a>.
followers: В результате переезда все ваши подписчики будут перенесены с текущей учётной записи на новую
only_redirect_html: Также вы можете <a href="%{path}">настроить перенаправление без переноса подписчиков</a>.
other_data: Никакие другие данные не будут автоматически перенесены
redirect: Профиль этой учётной записи будет обновлён с заметкой о перенаправлении, а также исключён из поиска
redirect: Профиль текущей учётной записи будет исключён из поиска, а в нём появится объявление о переезде
moderation:
title: Модерация
move_handler:
carry_blocks_over_text: Этот пользователь переехал с учётной записи %{acct}, которую вы заблокировали.
carry_mutes_over_text: Этот пользователь перешёл с учётной записи %{acct}, которую вы игнорируете.
carry_mutes_over_text: Этот пользователь переехал с учётной записи %{acct}, которую вы игнорируете.
copy_account_note_text: 'Этот пользователь переехал с %{acct}, вот ваша предыдущая заметка о нём:'
navigation:
toggle_menu: Переключить меню
@ -1717,8 +1717,9 @@ ru:
billion: млрд
million: млн
quadrillion: квадрлн
thousand: тыс
thousand: тыс.
trillion: трлн
unit: ''
otp_authentication:
code_hint: Для подтверждения введите код, сгенерированный приложением-аутентификатором
description_html: Подключив <strong>двуфакторную авторизацию</strong>, для входа в свою учётную запись вам будет необходим смартфон и приложение-аутентификатор на нём, которое будет генерировать специальные временные коды. Без этих кодов войти в учётную запись не получиться, даже если все данные верны, что существенно увеличивает безопасность вашей учётной записи.
@ -1728,32 +1729,32 @@ ru:
setup: Настроить
wrong_code: Введенный код недействителен! Время сервера и время устройства правильно?
pagination:
newer: Новее
next: След
older: Старше
prev: Пред
newer: Позже
next: Вперёд
older: Раньше
prev: Назад
truncate: "&hellip;"
polls:
errors:
already_voted: Вы уже голосовали в этом опросе
already_voted: Вы уже проголосовали в этом опросе
duplicate_options: не должны повторяться
duration_too_long: слишком велика
duration_too_short: слишком мала
expired: Опрос уже завершился
invalid_choice: Выбранного варианта голосования не существует
expired: Этот опрос уже завершился
invalid_choice: Выбранного вами варианта ответа не существует
over_character_limit: не должны превышать %{max} символов
self_vote: Вы не можете голосовать в своих опросах
too_few_options: должны содержать не меньше двух опций
too_many_options: должны ограничиваться максимум %{max} опциями
preferences:
other: Остальное
posting_defaults: Настройки отправки по умолчанию
other: Разное
posting_defaults: Предустановки для новых постов
public_timelines: Публичные ленты
privacy:
hint_html: "<strong>Настройте, как вы хотите, чтобы ваш профиль и ваши сообщения были найдены.</strong> Различные функции в Mastodon могут помочь вам охватить более широкую аудиторию, если они включены. Уделите время изучению этих настроек, чтобы убедиться, что они подходят для вашего случая использования."
privacy: Конфиденциальность
privacy: Приватность
privacy_hint_html: Определите, какую информацию вы хотите раскрыть в интересах других. Люди находят интересные профили и приложения, просматривая список подписчиков других людей и узнавая, из каких приложений они публикуют свои сообщения, но вы можете предпочесть скрыть это.
reach: Охват
reach: Видимость
reach_hint_html: Укажите, хотите ли вы, чтобы новые люди обнаруживали вас и могли следить за вами. Хотите ли вы, чтобы ваши сообщения появлялись на экране Обзора? Хотите ли вы, чтобы другие люди видели вас в своих рекомендациях? Хотите ли вы автоматически принимать всех новых подписчиков или иметь возможность детально контролировать каждого из них?
search: Поиск
search_hint_html: Определите, как вас могут найти. Хотите ли вы, чтобы люди находили вас по тому, о чём вы публично писали? Хотите ли вы, чтобы люди за пределами Mastodon находили ваш профиль при поиске в Интернете? Следует помнить, что полное исключение из всех поисковых систем не может быть гарантировано для публичной информации.

View File

@ -9,7 +9,7 @@ da:
fields: Din hjemmeside, dine pronominer, din alder, eller hvad du har lyst til.
indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset.
note: 'Du kan @omtale andre personer eller #hashtags.'
show_collections: Folk vil ikke kunne tjekke dine Følger og Følgere. Folk, du selv følger, vil stadig kunne se dette.
show_collections: Folk vil kunne se, hvem du følger, og hvem der følger dig. Personer, som du følger, vil kunne se, at du følger dem.
unlocked: Man vil kunne følges af folk uden først at godkende dem. Ønsker man at gennemgå Følg-anmodninger og individuelt acceptere/afvise nye følgere, så fjern markeringen.
account_alias:
acct: Angiv brugernavn@domain for den konto, hvorfra du vil flytte
@ -75,7 +75,7 @@ da:
featured_tag:
name: 'Her er nogle af dine hyppigst brugte hashtags:'
filters:
action: Vælg handlingen til eksekvering, når et indlæg matcher filteret
action: Vælg, hvilken handling, der skal udføres, når et indlæg matcher filteret
actions:
blur: Skjul medier bag en advarsel, uden at skjule selve teksten
hide: Skjul det filtrerede indhold fuldstændigt og gør, som om det ikke eksisterer

View File

@ -60,6 +60,7 @@ lt:
setting_display_media_default: Slėpti mediją, pažymėtą kaip jautrią
setting_display_media_hide_all: Visada slėpti mediją
setting_display_media_show_all: Visada rodyti mediją
setting_emoji_style: Kaip rodyti emodžius. „Auto“ bandys naudoti vietinius jaustukus, bet senesnėse naršyklėse grįš prie Tvejaustukų.
setting_system_scrollbars_ui: Taikoma tik darbalaukio naršyklėms, karkasiniais „Safari“ ir „Chrome“.
setting_use_blurhash: Gradientai pagrįsti paslėptų vizualizacijų spalvomis, bet užgožia bet kokias detales.
setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio srauto slinkimo.
@ -120,6 +121,11 @@ lt:
min_age: Neturėtų būti žemiau mažiausio amžiaus, reikalaujamo pagal jūsų jurisdikcijos įstatymus.
user:
chosen_languages: Kai pažymėta, viešose laiko skalėse bus rodomi tik įrašai pasirinktomis kalbomis.
date_of_birth:
few: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime.
many: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime.
one: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime.
other: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime.
role: Vaidmuo valdo, kokius leidimus naudotojas turi.
labels:
account:
@ -162,6 +168,7 @@ lt:
setting_display_media: Medijos rodymas
setting_display_media_hide_all: Slėpti viską
setting_display_media_show_all: Rodyti viską
setting_emoji_style: Jaustuko stilius
setting_expand_spoilers: Visada išplėsti įrašus, pažymėtus turinio įspėjimais
setting_hide_network: Slėpti savo socialinę diagramą
setting_missing_alt_text_modal: Rodyti patvirtinimo dialogo langą prieš skelbiant mediją be alternatyvaus teksto.

View File

@ -1872,6 +1872,7 @@ tr:
edited_at_html: "%{date} tarihinde düzenlendi"
errors:
in_reply_not_found: Yanıtlamaya çalıştığınız durum yok gibi görünüyor.
quoted_status_not_found: Alıntılamaya çalıştığınız gönderi mevcut görünmüyor.
over_character_limit: "%{max} karakter limiti aşıldı"
pin_errors:
direct: Sadece değinilen kullanıcıların görebileceği gönderiler üstte tutulamaz

View File

@ -1846,6 +1846,7 @@ uk:
edited_at_html: Відредаговано %{date}
errors:
in_reply_not_found: Допису, на який ви намагаєтеся відповісти, не існує.
quoted_status_not_found: Повідомлення, яке ви намагаєтеся цитувати, не існує.
over_character_limit: перевищено ліміт символів %{max}
pin_errors:
direct: Не можливо прикріпити дописи, які видимі лише згаданим користувачам

View File

@ -1829,6 +1829,7 @@ vi:
edited_at_html: Sửa %{date}
errors:
in_reply_not_found: Bạn đang trả lời một tút không còn tồn tại.
quoted_status_not_found: Bạn đang trích dẫn một tút không còn tồn tại.
over_character_limit: vượt quá giới hạn %{max} ký tự
pin_errors:
direct: Không thể ghim những tút nhắn riêng

View File

@ -1830,7 +1830,8 @@ zh-TW:
other: 含有不得使用的標籤: %{tags}
edited_at_html: 編輯於 %{date}
errors:
in_reply_not_found: 您嘗試回覆的嘟文看起來不存在。
in_reply_not_found: 您嘗試回覆之嘟文似乎不存在。
quoted_status_not_found: 您嘗試引用之嘟文似乎不存在。
over_character_limit: 已超過 %{max} 字的限制
pin_errors:
direct: 無法釘選只有僅提及使用者可見之嘟文

View File

@ -7,7 +7,17 @@ if Rails.env.development?
admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
admin.save(validate: false)
user = User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true)
user = User.where(email: "admin@#{domain}").first_or_initialize(
email: "admin@#{domain}",
password: 'mastodonadmin',
password_confirmation: 'mastodonadmin',
confirmed_at: Time.now.utc,
role: UserRole.find_by(name: 'Owner'),
account: admin,
agreement: true,
approved: true,
bypass_registration_checks: true
)
user.save!
user.approve!
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class Mastodon::WorkerBatchMiddleware
def call(_worker, msg, _queue, _redis_pool = nil)
if (batch = Thread.current[:batch])
batch.add_jobs([msg['jid']])
end
yield
end
end

View File

@ -180,6 +180,14 @@ RSpec.describe JsonLdHelper do
expect(compacted.dig('object', 'tag', 0, 'href')).to eq ['foo']
expect(safe_for_forwarding?(json, compacted)).to be true
end
context 'when array size mismatch exists' do
subject { helper.patch_for_forwarding!(json, alternate) }
let(:alternate) { json.merge('to' => %w(one two three)) }
it { is_expected.to be_nil }
end
end
describe 'safe_for_forwarding?' do

View File

@ -4,15 +4,15 @@ require 'rails_helper'
RSpec.describe Webfinger do
describe 'self link' do
subject { described_class.new('acct:alice@example.com').perform }
context 'when self link is specified with type application/activity+json' do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } }
it 'correctly parses the response' do
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
response = described_class.new('acct:alice@example.com').perform
expect(response.self_link_href).to eq 'https://example.com/alice'
expect(subject.self_link_href).to eq 'https://example.com/alice'
end
end
@ -22,9 +22,7 @@ RSpec.describe Webfinger do
it 'correctly parses the response' do
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
response = described_class.new('acct:alice@example.com').perform
expect(response.self_link_href).to eq 'https://example.com/alice'
expect(subject.self_link_href).to eq 'https://example.com/alice'
end
end
@ -34,7 +32,45 @@ RSpec.describe Webfinger do
it 'raises an error' do
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
expect { described_class.new('acct:alice@example.com').perform }.to raise_error(Webfinger::Error)
expect { subject }
.to raise_error(Webfinger::Error)
end
end
context 'when webfinger fails and host meta is used' do
before { stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(status: 404) }
context 'when host meta succeeds' do
let(:host_meta) do
<<~XML
<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml" template="https://example.com/.well-known/nonStandardWebfinger?resource={uri}"/>
</XRD>
XML
end
let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice-from-NS', type: 'application/activity+json' }] } }
before do
stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(body: host_meta, headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nonStandardWebfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'uses host meta details' do
expect(subject.self_link_href)
.to eq 'https://example.com/alice-from-NS'
end
end
context 'when host meta fails' do
before do
stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 500)
end
it 'raises error' do
expect { subject }
.to raise_error(Webfinger::Error)
end
end
end
end

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AnnouncementReaction do
describe 'Associations' do
it { is_expected.to belong_to(:account) }
it { is_expected.to belong_to(:announcement).inverse_of(:announcement_reactions) }
it { is_expected.to belong_to(:custom_emoji).optional }
end
describe 'Validations' do
subject { Fabricate.build :announcement_reaction }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to allow_values('😀').for(:name) }
it { is_expected.to_not allow_values('INVALID').for(:name) }
context 'when reaction limit is reached' do
subject { Fabricate.build :announcement_reaction, announcement: announcement_reaction.announcement }
let(:announcement_reaction) { Fabricate :announcement_reaction, name: '😊' }
before { stub_const 'ReactionValidator::LIMIT', 1 }
it { is_expected.to_not allow_values('😀').for(:name).against(:base) }
end
end
describe 'Callbacks' do
describe 'Setting custom emoji association' do
subject { Fabricate.build :announcement_reaction, name: }
context 'when name is missing' do
let(:name) { '' }
it 'does not set association' do
expect { subject.valid? }
.to not_change(subject, :custom_emoji).from(be_blank)
end
end
context 'when name matches a custom emoji shortcode' do
let(:name) { 'custom' }
let!(:custom_emoji) { Fabricate :custom_emoji, shortcode: 'custom' }
it 'sets association' do
expect { subject.valid? }
.to change(subject, :custom_emoji).from(be_blank).to(custom_emoji)
end
end
context 'when name does not match a custom emoji' do
let(:name) { 'custom' }
it 'does not set association' do
expect { subject.valid? }
.to not_change(subject, :custom_emoji).from(be_blank)
end
end
end
end
end

View File

@ -28,4 +28,26 @@ RSpec.describe List do
end
end
end
describe 'Scopes' do
describe '.with_list_account' do
let(:alice) { Fabricate :account }
let(:bob) { Fabricate :account }
let(:list) { Fabricate :list }
let(:other_list) { Fabricate :list }
before do
Fabricate :follow, account: list.account, target_account: alice
Fabricate :follow, account: other_list.account, target_account: bob
Fabricate :list_account, list: list, account: alice
Fabricate :list_account, list: other_list, account: bob
end
it 'returns lists connected to the account' do
expect(described_class.with_list_account(alice))
.to include(list)
.and not_include(other_list)
end
end
end
end

View File

@ -17,6 +17,7 @@ RSpec.describe Tag do
subject { Fabricate :tag, name: 'original' }
it { is_expected.to_not allow_value('changed').for(:name).with_message(previous_name_error_message) }
it { is_expected.to allow_value('ORIGINAL').for(:name) }
end
end
@ -31,6 +32,7 @@ RSpec.describe Tag do
subject { Fabricate :tag, name: 'original', display_name: 'OriginalDisplayName' }
it { is_expected.to_not allow_value('ChangedDisplayName').for(:display_name).with_message(previous_name_error_message) }
it { is_expected.to allow_value('ORIGINAL').for(:display_name) }
end
end

View File

@ -13791,8 +13791,8 @@ __metadata:
linkType: hard
"vite-plugin-pwa@npm:^1.0.0":
version: 1.0.1
resolution: "vite-plugin-pwa@npm:1.0.1"
version: 1.0.2
resolution: "vite-plugin-pwa@npm:1.0.2"
dependencies:
debug: "npm:^4.3.6"
pretty-bytes: "npm:^6.1.1"
@ -13807,7 +13807,7 @@ __metadata:
peerDependenciesMeta:
"@vite-pwa/assets-generator":
optional: true
checksum: 10c0/ceca04df97877ca97eb30805207d4826bd6340796194c9015afeefeb781931bf9019a630c5a0bdaa6dffcada11ce1fdf8595ac48a08d751dff81601aa0c7db38
checksum: 10c0/e4f2f4dfff843ee2585a0d89e74187168ba20da77cd0d127ce7ad7eebcf5a68b2bf09000afb6bb86d43a2034fea9f568cd6db2a2d4b47a72e175d999a5e07eb1
languageName: node
linkType: hard