<commit_before>(ns braid.core.server.sync
  (:require
    [clojure.set :refer [difference intersection]]
    [clojure.string :as string]
    [environ.core :refer [env]]
    [mount.core :refer [defstate]]
    [schema.core :as s]
    [taoensso.timbre :as timbre :refer [debugf]]
    [taoensso.truss :refer [have]]
    [braid.core.module-helpers :refer [defhook]]
    [braid.core.common.schema :refer [new-message-valid? upload-valid?]]
    [braid.core.common.util :as util :refer [valid-nickname? valid-tag-name?]]
    [braid.core.server.bots :as bots]
    [braid.core.server.db :as db]
    [braid.core.server.db.bot :as bot]
    [braid.core.server.db.common :refer [bot->display]]
    [braid.core.server.db.group :as group]
    [braid.core.server.db.invitation :as invitation]
    [braid.core.server.db.message :as message]
    [braid.core.server.db.tag :as tag]
    [braid.core.server.db.thread :as thread]
    [braid.core.server.db.upload :as upload]
    [braid.core.server.db.user :as user]
    [braid.core.server.digest :as digest]
    [braid.core.server.events :as events]
    [braid.core.server.invite :as invites]
    [braid.core.server.message-format :refer [parse-tags-and-mentions]]
    [braid.core.server.search :as search]
    [braid.core.server.socket :refer [chsk-send! connected-uids]]
    [braid.core.server.sync-handler :refer [event-msg-handler]]
    [braid.core.server.sync-helpers :refer [broadcast-thread broadcast-user-change user-can-message? notify-users]]
    [braid.core.server.uploads :as s3-uploads]
    [braid.core.server.util :refer [valid-url?]]))

(defn user-can-delete-message?
  [user-id message-id]
  (or (= user-id (message/message-author message-id))
      (group/user-is-group-admin?
        user-id
        (message/message-group message-id))))


(defn notify-bots [new-message]
  ; Notify bots mentioned in the message
  (when-let [bot-name (second (re-find #"^/(\w+)\b" (:content new-message)))]
    (when-let [bot (bot/bot-by-name-in-group bot-name (new-message :group-id))]
      (timbre/debugf "notifying bot %s" bot)
      (future (bots/send-message-notification bot new-message))))
  ; Notify bots subscribed to the thread
  (doseq [bot (bot/bots-watching-thread (new-message :thread-id))]
    (timbre/debugf "notifying bot %s" bot)
    (future (bots/send-message-notification bot new-message)))
  ; Notify bots subscribed to all messages
  (doseq [bot (bot/bots-for-message (new-message :group-id))]
    (when (thread/thread-has-tags? (new-message :thread-id))
      (future (bots/send-message-notification bot new-message)))))

(defn broadcast-new-user-to-group
  [user-id group-id]
  (broadcast-group-change
    group-id
    [:braid.client/new-user [(user/user-by-id user-id) group-id]]))

;; Handlers

(defmethod event-msg-handler :chsk/ws-ping
  [ev-msg])
  ; Do nothing, just avoid unhandled event message


(defmethod event-msg-handler :chsk/uidport-open
  [{:as ev-msg :keys [user-id]}]
  (doseq [{group-id :id} (group/user-groups user-id)]
    (broadcast-group-change group-id [:braid.client/user-connected [group-id user-id]])))

(defmethod event-msg-handler :chsk/uidport-close
  [{:as ev-msg :keys [user-id]}]
  (doseq [{group-id :id} (group/user-groups user-id)]
    (broadcast-group-change group-id [:braid.client/user-disconnected [group-id user-id]])))

(defmethod event-msg-handler :braid.server/new-message
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (let [new-message (-> ?data
                        (update :mentioned-tag-ids vec)
                        (update :mentioned-user-ids vec)
                        (update-in [:content] #(apply str (take 5000 %)))
                        (assoc :created-at (java.util.Date.)))]
    (if (new-message-valid? new-message)
      (when (user-can-message? user-id new-message)
        (db/run-txns! (message/create-message-txn new-message))
        (when-let [cb ?reply-fn]
          (cb :braid/ok))
        (broadcast-thread (new-message :thread-id) [])
        (notify-users new-message)
        (notify-bots new-message))
      (do
        (timbre/warnf "Malformed new message: %s" (pr-str new-message))
        (when-let [cb ?reply-fn]
          (cb :braid/error))))))

(defmethod event-msg-handler :braid.server/retract-message
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (let [message-id ?data]
    (when (user-can-delete-message? user-id message-id)
      (let [msg-group (message/message-group message-id)
            msg-thread (message/message-thread message-id)]
        (db/run-txns! (message/retract-message-txn message-id))
        (broadcast-group-change msg-group
                                [:braid.client/message-deleted
                                 {:message-id message-id
                                  :thread-id msg-thread}])))))

(defmethod event-msg-handler :braid.server/tag-thread
  [{:as ev-msg :keys [?data user-id]}]
  (let [{:keys [thread-id tag-id]} ?data]
    (let [group-id (tag/tag-group-id tag-id)]
      (db/run-txns! (thread/tag-thread-txn group-id thread-id tag-id))
      (broadcast-thread thread-id []))))
    ; TODO do we need to notify-users and notify-bots


(defmethod event-msg-handler :braid.server/subscribe-to-tag
  [{:as ev-msg :keys [?data user-id]}]
  (db/run-txns! (tag/user-subscribe-to-tag-txn user-id ?data)))

(defmethod event-msg-handler :braid.server/unsubscribe-from-tag
  [{:as ev-msg :keys [?data user-id]}]
  (db/run-txns! (tag/user-unsubscribe-from-tag-txn user-id ?data)))

(defmethod event-msg-handler :braid.server/set-nickname
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (if (valid-nickname? (?data :nickname))
    (try
      (do (db/run-txns! (user/set-nickname-txn user-id (?data :nickname)))
          (broadcast-user-change user-id [:braid.client/name-change
                                          {:user-id user-id
                                           :group-ids (map :id (group/user-groups user-id))
                                           :nickname (?data :nickname)}])
          (when ?reply-fn (?reply-fn {:ok true})))
      (catch java.util.concurrent.ExecutionException _
        (when ?reply-fn (?reply-fn {:error "Nickname taken"}))))
    (when ?reply-fn (?reply-fn {:error "Invalid nickname"}))))

(defmethod event-msg-handler :braid.server/set-user-avatar
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (if (valid-url? ?data)
    (do (db/run-txns! (user/set-user-avatar-txn user-id ?data))
        (doseq [{group-id :id} (group/user-groups user-id)]
          (broadcast-group-change group-id [:braid.client/user-new-avatar
                                            {:user-id user-id
                                             :group-id group-id
                                             :avatar-url ?data}]))
        (when-let [r ?reply-fn] (r {:braid/ok true})))
    (do (timbre/warnf "Couldn't set user avatar to %s" ?data)<commit_msg>Refer fewer functions from helper ns<commit_after>(ns braid.core.server.sync
  (:require
    [clojure.set :refer [difference intersection]]
    [clojure.string :as string]
    [environ.core :refer [env]]
    [mount.core :refer [defstate]]
    [schema.core :as s]
    [taoensso.timbre :as timbre :refer [debugf]]
    [taoensso.truss :refer [have]]
    [braid.core.module-helpers :refer [defhook]]
    [braid.core.common.schema :refer [new-message-valid? upload-valid?]]
    [braid.core.common.util :as util :refer [valid-nickname? valid-tag-name?]]
    [braid.core.server.bots :as bots]
    [braid.core.server.db :as db]
    [braid.core.server.db.bot :as bot]
    [braid.core.server.db.common :refer [bot->display]]
    [braid.core.server.db.group :as group]
    [braid.core.server.db.invitation :as invitation]
    [braid.core.server.db.message :as message]
    [braid.core.server.db.tag :as tag]
    [braid.core.server.db.thread :as thread]
    [braid.core.server.db.upload :as upload]
    [braid.core.server.db.user :as user]
    [braid.core.server.digest :as digest]
    [braid.core.server.events :as events]
    [braid.core.server.invite :as invites]
    [braid.core.server.message-format :refer [parse-tags-and-mentions]]
    [braid.core.server.search :as search]
    [braid.core.server.socket :refer [chsk-send! connected-uids]]
    [braid.core.server.sync-handler :refer [event-msg-handler]]
    [braid.core.server.sync-helpers :as helpers :refer [broadcast-group-change]]
    [braid.core.server.uploads :as s3-uploads]
    [braid.core.server.util :refer [valid-url?]]))

(defn user-can-delete-message?
  [user-id message-id]
  (or (= user-id (message/message-author message-id))
      (group/user-is-group-admin?
        user-id
        (message/message-group message-id))))


(defn notify-bots [new-message]
  ; Notify bots mentioned in the message
  (when-let [bot-name (second (re-find #"^/(\w+)\b" (:content new-message)))]
    (when-let [bot (bot/bot-by-name-in-group bot-name (new-message :group-id))]
      (timbre/debugf "notifying bot %s" bot)
      (future (bots/send-message-notification bot new-message))))
  ; Notify bots subscribed to the thread
  (doseq [bot (bot/bots-watching-thread (new-message :thread-id))]
    (timbre/debugf "notifying bot %s" bot)
    (future (bots/send-message-notification bot new-message)))
  ; Notify bots subscribed to all messages
  (doseq [bot (bot/bots-for-message (new-message :group-id))]
    (when (thread/thread-has-tags? (new-message :thread-id))
      (future (bots/send-message-notification bot new-message)))))

(defn broadcast-new-user-to-group
  [user-id group-id]
  (broadcast-group-change
    group-id
    [:braid.client/new-user [(user/user-by-id user-id) group-id]]))

;; Handlers

(defmethod event-msg-handler :chsk/ws-ping
  [ev-msg])
  ; Do nothing, just avoid unhandled event message


(defmethod event-msg-handler :chsk/uidport-open
  [{:as ev-msg :keys [user-id]}]
  (doseq [{group-id :id} (group/user-groups user-id)]
    (broadcast-group-change group-id [:braid.client/user-connected [group-id user-id]])))

(defmethod event-msg-handler :chsk/uidport-close
  [{:as ev-msg :keys [user-id]}]
  (doseq [{group-id :id} (group/user-groups user-id)]
    (broadcast-group-change group-id [:braid.client/user-disconnected [group-id user-id]])))

(defmethod event-msg-handler :braid.server/new-message
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (let [new-message (-> ?data
                        (update :mentioned-tag-ids vec)
                        (update :mentioned-user-ids vec)
                        (update-in [:content] #(apply str (take 5000 %)))
                        (assoc :created-at (java.util.Date.)))]
    (if (new-message-valid? new-message)
      (when (helpers/user-can-message? user-id new-message)
        (db/run-txns! (message/create-message-txn new-message))
        (when-let [cb ?reply-fn]
          (cb :braid/ok))
        (helpers/broadcast-thread (new-message :thread-id) [])
        (helpers/notify-users new-message)
        (notify-bots new-message))
      (do
        (timbre/warnf "Malformed new message: %s" (pr-str new-message))
        (when-let [cb ?reply-fn]
          (cb :braid/error))))))

(defmethod event-msg-handler :braid.server/retract-message
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (let [message-id ?data]
    (when (user-can-delete-message? user-id message-id)
      (let [msg-group (message/message-group message-id)
            msg-thread (message/message-thread message-id)]
        (db/run-txns! (message/retract-message-txn message-id))
        (broadcast-group-change msg-group
                                [:braid.client/message-deleted
                                 {:message-id message-id
                                  :thread-id msg-thread}])))))

(defmethod event-msg-handler :braid.server/tag-thread
  [{:as ev-msg :keys [?data user-id]}]
  (let [{:keys [thread-id tag-id]} ?data]
    (let [group-id (tag/tag-group-id tag-id)]
      (db/run-txns! (thread/tag-thread-txn group-id thread-id tag-id))
      (helpers/broadcast-thread thread-id []))))
    ; TODO do we need to notify-users and notify-bots


(defmethod event-msg-handler :braid.server/subscribe-to-tag
  [{:as ev-msg :keys [?data user-id]}]
  (db/run-txns! (tag/user-subscribe-to-tag-txn user-id ?data)))

(defmethod event-msg-handler :braid.server/unsubscribe-from-tag
  [{:as ev-msg :keys [?data user-id]}]
  (db/run-txns! (tag/user-unsubscribe-from-tag-txn user-id ?data)))

(defmethod event-msg-handler :braid.server/set-nickname
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (if (valid-nickname? (?data :nickname))
    (try
      (do (db/run-txns! (user/set-nickname-txn user-id (?data :nickname)))
          (helpers/broadcast-user-change user-id [:braid.client/name-change
                                          {:user-id user-id
                                           :group-ids (map :id (group/user-groups user-id))
                                           :nickname (?data :nickname)}])
          (when ?reply-fn (?reply-fn {:ok true})))
      (catch java.util.concurrent.ExecutionException _
        (when ?reply-fn (?reply-fn {:error "Nickname taken"}))))
    (when ?reply-fn (?reply-fn {:error "Invalid nickname"}))))

(defmethod event-msg-handler :braid.server/set-user-avatar
  [{:as ev-msg :keys [?data ?reply-fn user-id]}]
  (if (valid-url? ?data)
    (do (db/run-txns! (user/set-user-avatar-txn user-id ?data))
        (doseq [{group-id :id} (group/user-groups user-id)]
          (broadcast-group-change group-id [:braid.client/user-new-avatar
                                            {:user-id user-id
                                             :group-id group-id
                                             :avatar-url ?data}]))
        (when-let [r ?reply-fn] (r {:braid/ok true})))
    (do (timbre/warnf "Couldn't set user avatar to %s" ?data)
{'fraction_non_alphanumeric': 0.16432299217164395, 'fraction_numerical': 0.0007248477819657872, 'mean_word_length': 2.57991696938246, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '793658', 'n_tokens_mistral': 4851, 'n_tokens_neox': 4804, 'n_words': 991}