# 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. class Chat::ChatReviewQueue 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 = ReviewableChatMessage.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) ChatPublisher.publish_delete!(chat_message.chat_channel, chat_message) else enforce_auto_silence_threshold(reviewable) ChatPublisher.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 <= ReviewableChatMessage.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 = ChatMessage .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 = ChatTranscriptService.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