mirror of
https://github.com/discourse/discourse.git
synced 2025-04-26 09:24:29 +08:00

This commit main goal was to comply with Zeitwerk and properly rely on autoloading. To achieve this, most resources have been namespaced under the `Chat` module. - Given all models are now namespaced with `Chat::` and would change the stored types in DB when using polymorphism or STI (single table inheritance), this commit uses various Rails methods to ensure proper class is loaded and the stored name in DB is unchanged, eg: `Chat::Message` model will be stored as `"ChatMessage"`, and `"ChatMessage"` will correctly load `Chat::Message` model. - Jobs are now using constants only, eg: `Jobs::Chat::Foo` and should only be enqueued this way Notes: - This commit also used this opportunity to limit the number of registered css files in plugin.rb - `discourse_dev` support has been removed within this commit and will be reintroduced later <!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
212 lines
7.1 KiB
Ruby
212 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 reviwable 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 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
|