account_interactions.rb 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # frozen_string_literal: true
  2. module AccountInteractions
  3. extend ActiveSupport::Concern
  4. class_methods do
  5. def following_map(target_account_ids, account_id)
  6. Follow.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow, mapping|
  7. mapping[follow.target_account_id] = {
  8. reblogs: follow.show_reblogs?,
  9. notify: follow.notify?,
  10. }
  11. end
  12. end
  13. def followed_by_map(target_account_ids, account_id)
  14. follow_mapping(Follow.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
  15. end
  16. def blocking_map(target_account_ids, account_id)
  17. follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
  18. end
  19. def blocked_by_map(target_account_ids, account_id)
  20. follow_mapping(Block.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
  21. end
  22. def muting_map(target_account_ids, account_id)
  23. Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
  24. mapping[mute.target_account_id] = {
  25. notifications: mute.hide_notifications?,
  26. }
  27. end
  28. end
  29. def requested_map(target_account_ids, account_id)
  30. FollowRequest.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow_request, mapping|
  31. mapping[follow_request.target_account_id] = {
  32. reblogs: follow_request.show_reblogs?,
  33. notify: follow_request.notify?,
  34. }
  35. end
  36. end
  37. def endorsed_map(target_account_ids, account_id)
  38. follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id)
  39. end
  40. def account_note_map(target_account_ids, account_id)
  41. AccountNote.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |note, mapping|
  42. mapping[note.target_account_id] = {
  43. comment: note.comment,
  44. }
  45. end
  46. end
  47. def domain_blocking_map(target_account_ids, account_id)
  48. accounts_map = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain }
  49. blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
  50. accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) }
  51. end
  52. def domain_blocking_map_by_domain(target_domains, account_id)
  53. follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
  54. end
  55. private
  56. def follow_mapping(query, field)
  57. query.pluck(field).index_with(true)
  58. end
  59. end
  60. included do
  61. # Follow relations
  62. has_many :follow_requests, dependent: :destroy
  63. has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
  64. has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
  65. has_many :following, -> { order('follows.id desc') }, through: :active_relationships, source: :target_account
  66. has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
  67. # Block relationships
  68. has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
  69. has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
  70. has_many :blocked_by_relationships, class_name: 'Block', foreign_key: :target_account_id, dependent: :destroy
  71. has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
  72. # Mute relationships
  73. has_many :mute_relationships, class_name: 'Mute', foreign_key: 'account_id', dependent: :destroy
  74. has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
  75. has_many :muted_by_relationships, class_name: 'Mute', foreign_key: :target_account_id, dependent: :destroy
  76. has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
  77. has_many :conversation_mutes, dependent: :destroy
  78. has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
  79. has_many :announcement_mutes, dependent: :destroy
  80. end
  81. def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false)
  82. rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
  83. .find_or_create_by!(target_account: other_account)
  84. rel.show_reblogs = reblogs unless reblogs.nil?
  85. rel.notify = notify unless notify.nil?
  86. rel.save! if rel.changed?
  87. remove_potential_friendship(other_account)
  88. rel
  89. end
  90. def request_follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false)
  91. rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
  92. .find_or_create_by!(target_account: other_account)
  93. rel.show_reblogs = reblogs unless reblogs.nil?
  94. rel.notify = notify unless notify.nil?
  95. rel.save! if rel.changed?
  96. remove_potential_friendship(other_account)
  97. rel
  98. end
  99. def block!(other_account, uri: nil)
  100. remove_potential_friendship(other_account)
  101. block_relationships.create_with(uri: uri)
  102. .find_or_create_by!(target_account: other_account)
  103. end
  104. def mute!(other_account, notifications: nil, duration: 0)
  105. notifications = true if notifications.nil?
  106. mute = mute_relationships.create_with(hide_notifications: notifications).find_or_initialize_by(target_account: other_account)
  107. mute.expires_in = duration.zero? ? nil : duration
  108. mute.save!
  109. remove_potential_friendship(other_account)
  110. # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
  111. if mute.hide_notifications? != notifications
  112. mute.update!(hide_notifications: notifications)
  113. end
  114. mute
  115. end
  116. def mute_conversation!(conversation)
  117. conversation_mutes.find_or_create_by!(conversation: conversation)
  118. end
  119. def block_domain!(other_domain)
  120. domain_blocks.find_or_create_by!(domain: other_domain)
  121. end
  122. def unfollow!(other_account)
  123. follow = active_relationships.find_by(target_account: other_account)
  124. follow&.destroy
  125. end
  126. def unblock!(other_account)
  127. block = block_relationships.find_by(target_account: other_account)
  128. block&.destroy
  129. end
  130. def unmute!(other_account)
  131. mute = mute_relationships.find_by(target_account: other_account)
  132. mute&.destroy
  133. end
  134. def unmute_conversation!(conversation)
  135. mute = conversation_mutes.find_by(conversation: conversation)
  136. mute&.destroy!
  137. end
  138. def unblock_domain!(other_domain)
  139. block = domain_blocks.find_by(domain: other_domain)
  140. block&.destroy
  141. end
  142. def following?(other_account)
  143. active_relationships.where(target_account: other_account).exists?
  144. end
  145. def blocking?(other_account)
  146. block_relationships.where(target_account: other_account).exists?
  147. end
  148. def domain_blocking?(other_domain)
  149. domain_blocks.where(domain: other_domain).exists?
  150. end
  151. def muting?(other_account)
  152. mute_relationships.where(target_account: other_account).exists?
  153. end
  154. def muting_conversation?(conversation)
  155. conversation_mutes.where(conversation: conversation).exists?
  156. end
  157. def muting_notifications?(other_account)
  158. mute_relationships.where(target_account: other_account, hide_notifications: true).exists?
  159. end
  160. def muting_reblogs?(other_account)
  161. active_relationships.where(target_account: other_account, show_reblogs: false).exists?
  162. end
  163. def requested?(other_account)
  164. follow_requests.where(target_account: other_account).exists?
  165. end
  166. def favourited?(status)
  167. status.proper.favourites.where(account: self).exists?
  168. end
  169. def bookmarked?(status)
  170. status.proper.bookmarks.where(account: self).exists?
  171. end
  172. def reblogged?(status)
  173. status.proper.reblogs.where(account: self).exists?
  174. end
  175. def pinned?(status)
  176. status_pins.where(status: status).exists?
  177. end
  178. def endorsed?(account)
  179. account_pins.where(target_account: account).exists?
  180. end
  181. def followers_for_local_distribution
  182. followers.local
  183. .joins(:user)
  184. .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
  185. end
  186. def lists_for_local_distribution
  187. lists.joins(account: :user)
  188. .where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
  189. end
  190. def remote_followers_hash(url_prefix)
  191. Rails.cache.fetch("followers_hash:#{id}:#{url_prefix}") do
  192. digest = "\x00" * 32
  193. followers.where(Account.arel_table[:uri].matches(url_prefix + '%', false, true)).pluck_each(:uri) do |uri|
  194. Xorcist.xor!(digest, Digest::SHA256.digest(uri))
  195. end
  196. digest.unpack('H*')[0]
  197. end
  198. end
  199. def local_followers_hash
  200. Rails.cache.fetch("followers_hash:#{id}:local") do
  201. digest = "\x00" * 32
  202. followers.where(domain: nil).pluck_each(:username) do |username|
  203. Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username)))
  204. end
  205. digest.unpack('H*')[0]
  206. end
  207. end
  208. private
  209. def remove_potential_friendship(other_account, mutual = false)
  210. PotentialFriendshipTracker.remove(id, other_account.id)
  211. PotentialFriendshipTracker.remove(other_account.id, id) if mutual
  212. end
  213. end