Add basic support for group actors (#12071)
* Show badge on group actor in WebUI * Do not notify in case of by following group actor * If you mention group actor, also mention group actor followers * Relax characters that can be used in username (same as Application) * Revert "Relax characters that can be used in username (same as Application)" This reverts commit 7e10a137b878d0db1b5252c52106faef5e09ca4b. * Delete display_name method
This commit is contained in:
		
							parent
							
								
									f92ed32df4
								
							
						
					
					
						commit
						f43f1e0184
					
				
					 8 changed files with 118 additions and 9 deletions
				
			
		|  | @ -4,6 +4,74 @@ module StatusesHelper | |||
|   EMBEDDED_CONTROLLER = 'statuses' | ||||
|   EMBEDDED_ACTION = 'embed' | ||||
| 
 | ||||
|   def account_action_button(account) | ||||
|     if user_signed_in? | ||||
|       if account.id == current_user.account_id | ||||
|         link_to settings_profile_url, class: 'button logo-button' do | ||||
|           safe_join([svg_logo, t('settings.edit_profile')]) | ||||
|         end | ||||
|       elsif current_account.following?(account) || current_account.requested?(account) | ||||
|         link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do | ||||
|           safe_join([svg_logo, t('accounts.unfollow')]) | ||||
|         end | ||||
|       elsif !(account.memorial? || account.moved?) | ||||
|         link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do | ||||
|           safe_join([svg_logo, t('accounts.follow')]) | ||||
|         end | ||||
|       end | ||||
|     elsif !(account.memorial? || account.moved?) | ||||
|       link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do | ||||
|         safe_join([svg_logo, t('accounts.follow')]) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def minimal_account_action_button(account) | ||||
|     if user_signed_in? | ||||
|       return if account.id == current_user.account_id | ||||
| 
 | ||||
|       if current_account.following?(account) || current_account.requested?(account) | ||||
|         link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do | ||||
|           fa_icon('user-times fw') | ||||
|         end | ||||
|       elsif !(account.memorial? || account.moved?) | ||||
|         link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do | ||||
|           fa_icon('user-plus fw') | ||||
|         end | ||||
|       end | ||||
|     elsif !(account.memorial? || account.moved?) | ||||
|       link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do | ||||
|         fa_icon('user-plus fw') | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def svg_logo | ||||
|     content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976') | ||||
|   end | ||||
| 
 | ||||
|   def svg_logo_full | ||||
|     content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678') | ||||
|   end | ||||
| 
 | ||||
|   def account_badge(account, all: false) | ||||
|     if account.bot? | ||||
|       content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') | ||||
|     elsif account.group? | ||||
|       content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles') | ||||
|     elsif (Setting.show_staff_badge && account.user_staff?) || all | ||||
|       content_tag(:div, class: 'roles') do | ||||
|         if all && !account.user_staff? | ||||
|           content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role') | ||||
|         elsif account.user_admin? | ||||
|           content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') | ||||
|         elsif account.user_moderator? | ||||
|           content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def link_to_more(url) | ||||
|     link_to t('statuses.show_more'), url, class: 'load-more load-gap' | ||||
|   end | ||||
|  |  | |||
|  | @ -238,9 +238,18 @@ class Header extends ImmutablePureComponent { | |||
|     const content         = { __html: account.get('note_emojified') }; | ||||
|     const displayNameHtml = { __html: account.get('display_name_html') }; | ||||
|     const fields          = account.get('fields'); | ||||
|     const badge           = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null; | ||||
|     const acct            = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); | ||||
| 
 | ||||
|     let badge; | ||||
| 
 | ||||
|     if (account.get('bot')) { | ||||
|       badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>); | ||||
|     } else if (account.get('group')) { | ||||
|       badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>); | ||||
|     } else { | ||||
|       badge = null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={classNames('account__header', { inactive: !!account.get('moved') })} ref={this.setRef}> | ||||
|         <div className='account__header__image'> | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ class ActivityPub::Activity | |||
|   def distribute(status) | ||||
|     crawl_links(status) | ||||
| 
 | ||||
|     notify_about_reblog(status) if reblog_of_local_account?(status) | ||||
|     notify_about_reblog(status) if reblog_of_local_account?(status) && !reblog_by_following_group_account?(status) | ||||
|     notify_about_mentions(status) | ||||
| 
 | ||||
|     # Only continue if the status is supposed to have arrived in real-time. | ||||
|  | @ -105,6 +105,10 @@ class ActivityPub::Activity | |||
|     status.reblog? && status.reblog.account.local? | ||||
|   end | ||||
| 
 | ||||
|   def reblog_by_following_group_account?(status) | ||||
|     status.reblog? && status.account.group? && status.reblog.account.following?(status.account) | ||||
|   end | ||||
| 
 | ||||
|   def notify_about_reblog(status) | ||||
|     NotifyService.new.call(status.reblog.account, status) | ||||
|   end | ||||
|  |  | |||
|  | @ -68,10 +68,19 @@ class ActivityPub::TagManager | |||
|       if status.account.silenced? | ||||
|         # Only notify followers if the account is locally silenced | ||||
|         account_ids = status.active_mentions.pluck(:account_id) | ||||
|         to = status.account.followers.where(id: account_ids).map { |account| uri_for(account) } | ||||
|         to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) | ||||
|         to = status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| | ||||
|           result << uri_for(account) | ||||
|           result << account.followers_url if account.group? | ||||
|         end | ||||
|         to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| | ||||
|           result << uri_for(request.account) | ||||
|           result << request.account.followers_url if request.account.group? | ||||
|         end) | ||||
|       else | ||||
|         status.active_mentions.map { |mention| uri_for(mention.account) } | ||||
|         status.active_mentions.each_with_object([]) do |mention, result| | ||||
|           result << uri_for(mention.account) | ||||
|           result << mention.account.followers_url if mention.account.group? | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | @ -97,10 +106,19 @@ class ActivityPub::TagManager | |||
|       if status.account.silenced? | ||||
|         # Only notify followers if the account is locally silenced | ||||
|         account_ids = status.active_mentions.pluck(:account_id) | ||||
|         cc.concat(status.account.followers.where(id: account_ids).map { |account| uri_for(account) }) | ||||
|         cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) | ||||
|         cc.concat(status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| | ||||
|           result << uri_for(account) | ||||
|           result << account.followers_url if account.group? | ||||
|         end) | ||||
|         cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| | ||||
|           result << uri_for(request.account) | ||||
|           result << request.account.followers_url if request.account.group? | ||||
|         end) | ||||
|       else | ||||
|         cc.concat(status.active_mentions.map { |mention| uri_for(mention.account) }) | ||||
|         cc.concat(status.active_mentions.each_with_object([]) do |mention, result| | ||||
|           result << uri_for(mention.account) | ||||
|           result << mention.account.followers_url if mention.account.group? | ||||
|         end) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -93,6 +93,7 @@ class Account < ApplicationRecord | |||
|   scope :without_silenced, -> { where(silenced_at: nil) } | ||||
|   scope :recent, -> { reorder(id: :desc) } | ||||
|   scope :bots, -> { where(actor_type: %w(Application Service)) } | ||||
|   scope :groups, -> { where(actor_type: 'Group') } | ||||
|   scope :alphabetic, -> { order(domain: :asc, username: :asc) } | ||||
|   scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') } | ||||
|   scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) } | ||||
|  | @ -153,6 +154,12 @@ class Account < ApplicationRecord | |||
|     self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' | ||||
|   end | ||||
| 
 | ||||
|   def group? | ||||
|     actor_type == 'Group' | ||||
|   end | ||||
| 
 | ||||
|   alias group group? | ||||
| 
 | ||||
|   def acct | ||||
|     local? ? username : "#{username}@#{domain}" | ||||
|   end | ||||
|  |  | |||
|  | @ -49,6 +49,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | |||
|       'Application' | ||||
|     elsif object.bot? | ||||
|       'Service' | ||||
|     elsif object.group? | ||||
|       'Group' | ||||
|     else | ||||
|       'Person' | ||||
|     end | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| class REST::AccountSerializer < ActiveModel::Serializer | ||||
|   include RoutingHelper | ||||
| 
 | ||||
|   attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :created_at, | ||||
|   attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, | ||||
|              :note, :url, :avatar, :avatar_static, :header, :header_static, | ||||
|              :followers_count, :following_count, :statuses_count, :last_status_at | ||||
| 
 | ||||
|  |  | |||
|  | @ -78,6 +78,7 @@ en: | |||
|     roles: | ||||
|       admin: Admin | ||||
|       bot: Bot | ||||
|       group: Group | ||||
|       moderator: Mod | ||||
|     unavailable: Profile unavailable | ||||
|     unfollow: Unfollow | ||||
|  |  | |||
		Reference in a new issue