diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/account.rb | 16 | ||||
-rw-r--r-- | app/models/direct_feed.rb | 31 | ||||
-rw-r--r-- | app/models/form/admin_settings.rb | 27 | ||||
-rw-r--r-- | app/models/list.rb | 13 | ||||
-rw-r--r-- | app/models/media_attachment.rb | 4 | ||||
-rw-r--r-- | app/models/mute.rb | 2 | ||||
-rw-r--r-- | app/models/status.rb | 81 | ||||
-rw-r--r-- | app/models/user.rb | 7 |
8 files changed, 155 insertions, 26 deletions
diff --git a/app/models/account.rb b/app/models/account.rb index 6b7ebda9e..0b3c48543 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -66,6 +66,10 @@ class Account < ApplicationRecord include AccountCounters include DomainNormalizable + MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i + MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i + MAX_FIELDS = (ENV['MAX_PROFILE_FIELDS'] || 4).to_i + TRUST_LEVELS = { untrusted: 0, trusted: 1, @@ -82,9 +86,9 @@ class Account < ApplicationRecord # Local user validations validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? } - validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } - validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? } - validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? } + validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? } + validates :note, note_length: { maximum: MAX_NOTE_LENGTH }, if: -> { local? && will_save_change_to_note? } + validates :fields, length: { maximum: MAX_FIELDS }, if: -> { local? && will_save_change_to_fields? } scope :remote, -> { where.not(domain: nil) } scope :local, -> { where(domain: nil) } @@ -303,15 +307,13 @@ class Account < ApplicationRecord self[:fields] = fields end - DEFAULT_FIELDS_SIZE = 4 - def build_fields - return if fields.size >= DEFAULT_FIELDS_SIZE + return if fields.size >= MAX_FIELDS tmp = self[:fields] || [] tmp = [] if tmp.is_a?(Hash) - (DEFAULT_FIELDS_SIZE - tmp.size).times do + (MAX_FIELDS - tmp.size).times do tmp << { name: '', value: '' } end diff --git a/app/models/direct_feed.rb b/app/models/direct_feed.rb new file mode 100644 index 000000000..c0b8a0a35 --- /dev/null +++ b/app/models/direct_feed.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class DirectFeed < Feed + include Redisable + + def initialize(account) + @type = :direct + @id = account.id + @account = account + end + + def get(limit, max_id = nil, since_id = nil, min_id = nil) + unless redis.exists("account:#{@account.id}:regeneration") + statuses = super + return statuses unless statuses.empty? + end + from_database(limit, max_id, since_id, min_id) + end + + private + + def from_database(limit, max_id, since_id, min_id) + loop do + statuses = Status.as_direct_timeline(@account, limit, max_id, since_id, min_id) + return statuses if statuses.empty? + max_id = statuses.last.id + statuses = statuses.reject { |status| FeedManager.instance.filter?(:direct, status, @account.id) } + return statuses unless statuses.empty? + end + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 390836f28..fcec3e686 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -18,7 +18,8 @@ class Form::AdminSettings show_staff_badge enable_bootstrap_timeline_accounts bootstrap_timeline_accounts - theme + flavour + skin min_invite_role activity_api_enabled peers_api_enabled @@ -26,15 +27,21 @@ class Form::AdminSettings preview_sensitive_media custom_css profile_directory + hide_followers_count + enable_keybase + flavour_and_skin thumbnail hero mascot + show_reblogs_in_public_timelines + show_replies_in_public_timelines spam_check_enabled trends trendable_by_default show_domain_blocks show_domain_blocks_rationale noindex + outgoing_spoilers ).freeze BOOLEAN_KEYS = %i( @@ -47,6 +54,10 @@ class Form::AdminSettings show_known_fediverse_at_about_page preview_sensitive_media profile_directory + hide_followers_count + enable_keybase + show_reblogs_in_public_timelines + show_replies_in_public_timelines spam_check_enabled trends trendable_by_default @@ -59,6 +70,10 @@ class Form::AdminSettings mascot ).freeze + PSEUDO_KEYS = %i( + flavour_and_skin + ).freeze + attr_accessor(*KEYS) validates :site_short_description, :site_description, html: { wrap_with: :p } @@ -80,6 +95,7 @@ class Form::AdminSettings return false unless valid? KEYS.each do |key| + next if PSEUDO_KEYS.include?(key) value = instance_variable_get("@#{key}") if UPLOAD_KEYS.include?(key) && !value.nil? @@ -92,10 +108,19 @@ class Form::AdminSettings end end + def flavour_and_skin + "#{Setting.flavour}/#{Setting.skin}" + end + + def flavour_and_skin=(value) + @flavour, @skin = value.split('/', 2) + end + private def initialize_attributes KEYS.each do |key| + next if PSEUDO_KEYS.include?(key) instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil? end end diff --git a/app/models/list.rb b/app/models/list.rb index c9c94fca1..8493046e5 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -3,11 +3,12 @@ # # Table name: lists # -# id :bigint(8) not null, primary key -# account_id :bigint(8) not null -# title :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# title :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# replies_policy :integer default("list_replies"), not null # class List < ApplicationRecord @@ -15,6 +16,8 @@ class List < ApplicationRecord PER_ACCOUNT_LIMIT = 50 + enum replies_policy: [:list_replies, :all_replies, :no_replies], _prefix: :show + belongs_to :account, optional: true has_many :list_accounts, inverse_of: :list, dependent: :destroy diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 519711401..cc0a1ab27 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -150,8 +150,8 @@ class MediaAttachment < ApplicationRecord all: '-quality 90 -strip +set modify-date +set create-date', }.freeze - IMAGE_LIMIT = 10.megabytes - VIDEO_LIMIT = 40.megabytes + IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i + VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px MAX_VIDEO_FRAME_RATE = 60 diff --git a/app/models/mute.rb b/app/models/mute.rb index 0e00c2278..639120f7d 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -6,9 +6,9 @@ # id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null +# hide_notifications :boolean default(TRUE), not null # account_id :bigint(8) not null # target_account_id :bigint(8) not null -# hide_notifications :boolean default(TRUE), not null # class Mute < ApplicationRecord diff --git a/app/models/status.rb b/app/models/status.rb index 71596ec2f..594ae98c0 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -21,7 +21,10 @@ # account_id :bigint(8) not null # application_id :bigint(8) # in_reply_to_account_id :bigint(8) +# local_only :boolean +# full_status_text :text default(""), not null # poll_id :bigint(8) +# content_type :string # deleted_at :datetime # @@ -77,6 +80,7 @@ class Status < ApplicationRecord validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? + validates :content_type, inclusion: { in: %w(text/plain text/markdown text/html) }, allow_nil: true accepts_nested_attributes_for :poll @@ -107,6 +111,8 @@ class Status < ApplicationRecord end } + scope :not_local_only, -> { where(local_only: [false, nil]) } + cache_associated :application, :media_attachments, :conversation, @@ -264,6 +270,8 @@ class Status < ApplicationRecord around_create Mastodon::Snowflake::Callbacks + before_create :set_locality + before_validation :prepare_contents, if: :local? before_validation :set_reblog before_validation :set_visibility @@ -281,8 +289,50 @@ class Status < ApplicationRecord where(language: nil).or where(language: account.chosen_languages) end + def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false) + # direct timeline is mix of direct message from_me and to_me. + # 2 queries are executed with pagination. + # constant expression using arel_table is required for partial index + + # _from_me part does not require any timeline filters + query_from_me = where(account_id: account.id) + .where(Status.arel_table[:visibility].eq(3)) + .limit(limit) + .order('statuses.id DESC') + + # _to_me part requires mute and block filter. + # FIXME: may we check mutes.hide_notifications? + query_to_me = Status + .joins(:mentions) + .merge(Mention.where(account_id: account.id)) + .where(Status.arel_table[:visibility].eq(3)) + .limit(limit) + .order('mentions.status_id DESC') + .not_excluded_by_account(account) + + if max_id.present? + query_from_me = query_from_me.where('statuses.id < ?', max_id) + query_to_me = query_to_me.where('mentions.status_id < ?', max_id) + end + + if since_id.present? + query_from_me = query_from_me.where('statuses.id > ?', since_id) + query_to_me = query_to_me.where('mentions.status_id > ?', since_id) + end + + if cache_ids + # returns array of cache_ids object that have id and updated_at + (query_from_me.cache_ids.to_a + query_to_me.cache_ids.to_a).uniq(&:id).sort_by(&:id).reverse.take(limit) + else + # returns ActiveRecord.Relation + items = (query_from_me.select(:id).to_a + query_to_me.select(:id).to_a).uniq(&:id).sort_by(&:id).reverse.take(limit) + Status.where(id: items.map(&:id)) + end + end + def as_public_timeline(account = nil, local_only = false) - query = timeline_scope(local_only).without_replies + query = timeline_scope(local_only) + query = query.without_replies unless Setting.show_replies_in_public_timelines apply_timeline_filters(query, account, [:local, true].include?(local_only)) end @@ -341,7 +391,7 @@ class Status < ApplicationRecord visibility = [:public, :unlisted] if account.nil? - where(visibility: visibility) + where(visibility: visibility).not_local_only elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff @@ -385,10 +435,12 @@ class Status < ApplicationRecord else Status end - - starting_scope - .with_public_visibility - .without_reblogs + starting_scope = starting_scope.with_public_visibility + if Setting.show_reblogs_in_public_timelines + starting_scope + else + starting_scope.without_reblogs + end end def apply_timeline_filters(query, account, local_only) @@ -407,7 +459,7 @@ class Status < ApplicationRecord end def filter_timeline_default(query) - query.excluding_silenced_accounts + query.not_local_only.excluding_silenced_accounts end def account_silencing_filter(account) @@ -420,6 +472,15 @@ class Status < ApplicationRecord end end + def marked_local_only? + # match both with and without U+FE0F (the emoji variation selector) + /#{local_only_emoji}\ufe0f?\z/.match?(content) + end + + def local_only_emoji + '👁' + end + def status_stat super || build_status_stat end @@ -455,6 +516,12 @@ class Status < ApplicationRecord self.sensitive = false if sensitive.nil? end + def set_locality + if account.domain.nil? && !attribute_changed?(:local_only) + self.local_only = marked_local_only? + end + end + def set_conversation self.thread = thread.reblog if thread&.reblog? diff --git a/app/models/user.rb b/app/models/user.rb index 306e2d435..a05d98d88 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -109,10 +109,11 @@ class User < ApplicationRecord has_many :session_activations, dependent: :destroy - delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, - :reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network, + delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal, + :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count, :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, :advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images, + :default_content_type, :system_emoji_font, to: :settings, prefix: :setting, allow_nil: false attr_reader :invite_code, :sign_in_token_attempt @@ -174,7 +175,7 @@ class User < ApplicationRecord end def functional? - confirmed? && approved? && !disabled? && !account.suspended? && account.moved_to_account_id.nil? + confirmed? && approved? && !disabled? && !account.suspended? end def unconfirmed_or_pending? |