mirror of
https://github.com/discourse/discourse.git
synced 2025-05-14 02:50:29 +08:00

Currently in chat it was possible to have TL4 users to flag admins and silence them, this change should ensure it's never possible.
213 lines
7.1 KiB
Ruby
213 lines
7.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Acceptable options:
|
|
# - message: Used when the flag type is notify_user or notify_moderators and we have to create
|
|
# a separate PM.
|
|
# - is_warning: Staff can send warnings when using the notify_user flag.
|
|
# - take_action: Automatically approves the created reviewable and deletes the chat message.
|
|
# - queue_for_review: Adds a special reason to the reviewable score and creates the reviewable using
|
|
# the force_review option.
|
|
|
|
module Chat
|
|
class ReviewQueue
|
|
def flag_message(chat_message, guardian, flag_type_id, opts = {})
|
|
result = { success: false, errors: [] }
|
|
|
|
is_notify_type =
|
|
ReviewableScore.types.slice(:notify_user, :notify_moderators).values.include?(flag_type_id)
|
|
is_dm = chat_message.chat_channel.direct_message_channel?
|
|
|
|
raise Discourse::InvalidParameters.new(:flag_type) if is_dm && is_notify_type
|
|
|
|
guardian.ensure_can_flag_chat_message!(chat_message)
|
|
guardian.ensure_can_flag_message_as!(chat_message, flag_type_id, opts)
|
|
|
|
existing_reviewable = Reviewable.includes(:reviewable_scores).find_by(target: chat_message)
|
|
|
|
if !can_flag_again?(existing_reviewable, chat_message, guardian.user, flag_type_id)
|
|
result[:errors] << I18n.t("chat.reviewables.message_already_handled")
|
|
return result
|
|
end
|
|
|
|
payload = { message_cooked: chat_message.cooked }
|
|
|
|
if opts[:message].present? && !is_dm && is_notify_type
|
|
creator = companion_pm_creator(chat_message, guardian.user, flag_type_id, opts)
|
|
post = creator.create
|
|
|
|
if creator.errors.present?
|
|
creator.errors.full_messages.each { |msg| result[:errors] << msg }
|
|
return result
|
|
end
|
|
elsif is_dm
|
|
transcript = find_or_create_transcript(chat_message, guardian.user, existing_reviewable)
|
|
payload[:transcript_topic_id] = transcript.topic_id if transcript
|
|
end
|
|
|
|
queued_for_review = !!ActiveRecord::Type::Boolean.new.deserialize(opts[:queue_for_review])
|
|
|
|
reviewable =
|
|
Chat::ReviewableMessage.needs_review!(
|
|
created_by: guardian.user,
|
|
target: chat_message,
|
|
reviewable_by_moderator: true,
|
|
potential_spam: flag_type_id == ReviewableScore.types[:spam],
|
|
payload: payload,
|
|
)
|
|
reviewable.update(target_created_by: chat_message.user)
|
|
score =
|
|
reviewable.add_score(
|
|
guardian.user,
|
|
flag_type_id,
|
|
meta_topic_id: post&.topic_id,
|
|
take_action: opts[:take_action],
|
|
reason: queued_for_review ? "chat_message_queued_by_staff" : nil,
|
|
force_review: queued_for_review,
|
|
)
|
|
|
|
if opts[:take_action]
|
|
reviewable.perform(guardian.user, :agree_and_delete)
|
|
Chat::Publisher.publish_delete!(chat_message.chat_channel, chat_message)
|
|
else
|
|
enforce_auto_silence_threshold(reviewable)
|
|
Chat::Publisher.publish_flag!(chat_message, guardian.user, reviewable, score)
|
|
end
|
|
|
|
result.tap do |r|
|
|
r[:success] = true
|
|
r[:reviewable] = reviewable
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def enforce_auto_silence_threshold(reviewable)
|
|
auto_silence_duration = SiteSetting.chat_auto_silence_from_flags_duration
|
|
return if auto_silence_duration.zero?
|
|
return if reviewable.score <= Chat::ReviewableMessage.score_to_silence_user
|
|
|
|
user = reviewable.target_created_by
|
|
return if user.admin?
|
|
return unless user
|
|
return if user.silenced?
|
|
|
|
UserSilencer.silence(
|
|
user,
|
|
Discourse.system_user,
|
|
silenced_till: auto_silence_duration.minutes.from_now,
|
|
reason: I18n.t("chat.errors.auto_silence_from_flags"),
|
|
)
|
|
end
|
|
|
|
def companion_pm_creator(chat_message, flagger, flag_type_id, opts)
|
|
notifying_user = flag_type_id == ReviewableScore.types[:notify_user]
|
|
|
|
i18n_key = notifying_user ? "notify_user" : "notify_moderators"
|
|
|
|
title =
|
|
I18n.t(
|
|
"reviewable_score_types.#{i18n_key}.chat_pm_title",
|
|
channel_name: chat_message.chat_channel.title(flagger),
|
|
locale: SiteSetting.default_locale,
|
|
)
|
|
|
|
body =
|
|
I18n.t(
|
|
"reviewable_score_types.#{i18n_key}.chat_pm_body",
|
|
message: opts[:message],
|
|
link: chat_message.full_url,
|
|
locale: SiteSetting.default_locale,
|
|
)
|
|
|
|
create_args = {
|
|
archetype: Archetype.private_message,
|
|
title: title.truncate(SiteSetting.max_topic_title_length, separator: /\s/),
|
|
raw: body,
|
|
}
|
|
|
|
if notifying_user
|
|
create_args[:subtype] = TopicSubtype.notify_user
|
|
create_args[:target_usernames] = chat_message.user.username
|
|
|
|
create_args[:is_warning] = opts[:is_warning] if flagger.staff?
|
|
else
|
|
create_args[:subtype] = TopicSubtype.notify_moderators
|
|
create_args[:target_group_names] = [Group[:moderators].name]
|
|
end
|
|
|
|
PostCreator.new(flagger, create_args)
|
|
end
|
|
|
|
def find_or_create_transcript(chat_message, flagger, existing_reviewable)
|
|
previous_message_ids =
|
|
Chat::Message
|
|
.where(chat_channel: chat_message.chat_channel)
|
|
.where("id < ?", chat_message.id)
|
|
.order("created_at DESC")
|
|
.limit(10)
|
|
.pluck(:id)
|
|
.reverse
|
|
|
|
return if previous_message_ids.empty?
|
|
|
|
service =
|
|
Chat::TranscriptService.new(
|
|
chat_message.chat_channel,
|
|
Discourse.system_user,
|
|
messages_or_ids: previous_message_ids,
|
|
)
|
|
|
|
title =
|
|
I18n.t(
|
|
"chat.reviewables.direct_messages.transcript_title",
|
|
channel_name: chat_message.chat_channel.title(flagger),
|
|
locale: SiteSetting.default_locale,
|
|
)
|
|
|
|
body =
|
|
I18n.t(
|
|
"chat.reviewables.direct_messages.transcript_body",
|
|
transcript: service.generate_markdown,
|
|
locale: SiteSetting.default_locale,
|
|
)
|
|
|
|
create_args = {
|
|
archetype: Archetype.private_message,
|
|
title: title.truncate(SiteSetting.max_topic_title_length, separator: /\s/),
|
|
raw: body,
|
|
subtype: TopicSubtype.notify_moderators,
|
|
target_group_names: [Group[:moderators].name],
|
|
}
|
|
|
|
PostCreator.new(Discourse.system_user, create_args).create
|
|
end
|
|
|
|
def can_flag_again?(reviewable, message, flagger, flag_type_id)
|
|
return true if reviewable.blank?
|
|
|
|
flagger_has_pending_flags =
|
|
reviewable.reviewable_scores.any? { |rs| rs.user == flagger && rs.pending? }
|
|
|
|
if !flagger_has_pending_flags && flag_type_id == ReviewableScore.types[:notify_moderators]
|
|
return true
|
|
end
|
|
|
|
flag_used =
|
|
reviewable.reviewable_scores.any? do |rs|
|
|
rs.reviewable_score_type == flag_type_id && rs.pending?
|
|
end
|
|
handled_recently =
|
|
!(
|
|
reviewable.pending? ||
|
|
reviewable.updated_at < SiteSetting.cooldown_hours_until_reflag.to_i.hours.ago
|
|
)
|
|
|
|
latest_revision = message.revisions.last
|
|
edited_since_last_review =
|
|
latest_revision && latest_revision.updated_at > reviewable.updated_at
|
|
|
|
!flag_used && !flagger_has_pending_flags && (!handled_recently || edited_since_last_review)
|
|
end
|
|
end
|
|
end
|