# frozen_string_literal: true module Chat::DirectMessageChannelCreator class NotAllowed < StandardError end def self.create!(acting_user:, target_users:) Guardian.new(acting_user).ensure_can_create_direct_message! target_users.uniq! direct_message = DirectMessage.for_user_ids(target_users.map(&:id)) if direct_message chat_channel = ChatChannel.find_by!(chatable: direct_message) else enforce_max_direct_message_users!(acting_user, target_users) ensure_actor_can_communicate!(acting_user, target_users) direct_message = DirectMessage.create!(user_ids: target_users.map(&:id)) chat_channel = direct_message.create_chat_channel! end update_memberships(acting_user, target_users, chat_channel.id) ChatPublisher.publish_new_channel(chat_channel, target_users) chat_channel end private def self.enforce_max_direct_message_users!(acting_user, target_users) # We never want to prevent the actor from communicating with themself. target_users = target_users.reject { |user| user.id == acting_user.id } if !acting_user.staff? && target_users.size > SiteSetting.chat_max_direct_message_users if SiteSetting.chat_max_direct_message_users == 0 raise NotAllowed.new(I18n.t("chat.errors.over_chat_max_direct_message_users_allow_self")) else raise NotAllowed.new( I18n.t( "chat.errors.over_chat_max_direct_message_users", count: SiteSetting.chat_max_direct_message_users + 1, # +1 for the acting_user ), ) end end end def self.update_memberships(acting_user, target_users, chat_channel_id) sql_params = { acting_user_id: acting_user.id, user_ids: target_users.map(&:id), chat_channel_id: chat_channel_id, always_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], } DB.exec(<<~SQL, sql_params) INSERT INTO user_chat_channel_memberships( user_id, chat_channel_id, muted, following, desktop_notification_level, mobile_notification_level, created_at, updated_at ) VALUES( unnest(array[:user_ids]), :chat_channel_id, false, false, :always_notification_level, :always_notification_level, NOW(), NOW() ) ON CONFLICT (user_id, chat_channel_id) DO NOTHING; UPDATE user_chat_channel_memberships SET following = true WHERE user_id = :acting_user_id AND chat_channel_id = :chat_channel_id; SQL end def self.ensure_actor_can_communicate!(acting_user, target_users) # We never want to prevent the actor from communicating with themself. target_users = target_users.reject { |user| user.id == acting_user.id } screener = UserCommScreener.new(acting_user: acting_user, target_user_ids: target_users.map(&:id)) # People blocking the actor. screener.preventing_actor_communication.each do |user_id| raise NotAllowed.new( I18n.t( "chat.errors.not_accepting_dms", username: target_users.find { |user| user.id == user_id }.username, ), ) end # The actor cannot start DMs with people if they are not allowing anyone # to start DMs with them, that's no fair! if screener.actor_disallowing_all_pms? raise NotAllowed.new(I18n.t("chat.errors.actor_disallowed_dms")) end # People the actor is blocking. target_users.each do |target_user| if screener.actor_disallowing_pms?(target_user.id) raise NotAllowed.new( I18n.t( "chat.errors.actor_preventing_target_user_from_dm", username: target_user.username, ), ) end if screener.actor_ignoring?(target_user.id) raise NotAllowed.new( I18n.t("chat.errors.actor_ignoring_target_user", username: target_user.username), ) end if screener.actor_muting?(target_user.id) raise NotAllowed.new( I18n.t("chat.errors.actor_muting_target_user", username: target_user.username), ) end end end end