account.rb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. # frozen_string_literal: true
  2. # == Schema Information
  3. #
  4. # Table name: accounts
  5. #
  6. # id :bigint(8) not null, primary key
  7. # username :string default(""), not null
  8. # domain :string
  9. # private_key :text
  10. # public_key :text default(""), not null
  11. # created_at :datetime not null
  12. # updated_at :datetime not null
  13. # note :text default(""), not null
  14. # display_name :string default(""), not null
  15. # uri :string default(""), not null
  16. # url :string
  17. # avatar_file_name :string
  18. # avatar_content_type :string
  19. # avatar_file_size :integer
  20. # avatar_updated_at :datetime
  21. # header_file_name :string
  22. # header_content_type :string
  23. # header_file_size :integer
  24. # header_updated_at :datetime
  25. # avatar_remote_url :string
  26. # locked :boolean default(FALSE), not null
  27. # header_remote_url :string default(""), not null
  28. # last_webfingered_at :datetime
  29. # inbox_url :string default(""), not null
  30. # outbox_url :string default(""), not null
  31. # shared_inbox_url :string default(""), not null
  32. # followers_url :string default(""), not null
  33. # protocol :integer default("ostatus"), not null
  34. # memorial :boolean default(FALSE), not null
  35. # moved_to_account_id :bigint(8)
  36. # featured_collection_url :string
  37. # fields :jsonb
  38. # actor_type :string
  39. # discoverable :boolean
  40. # also_known_as :string is an Array
  41. # silenced_at :datetime
  42. # suspended_at :datetime
  43. # hide_collections :boolean
  44. # avatar_storage_schema_version :integer
  45. # header_storage_schema_version :integer
  46. # devices_url :string
  47. # suspension_origin :integer
  48. # sensitized_at :datetime
  49. # trendable :boolean
  50. # reviewed_at :datetime
  51. # requested_review_at :datetime
  52. #
  53. class Account < ApplicationRecord
  54. self.ignored_columns = %w(
  55. subscription_expires_at
  56. secret
  57. remote_url
  58. salmon_url
  59. hub_url
  60. trust_level
  61. )
  62. USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
  63. MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i
  64. URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/
  65. include Attachmentable
  66. include AccountAssociations
  67. include AccountAvatar
  68. include AccountFinderConcern
  69. include AccountHeader
  70. include AccountInteractions
  71. include Paginable
  72. include AccountCounters
  73. include DomainNormalizable
  74. include DomainMaterializable
  75. include AccountMerging
  76. enum protocol: [:ostatus, :activitypub]
  77. enum suspension_origin: [:local, :remote], _prefix: true
  78. validates :username, presence: true
  79. validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
  80. # Remote user validations
  81. validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, if: -> { !local? && will_save_change_to_username? }
  82. # Local user validations
  83. validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
  84. validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
  85. validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
  86. validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
  87. validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
  88. scope :remote, -> { where.not(domain: nil) }
  89. scope :local, -> { where(domain: nil) }
  90. scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
  91. scope :silenced, -> { where.not(silenced_at: nil) }
  92. scope :suspended, -> { where.not(suspended_at: nil) }
  93. scope :sensitized, -> { where.not(sensitized_at: nil) }
  94. scope :without_suspended, -> { where(suspended_at: nil) }
  95. scope :without_silenced, -> { where(silenced_at: nil) }
  96. scope :without_instance_actor, -> { where.not(id: -99) }
  97. scope :recent, -> { reorder(id: :desc) }
  98. scope :bots, -> { where(actor_type: %w(Application Service)) }
  99. scope :groups, -> { where(actor_type: 'Group') }
  100. scope :alphabetic, -> { order(domain: :asc, username: :asc) }
  101. scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
  102. scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
  103. scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
  104. scope :without_unapproved, -> { left_outer_joins(:user).remote.or(left_outer_joins(:user).merge(User.approved.confirmed)) }
  105. scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
  106. scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
  107. scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
  108. scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
  109. scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
  110. scope :popular, -> { order('account_stats.followers_count desc') }
  111. scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
  112. scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
  113. scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
  114. delegate :email,
  115. :unconfirmed_email,
  116. :current_sign_in_at,
  117. :created_at,
  118. :sign_up_ip,
  119. :confirmed?,
  120. :approved?,
  121. :pending?,
  122. :disabled?,
  123. :unconfirmed?,
  124. :unconfirmed_or_pending?,
  125. :role,
  126. :locale,
  127. :shows_application?,
  128. :prefers_noindex?,
  129. to: :user,
  130. prefix: true,
  131. allow_nil: true
  132. delegate :chosen_languages, to: :user, prefix: false, allow_nil: true
  133. update_index('accounts', :self)
  134. def local?
  135. domain.nil?
  136. end
  137. def moved?
  138. moved_to_account_id.present?
  139. end
  140. def bot?
  141. %w(Application Service).include? actor_type
  142. end
  143. def instance_actor?
  144. id == -99
  145. end
  146. alias bot bot?
  147. def bot=(val)
  148. self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
  149. end
  150. def group?
  151. actor_type == 'Group'
  152. end
  153. alias group group?
  154. def acct
  155. local? ? username : "#{username}@#{domain}"
  156. end
  157. def pretty_acct
  158. local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
  159. end
  160. def local_username_and_domain
  161. "#{username}@#{Rails.configuration.x.local_domain}"
  162. end
  163. def local_followers_count
  164. Follow.where(target_account_id: id).count
  165. end
  166. def to_webfinger_s
  167. "acct:#{local_username_and_domain}"
  168. end
  169. def possibly_stale?
  170. last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
  171. end
  172. def refresh!
  173. ResolveAccountService.new.call(acct) unless local?
  174. end
  175. def silenced?
  176. silenced_at.present?
  177. end
  178. def silence!(date = Time.now.utc)
  179. update!(silenced_at: date)
  180. end
  181. def unsilence!
  182. update!(silenced_at: nil)
  183. end
  184. def suspended?
  185. suspended_at.present? && !instance_actor?
  186. end
  187. def suspended_permanently?
  188. suspended? && deletion_request.nil?
  189. end
  190. def suspended_temporarily?
  191. suspended? && deletion_request.present?
  192. end
  193. def suspend!(date: Time.now.utc, origin: :local, block_email: true)
  194. transaction do
  195. create_deletion_request!
  196. update!(suspended_at: date, suspension_origin: origin)
  197. create_canonical_email_block! if block_email
  198. end
  199. end
  200. def unsuspend!
  201. transaction do
  202. deletion_request&.destroy!
  203. update!(suspended_at: nil, suspension_origin: nil)
  204. destroy_canonical_email_block!
  205. end
  206. end
  207. def sensitized?
  208. sensitized_at.present?
  209. end
  210. def sensitize!(date = Time.now.utc)
  211. update!(sensitized_at: date)
  212. end
  213. def unsensitize!
  214. update!(sensitized_at: nil)
  215. end
  216. def memorialize!
  217. update!(memorial: true)
  218. end
  219. def trendable
  220. boolean_with_default('trendable', Setting.trendable_by_default)
  221. end
  222. def sign?
  223. true
  224. end
  225. def previous_strikes_count
  226. strikes.where(overruled_at: nil).count
  227. end
  228. def keypair
  229. @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
  230. end
  231. def tags_as_strings=(tag_names)
  232. hashtags_map = Tag.find_or_create_by_names(tag_names).index_by(&:name)
  233. # Remove hashtags that are to be deleted
  234. tags.each do |tag|
  235. if hashtags_map.key?(tag.name)
  236. hashtags_map.delete(tag.name)
  237. else
  238. tags.delete(tag)
  239. end
  240. end
  241. # Add hashtags that were so far missing
  242. hashtags_map.each_value do |tag|
  243. tags << tag
  244. end
  245. end
  246. def also_known_as
  247. self[:also_known_as] || []
  248. end
  249. def fields
  250. (self[:fields] || []).map do |f|
  251. Field.new(self, f)
  252. rescue
  253. nil
  254. end.compact
  255. end
  256. def fields_attributes=(attributes)
  257. fields = []
  258. old_fields = self[:fields] || []
  259. old_fields = [] if old_fields.is_a?(Hash)
  260. if attributes.is_a?(Hash)
  261. attributes.each_value do |attr|
  262. next if attr[:name].blank?
  263. previous = old_fields.find { |item| item['value'] == attr[:value] }
  264. if previous && previous['verified_at'].present?
  265. attr[:verified_at] = previous['verified_at']
  266. end
  267. fields << attr
  268. end
  269. end
  270. self[:fields] = fields
  271. end
  272. DEFAULT_FIELDS_SIZE = 4
  273. def build_fields
  274. return if fields.size >= DEFAULT_FIELDS_SIZE
  275. tmp = self[:fields] || []
  276. tmp = [] if tmp.is_a?(Hash)
  277. (DEFAULT_FIELDS_SIZE - tmp.size).times do
  278. tmp << { name: '', value: '' }
  279. end
  280. self.fields = tmp
  281. end
  282. def save_with_optional_media!
  283. save!
  284. rescue ActiveRecord::RecordInvalid
  285. self.avatar = nil
  286. self.header = nil
  287. save!
  288. end
  289. def hides_followers?
  290. hide_collections?
  291. end
  292. def hides_following?
  293. hide_collections?
  294. end
  295. def object_type
  296. :person
  297. end
  298. def to_param
  299. username
  300. end
  301. def to_log_human_identifier
  302. acct
  303. end
  304. def excluded_from_timeline_account_ids
  305. Rails.cache.fetch("exclude_account_ids_for:#{id}") { block_relationships.pluck(:target_account_id) + blocked_by_relationships.pluck(:account_id) + mute_relationships.pluck(:target_account_id) }
  306. end
  307. def excluded_from_timeline_domains
  308. Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
  309. end
  310. def preferred_inbox_url
  311. shared_inbox_url.presence || inbox_url
  312. end
  313. def synchronization_uri_prefix
  314. return 'local' if local?
  315. @synchronization_uri_prefix ||= "#{uri[URL_PREFIX_RE]}/"
  316. end
  317. def requires_review?
  318. reviewed_at.nil?
  319. end
  320. def reviewed?
  321. reviewed_at.present?
  322. end
  323. def requested_review?
  324. requested_review_at.present?
  325. end
  326. def requires_review_notification?
  327. requires_review? && !requested_review?
  328. end
  329. class Field < ActiveModelSerializers::Model
  330. attributes :name, :value, :verified_at, :account
  331. def initialize(account, attributes)
  332. @original_field = attributes
  333. string_limit = account.local? ? 255 : 2047
  334. super(
  335. account: account,
  336. name: attributes['name'].strip[0, string_limit],
  337. value: attributes['value'].strip[0, string_limit],
  338. verified_at: attributes['verified_at']&.to_datetime,
  339. )
  340. end
  341. def verified?
  342. verified_at.present?
  343. end
  344. def value_for_verification
  345. @value_for_verification ||= begin
  346. if account.local?
  347. value
  348. else
  349. ActionController::Base.helpers.strip_tags(value)
  350. end
  351. end
  352. end
  353. def verifiable?
  354. value_for_verification.present? && value_for_verification.start_with?('http://', 'https://')
  355. end
  356. def mark_verified!
  357. self.verified_at = Time.now.utc
  358. @original_field['verified_at'] = verified_at
  359. end
  360. def to_h
  361. { name: name, value: value, verified_at: verified_at }
  362. end
  363. end
  364. class << self
  365. DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
  366. TEXTSEARCH = "(setweight(to_tsvector('simple', accounts.display_name), 'A') || setweight(to_tsvector('simple', accounts.username), 'A') || setweight(to_tsvector('simple', coalesce(accounts.domain, '')), 'C'))"
  367. REPUTATION_SCORE_FUNCTION = '(greatest(0, coalesce(s.followers_count, 0)) / (greatest(0, coalesce(s.following_count, 0)) + 1.0))'
  368. FOLLOWERS_SCORE_FUNCTION = 'log(greatest(0, coalesce(s.followers_count, 0)) + 2)'
  369. TIME_DISTANCE_FUNCTION = '(case when s.last_status_at is null then 0 else exp(-1.0 * ((greatest(0, abs(extract(DAY FROM age(s.last_status_at))) - 30.0)^2) / (2.0 * ((-1.0 * 30^2) / (2.0 * ln(0.3)))))) end)'
  370. BOOST = "((#{REPUTATION_SCORE_FUNCTION} + #{FOLLOWERS_SCORE_FUNCTION} + #{TIME_DISTANCE_FUNCTION}) / 3.0)"
  371. def readonly_attributes
  372. super - %w(statuses_count following_count followers_count)
  373. end
  374. def inboxes
  375. urls = reorder(nil).where(protocol: :activitypub).group(:preferred_inbox_url).pluck(Arel.sql("coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url) AS preferred_inbox_url"))
  376. DeliveryFailureTracker.without_unavailable(urls)
  377. end
  378. def search_for(terms, limit: 10, offset: 0)
  379. tsquery = generate_query_for_search(terms)
  380. sql = <<-SQL.squish
  381. SELECT
  382. accounts.*,
  383. #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
  384. FROM accounts
  385. LEFT JOIN users ON accounts.id = users.account_id
  386. LEFT JOIN account_stats AS s ON accounts.id = s.account_id
  387. WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
  388. AND accounts.suspended_at IS NULL
  389. AND accounts.moved_to_account_id IS NULL
  390. AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
  391. ORDER BY rank DESC
  392. LIMIT :limit OFFSET :offset
  393. SQL
  394. records = find_by_sql([sql, limit: limit, offset: offset, tsquery: tsquery])
  395. ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
  396. records
  397. end
  398. def advanced_search_for(terms, account, limit: 10, following: false, offset: 0)
  399. tsquery = generate_query_for_search(terms)
  400. sql = advanced_search_for_sql_template(following)
  401. records = find_by_sql([sql, id: account.id, limit: limit, offset: offset, tsquery: tsquery])
  402. ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
  403. records
  404. end
  405. def from_text(text)
  406. return [] if text.blank?
  407. text.scan(MENTION_RE).map { |match| match.first.split('@', 2) }.uniq.filter_map do |(username, domain)|
  408. domain = begin
  409. if TagManager.instance.local_domain?(domain)
  410. nil
  411. else
  412. TagManager.instance.normalize_domain(domain)
  413. end
  414. end
  415. EntityCache.instance.mention(username, domain)
  416. end
  417. end
  418. private
  419. def generate_query_for_search(unsanitized_terms)
  420. terms = unsanitized_terms.gsub(DISALLOWED_TSQUERY_CHARACTERS, ' ')
  421. # The final ":*" is for prefix search.
  422. # The trailing space does not seem to fit any purpose, but `to_tsquery`
  423. # behaves differently with and without a leading space if the terms start
  424. # with `./`, `../`, or `.. `. I don't understand why, so, in doubt, keep
  425. # the same query.
  426. "' #{terms} ':*"
  427. end
  428. def advanced_search_for_sql_template(following)
  429. if following
  430. <<-SQL.squish
  431. WITH first_degree AS (
  432. SELECT target_account_id
  433. FROM follows
  434. WHERE account_id = :id
  435. UNION ALL
  436. SELECT :id
  437. )
  438. SELECT
  439. accounts.*,
  440. (count(f.id) + 1) * #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
  441. FROM accounts
  442. LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id)
  443. LEFT JOIN account_stats AS s ON accounts.id = s.account_id
  444. WHERE accounts.id IN (SELECT * FROM first_degree)
  445. AND to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
  446. AND accounts.suspended_at IS NULL
  447. AND accounts.moved_to_account_id IS NULL
  448. GROUP BY accounts.id, s.id
  449. ORDER BY rank DESC
  450. LIMIT :limit OFFSET :offset
  451. SQL
  452. else
  453. <<-SQL.squish
  454. SELECT
  455. accounts.*,
  456. (count(f.id) + 1) * #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
  457. FROM accounts
  458. LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id) OR (accounts.id = f.target_account_id AND f.account_id = :id)
  459. LEFT JOIN users ON accounts.id = users.account_id
  460. LEFT JOIN account_stats AS s ON accounts.id = s.account_id
  461. WHERE to_tsquery('simple', :tsquery) @@ #{TEXTSEARCH}
  462. AND accounts.suspended_at IS NULL
  463. AND accounts.moved_to_account_id IS NULL
  464. AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
  465. GROUP BY accounts.id, s.id
  466. ORDER BY rank DESC
  467. LIMIT :limit OFFSET :offset
  468. SQL
  469. end
  470. end
  471. end
  472. def emojis
  473. @emojis ||= CustomEmoji.from_text(emojifiable_text, domain)
  474. end
  475. before_create :generate_keys
  476. before_validation :prepare_contents, if: :local?
  477. before_validation :prepare_username, on: :create
  478. before_destroy :clean_feed_manager
  479. def ensure_keys!
  480. return unless local? && private_key.blank? && public_key.blank?
  481. generate_keys
  482. save!
  483. end
  484. private
  485. def prepare_contents
  486. display_name&.strip!
  487. note&.strip!
  488. end
  489. def prepare_username
  490. username&.squish!
  491. end
  492. def generate_keys
  493. return unless local? && private_key.blank? && public_key.blank?
  494. keypair = OpenSSL::PKey::RSA.new(2048)
  495. self.private_key = keypair.to_pem
  496. self.public_key = keypair.public_key.to_pem
  497. end
  498. def normalize_domain
  499. return if local?
  500. super
  501. end
  502. def emojifiable_text
  503. [note, display_name, fields.map(&:name), fields.map(&:value)].join(' ')
  504. end
  505. def clean_feed_manager
  506. FeedManager.instance.clean_feeds!(:home, [id])
  507. end
  508. def create_canonical_email_block!
  509. return unless local? && user_email.present?
  510. begin
  511. CanonicalEmailBlock.create(reference_account: self, email: user_email)
  512. rescue ActiveRecord::RecordNotUnique
  513. # A canonical e-mail block may already exist for the same e-mail
  514. end
  515. end
  516. def destroy_canonical_email_block!
  517. return unless local?
  518. CanonicalEmailBlock.where(reference_account: self).delete_all
  519. end
  520. end