[mentions] Replace all mentions with public keys before sending message

This commit is contained in:
Roman Volosovskyi 2020-08-24 21:29:07 +03:00
parent 001789f761
commit 2934de9b8c
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
4 changed files with 214 additions and 8 deletions

View File

@ -9,7 +9,8 @@
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
["emojilib" :as emojis])) ["emojilib" :as emojis]
[status-im.chat.models.mentions :as mentions]))
(defn text->emoji (defn text->emoji
"Replaces emojis in a specified `text`" "Replaces emojis in a specified `text`"
@ -130,7 +131,8 @@
(fx/defn send-current-message (fx/defn send-current-message
"Sends message from current chat input" "Sends message from current chat input"
[{{:keys [current-chat-id] :as db} :db :as cofx}] [{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])] (let [{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])
input-text-with-mentions (mentions/check-mentions cofx input-text)]
(fx/merge cofx (fx/merge cofx
(send-image) (send-image)
(send-plain-text-message input-text current-chat-id)))) (send-plain-text-message input-text-with-mentions current-chat-id))))

View File

@ -0,0 +1,123 @@
(ns status-im.chat.models.mentions
(:require [clojure.string :as string]
[status-im.contact.db :as contact.db]))
(defn get-mentionable-users
[{{:keys [current-chat-id messages]
:contacts/keys [contacts] :as db} :db}]
(let [{:keys [group-chat public?] :as chat}
(get-in db [:chats current-chat-id])
chat-specific-suggestions
(cond
(and group-chat (not public?))
(let [{:keys [public-key]} (:multiaccount db)
all-contacts (:contacts/contacts db)
group-contacts
(contact.db/get-all-contacts-in-group-chat
(disj (:contacts chat) public-key)
nil
all-contacts
nil)]
(reduce
(fn [acc {:keys [alias public-key identicon name]}]
(assoc acc alias
{:alias alias
:identicon identicon
:public-key public-key
:name name}))
{}
group-contacts))
(and group-chat public?)
(reduce
(fn [acc [_ {:keys [alias name identicon from]}]]
(assoc acc alias {:alias alias
:name (or name alias)
:identicon identicon
:public-key from}))
nil
(get messages current-chat-id))
:else {})]
(reduce
(fn [acc [key {:keys [alias name identicon]}]]
(let [name (string/replace name ".stateofus.eth" "")]
(assoc acc alias {:alias alias
:name (or name alias)
:identicon identicon
:public-key key})))
chat-specific-suggestions
contacts)))
(def word-regex #"^[\S]*\s|^[\S]*$")
(defn mentioned? [{:keys [alias name]} text]
(let [lcase-name (string/lower-case name)
lcase-alias (string/lower-case alias)
regex (re-pattern
(string/join
"|"
[(str "^" lcase-name "\\s")
(str "^" lcase-name "$")
(str "^" lcase-alias "\\s")
(str "^" lcase-alias "$")]))
lcase-text (string/lower-case text)]
(re-find regex lcase-text)))
(defn match-mention
([text users mention-key-idx]
(match-mention text users mention-key-idx (inc mention-key-idx) []))
([text users mention-key-idx next-word-idx words]
(let [trimmed-text (subs text next-word-idx)]
(when-let [word (string/trim (re-find word-regex trimmed-text))]
(let [words (conj words word)
searched-text (string/lower-case (string/join " " words))
suggestions (filter
(fn [[_ {:keys [alias name]}]]
(let [names (set [alias name])]
(some
(fn [username]
(string/starts-with?
(string/lower-case username)
searched-text))
names)))
users)
suggestions-cnt (count suggestions)]
(cond (zero? suggestions-cnt)
nil
(and (= 1 suggestions-cnt)
(mentioned? (second (first suggestions))
(subs text (inc mention-key-idx))))
(second (first suggestions))
(> suggestions-cnt 1)
(let [word-len (count word)
text-len (count text)
next-word-start (+ next-word-idx (inc word-len))]
(when (> text-len next-word-start)
(match-mention text users mention-key-idx
next-word-start words)))))))))
(defn replace-mentions
([text users-fn]
(replace-mentions text users-fn 0))
([text users-fn idx]
(let [mention-key-idx (string/index-of text "@" idx)]
(if-not mention-key-idx
text
(let [users (users-fn)
{:keys [public-key alias]}
(match-mention text users mention-key-idx)]
(if-not alias
(recur text (fn [] users) (inc mention-key-idx))
(let [new-text (string/join
[(subs text 0 (inc mention-key-idx))
public-key
(subs text (+ (inc mention-key-idx)
(count alias)))])
mention-end (+ (inc mention-key-idx) (count public-key))]
(recur new-text (fn [] users) mention-end))))))))
(defn check-mentions [cofx text]
(replace-mentions text #(get-mentionable-users cofx)))

View File

@ -0,0 +1,78 @@
(ns status-im.chat.models.mentions-test
(:require [status-im.chat.models.mentions :as mentions]
[clojure.string :as string]
[cljs.test :as test :include-macros true]))
(test/deftest test-replace-mentions
(let [users (fn []
{"User Number One"
{:name "User Number One"
:alias "User Number One"
:public-key "0xpk1"}
"User Number Two"
{:name "user2"
:alias "User Number Two"
:public-key "0xpk2"}
"User Number Three"
{:name "user3"
:alias "User Number Three"
:public-key "0xpk3"}})]
(test/testing "no mentions"
(let [text "foo bar @buzz kek @foo"
result (mentions/replace-mentions text users)]
(test/is (= result text) (pr-str text))))
(test/testing "starts with mention"
(let [text "@User Number One"
result (mentions/replace-mentions text users)]
(test/is (= result "@0xpk1") (pr-str text))))
(test/testing "starts with mention but no space after"
(let [text "@User Number Onefoo"
result (mentions/replace-mentions text users)]
(test/is (= result text) (pr-str text))))
(test/testing "starts with mention, some text after mention"
(let [text "@User Number One foo"
result (mentions/replace-mentions text users)]
(test/is (= result "@0xpk1 foo") (pr-str text))))
(test/testing "starts with some text, then mention"
(let [text "text @User Number One"
result (mentions/replace-mentions text users)]
(test/is (= result "text @0xpk1") (pr-str text))))
(test/testing "starts with some text, then mention, then more text"
(let [text "text @User Number One foo"
result (mentions/replace-mentions text users)]
(test/is (= result "text @0xpk1 foo") (pr-str text))))
(test/testing "no space before mention"
(let [text "text@User Number One"
result (mentions/replace-mentions text users)]
(test/is (= result "text@0xpk1") (pr-str text))))
(test/testing "two different mentions"
(let [text "@User Number One @User Number two"
result (mentions/replace-mentions text users)]
(test/is (= result "@0xpk1 @0xpk2") (pr-str text))))
(test/testing "two different mentions inside text"
(let [text "foo@User Number One bar @User Number two baz"
result (mentions/replace-mentions text users)]
(test/is (= result "foo@0xpk1 bar @0xpk2 baz") (pr-str text))))
(test/testing "ens mention"
(let [text "@user2"
result (mentions/replace-mentions text users)]
(test/is (= result "@0xpk2") (pr-str text))))
(test/testing "multiple mentions"
(let [text (string/join
" "
(repeat 1000 "@User Number One @User Number two"))
result (mentions/replace-mentions text users)
exprected-result (string/join
" "
(repeat 1000 "@0xpk1 @0xpk2"))]
(test/is (= exprected-result result))))))

View File

@ -51,11 +51,14 @@
(defn get-all-contacts-in-group-chat (defn get-all-contacts-in-group-chat
[members admins contacts {:keys [public-key] :as current-account}] [members admins contacts {:keys [public-key] :as current-account}]
(let [current-contact (-> current-account (let [current-contact (some->
current-account
(select-keys [:name :preferred-name :public-key :photo-path]) (select-keys [:name :preferred-name :public-key :photo-path])
(clojure.set/rename-keys {:name :alias (clojure.set/rename-keys {:name :alias
:preferred-name :name})) :preferred-name :name}))
all-contacts (assoc contacts public-key current-contact)] all-contacts (cond-> contacts
current-contact
(assoc public-key current-contact))]
(->> members (->> members
(map #(or (get all-contacts %) (map #(or (get all-contacts %)
(public-key->new-contact %))) (public-key->new-contact %)))