diff options
47 files changed, 236 insertions, 112 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 3359eafdf..8bff3ab18 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -16,7 +16,8 @@ class AccountsController < ApplicationController unless current_account&.id == @account.id if @account.hidden || @account&.user&.hides_public_profile? - return not_found unless current_account&.following?(@account) + not_found unless current_account&.following?(@account) + return end end @@ -44,10 +45,12 @@ class AccountsController < ApplicationController format.rss do expires_in 1.minute, public: true - return not_found unless current_account&.user&.allows_rss? - - @statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE) - @statuses = cache_collection(@statuses, Status) + if current_account&.user&.allows_rss? + @statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE) + @statuses = cache_collection(@statuses, Status) + else + @statuses = [] + end render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag]) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3169151a8..b6c2feafb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -63,6 +63,10 @@ class ApplicationController < ActionController::Base forbidden unless current_user&.staff? end + def require_halfmod! + forbidden unless current_user&.halfmod? + end + def check_user_permissions forbidden if current_user.disabled? || current_user.account.suspended? end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index 6b3f0d311..dab613085 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -25,7 +25,7 @@ class Settings::ProfilesController < Settings::BaseController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :replies, :locked, :hidden, :unlisted, :block_anon, :gently, :kobold, :adult_content, :bot, :discoverable, :filter_undescribed, fields_attributes: [:name, :value]) + params.require(:account).permit(:display_name, :note, :avatar, :header, :replies, :locked, :hidden, :unlisted, :block_anon, :gently, :kobold, :adult_content, :bot, :discoverable, :filter_undescribed, :user_defanged, fields_attributes: [:name, :value]) end def set_account diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index c9e95d8d8..1a5f22e2a 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -64,14 +64,16 @@ module AccountsHelper 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 + elsif (Setting.show_staff_badge && account.user_can_moderate?) || all content_tag(:div, class: 'roles') do - if all && !account.user_staff? + if all && !account.user_can_moderate? 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') + elsif account.user_halfmod? + content_tag(:div, t('accounts.roles.halfmod'), class: 'account-role halfmod') end end end diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index d122a2860..574b3ad5c 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -56,13 +56,15 @@ module StatusesHelper roles << content_tag(:div, t('accounts.roles.gently'), class: 'account-role gently') if account.gently? roles << content_tag(:div, t('accounts.roles.kobold'), class: 'account-role kobold') if account.kobold? - if (Setting.show_staff_badge && account.user_staff?) || all + if (Setting.show_staff_badge && account.user_can_moderate?) || all if all && !account.user_staff? roles << content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role') elsif account.user_admin? roles << content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') elsif account.user_moderator? roles << content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') + elsif account.user_halfmod? + roles << content_tag(:div, t('accounts.roles.halfmod'), class: 'account-role halfmod') end end diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index d762e1087..553302cbc 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -193,6 +193,7 @@ class Header extends ImmutablePureComponent { const badge_gently = account.get('gently') ? (<div className='account-role gently'><FormattedMessage id='account.badges.gently' defaultMessage="Gentlies kobolds" /></div>) : null; const badge_kobold = account.get('kobold') ? (<div className='account-role kobold'><FormattedMessage id='account.badges.kobold' defaultMessage="Gently the kobold" /></div>) : null; const badge_mod = account.get('role') == 'moderator' ? (<div className='account-role moderator'><FormattedMessage id='account.badges.moderator' defaultMessage="Moderator" /></div>) : null; + const badge_halfmod = account.get('role') == 'halfmod' ? (<div className='account-role halfmod'><FormattedMessage id='account.badges.halfmod' defaultMessage="Half-moderator" /></div>) : null; const badge_admin = account.get('role') == 'admin' ? (<div className='account-role admin'><FormattedMessage id='account.badges.admin' defaultMessage="Admin" /></div>) : null; const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); @@ -225,7 +226,7 @@ class Header extends ImmutablePureComponent { <h1> <span dangerouslySetInnerHTML={displayNameHtml} /> <small>@{acct}</small> - <div className='roles'>{badge_admin}{badge_mod}{badge_froze}{badge_locked}{badge_limited}{badge_ac}{badge_bot}{badge_gently}{badge_kobold}</div> + <div className='roles'>{badge_admin}{badge_mod}{badge_halfmod}{badge_froze}{badge_locked}{badge_limited}{badge_ac}{badge_bot}{badge_gently}{badge_kobold}</div> </h1> </div> diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss index 108fd3451..7202ea8a3 100644 --- a/app/javascript/flavours/glitch/styles/accounts.scss +++ b/app/javascript/flavours/glitch/styles/accounts.scss @@ -212,6 +212,12 @@ background-color: rgba($ui-secondary-color, 0.1); border: 1px solid rgba($ui-secondary-color, 0.5); + &.halfmod { + color: lighten($success-green, 10%); + background-color: rgba(lighten($success-green, 10%), 0.1); + border-color: rgba(lighten(success-green, 10%), 0.5); + } + &.moderator { color: $success-green; background-color: rgba($success-green, 0.1); diff --git a/app/lib/bangtags.rb b/app/lib/bangtags.rb index 59ec16a23..ac5a4fce3 100644 --- a/app/lib/bangtags.rb +++ b/app/lib/bangtags.rb @@ -1299,6 +1299,18 @@ class Bangtags @vars.delete("_media:#{media_idx}:desc") end + when 'op', 'oper', 'fang', 'fangs' + chunk = nil + next unless @user.can_moderate? && @user.defanged? + @user.fangs_out! + service_dm('announcements', @account, "You are now in #{@user.role} mode. This will expire after 15 minutes.", footer: '#!fangs') + + when 'deop', 'deoper', 'defang' + chunk = nil + next if @user.defanged? + @user.defang! + service_dm('announcements', @account, "You are no longer in #{@user.role} mode.", footer: '#!defang') + when 'admin' next unless @user.admin? next if post_cmd[1].nil? diff --git a/app/models/account.rb b/app/models/account.rb index b0b9e9191..6f5a11ce0 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -132,7 +132,12 @@ class Account < ApplicationRecord :pending?, :admin?, :moderator?, + :halfmod?, :staff?, + :can_moderate?, + :defanged?, + :defanged, + :defanged=, :locale, :default_language, diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb index 58dffdc46..2da039efd 100644 --- a/app/models/concerns/user_roles.rb +++ b/app/models/concerns/user_roles.rb @@ -6,6 +6,7 @@ module UserRoles included do scope :admins, -> { where(admin: true) } scope :moderators, -> { where(moderator: true) } + scope :halfmods, -> { where(halfmod: true) } scope :staff, -> { admins.or(moderators) } end @@ -13,11 +14,17 @@ module UserRoles admin? || moderator? end + def can_moderate? + staff? || halfmod? + end + def role if admin? 'admin' elsif moderator? 'moderator' + elsif halfmod? + 'halfmod' else 'user' end @@ -27,6 +34,8 @@ module UserRoles case role when 'user' true + when 'halfmod' + halfmod? when 'moderator' staff? when 'admin' @@ -36,19 +45,45 @@ module UserRoles end end + def has_more_authority_than?(other_user) + if admin? + !other_user&.admin? + elsif moderator? + !other_user&.staff? + elsif halfmod? + !other_user&.can_moderate? + else + false + end + end + def promote! - if moderator? - update!(moderator: false, admin: true) + if halfmod? + update!(halfmod: false, moderator: true, admin: false) + elsif moderator? + update!(halfmod: false, moderator: false, admin: true) elsif !admin? - update!(moderator: true) + update!(halfmod: true, moderator: false, admin: false) end end def demote! if admin? - update!(admin: false, moderator: true) + update!(halfmod: false, moderator: true, admin: false) elsif moderator? - update!(moderator: false) + update!(halfmod: true, moderator: false, admin: false) + elsif halfmod? + update!(halfmod: false, moderator: false, admin: false) end end + + def fangs_out! + update!(defanged: false, last_fanged_at: Time.now.utc) + LogWorker.perform_async("\u23eb <#{self.account.username}> switched to fanged #{role} mode.") + end + + def defang! + update!(defanged: true, last_fanged_at: nil) + LogWorker.perform_async("\u23ec <#{self.account.username}> is no longer in fanged #{role} mode.") + end end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 00abb3906..c9cd3a87f 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -88,7 +88,7 @@ class Form::AdminSettings validates :site_short_description, :site_description, html: { wrap_with: :p } validates :site_extended_description, :site_terms, :closed_registrations_message, html: true validates :registrations_mode, inclusion: { in: %w(open approved none) } - validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) } + validates :min_invite_role, inclusion: { in: %w(disabled user halfmod moderator admin) } validates :site_contact_email, :site_contact_username, presence: true validates :site_contact_username, existing_username: true validates :bootstrap_timeline_accounts, existing_username: { multiple: true } diff --git a/app/models/user.rb b/app/models/user.rb index 267818eff..00e2af458 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -48,6 +48,9 @@ # filters_enabled :boolean default(FALSE), not null # monsterfork_api :integer default("full"), not null # allow_unknown_follows :boolean default(FALSE), not null +# defanged :boolean default(TRUE), not null +# halfmod :boolean default(FALSE), not null +# last_fanged_at :datetime # class User < ApplicationRecord diff --git a/app/policies/account_moderation_note_policy.rb b/app/policies/account_moderation_note_policy.rb index 885411a5b..781cf75ff 100644 --- a/app/policies/account_moderation_note_policy.rb +++ b/app/policies/account_moderation_note_policy.rb @@ -2,11 +2,11 @@ class AccountModerationNotePolicy < ApplicationPolicy def create? - staff? + !defanged? && can_moderate? end def destroy? - admin? || owner? + (!defanged? && admin?) || owner? end private diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index b05709183..3ac0c4c6a 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -2,90 +2,90 @@ class AccountPolicy < ApplicationPolicy def index? - staff? + !defanged? && can_moderate? end def show? - staff? + !defanged? && can_moderate? end def warn? - staff? && !record.user&.staff? + !defanged? && staff? && has_more_authority_than?(record&.user) end def mark_known? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def mark_unknown? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def manual_only? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def auto_trust? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def suspend? - staff? && !record.user&.staff? + !defanged? && staff? && has_more_authority_than?(record&.user) end def unsuspend? - staff? + !defanged? && staff? && has_more_authority_than?(record&.user) end def silence? - staff? && !record.user&.staff? + !defanged? && can_moderate? && has_more_authority_than?(record.user) end def unsilence? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def force_unlisted? - staff? + !defanged? && staff? && has_more_authority_than?(record&.user) end def allow_public? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def force_sensitive? - staff? + !defanged? && staff? && has_more_authority_than?(record&.user) end def allow_nonsensitive? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def redownload? - staff? + !defanged? && can_moderate? end def sync? - staff? + !defanged? && can_moderate? end def remove_avatar? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def remove_header? - staff? + !defanged? && can_moderate? && has_more_authority_than?(record&.user) end def subscribe? - admin? + !defanged? && admin? end def unsubscribe? - admin? + !defanged? && admin? end def memorialize? - admin? && !record.user&.admin? + !defanged? && staff? && !record.user&.staff? end end diff --git a/app/policies/account_warning_preset_policy.rb b/app/policies/account_warning_preset_policy.rb index bccbd33ef..4667c86b0 100644 --- a/app/policies/account_warning_preset_policy.rb +++ b/app/policies/account_warning_preset_policy.rb @@ -2,18 +2,18 @@ class AccountWarningPresetPolicy < ApplicationPolicy def index? - staff? + !defanged? && staff? end def create? - staff? + !defanged? && staff? end def update? - staff? + !defanged? && staff? end def destroy? - staff? + !defanged? && staff? end end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index d1de5e81a..7b1332209 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -8,7 +8,7 @@ class ApplicationPolicy @record = record end - delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true + delegate :admin?, :moderator?, :halfmod?, :staff?, :can_moderate?, :has_more_authority_than?, to: :current_user, allow_nil: true private diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb index 768afc3e9..74a38e47d 100644 --- a/app/policies/custom_emoji_policy.rb +++ b/app/policies/custom_emoji_policy.rb @@ -10,7 +10,7 @@ class CustomEmojiPolicy < ApplicationPolicy end def update? - staff? + can_moderate? end def copy? @@ -18,11 +18,11 @@ class CustomEmojiPolicy < ApplicationPolicy end def enable? - staff? + can_moderate? end def disable? - staff? + can_moderate? end def destroy? diff --git a/app/policies/domain_block_policy.rb b/app/policies/domain_block_policy.rb index 0ce6baccf..4cd4d550a 100644 --- a/app/policies/domain_block_policy.rb +++ b/app/policies/domain_block_policy.rb @@ -2,22 +2,22 @@ class DomainBlockPolicy < ApplicationPolicy def index? - staff? + !defanged? && staff? end def show? - staff? + !defanged? && staff? end def create? - staff? + !defanged? && staff? end def destroy? - staff? + !defanged? && staff? end def update? - staff? + !defanged? && staff? end end diff --git a/app/policies/email_domain_block_policy.rb b/app/policies/email_domain_block_policy.rb index 5a75ee183..36d547539 100644 --- a/app/policies/email_domain_block_policy.rb +++ b/app/policies/email_domain_block_policy.rb @@ -2,14 +2,14 @@ class EmailDomainBlockPolicy < ApplicationPolicy def index? - admin? + !defanged? && staff? end def create? - admin? + !defanged? && staff? end def destroy? - admin? + !defanged? && staff? end end diff --git a/app/policies/instance_policy.rb b/app/policies/instance_policy.rb index a73823556..f63107815 100644 --- a/app/policies/instance_policy.rb +++ b/app/policies/instance_policy.rb @@ -2,10 +2,10 @@ class InstancePolicy < ApplicationPolicy def index? - admin? + !defanged? && admin? end def show? - admin? + !defanged? && admin? end end diff --git a/app/policies/invite_policy.rb b/app/policies/invite_policy.rb index 14236f78b..44fa56049 100644 --- a/app/policies/invite_policy.rb +++ b/app/policies/invite_policy.rb @@ -2,7 +2,7 @@ class InvitePolicy < ApplicationPolicy def index? - staff? + !defanged? && can_moderate? end def create? @@ -10,11 +10,11 @@ class InvitePolicy < ApplicationPolicy end def deactivate_all? - admin? + !defanged? && admin? end def destroy? - owner? || (Setting.min_invite_role == 'admin' ? admin? : staff?) + owner? || (!defanged? && (Setting.min_invite_role == 'admin' ? admin? : can_moderate?)) end private diff --git a/app/policies/relay_policy.rb b/app/policies/relay_policy.rb index bd75e2197..5ad61a16d 100644 --- a/app/policies/relay_policy.rb +++ b/app/policies/relay_policy.rb @@ -2,6 +2,6 @@ class RelayPolicy < ApplicationPolicy def update? - admin? + !defanged? && admin? end end diff --git a/app/policies/report_note_policy.rb b/app/policies/report_note_policy.rb index 694bc096b..b6dde2f2b 100644 --- a/app/policies/report_note_policy.rb +++ b/app/policies/report_note_policy.rb @@ -2,11 +2,11 @@ class ReportNotePolicy < ApplicationPolicy def create? - staff? + !defanged? && staff? end def destroy? - admin? || owner? + (!defanged? && admin?) || owner? end private diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb index 95b5c30c8..6dbd37916 100644 --- a/app/policies/report_policy.rb +++ b/app/policies/report_policy.rb @@ -2,14 +2,14 @@ class ReportPolicy < ApplicationPolicy def update? - staff? + !defanged? && staff? end def index? - staff? + !defanged? && staff? end def show? - staff? + !defanged? && staff? end end diff --git a/app/policies/settings_policy.rb b/app/policies/settings_policy.rb index 2dcb79f51..3b170f6e2 100644 --- a/app/policies/settings_policy.rb +++ b/app/policies/settings_policy.rb @@ -2,10 +2,10 @@ class SettingsPolicy < ApplicationPolicy def update? - admin? + !defanged? && admin? end def show? - admin? + !defanged? && admin? end end diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index c573ba7a1..8600183dc 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -8,7 +8,7 @@ class StatusPolicy < ApplicationPolicy end def index? - staff? + !defanged? && staff? end def show? @@ -33,13 +33,13 @@ class StatusPolicy < ApplicationPolicy end def destroy? - staff? || owned? + (!defanged? && staff?) || owned? end alias unreblog? destroy? def update? - staff? + (!defanged? && staff?) || owned? end private diff --git a/app/policies/tag_policy.rb b/app/policies/tag_policy.rb index c63de01db..935040a21 100644 --- a/app/policies/tag_policy.rb +++ b/app/policies/tag_policy.rb @@ -2,14 +2,14 @@ class TagPolicy < ApplicationPolicy def index? - staff? + !defanged? && can_moderate? end def hide? - staff? + !defanged? && can_moderate? end def unhide? - staff? + !defanged? && can_moderate? end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index d832bff75..aad20f366 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -2,52 +2,52 @@ class UserPolicy < ApplicationPolicy def reset_password? - staff? && !record.staff? + !defanged? && staff? && has_more_authority_than?(record) end def change_email? - staff? && !record.staff? + !defanged? && staff? && has_more_authority_than?(record) end def disable_2fa? - admin? && !record.staff? + !defanged? && admin? && has_more_authority_than?(record) end def confirm? - staff? && !record.confirmed? + !defanged? && staff? && !record.confirmed? end def enable? - staff? + !defanged? && staff? end def approve? - staff? && !record.approved? + !defanged? && staff? && !record.approved? end def reject? - staff? && !record.approved? + !defanged? && staff? && !record.approved? end def disable? - staff? && !record.admin? + !defanged? && staff? && has_more_authority_than?(record) end def promote? - admin? && promoteable? + !defanged? && admin? && promoteable? end def demote? - admin? && !record.admin? && demoteable? + !defanged? && admin? && has_more_authority_than?(record) && demoteable? end private def promoteable? - record.approved? && (!record.staff? || !record.admin?) + record.approved? && !record.can_moderate? end def demoteable? - record.staff? + record.can_moderate? end end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index e22ebfd4d..5501ea040 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -49,7 +49,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers store[:reduce_motion] = object.current_account.user.setting_reduce_motion store[:advanced_layout] = object.current_account.user.setting_advanced_layout - store[:is_staff] = object.current_account.user.staff? + store[:is_staff] = object.current_account.user.staff? && !object.current_account.user.defanged? store[:default_content_type] = object.current_account.user.setting_default_content_type end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 43b05964e..28b9fde44 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -69,6 +69,7 @@ class REST::AccountSerializer < ActiveModel::Serializer def role return 'admin' if object.user_admin? return 'moderator' if object.user_moderator? + return 'halfmod' if object.user_halfmod? 'user' end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index b5c721589..34cd3e99d 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -68,7 +68,7 @@ class NotifyService < BaseService end def from_staff? - @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user.staff? + @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user.staff? && !@notification.from_account.user.defanged? end def optional_non_following_and_direct? diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index 966ee0a91..fd3429d94 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -10,21 +10,21 @@ = link_to custom_emoji.domain, admin_custom_emojis_path(by_domain: custom_emoji.domain) %td - if custom_emoji.local? - - if current_user.staff? + - if current_user.can_moderate? - if custom_emoji.visible_in_picker = table_link_to 'eye', t('admin.custom_emojis.listed'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: false }, page: params[:page], **@filter_params), method: :patch - else = table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }, page: params[:page], **@filter_params), method: :patch - else - if custom_emoji.local_counterpart.present? - - if current_user.staff? + - if current_user.can_moderate? = link_to safe_join([custom_emoji_tag(custom_emoji.local_counterpart), t('admin.custom_emojis.overwrite')]), copy_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post, class: 'table-action-link' - else %span.table-action-link="" - else = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post %td - - if current_user.staff? + - if current_user.can_moderate? - if custom_emoji.disabled? = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } - else diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml index bd1250ebd..1bc581652 100644 --- a/app/views/admin/settings/edit.html.haml +++ b/app/views/admin/settings/edit.html.haml @@ -104,7 +104,7 @@ %hr.spacer/ .fields-group - = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user halfmod moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' .fields-group = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 } diff --git a/app/views/settings/notifications/show.html.haml b/app/views/settings/notifications/show.html.haml index 6ec57b502..d7de635c6 100644 --- a/app/views/settings/notifications/show.html.haml +++ b/app/views/settings/notifications/show.html.haml @@ -12,7 +12,7 @@ = ff.input :favourite, as: :boolean, wrapper: :with_label = ff.input :mention, as: :boolean, wrapper: :with_label - - if current_user.staff? + - if current_user.can_moderate? = ff.input :report, as: :boolean, wrapper: :with_label = ff.input :pending_account, as: :boolean, wrapper: :with_label diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 4fabfb9f4..ba2fd7495 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -18,6 +18,12 @@ = f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT)) + - if @account.user_can_moderate? + %hr.spacer/ + + .fields-group + = f.input :user_defanged, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.user_defanged') + %hr.spacer/ .fields-group diff --git a/app/workers/scheduler/defang_scheduler.rb b/app/workers/scheduler/defang_scheduler.rb new file mode 100644 index 000000000..a20242a23 --- /dev/null +++ b/app/workers/scheduler/defang_scheduler.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Scheduler::DefangScheduler + include Sidekiq::Worker + include ServiceAccountHelper + + def perform + User.where(defanged: false, last_fanged_at: nil).or(User.where('last_fanged_at >= ?', 15.minutes.ago)) do + |user| user.defang! + next unless user&.account.present? + service_dm('announcements', user.account, "You are no longer in #{user.role} mode.", footer: 'auto-defang') + end + end +end diff --git a/config/locales/de.yml b/config/locales/de.yml index 63a89ee34..acba57868 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -160,7 +160,7 @@ de: role: Berechtigungen roles: admin: Administrator - moderator: Moderator:in + moderator: Moderator staff: Mitarbeiter user: Nutzer salmon_url: Salmon-URL diff --git a/config/locales/en.yml b/config/locales/en.yml index 426da8847..8bee7423e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -74,7 +74,9 @@ en: roles: admin: Admin bot: Bot + halfmod: Half-mod moderator: Mod + halfmod: Half-mod kobold: Gently the kobold gently: Gentlies kobolds adult: 🔞 Adult content @@ -177,6 +179,7 @@ en: roles: admin: Administrator moderator: Moderator + halfmod: Half-moderator staff: Staff user: User salmon_url: Salmon URL diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index d428a95c3..8a34b0167 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -157,6 +157,7 @@ en_GB: roles: admin: Administrator moderator: Moderator + halfmod: Half-moderator staff: Staff user: User salmon_url: Salmon URL diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 5e70347bf..a3c7748fe 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -10,6 +10,7 @@ en: type_html: Choose what to do with <strong>%{acct}</strong> warning_preset_id: Optional. You can still add custom text to end of the preset defaults: + user_defanged: Disable this to access to moderation and admin features for 15 minutes. block_anon: Links to your public roars are disabled unless they are made accessible by sharekey. Be aware that roars sent to other Fediverse servers can be publically indexed! unlisted: Excludes you from public repeated/admired by lists of *local* monsters autofollow: People who sign up through the invite will automatically join your pack @@ -83,6 +84,7 @@ en: suspend: Suspend and irreversibly delete account data warning_preset_id: Use a warning preset defaults: + user_defanged: Defanged mode hidden: Invisible mode (affects outgoing federation and discovery!) adult_content: Contains adult content gently: Gently the kobolds diff --git a/config/navigation.rb b/config/navigation.rb index 7862e219c..b2a5b555d 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -38,21 +38,21 @@ SimpleNavigation::Configuration.run do |navigation| n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_action_logs_url, if: proc { !current_user.nil? } do |s| s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url - s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}, if: -> { current_user.staff? } - s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}, if: -> { current_user.staff? } - s.item :pending, safe_join([fa_icon('clock-o fw'), t('admin.pending_accounts.title', count: User.pending.count)]), admin_pending_accounts_url, highlights_on: %r{/admin/pending_accounts}, if: -> { current_user.staff? } - s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.staff? } - s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path, if: -> { current_user.staff? } - s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.staff? } - s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.staff? } + s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}, if: -> { current_user.can_moderate? && !current_user.defanged? } + s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}, if: -> { current_user.can_moderate? && !current_user.defanged? } + s.item :pending, safe_join([fa_icon('clock-o fw'), t('admin.pending_accounts.title', count: User.pending.count)]), admin_pending_accounts_url, highlights_on: %r{/admin/pending_accounts}, if: -> { current_user.can_moderate? && !current_user.defanged? } + s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can_moderate? && !current_user.defanged? } + s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path, if: -> { current_user.can_moderate? && !current_user.defanged? } + s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.staff? && !current_user.defanged? } + s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.staff? && !current_user.defanged? } end - n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s| + n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? && !current_user.defanged? } do |s| s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url - s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings} - s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/relays} - s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } - s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } + s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? && !current_user.defanged? }, highlights_on: %r{/admin/settings} + s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !current_user.defanged? }, highlights_on: %r{/admin/relays} + s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? && !current_user.defanged? } + s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? && !current_user.defanged? } end n.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_url, link_html: { 'data-method' => 'delete' } diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 20ea95aad..230c85c43 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -34,6 +34,9 @@ ambassador_scheduler: every: '<%= ENV['AMBASSADOR_DELAY'] || 10 %>m' class: Scheduler::AmbassadorScheduler + defang_scheduler: + every: '15m' + class: Scheduler::DefangScheduler media_cleanup_scheduler: cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' class: Scheduler::MediaCleanupScheduler diff --git a/db/migrate/20200218032023_add_defanged_to_users.rb b/db/migrate/20200218032023_add_defanged_to_users.rb new file mode 100644 index 000000000..b5e6d02f6 --- /dev/null +++ b/db/migrate/20200218032023_add_defanged_to_users.rb @@ -0,0 +1,5 @@ +class AddDefangedToUsers < ActiveRecord::Migration[5.2] + def change + safety_assured { add_column :users, :defanged, :boolean, null: false, default: true } + end +end diff --git a/db/migrate/20200218033651_add_halfmod_to_users.rb b/db/migrate/20200218033651_add_halfmod_to_users.rb new file mode 100644 index 000000000..7693e8135 --- /dev/null +++ b/db/migrate/20200218033651_add_halfmod_to_users.rb @@ -0,0 +1,5 @@ +class AddHalfmodToUsers < ActiveRecord::Migration[5.2] + def change + safety_assured { add_column :users, :halfmod, :boolean, null: false, default: false } + end +end diff --git a/db/migrate/20200218070510_add_last_fanged_at_to_users.rb b/db/migrate/20200218070510_add_last_fanged_at_to_users.rb new file mode 100644 index 000000000..727a2cf55 --- /dev/null +++ b/db/migrate/20200218070510_add_last_fanged_at_to_users.rb @@ -0,0 +1,5 @@ +class AddLastFangedAtToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :last_fanged_at, :datetime + end +end diff --git a/db/structure.sql b/db/structure.sql index e8d415200..3bd504a46 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2402,7 +2402,10 @@ CREATE TABLE public.users ( filter_undescribed boolean DEFAULT false NOT NULL, filters_enabled boolean DEFAULT false NOT NULL, monsterfork_api smallint DEFAULT 2 NOT NULL, - allow_unknown_follows boolean DEFAULT false NOT NULL + allow_unknown_follows boolean DEFAULT false NOT NULL, + defanged boolean DEFAULT true NOT NULL, + halfmod boolean DEFAULT false NOT NULL, + last_fanged_at timestamp without time zone ); @@ -5424,6 +5427,9 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200215021732'), ('20200216000613'), ('20200217052742'), -('20200217055054'); +('20200217055054'), +('20200218032023'), +('20200218033651'), +('20200218070510'); diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 7d0215313..fb1aca639 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -61,8 +61,8 @@ module Mastodon With the --confirmed option, the confirmation e-mail will be skipped and the account will be active straight away. - With the --role option one of "user", "admin" or "moderator" - can be supplied. Defaults to "user" + With the --role option one of "user", "admin", "moderator", + or "halfmod" can be supplied. Defaults to "user" With the --reattach option, the new user will be reattached to a given existing username of an old account. If the old @@ -73,7 +73,7 @@ module Mastodon def create(username) account = Account.new(username: username) password = SecureRandom.hex - user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil) + user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', halfmod: options[:role] == 'halfmod', confirmed_at: options[:confirmed] ? Time.now.utc : nil) if options[:reattach] account = Account.find_local(username) || Account.new(username: username) |