Cljs commands

This commit is contained in:
janherich 2018-07-17 20:58:56 +02:00
parent d1bd86c894
commit 76a509ed22
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
57 changed files with 720 additions and 2722 deletions

View File

@ -1,106 +0,0 @@
(ns status-im.bots.events
(:require [re-frame.core :as re-frame]
[status-im.utils.core :as utils]
[status-im.utils.handlers :as handlers]
[status-im.chat.models.input :as input-model]
[taoensso.timbre :as log]))
;;;; Helper fns
(defn- subscription-values [sub-params current-bot-db]
(reduce (fn [sub-values [sub-key sub-path]]
(assoc sub-values sub-key (get-in current-bot-db sub-path)))
{}
sub-params))
(defn- check-subscriptions-fx
[{:keys [bot-db] :contacts/keys [contacts] :as app-db} {:keys [bot path]}]
(when-let [subscriptions (and bot (get-in contacts (concat [bot :subscriptions] [path])))]
{:call-jail-function-n
(for [[sub-name sub-params] subscriptions]
{:chat-id bot
:function :subscription
:parameters {:name sub-name
:subscriptions (subscription-values sub-params (get bot-db bot))}
:callback-event-creator (fn [jail-response]
[::calculated-subscription
{:bot bot
:path [sub-name]
:result jail-response}])})}))
(defn set-in-bot-db
"Associates value at specified path in bot-db and checks if there are any subscriptions
dependent on that path, if yes, adds effects for calling jail-functions to recalculate
relevant subscriptions."
[app-db {:keys [bot path value] :as params}]
(let [new-db (assoc-in app-db (concat [:bot-db bot] path) value)]
(merge {:db new-db}
(check-subscriptions-fx new-db params))))
(defn update-bot-db
[app-db {:keys [bot db]}]
(update-in app-db [:bot-db bot] merge db))
(defn clear-bot-db
[app-db bot-id]
(assoc-in app-db [:bot-db bot-id] nil))
(def ^:private keywordize-vector (partial mapv keyword))
(defn transform-bot-subscriptions
"Transforms bot subscriptions as returned from jail in the following format:
`{:calculatedFee {:subscriptions {:value [\"sliderValue\"]
:tx [\"transaction\"]}}
:feeExplanation {:subscriptions {:value [\"sliderValue\"]}}}`
into data-structure better suited for subscription lookups based on changes
in `:bot-db`:
`{[:sliderValue] {:calculatedFee {:value [:sliderValue]
:tx [:transaction]}
:feeExplanation {:value [:sliderValue]}}
[:transaction] {:calculatedFee {:value [:sliderValue]
:tx [:transaction]}}}`
In the resulting data-structure, the top level keys are the (keywordized) paths
in the `:bot-db` data structure, so it's quick and easy to look-up all the
subscriptions which must be recalculated when something in their path changes."
[bot-subscriptions]
(reduce-kv (fn [acc sub-key {:keys [subscriptions]}]
(reduce-kv (fn [acc sub-param-key sub-param-path]
(update acc
(keywordize-vector sub-param-path)
assoc sub-key
(utils/map-values subscriptions keywordize-vector)))
acc
subscriptions))
{}
bot-subscriptions))
(defn calculated-subscription
[db {:keys [bot path]
{:keys [error result]} :result}]
(if error
db
(assoc-in db (concat [:bot-db bot] path) (:returned result))))
;;;; Handlers
(handlers/register-handler-fx
:set-in-bot-db
[re-frame/trim-v]
(fn [{:keys [db]} [params]]
(set-in-bot-db db params)))
(handlers/register-handler-db
:update-bot-db
[re-frame/trim-v]
(fn [db [params]]
(update-bot-db db params)))
(handlers/register-handler-db
::calculated-subscription
[re-frame/trim-v]
(fn [db [params]]
(calculated-subscription db params)))

View File

@ -1,13 +0,0 @@
(ns status-im.bots.subs
(:require [re-frame.core :as re-frame]
[status-im.chat.models.input :as input-model]))
(re-frame/reg-sub :get-bot-db :bot-db)
(re-frame/reg-sub
:current-bot-db
:<- [:get-bot-db]
:<- [:selected-chat-command]
(fn [[bot-db command]]
(let [command-owner (get-in command [:command :owner-id])]
[command-owner (get bot-db command-owner)])))

View File

@ -1,31 +1,50 @@
(ns status-im.chat.commands.core
(:require [clojure.set :as set]
[clojure.string :as string]
[status-im.constants :as constants]
[status-im.chat.constants :as chat-constants]
[status-im.chat.commands.protocol :as protocol]
[status-im.chat.commands.impl.transactions :as transactions]
[status-im.chat.models.input :as input-model]))
[status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model]
[status-im.utils.handlers-macro :as handlers-macro]))
(def ^:private arg-wrapping-char "\"")
(def ^:private command-char "/")
(def ^:private space-char " ")
(def commands-register
(def register
"Register of all commands. Whenever implementing a new command,
provide the implementation in the `status-im.chat.commands.impl.*` ns,
and add its instance here."
#{(transactions/PersonalSendCommand.)})
(defn validate-and-send
"Validates and sends command in current chat"
[command cofx]
nil)
(defn send
"Sends command with given arguments in particular chat"
[command chat-id cofx]
nil)
#{(transactions/PersonalSendCommand.)
(transactions/PersonalRequestCommand.)})
(def command-id (juxt protocol/id protocol/scope))
(defn command-name
"Given the command instance, returns command name as displayed in chat input,
with leading `/` character."
[type]
(str chat-constants/command-char (protocol/id type)))
(defn command-description
"Returns description for the command."
[type]
(protocol/description type))
(defn accessibility-label
"Returns accessibility button label for command, derived from its id"
[type]
(keyword (str (protocol/id type) "-button")))
(defn generate-short-preview
"Returns short preview for command"
[{:keys [type]} command-message]
(protocol/short-preview type command-message))
(defn generate-preview
"Returns preview for command"
[{:keys [type]} command-message]
(protocol/preview type command-message))
(defn- prepare-params
"Prepares parameters sequence of command by providing suggestion components with
selected-event injected with correct arg indexes and `last-arg?` flag."
@ -73,9 +92,10 @@
(protocol/scope type)
protocol/or-scopes)]
(reduce (fn [acc access-scope]
(assoc acc
access-scope
command-id))
(update acc
access-scope
(fnil conj #{})
command-id))
acc
access-scopes)))
{}
@ -84,11 +104,76 @@
:id->command id->command
:access-scope->command-id access-scope->command-id)}))
(defn chat-commands
"Takes `id->command`, `access-scope->command-id` and `chat` parameters and returns
entries map of `id->command` map eligible for given chat.
Note that the result map is keyed just by `protocol/id` of command entries,
not the unique composite ids of the global `id->command` map.
That's because this function is already returning local commands for particular
chat and locally, they should always have unique `protocol/id`."
[id->command access-scope->command-id {:keys [chat-id group-chat public?]}]
(let [global-access-scope (cond-> #{}
(not group-chat) (conj :personal-chats)
(and group-chat (not public?)) (conj :group-chats)
public? (conj :public-chats))
chat-access-scope #{chat-id}]
(reduce (fn [acc command-id]
(let [{:keys [type] :as command-props} (get id->command command-id)]
(assoc acc (protocol/id type) command-props)))
{}
(concat (get access-scope->command-id global-access-scope)
(get access-scope->command-id chat-access-scope)))))
(defn- current-param-position [input-text selection]
(when selection
(when-let [subs-input-text (subs input-text 0 selection)]
(let [input-params (input-model/split-command-args subs-input-text)
param-index (dec (count input-params))
wrapping-count (get (frequencies subs-input-text) chat-constants/arg-wrapping-char 0)]
(if (and (string/ends-with? subs-input-text chat-constants/spacing-char)
(even? wrapping-count))
param-index
(dec param-index))))))
(defn- command-completion [input-params params]
(let [input-params-count (count input-params)
params-count (count params)]
(cond
(= input-params-count params-count) :complete
(< input-params-count params-count) :less-then-needed
(> input-params-count params-count) :more-than-needed)))
(defn selected-chat-command
"Takes input text, text-selection and `protocol/id->command-props` map (result of
the `chat-commands` fn) and returns the corresponding `command-props` entry,
or nothing if input text doesn't match any available command.
Besides keys `:params` and `:type`, the returned map contains:
* `:input-params` - parsed parameters from the input text as map of `param-id->entered-value`
# `:current-param-position` - index of the parameter the user is currently focused on (cursor position
in relation to parameters), could be nil if the input is not selected
# `:command-completion` - indication of command completion, possible values are `:complete`,
`:less-then-needed` and `more-then-needed`"
[input-text text-selection id->command-props]
(when (input-model/starts-as-command? input-text)
(let [[command-name & input-params] (input-model/split-command-args input-text)]
(when-let [{:keys [params] :as command-props} (get id->command-props (subs command-name 1))] ;; trim leading `/` for lookup
command-props
(let [input-params (into {}
(keep-indexed (fn [idx input-value]
(when (not (string/blank? input-value))
(when-let [param-name (get-in params [idx :id])]
[param-name input-value]))))
input-params)]
(assoc command-props
:input-params input-params
:current-param-position (current-param-position input-text text-selection)
:command-completion (command-completion input-params params)))))))
(defn set-command-parameter
"Set value as command parameter for the current chat"
[last-param? param-index value {:keys [db]}]
(let [{:keys [current-chat-id]} db
[command & params] (-> (get-in db [:chats current-chat-id :input-text])
(let [{:keys [current-chat-id chats]} db
[command & params] (-> (get-in chats [current-chat-id :input-text])
input-model/split-command-args)
param-count (count params)
;; put the new value at the right place in parameters array
@ -96,12 +181,36 @@
(< param-index param-count) (assoc param-index value)
(>= param-index param-count) (conj value))
;; if the parameter is not the last one for the command, add space
input-text (cond-> (str command space-char
input-text (cond-> (str command chat-constants/spacing-char
(input-model/join-command-args
new-params))
(and (not last-param?)
(or (= 0 param-count)
(= param-index (dec param-count))))
(str space-char))]
{:db (assoc-in db [:chats current-chat-id :input-text]
(input-model/text->emoji input-text))}))
(str chat-constants/spacing-char))]
{:db (-> db
(chat-model/set-chat-ui-props {:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text] input-text))}))
(defn select-chat-input-command
"Takes command and (optional) map of input-parameters map and sets it as current-chat input"
[{:keys [type params]} input-params {:keys [db]}]
(let [{:keys [current-chat-id chat-ui-props]} db]
{:db (-> db
(chat-model/set-chat-ui-props {:show-suggestions? false
:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text]
(str (command-name type)
chat-constants/spacing-char
(input-model/join-command-args input-params))))}))
(defn parse-parameters
"Parses parameters from input for defined command params,
returns map of `param-name->param-value`"
[params input-text]
(let [input-params (->> (input-model/split-command-args input-text) rest (into []))]
(into {}
(keep-indexed (fn [idx {:keys [id]}]
(when-let [value (get input-params idx)]
[id value])))
params)))

View File

@ -9,6 +9,7 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.chat-preview :as chat-preview]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.animation :as animation]
[status-im.i18n :as i18n]
@ -16,8 +17,10 @@
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.datetime :as datetime]
[status-im.ui.screens.wallet.send.events :as send.events]
[status-im.utils.money :as money]
[status-im.ui.screens.wallet.db :as wallet.db]
[status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events]
[status-im.wallet.transactions :as wallet.transactions]
[status-im.ui.screens.navigation :as navigation]))
;; common `send/request` functionality
@ -25,7 +28,7 @@
(defn- render-asset [selected-event-creator]
(fn [{:keys [name symbol amount decimals] :as asset}]
[react/touchable-highlight
{:on-press #(re-frame/dispatch (selected-event-creator symbol))}
{:on-press #(re-frame/dispatch (selected-event-creator (clojure.core/name symbol)))}
[react/view transactions-styles/asset-container
[react/view transactions-styles/asset-main
[react/image {:source (-> asset :icon :source)
@ -50,14 +53,17 @@
:keyboardShouldPersistTaps :always
:bounces false}]]))
(defn choose-asset-suggestion [selected-event-creator]
[choose-asset selected-event-creator])
(defn personal-send-request-short-preview
[{:keys [content]}]
(let [parameters (:params content)]
[react/text {}
(str (i18n/label :command-sending)
(i18n/label-number (:amount parameters))
[label-key {:keys [content]}]
(let [{:keys [amount asset]} (:params content)]
[chat-preview/text {}
(str (i18n/label label-key)
(i18n/label-number amount)
" "
(:asset parameters))]))
asset)]))
(def personal-send-request-params
[{:id :asset
@ -66,7 +72,7 @@
;; Suggestion components should be structured in such way that they will just take
;; one argument, event-creator fn used to construct event to fire whenever something
;; is selected.
:suggestions choose-asset}
:suggestions choose-asset-suggestion}
{:id :amount
:type :number
:placeholder "Amount"}])
@ -98,7 +104,7 @@
:else
(let [sanitised-str (string/replace amount #"," ".")
portions (string/split sanitised-str ".")
decimals (get portions 1)
decimals (count (get portions 1))
amount (js/parseFloat sanitised-str)]
(cond
@ -173,57 +179,76 @@
:gas (ethereum/estimate-gas symbol)
:from-chat? true)))
(defn- inject-network-price-info [{:keys [amount asset] :as parameters} {:keys [db]}]
(let [{:keys [chain prices]} db
currency (-> db
(get-in [:account/account :settings :wallet :currency] :usd)
name
string/upper-case)]
(assoc parameters
:network chain
;; TODO(janherich) - shouldn't this be rather computed on the receiver side ?
:fiat-amount (money/fiat-amount-value amount
(keyword asset)
(keyword currency)
prices)
:currency currency)))
(deftype PersonalSendCommand []
protocol/Command
(id [_]
"send")
(scope [_]
#{:personal-chats})
(parameters [_]
personal-send-request-params)
(id [_] "send")
(scope [_] #{:personal-chats})
(description [_] "Send a payment")
(parameters [_] personal-send-request-params)
(validate [_ parameters cofx]
;; Only superficial/formatting validation, "real validation" will be performed
;; by the wallet, where we yield control in the next step
(personal-send-request-validation parameters cofx))
(on-send [_ {:keys [chat-id]} {:keys [db]}]
(when-let [{:keys [responding-to]} (get-in db [:chats chat-id :input-metadata])]
{:db (update-in db [:chats chat-id :requests] dissoc responding-to)
:data-store/tx [(requests-store/mark-request-as-answered-tx chat-id responding-to)]}))
(on-receive [_ command-message cofx]
(when-let [tx-hash (get-in command-message [:content :params :tx-hash])]
(wallet.transactions/store-chat-transaction-hash tx-hash cofx)))
(short-preview [_ command-message]
(personal-send-request-short-preview :command-sending command-message))
(preview [_ command-message]
(send-preview command-message))
protocol/Yielding
(yield-control [_ {:keys [amount asset]} {:keys [db]}]
;; Prefill wallet and navigate there
(let [recipient-contact (get-in db [:contacts/contacts (:current-chat-id db)])
sender-account (:account/account db)
chain (keyword (:chain db))
symbol (keyword asset)
{:keys [decimals]} (tokens/asset-for chain symbol)]
(merge {:db (-> db
(send.events/set-and-validate-amount-db amount symbol decimals)
(choose-recipient.events/fill-request-details
(transaction-details recipient-contact symbol))
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
(navigation/navigate-to
(if (:wallet-set-up-passed? sender-account)
:wallet-send-transaction-chat
:wallet-onboarding-setup)))}
(send.events/update-gas-price db false))))
(on-send [_ _ _ _]
;; TODO(janherich) - remove this once periodic updates are implemented
{:dispatch [:update-transactions]})
(on-receive [_ _ _]
;; TODOD(janherich) - this just copyies the current logic but still seems super weird,
;; remove/reconsider once periodic updates are implemented
{:dispatch [:update-transactions]
:dispatch-later [{:ms constants/command-send-status-update-interval-ms
:dispatch [:update-transactions]}]})
(short-preview [_ command-message _]
(personal-send-request-short-preview command-message))
(preview [_ command-message _]
(send-preview command-message)))
(let [recipient-contact (get-in db [:contacts/contacts (:current-chat-id db)])
sender-account (:account/account db)
chain (keyword (:chain db))
symbol (keyword asset)
{:keys [decimals]} (tokens/asset-for chain symbol)
{:keys [value error]} (wallet.db/parse-amount amount decimals)]
{:db (-> db
(assoc-in [:wallet :send-transaction :amount] (money/formatted->internal value symbol decimals))
(assoc-in [:wallet :send-transaction :amount-text] amount)
(assoc-in [:wallet :send-transaction :amount-error] error)
(choose-recipient.events/fill-request-details
(transaction-details recipient-contact symbol))
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
(navigation/navigate-to
(if (:wallet-set-up-passed? sender-account)
:wallet-send-transaction-chat
:wallet-onboarding-setup)))
;; TODO(janherich) - refactor wallet send events, updating gas price
;; is generic thing which shouldn't be defined in wallet.send, then
;; we can include the utility helper without running into circ-dep problem
:update-gas-price {:web3 (:web3 db)
:success-event :wallet/update-gas-price-success
:edit? false}}))
protocol/EnhancedParameters
(enhance-parameters [_ parameters cofx]
(inject-network-price-info parameters cofx)))
;; `/request` command
(def request-message-icon-scale-delay 600)
(defn set-chat-command [message-id command]
(let [metadata {:to-message-id message-id}]
(re-frame/dispatch [:select-chat-input-command command metadata])))
(def min-scale 1)
(def max-scale 1.3)
@ -278,19 +303,25 @@
[react/icon command-icon transactions-styles/command-request-image])]]))})))
(defview request-preview
[{:keys [message-id content outgoing timestamp timestamp-str group-chat]} {:keys [db]}]
(letsubs [answered? [:is-request-answered? message-id]
[{:keys [message-id content outgoing timestamp timestamp-str group-chat]}]
(letsubs [id->command [:get-id->command]
answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?]
network [:network-name]
prices [:prices]]
network [:network-name]
prices [:prices]]
(let [{:keys [amount asset fiat-amount currency] request-network :network} (:params content)
recipient-name (get-in content [:params :bot-db :public :recipient])
network-mismatch? (and request-network (not= request-network network))
command (get-in db [:id->command ["send" #{:personal-chats}] :type])
command (get id->command ["send" #{:personal-chats}])
on-press-handler (cond
network-mismatch? nil
network-mismatch?
nil
(and (not answered?)
status-initialized?) #(set-chat-command message-id command))]
status-initialized?)
#(re-frame/dispatch [:select-chat-input-command
command
[(or asset "ETH") amount]
{:responding-to message-id}]))]
[react/view
[react/touchable-highlight
{:on-press on-press-handler}
@ -338,16 +369,13 @@
(deftype PersonalRequestCommand []
protocol/Command
(id [_]
"request")
(scope [_]
#{:personal-chats})
(parameters [_]
personal-send-request-params)
(id [_] "request")
(scope [_] #{:personal-chats})
(description [_] "Send a payment")
(parameters [_] personal-send-request-params)
(validate [_ parameters cofx]
(personal-send-request-validation parameters cofx))
(yield-control [_ _ _])
(on-send [_ _ _ _])
(on-send [_ _ _])
(on-receive [_ {:keys [message-id chat-id]} {:keys [db]}]
(let [request {:chat-id chat-id
:message-id message-id
@ -355,7 +383,10 @@
:status "open"}]
{:db (assoc-in db [:chats chat-id :requests message-id] request)
:data-store/tx [(requests-store/save-request-tx request)]}))
(short-preview [_ command-message _]
(personal-send-request-short-preview command-message))
(preview [_ command-message cofx]
(request-preview command-message cofx)))
(short-preview [_ command-message]
(personal-send-request-short-preview :command-requesting command-message))
(preview [_ command-message]
(request-preview command-message))
protocol/EnhancedParameters
(enhance-parameters [_ parameters cofx]
(inject-network-price-info parameters cofx)))

View File

@ -15,8 +15,8 @@
`id-of-the-any-chat` - command if available only for the specified chat
`:personal-chats` - command is available for any personal 1-1 chat
`:group-chats` - command is available for any group chat
`:public-chats` - command is available for any public chat
`:requested` - command is available only when there is an outstanding request")
`:public-chats` - command is available for any public chat ")
(description [this] "Description of the command")
(parameters [this]
"Ordered sequence of command parameter templates, where each parameter
is defined as map consisting of mandatory `:id`, `:title` and `:type` keys,
@ -29,21 +29,36 @@
and `cofx` map as argument, returns either `nil` meaning that no errors were
found and command send workflow can proceed, or one/more errors to display.
Each error is represented by the map containing `:title` and `:description` keys.")
(yield-control [this parameters cofx]
"Optional function, which if implemented, can step out of the normal command
workflow (`validate-and-send`) and yield control back to application before sending.
Useful for cases where we want to use command input handling (parameters) and/or
validating, but we don't want to send message before yielding control elsewhere.")
(on-send [this message-id parameters cofx]
(on-send [this command-message cofx]
"Function which can provide any extra effects to be produced in addition to
normal message effects which happen whenever message is sent")
(on-receive [this command-message cofx]
"Function which can provide any extre effects to be produced in addition to
normal message effects which happen when particular command message is received")
(short-preview [this command-message cofx]
(short-preview [this command-message]
"Function rendering the short-preview of the command message, used when
displaying the last message in list of chats on home tab.
There is no argument names `parameters` anymore, as the message object
contains everything needed for short-preview/preview to render.")
(preview [this command-message cofx]
(preview [this command-message]
"Function rendering preview of the command message in message stream"))
(defprotocol Yielding
"Protocol for defining commands yielding control back to application during the send flow"
(yield-control [this parameters cofx]
"Function, which steps out of the normal command workflow (`validate-and-send`)
and yields control back to application before sending.
Useful for cases where we want to use command input handling (parameters) and/or
validating, but we don't want to send message before yielding control elsewhere."))
(defprotocol EnhancedParameters
"Protocol for command messages which wish to modify/inject additional data into parameters,
other then those collected from the chat input.
Good example would be the `/send` and `/receive` commands - we would like to indicate
network selected in sender's device, but we of course don't want to force user to type
it in when it could be effortlessly looked up from context.
Another usage would be for example command where one of the input parameters will be
hashed after validation and we would want to avoid the original unhashed parameter
to be ever saved on the sender device, nor to be sent over the wire."
(enhance-parameters [this parameters cofx]
"Function which takes original parameters + cofx map and returns new map of parameters"))

View File

@ -0,0 +1,25 @@
(ns status-im.chat.commands.receiving
(:require [status-im.chat.commands.protocol :as protocol]))
;; TODO(janherich) remove after couple of releases when danger of messages
;; with old command references will be low
(def ^:private old->new-path
{["transactor" :command 83 "send"] ["send" #{:personal-chats}]
["transactor" :command 83 "request"] ["request" #{:personal-chats}]})
(defn lookup-command-by-ref
"Function which takes message object and looks up associated entry from the
`id->command` map if it can be found"
[{:keys [content]} id->command]
(when-let [path (or (:command-path content)
(:command-ref content))]
(or (get id->command path)
(get id->command (get old->new-path path)))))
(defn receive
"Performs receive effects for command message. Does nothing
when message is not of the command type or command can't be found."
[message {:keys [db] :as cofx}]
(let [id->command (:id->command db)]
(when-let [{:keys [type]} (lookup-command-by-ref message id->command)]
(protocol/on-receive type message cofx))))

View File

@ -0,0 +1,78 @@
(ns status-im.chat.commands.sending
(:require [status-im.constants :as constants]
[status-im.chat.commands.protocol :as protocol]
[status-im.chat.commands.core :as commands]
[status-im.chat.models :as chat-model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.message :as message-model]
[status-im.utils.handlers-macro :as handlers-macro]))
;; TODO(janherich) remove after couple of releases when danger of messages
;; with old command references will be low
(defn- new->old
[path parameter-map]
(get {["send" #{:personal-chats}] {:content {:command-ref ["transactor" :command 83 "send"]
:command "send"
:bot "transactor"
:command-scope-bitmask 83}
:content-type constants/content-type-command}
["request" #{:personal-chats}] {:content {:command-ref ["transactor" :command 83 "request"]
:request-command-ref ["transactor" :command 83 "send"]
:command "request"
:request-command "send"
:bot "transactor"
:command-scope-bitmask 83
:prefill [(get parameter-map :asset)
(get parameter-map :amount)]}
:content-type constants/content-type-command-request}}
path))
(defn- create-command-message
"Create message map from chat-id, command & input parameters"
[chat-id type parameter-map cofx]
(let [command-path (commands/command-id type)
;; TODO(janherich) this is just for backward compatibility, can be removed later
{:keys [content content-type]} (new->old command-path parameter-map)]
{:chat-id chat-id
:content-type content-type
:content (merge {:command-path command-path
:params (if (satisfies? protocol/EnhancedParameters type)
(protocol/enhance-parameters type parameter-map cofx)
parameter-map)}
content)}))
(defn validate-and-send
"Validates and sends command in current chat"
[input-text {:keys [type params] :as command} {:keys [db now random-id] :as cofx}]
(let [chat-id (:current-chat-id db)
parameter-map (commands/parse-parameters params input-text)]
(if-let [validation-error (protocol/validate type parameter-map cofx)]
;; errors during validation
{:db (chat-model/set-chat-ui-props db {:validation-messages validation-error
:sending-in-progress? false})}
;; no errors, define clean-up effects which will have to be performed in all cases
(let [cleanup-fx (handlers-macro/merge-fx cofx
{:db (chat-model/set-chat-ui-props
db {:sending-in-progress? false})}
(input-model/set-chat-input-text nil))]
(if (satisfies? protocol/Yielding type)
;; yield control implemented, don't send the message
(handlers-macro/merge-fx cofx
cleanup-fx
(protocol/yield-control type parameter-map))
;; no yield control, proceed with sending the command message
(let [command-message (create-command-message chat-id type parameter-map cofx)]
(handlers-macro/merge-fx cofx
cleanup-fx
(protocol/on-send type command-message)
(input-model/set-chat-input-metadata nil)
(message-model/send-message command-message))))))))
(defn send
"Sends command with given parameters in particular chat"
[chat-id {:keys [type]} parameter-map cofx]
(let [command-message (create-command-message chat-id type parameter-map cofx)]
(handlers-macro/merge-fx cofx
(protocol/on-send type command-message)
(input-model/set-chat-input-metadata nil)
(message-model/send-message command-message))))

View File

@ -0,0 +1,5 @@
(ns status-im.chat.commands.specs
(:require [cljs.spec.alpha :as spec]))
(spec/def :chat/id->command (spec/nilable map?))
(spec/def :chat/access-scope->command-id (spec/nilable map?))

View File

@ -1,10 +0,0 @@
(ns status-im.chat.commands.validation
(:require [status-im.ui.components.react :as react]
[status-im.chat.commands.styles.validation :as styles]))
(defn validation-message [{:keys [title description]}]
[react/view styles/message-container
[react/text {:style styles/message-title}
title]
[react/text {:style styles/message-description}
description]])

View File

@ -1,37 +0,0 @@
(ns status-im.chat.console
(:require [status-im.ui.components.styles :refer [default-chat-color]]
[status-im.constants :as constants]
[status-im.utils.clocks :as utils.clocks]
[status-im.i18n :as i18n]))
(defn console-message [{:keys [timestamp message-id content content-type]}]
{:message-id message-id
:chat-id constants/console-chat-id
:from constants/console-chat-id
:to "me"
:timestamp timestamp
:clock-value (utils.clocks/send 0)
:content content
:content-type content-type
:show? true})
(def chat
{:chat-id constants/console-chat-id
:name (i18n/label :t/status-console)
:color default-chat-color
:group-chat false
:is-active true
:unremovable? true
:timestamp (.getTime (js/Date.))
:photo-path (str "contacts://" constants/console-chat-id)
:contacts [constants/console-chat-id]
:last-clock-value 0})
(def contact
{:whisper-identity constants/console-chat-id
:name (i18n/label :t/status-console)
:photo-path (str "contacts://" constants/console-chat-id)
:dapp? true
:unremovable? true
:bot-url "local://console-bot"
:status (i18n/label :t/intro-status)})

View File

@ -9,10 +9,6 @@
(def console-chat-id "console")
;; TODO(janherich): figure out something better then this
(def send-command-ref ["transactor" :command 83 "send"])
(def request-command-ref ["transactor" :command 83 "request"])
(def spam-message-frequency-threshold 4)
(def spam-interval-ms 1000)
(def default-cooldown-period-ms 10000)

View File

@ -6,8 +6,7 @@
[status-im.i18n :as i18n]
[status-im.chat.models :as models]
[status-im.chat.models.message :as models.message]
[status-im.chat.console :as console]
[status-im.commands.events.loading :as events.loading]
[status-im.chat.commands.core :as commands]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
@ -21,11 +20,10 @@
[status-im.data-store.messages :as messages-store]
[status-im.data-store.user-statuses :as user-statuses-store]
[status-im.data-store.contacts :as contacts-store]
[status-im.chat.events.commands :as events.commands]
status-im.chat.events.input
status-im.chat.events.requests
status-im.chat.events.send-message
status-im.chat.events.receive-message
status-im.chat.events.console))
status-im.chat.events.receive-message))
;;;; Effects
@ -109,14 +107,6 @@
db
updated-statuses)})))
(defn init-console-chat
[{:keys [db]}]
(when-not (get-in db [:chats constants/console-chat-id])
{:db (-> db
(assoc :current-chat-id constants/console-chat-id)
(update :chats assoc constants/console-chat-id console/chat))
:data-store/tx [(chats-store/save-chat-tx console/chat)]}))
(defn- add-default-contacts
[{:keys [db default-contacts] :as cofx}]
(let [new-contacts (-> {}
@ -134,16 +124,12 @@
:dapp-url (-> props :dapp-url :en)
:bot-url (:bot-url props)
:description (:description props)}])))
default-contacts)
(assoc constants/console-chat-id console/contact))
default-contacts))
existing-contacts (:contacts/contacts db)
contacts-to-add (select-keys new-contacts (set/difference (set (keys new-contacts))
(set (keys existing-contacts))))]
(handlers-macro/merge-fx cofx
{:db (update db :contacts/contacts merge contacts-to-add)
:data-store/tx [(contacts-store/save-contacts-tx
(vals contacts-to-add))]}
(events.loading/load-commands))))
{:db (update db :contacts/contacts merge contacts-to-add)
:data-store/tx [(contacts-store/save-contacts-tx (vals contacts-to-add))]}))
(defn- group-chat-messages
[{:keys [db]}]
@ -193,9 +179,9 @@
{:db (assoc db
:chats chats
:contacts/dapps default-dapps)}
(init-console-chat)
(group-chat-messages)
(add-default-contacts)))))
(add-default-contacts)
(commands/index-commands commands/register)))))
(defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
(when (and (not (get-in db [:chats chat-id :public?]))

View File

@ -1,83 +0,0 @@
(ns status-im.chat.events.commands
(:require [re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.chat.models.message :as models.message]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.handlers :as handlers]
[status-im.i18n :as i18n]
[status-im.utils.platform :as platform]
[status-im.chat.events.shortcuts :as shortcuts]
[status-im.utils.ethereum.tokens :as tokens]))
;;;; Helper fns
;;TODO(goranjovic): currently we only allow tokens which are enabled in Manage assets here
;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences
;; if we were to update all balances.
(defn- allowed-assets [chain account]
(let [visible-token-symbols (get-in account [:settings :wallet :visible-tokens (keyword chain)])]
(->> (tokens/tokens-for (keyword chain))
(filter #(not (:nft? %)))
(filter #(contains? visible-token-symbols (:symbol %)))
(map #(vector (-> % :symbol clojure.core/name)
(:decimals %)))
(into {"ETH" 18}))))
(defn- generate-context
"Generates context for jail call"
[account current-account-id chat-id group-chat? to chain]
(merge {:platform platform/os
:network chain
:from current-account-id
:to to
:allowed-assets (clj->js (allowed-assets chain account))
:chat {:chat-id chat-id
:group-chat (boolean group-chat?)}}
i18n/delimeters))
(defn request-command-message-data
"Requests command message data from jail"
[{:contacts/keys [contacts] :account/keys [account] :keys [chain] :as db}
{{:keys [command command-scope-bitmask bot params type]} :content
:keys [chat-id group-id] :as message}
{:keys [data-type] :as opts}]
(if-not (get contacts bot) ;; bot is not even in contacts, do nothing
{:db db}
(if (get-in contacts [bot :jail-loaded?])
(let [path [(if (= :response (keyword type)) :responses :commands)
[command command-scope-bitmask]
data-type]
to (get-in contacts [chat-id :address])
address (get-in db [:account/account :address])
jail-params {:parameters params
:context (generate-context account address chat-id (models.message/group-message? message) to chain)}]
{:db db
:call-jail [{:jail-id bot
:path path
:params jail-params
:callback-event-creator (fn [jail-response]
[::jail-command-data-response
jail-response message opts])}]})
{:db (update-in db [:contacts/contacts bot :jail-loaded-events]
conj [:request-command-message-data message opts])})))
;;;; Handlers
(handlers/register-handler-fx
::jail-command-data-response
[re-frame/trim-v]
(fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [chat-id]} {:keys [proceed-event-creator]}]]
(when proceed-event-creator
{:dispatch (proceed-event-creator returned)})))
(defn short-preview? [opts]
(= :short-preview (:data-type opts)))
(handlers/register-handler-fx
:request-command-message-data
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [{:keys [db]} [message opts]]
(if (and (short-preview? opts)
(shortcuts/shortcut-override? message))
(shortcuts/shortcut-override-fx db message)
(request-command-message-data db message opts))))

View File

@ -1,63 +0,0 @@
(ns status-im.chat.events.console
(:require [status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.chat.console :as console-chat]
[taoensso.timbre :as log]
[status-im.i18n :as i18n]
[goog.string :as gstring]
goog.string.format))
(defn console-respond-command-messages
[{:keys [name] :as command} handler-data {:keys [random-id-seq now]}]
(when-let [messages (case name
"js" (let [{:keys [err data messages]} handler-data
content (or err data)
message-events (mapv (fn [{:keys [message type]} id]
(console-chat/console-message
{:message-id id
:timestamp now
:content (str type ": " message)
:content-type constants/text-content-type}))
messages random-id-seq)]
(conj message-events
(console-chat/console-message
{:message-id (first random-id-seq)
:timestamp now
:content (str content)
:content-type constants/text-content-type})))
(log/debug "ignoring command: " name))]
{:dispatch [:chat-received-message/add messages]}))
(defn faucet-base-url->url [url]
(str url "/donate/0x%s"))
(defn- faucet-response-event [now message-id content]
[:chat-received-message/add
[(console-chat/console-message
{:message-id message-id
:timestamp now
:content content
:content-type constants/text-content-type})]])
(def console-commands->fx
{"faucet"
(fn [{:keys [db random-id now]} {:keys [params]}]
(let [current-address (get-in db [:account/account :address])
faucet-url (faucet-base-url->url (:url params))]
{:http-get {:url (gstring/format faucet-url current-address)
:success-event-creator (fn [_]
(faucet-response-event
now
random-id
(i18n/label :t/faucet-success)))
:failure-event-creator (fn [event]
(log/error "Faucet error" event)
(faucet-response-event
now
random-id
(i18n/label :t/faucet-error)))}}))})
(def commands-names (set (keys console-commands->fx)))
(def commands-with-delivery-status
(disj commands-names "faucet"))

View File

@ -2,16 +2,16 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.chat.constants :as constants]
[status-im.constants :as constants]
[status-im.chat.constants :as chat-constants]
[status-im.chat.models :as model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.models.message :as message-model]
[status-im.chat.events.commands :as commands-events]
[status-im.bots.events :as bots-events]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.sending :as commands-sending]
[status-im.ui.components.react :as react-comp]
[status-im.utils.handlers :as handlers]))
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]))
;;;; Effects
@ -43,260 +43,40 @@
;;;; Helper functions
(defn set-chat-input-text
"Set input text for current-chat and updates suggestions relevant to current input.
Takes db, input text and `:append?` flag as arguments and returns new db.
When `:append?` is false or not provided, resets the current chat input with input text,
otherwise input text is appended to the current chat input."
[{:keys [current-chat-id] :as db} new-input & {:keys [append?]}]
(let [current-input (get-in db [:chats current-chat-id :input-text])
{:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id])
chat-text (if append?
(str current-input new-input)
new-input)]
(cond-> (model/set-chat-ui-props db {:validation-messages nil})
true
(assoc-in [:chats current-chat-id :input-text] (input-model/text->emoji chat-text))
(and dapp? (string/blank? chat-text))
(assoc-in [:chats current-chat-id :parameter-boxes :message] nil))))
;; TODO janherich: this is super fragile and won't work at all for group chats with bots.
;; The proper way how to do it is to check each chat participant and call `:on-message-input-change`
;; jail function in each participant's jail
(defn call-on-message-input-change
"Calls bot's `on-message-input-change` function"
[{:keys [current-chat-id chats local-storage] :as db}]
(let [chat-text (string/trim (or (get-in chats [current-chat-id :input-text]) ""))
{:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id])
address (get-in db [:account/account :address])]
(cond-> {:db db}
(and dapp? (not (string/blank? chat-text)))
(assoc :call-jail-function {:chat-id current-chat-id
:function :on-message-input-change
:parameters {:message chat-text}
:context {:data (get local-storage current-chat-id)
:from address}}))))
(defn set-chat-input-metadata
"Set input metadata for active chat. Takes db and metadata and returns updated db."
[{:keys [current-chat-id] :as db} metadata]
(assoc-in db [:chats current-chat-id :input-metadata] metadata))
(defn set-command-argument
"Sets command argument in active chat"
[{:keys [current-chat-id] :as db} index arg move-to-next?]
(let [command (-> (get-in db [:chats current-chat-id :input-text])
(input-model/split-command-args))]
(let [arg (string/replace arg (re-pattern constants/arg-wrapping-char) "")
command-name (first command)
command-args (into [] (rest command))
command-args (if (< index (count command-args))
(assoc command-args index arg)
(conj command-args arg))
input-text (str command-name
constants/spacing-char
(input-model/join-command-args command-args)
(when (and move-to-next?
(= index (dec (count command-args))))
constants/spacing-char))]
(set-chat-input-text db input-text))))
(defn load-chat-parameter-box
"Returns fx for loading chat parameter box for active chat"
[{:keys [current-chat-id bot-db] :as db}
{:keys [name scope-bitmask type bot owner-id] :as command}]
(let [parameter-index (input-model/argument-position db)]
(when (and command (> parameter-index -1))
(let [data (get-in db [:local-storage current-chat-id])
bot-db (get bot-db owner-id)
path [(if (= :command type) :commands :responses)
[name scope-bitmask]
:params
parameter-index
:suggestions]
args (-> (get-in db [:chats current-chat-id :input-text])
input-model/split-command-args
rest)
to (get-in db [:contacts/contacts current-chat-id :address])
from (get-in db [:account/account :address])
params {:parameters {:args args
:bot-db bot-db}
:context {:data data
:from from
:to to}}]
{:call-jail [{:jail-id owner-id
:path path
:params params
:callback-event-creator (fn [jail-response]
[:chat-received-message/bot-response
{:chat-id current-chat-id
:command command
:parameter-index parameter-index}
jail-response])}]}))))
(defn chat-input-focus
"Returns fx for focusing on active chat input reference"
[{:keys [current-chat-id chat-ui-props]} ref]
[ref {{:keys [current-chat-id chat-ui-props]} :db}]
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::focus-rn-component cmp-ref}))
(defn update-text-selection
"Updates text selection in active chat input"
[db selection]
(let [command (input-model/selected-chat-command db)
new-db (model/set-chat-ui-props db {:selection selection})
chat-parameter-box-fx (when command
(load-chat-parameter-box new-db (:command command)))]
(cond-> {:db new-db}
chat-parameter-box-fx
(merge chat-parameter-box-fx))))
(defn select-chat-input-command
"Selects command + (optional) arguments as input for active chat"
[{:keys [prefill prefill-bot-db name owner-id] :as command} metadata prevent-auto-focus? {:keys [db]}]
(let [{:keys [current-chat-id chat-ui-props]} db
db' (-> db
(bots-events/clear-bot-db owner-id)
(model/set-chat-ui-props {:show-suggestions? false
:result-box nil})
(set-chat-input-metadata metadata)
(set-chat-input-text (str (commands-model/command-name command)
constants/spacing-char
(input-model/join-command-args prefill))))
fx (assoc (load-chat-parameter-box db' command) :db db')]
(cond-> fx
prefill-bot-db (update :db bots-events/update-bot-db {:db prefill-bot-db
:bot owner-id})
(not prevent-auto-focus?)
(merge (chat-input-focus (:db fx) :input-ref)))))
;; TODO(goranjovic) - generalize setting something as a command argument
(defn set-contact-as-command-argument
"Sets contact as command argument for active chat"
[db {:keys [bot-db-key contact arg-index]}]
(let [name (string/replace (:name contact) (re-pattern constants/arg-wrapping-char) "")
contact (select-keys contact [:address :whisper-identity :name :photo-path :dapp?])
command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])]
(-> db
(set-command-argument arg-index name true)
(bots-events/set-in-bot-db {:bot command-owner
:path [:public (keyword bot-db-key)]
:value contact})
(as-> fx
(let [{:keys [current-chat-id]
:as new-db} (:db fx)
arg-position (input-model/argument-position new-db)
input-text (get-in new-db [:chats current-chat-id :input-text])
command-args (cond-> (input-model/split-command-args input-text)
(input-model/text-ends-with-space? input-text) (conj ""))
new-selection (->> command-args
(take (+ 3 arg-position))
(input-model/join-command-args)
count
(min (count input-text)))]
(merge fx (update-text-selection new-db new-selection)))))))
;; TODO(goranjovic) - generalize setting something as a command argument
(defn set-asset-as-command-argument
"Sets asset as command argument for active chat"
[db {:keys [bot-db-key asset arg-index]}]
(let [name (string/replace (name (:symbol asset)) (re-pattern constants/arg-wrapping-char) "")
command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])]
(-> db
(set-command-argument arg-index name true)
(bots-events/set-in-bot-db {:bot command-owner
:path [:public (keyword bot-db-key)]
:value asset})
(as-> fx
(let [{:keys [current-chat-id]
:as new-db} (:db fx)
arg-position (input-model/argument-position new-db)
input-text (get-in new-db [:chats current-chat-id :input-text])
command-args (cond-> (input-model/split-command-args input-text)
(input-model/text-ends-with-space? input-text) (conj ""))
new-selection (->> command-args
(take (+ 3 arg-position))
(input-model/join-command-args)
count
(min (count input-text)))]
(merge fx (update-text-selection new-db new-selection)))))))
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
(defn- command->message
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
(message-model/add-message-type
{:chat-id current-chat-id
:content {:bot (:owner-id command)
:command (:name command)
:type (:type command)
:command-scope-bitmask (:scope-bitmask command)
:params (assoc (input-model/args->params command-params)
:bot-db (get bot-db (:owner-id command)))}}
(get chats current-chat-id)))
(defn proceed-command
"Proceed with command processing by creating command message + setting up and executing chain of events:
1. Params validation
2. Short preview fetching
3. Preview fetching"
[{:keys [bot-db current-chat-id chats] :as db} {:keys [command metadata] :as command-params} message-id current-time]
(let [message (command->message db command-params)
cmd-params {:command command
:params (get-in message [:content :params])
:to-message (:to-message-id metadata)
:created-at current-time
:id message-id
:chat-id current-chat-id}
event-chain {:data-type :validator
:proceed-event-creator (fn [validation-response]
[::proceed-validation
validation-response
[[:request-command-message-data
message
{:data-type :short-preview
:proceed-event-creator (fn [short-preview]
[:request-command-message-data
message
{:data-type :preview
:proceed-event-creator (fn [preview]
[::send-command
(update cmd-params :command merge
{:short-preview short-preview
:preview preview})])}])}]]])}]
(commands-events/request-command-message-data db message event-chain)))
;;;; Handlers
(handlers/register-handler-fx
:set-chat-input-text
[re-frame/trim-v]
(fn [{:keys [db]} [text]]
(let [new-db (set-chat-input-text db text)
fx (call-on-message-input-change new-db)]
(if-let [{:keys [command]} (input-model/selected-chat-command new-db)]
(merge fx (load-chat-parameter-box new-db command))
fx))))
(fn [cofx [text]]
(input-model/set-chat-input-text text cofx)))
(handlers/register-handler-fx
:select-chat-input-command
[re-frame/trim-v]
(fn [cofx [command metadata prevent-auto-focus?]]
(select-chat-input-command command metadata prevent-auto-focus? cofx)))
(fn [{:keys [db] :as cofx} [command params metadata]]
(handlers-macro/merge-fx cofx
(input-model/set-chat-input-metadata metadata)
(commands/select-chat-input-command command params)
(chat-input-focus :input-ref))))
(handlers/register-handler-db
:set-command-argument
(handlers/register-handler-fx
:set-command-parameter
[re-frame/trim-v]
(fn [db [[index arg move-to-next?]]]
(set-command-argument db index arg move-to-next?)))
(fn [cofx [last-param? index value]]
(commands/set-command-parameter last-param? index value cofx)))
(handlers/register-handler-fx
:chat-input-focus
[re-frame/trim-v]
(fn [{:keys [db]} [ref]]
(chat-input-focus db ref)))
(fn [cofx [ref]]
(chat-input-focus ref cofx)))
(handlers/register-handler-fx
:chat-input-blur
@ -305,105 +85,56 @@
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::blur-rn-component cmp-ref})))
(handlers/register-handler-fx
::proceed-validation
[re-frame/trim-v]
(fn [_ [{:keys [markup parameters]} proceed-events]]
(let [error-events-creator (fn [validator-result]
[[:set-chat-ui-props {:validation-messages validator-result
:sending-in-progress? false}]])
events (if markup
(error-events-creator markup)
proceed-events)]
{:dispatch-n events})))
(defn cleanup-chat-command [db]
(-> (model/set-chat-ui-props db {:sending-in-progress? false})
(set-chat-input-metadata nil)
(set-chat-input-text nil)))
(handlers/register-handler-fx
:cleanup-chat-command
(fn [{:keys [db]}]
{:db (cleanup-chat-command db)}))
(handlers/register-handler-fx
::send-command
message-model/send-interceptors
(fn [{:keys [db] :as cofx} [command-message]]
(let [{:keys [current-chat-id current-public-key]} db
new-db (cleanup-chat-command db)
address (get-in db [:account/account :address])]
(merge {:db new-db}
(message-model/process-command (assoc cofx :db new-db)
{:message (get-in db [:chats current-chat-id :input-text])
:command command-message
:chat-id current-chat-id
:identity current-public-key
:address address})))))
(defn command-complete?
[chat-command]
(= :complete (input-model/command-completion chat-command)))
(defn command-complete-fx
"command is complete, set `:sendint-in-progress?` flag and proceed with command processing"
[db chat-command message-id current-time]
(-> db
(model/set-chat-ui-props {:sending-in-progress? true})
(proceed-command chat-command message-id current-time)))
"command is complete, set `:sending-in-progress?` flag and proceed with command processing"
[input-text command {:keys [db now random-id] :as cofx}]
(handlers-macro/merge-fx
cofx
{:db (model/set-chat-ui-props db {:sending-in-progress? true})}
(commands-sending/validate-and-send input-text command)))
(defn command-not-complete-fx
"command is not complete, just add space after command if necessary"
[db input-text]
[input-text current-chat-id {:keys [db]}]
{:db (cond-> db
(not (input-model/text-ends-with-space? input-text))
(set-chat-input-text constants/spacing-char :append? true))})
(assoc-in [:chats current-chat-id :input-text]
(str input-text chat-constants/spacing-char)))})
(defn plain-text-message-fx
"no command detected, when not empty, proceed by sending text message without command processing"
[db cofx input-text current-chat-id current-public-key]
[input-text current-chat-id {:keys [db] :as cofx}]
(when-not (string/blank? input-text)
(message-model/send-message (assoc cofx :db (-> db
(set-chat-input-metadata nil)
(set-chat-input-text nil)))
{:message-text input-text
:chat-id current-chat-id
:identity current-public-key})))
(handlers-macro/merge-fx
cofx
(input-model/set-chat-input-text nil)
(input-model/set-chat-input-metadata nil)
(message-model/send-message {:chat-id current-chat-id
:content-type constants/text-content-type
:content input-text}))))
(handlers/register-handler-fx
:send-current-message
message-model/send-interceptors
(fn [{{:keys [current-chat-id current-public-key] :as db} :db message-id :random-id current-time :now
:as cofx} _]
(fn [{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx} _]
(when-not (get-in db [:chat-ui-props current-chat-id :sending-in-progress?])
(let [input-text (get-in db [:chats current-chat-id :input-text])
chat-command (-> (input-model/selected-chat-command db)
(update :args (partial remove string/blank?)))]
(if (:command chat-command)
command (commands/selected-chat-command
input-text nil (commands/chat-commands id->command
access-scope->command-id
(get-in db [:chats current-chat-id])))]
(if command
;; Returns true if current input contains command
(if (command-complete? chat-command)
(command-complete-fx db chat-command message-id current-time)
(command-not-complete-fx db input-text))
(plain-text-message-fx db cofx input-text current-chat-id current-public-key))))))
(if (= :complete (:command-completion command))
(command-complete-fx input-text command cofx)
(command-not-complete-fx input-text current-chat-id cofx))
(plain-text-message-fx input-text current-chat-id cofx))))))
(handlers/register-handler-fx
(handlers/register-handler-db
:update-text-selection
[re-frame/trim-v]
(fn [{:keys [db]} [selection]]
(update-text-selection db selection)))
(handlers/register-handler-fx
:set-contact-as-command-argument
[re-frame/trim-v]
(fn [{:keys [db]} [params]]
(set-contact-as-command-argument db params)))
(handlers/register-handler-fx
:set-asset-as-command-argument
[re-frame/trim-v]
(fn [{:keys [db]} [params]]
(set-asset-as-command-argument db params)))
(fn [db [selection]]
(model/set-chat-ui-props db {:selection selection})))
(handlers/register-handler-db
:show-suggestions

View File

@ -1,57 +1,18 @@
(ns status-im.chat.events.receive-message
(:require [re-frame.core :as re-frame]
[taoensso.timbre :as log]
[status-im.chat.events.commands :as commands-events]
[status-im.chat.models.message :as message-model]
[status-im.constants :as constants]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.wallet.transactions :as wallet.transactions]))
;;;; Handlers
(handlers/register-handler-fx
::received-message
message-model/receive-interceptors
(fn [cofx [message]]
(when (message-model/add-to-chat? cofx message)
(message-model/receive message cofx))))
(re-frame.core/reg-fx
:chat-received-message/add-fx
(fn [messages]
(re-frame/dispatch [:chat-received-message/add messages])))
(defn- request-command-message-data [message {:keys [db]}]
(commands-events/request-command-message-data
db message
{:data-type :short-preview
:proceed-event-creator (fn [short-preview]
[:request-command-message-data
message
{:data-type :preview
:proceed-event-creator (fn [preview]
[::received-message
(update message :content merge
{:short-preview short-preview
:preview preview})])}])}))
(defn add-message [{:keys [content] :as message} {:keys [db] :as cofx}]
(when (message-model/add-to-chat? cofx message)
(if (:command content)
;; we are dealing with received command message, we can't add it right away,
;; we first need to fetch short-preview + preview and add it only after we already have those.
;; note that `request-command-message-data` implicitly wait till jail is ready and
;; calls are made only after that
(let [{:keys [command params]} content
tx-hash (:tx-hash params)]
(handlers-macro/merge-fx cofx
(wallet.transactions/store-chat-transaction-hash tx-hash)
(request-command-message-data message)))
;; regular non command message, we can add it right away
(message-model/receive message cofx))))
(defn- filter-messages [messages cofx]
(:accumulated (reduce (fn [{:keys [seen-ids] :as acc}
{:keys [message-id] :as message}]
@ -65,45 +26,9 @@
:accumulated []}
messages)))
(defn add-messages [messages {:keys [db] :as cofx}]
(let [messages-to-add (filter-messages messages cofx)
plain-messages (remove (comp :command :content) messages-to-add)
command-messages (filter (comp :command :content) messages-to-add)]
(handlers-macro/merge-effects (message-model/receive-many plain-messages cofx)
cofx
add-message
command-messages)))
(handlers/register-handler-fx
:chat-received-message/add
message-model/receive-interceptors
(fn [cofx [messages]]
(add-messages messages cofx)))
(message-model/receive-many (filter-messages messages cofx) cofx)))
;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored
(handlers/register-handler-fx
:chat-received-message/bot-response
message-model/receive-interceptors
(fn [{:keys [random-id now]} [{:keys [chat-id] :as params} {:keys [result bot-id] :as data}]]
(let [{:keys [returned context]} result
{:keys [markup text-message err]} returned
{:keys [update-db default-db]} context
content (or err text-message)]
(when update-db
(re-frame/dispatch [:update-bot-db {:bot bot-id
:db update-db}]))
(re-frame/dispatch [:suggestions-handler (assoc params
:bot-id bot-id
:result data
:default-db default-db)])
(when content
(re-frame/dispatch [:chat-received-message/add
[{:message-id random-id
:timestamp now
:content (str content)
:content-type constants/text-content-type
:clock-value (utils.clocks/send 0)
:chat-id chat-id
:from chat-id
:to "me"
:show? true}]])))))

View File

@ -13,19 +13,3 @@
tokens-json (types/clj->json tokens)]
(log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json)
(status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %)))))
;;;; Handlers
(handlers/register-handler-fx
:chat-send-message/send-command
message-model/send-interceptors
(fn [cofx [_ params]]
(message-model/send-command cofx params)))
(handlers/register-handler-fx
:chat-send-message/from-jail
[re-frame/trim-v]
(fn [cofx [{:keys [chat-id message]}]]
(let [parsed-message (types/json->clj message)]
(message-model/handle-message-from-bot cofx {:message parsed-message
:chat-id chat-id}))))

View File

@ -1,45 +0,0 @@
(ns status-im.chat.events.shortcuts
(:require [status-im.ui.screens.wallet.send.events :as send.events]
[status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[taoensso.timbre :as log]))
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
(defn- transaction-details [contact symbol]
(-> contact
(select-keys [:name :address :whisper-identity])
(assoc :symbol symbol
:gas (ethereum/estimate-gas symbol)
:from-chat? true)))
(defn send-shortcut-fx [{:account/keys [account] :as db} contact params]
(let [chain (keyword (:chain db))
symbol (-> params :asset keyword)
{:keys [decimals]} (tokens/asset-for chain symbol)]
(merge {:db (-> db
(send.events/set-and-validate-amount-db (:amount params) symbol decimals)
(choose-recipient.events/fill-request-details (transaction-details contact symbol))
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
(navigation/navigate-to
(if (:wallet-set-up-passed? account)
:wallet-send-transaction-chat
:wallet-onboarding-setup)))}
(send.events/update-gas-price db false))))
(def shortcuts
{"send" send-shortcut-fx})
(defn shortcut-override? [message]
(get shortcuts (get-in message [:content :command])))
(defn shortcut-override-fx [db {:keys [chat-id content]}]
(let [command (:command content)
contact (get-in db [:contacts/contacts chat-id])
shortcut-specific-fx (get shortcuts command)]
(-> db
(shortcut-specific-fx contact (:params content))
;; TODO(goranjovic) - replace this dispatch with a function call
;; Need to refactor chat events namespaces for this to avoid circular dependecy
(assoc :dispatch [:cleanup-chat-command]))))

View File

@ -1,45 +0,0 @@
(ns status-im.chat.models.commands
(:require [status-im.chat.constants :as chat-consts]
[clojure.string :as str]
[taoensso.timbre :as log]))
(defn- resolve-references
[contacts name->ref]
(reduce-kv (fn [acc name ref]
(assoc acc name (get-in contacts ref)))
{}
name->ref))
(defn- is-dapp? [all-contacts identity]
(get-in all-contacts [identity :dapp?]))
(defn command-name [{:keys [name]}]
(str chat-consts/command-char name))
(defn commands-responses
"Returns map of commands/responses eligible for current chat."
[type access-scope->commands-responses {:keys [address]} {:keys [contacts group-chat public?]} all-contacts]
(let [dapps? (some (partial is-dapp? all-contacts) contacts)
humans? (some (comp not (partial is-dapp? all-contacts)) contacts)
basic-access-scope (cond-> #{}
group-chat (conj :group-chats)
(not group-chat) (conj :personal-chats)
address (conj :registered)
(not address) (conj :anonymous)
dapps? (conj :dapps)
humans? (conj :humans)
public? (conj :public-chats))
global-access-scope (conj basic-access-scope :global)
member-access-scopes (into #{} (map (partial conj basic-access-scope)) contacts)]
(reduce (fn [acc access-scope]
(merge acc (resolve-references all-contacts
(get-in access-scope->commands-responses [access-scope type]))))
{}
(cons global-access-scope member-access-scopes))))
(defn requested-responses
"Returns map of requested command responses eligible for current chat."
[access-scope->commands-responses account chat contacts requests]
(let [requested-responses (map :response requests)
responses-map (commands-responses :response access-scope->commands-responses account chat contacts)]
(select-keys responses-map requested-responses)))

View File

@ -3,11 +3,12 @@
[goog.object :as object]
[status-im.chat.constants :as const]
[status-im.chat.models :as chat-model]
[status-im.chat.models.commands :as commands-model]
[status-im.utils.config :as config]
[status-im.utils.datetime :as datetime]
[status-im.js-dependencies :as dependencies]))
(def space-char " ")
(defn text->emoji
"Replaces emojis in a specified `text`"
[text]
@ -88,118 +89,18 @@
(str const/arg-wrapping-char arg const/arg-wrapping-char)))))
(str/join const/spacing-char))))
(defn selected-chat-command
"Takes whole db, or current chat and available commands/responses and returns a map
containing `:command`, `:metadata` and `:args` keys. Can also return `nil` if there is no selected command.
(defn set-chat-input-text
"Set input text for current-chat. Takes db and input text and cofx
as arguments and returns new fx. Always clear all validation messages."
[new-input {{:keys [current-chat-id] :as db} :db}]
{:db (-> (chat-model/set-chat-ui-props db {:validation-messages nil})
(assoc-in [:chats current-chat-id :input-text] (text->emoji new-input)))})
* `:command` key contains a map with all information about command.
* `:metadata` is also a map which contains some additional information, usually not visible by user.
For instance, we can add a `:to-message-id` key to this map, and this key will allow us to identity
the request we're responding to.
* `:args` contains all arguments provided by user."
([{:keys [input-text input-metadata seq-arguments] :as current-chat} commands responses]
(let [[command-name :as command-args] (split-command-args input-text)]
(when-let [{{:keys [message-id]} :request :as command} (and command-name
(starts-as-command? command-name)
(get (merge commands
responses)
(subs command-name 1)))]
{:command command
:metadata (if (and message-id (not (:to-message-id input-metadata)))
(assoc input-metadata :to-message-id message-id)
input-metadata)
:args (into [] (if (empty? seq-arguments)
(rest command-args)
seq-arguments))})))
([{:keys [chats current-chat-id access-scope->commands-responses]
:contacts/keys [contacts] :as db}]
(let [chat (get chats current-chat-id)
account (:account/account db)]
(selected-chat-command chat
(commands-model/commands-responses :command
access-scope->commands-responses
account
chat
contacts)
(commands-model/requested-responses access-scope->commands-responses
account
chat
contacts
(vals (:requests chat)))))))
(def *no-argument-error* -1)
(defn current-chat-argument-position
"Returns the position of current argument. It's just an integer number from -1 to infinity.
-1 (`*no-argument-error*`) means error. It can happen if there is no selected command or selection."
[command input-text selection seq-arguments]
(if command
(if (get-in command [:command :sequential-params])
(count seq-arguments)
(let [subs-input-text (subs input-text 0 selection)]
(if subs-input-text
(let [args (split-command-args subs-input-text)
argument-index (dec (count args))
ends-with-space? (text-ends-with-space? subs-input-text)
arg-wrapping-count (-> (frequencies subs-input-text)
(get const/arg-wrapping-char)
(or 0))]
(if (and ends-with-space?
(even? arg-wrapping-count))
argument-index
(dec argument-index)))
*no-argument-error*)))
*no-argument-error*))
(defn argument-position
"Returns the position of current argument. It's just an integer from -1 to infinity.
-1 (`*no-argument-error*`) means error. It can happen if there is no command or selection.
This method is basically just another way of calling `current-chat-argument-position`."
[{:keys [current-chat-id] :as db}]
(let [input-text (get-in db [:chats current-chat-id :input-text])
seq-arguments (get-in db [:chats current-chat-id :seq-arguments])
selection (get-in db [:chat-ui-props current-chat-id :selection])
chat-command (selected-chat-command db)]
(current-chat-argument-position chat-command input-text selection seq-arguments)))
(defn command-completion
"Returns one of the following values indicating a command's completion status:
* `:complete` means that the command is complete and can be sent;
* `:less-than-needed` means that the command is not complete and additional arguments should be provided;
* `:more-than-needed` means that the command is more than complete and contains redundant arguments;
* `:no-command` means that there is no selected command."
[{:keys [args] :as chat-command}]
(let [args (remove str/blank? args)
params (get-in chat-command [:command :params])
required-params (remove :optional params)]
(if chat-command
(cond
(or (= (count args) (count params))
(= (count args) (count required-params)))
:complete
(< (count args) (count required-params))
:less-than-needed
(> (count args) (count params))
:more-than-needed
:default
:no-command)
:no-command)))
(defn args->params
"Uses `args` (which is a list or vector like ['Jarrad' '1.0']) and command's `params`
and returns a map that looks the following way:
{:recipient \"Jarrad\" :amount \"1.0\"}"
[{:keys [command args]}]
(let [params (:params command)]
(->> args
(map-indexed (fn [i value]
[(keyword (get-in params [i :name])) value]))
(remove #(nil? (first %)))
(into {}))))
(defn set-chat-input-metadata
"Sets user invisible chat input metadata for current-chat"
[metadata {:keys [db] :as cofx}]
(let [current-chat-id (:current-chat-id db)]
{:db (assoc-in db [:chats current-chat-id :input-metadata] metadata)}))
(defn- start-cooldown [{:keys [db]} cooldowns]
{:dispatch-later [{:dispatch [:disable-cooldown]

View File

@ -4,11 +4,9 @@
[status-im.utils.core :as utils]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.datetime :as time]
[status-im.chat.events.console :as console-events]
[status-im.chat.events.requests :as requests-events]
[status-im.chat.models :as chat-model]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.models.input :as input]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.utils.clocks :as utils.clocks]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.money :as money]
@ -25,15 +23,6 @@
[(re-frame/inject-cofx :random-id)
re-frame/trim-v])
(defn- lookup-response-ref
[access-scope->commands-responses account chat contacts response-name]
(let [available-commands-responses (commands-model/commands-responses :response
access-scope->commands-responses
account
chat
contacts)]
(:ref (get available-commands-responses response-name))))
(defn- emoji-only-content?
[content]
(and (string? content) (re-matches constants/regx-emoji content)))
@ -138,25 +127,10 @@
message
(assoc message :clock-value (utils.clocks/send last-clock-value))))
(defn add-command-request
[{:keys [content-type content] :as message} chat {:keys [db]}]
(let [request-command (:request-command content)
current-account (:account/account db)
command-request? (and (= content-type
constants/content-type-command-request)
request-command)
{:keys [access-scope->commands-responses]
:contacts/keys [contacts]} db]
(if command-request?
(assoc-in
message
[:content :request-command-ref]
(lookup-response-ref access-scope->commands-responses
current-account
chat
contacts
request-command))
message)))
(defn- update-legacy-type [{:keys [content-type] :as message}]
(cond-> message
(= constants/content-type-command-request content-type)
(assoc :content-type constants/content-type-command)))
(defn- add-received-message
[batch?
@ -170,7 +144,8 @@
(ensure-clock-value chat)
;; TODO (cammellos): Refactor so it's not computed twice
(add-outgoing-status cofx)
(add-command-request chat cofx))]
;; TODO (janherich): Remove after couple of releases
update-legacy-type)]
(handlers-macro/merge-fx cofx
{:confirm-messages-processed [{:web3 web3
:js-obj js-obj}]}
@ -181,7 +156,7 @@
(add-own-status chat-id message-id (cond (:outgoing message) :sent
current-chat? :seen
:else :received))
(requests-events/add-request chat-id message-id)
(commands-receiving/receive message)
(send-message-seen chat-id message-id (and (not public?)
current-chat?
(not (chat-model/bot-only-chat? db chat-id))
@ -251,47 +226,12 @@
(re-frame/inject-cofx :random-id-seq)
re-frame/trim-v])
(defn- handle-message-from-bot [{:keys [random-id] :as cofx} {:keys [message chat-id]}]
(when-let [message (cond
(string? message)
{:message-id random-id
:content (str message)
:content-type constants/text-content-type
:chat-id chat-id
:from chat-id
:to "me"}
(= "request" (:type message))
{:message-id random-id
:content (assoc (:content message) :bot chat-id)
:content-type constants/content-type-command-request
:chat-id chat-id
:from chat-id
:to "me"})]
(receive message cofx)))
(defn- send-dapp-message!
[{:keys [db] :as cofx} chat-id {:keys [content-type] :as message}]
(if (= content-type constants/content-type-command)
(when-let [text-message (get-in message [:content :handler-data :text-message])]
(handle-message-from-bot cofx {:message text-message
:chat-id chat-id}))
(let [data (get-in db [:local-storage chat-id])]
{:call-jail-function {:chat-id chat-id
:function :on-message-send
:parameters {:message (:content message)}
:context {:data data
:from (get-in db [:account/account :address])}}})))
(defn- send
[chat-id message-id send-record {{:contacts/keys [contacts] :keys [network-status current-public-key]} :db :as cofx}]
(let [{:keys [dapp?]} (get contacts chat-id)]
(if dapp?
(send-dapp-message! cofx chat-id send-record)
(if (= network-status :offline)
{:dispatch-later [{:ms 10000
:dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]}
(transport/send send-record chat-id cofx)))))
[chat-id message-id send-record {{:keys [network-status current-public-key]} :db :as cofx}]
(if (= network-status :offline)
{:dispatch-later [{:ms 10000
:dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]}
(transport/send send-record chat-id cofx)))
(defn add-message-type [message {:keys [chat-id group-chat public?]}]
(cond-> message
@ -302,17 +242,6 @@
(and group-chat (not public?))
(assoc :message-type :group-user-message)))
(defn- prepare-plain-message [chat-id {:keys [identity message-text]}
{:keys [last-clock-value] :as chat} now]
(add-message-type {:chat-id chat-id
:content message-text
:from identity
:content-type constants/text-content-type
:timestamp now
:clock-value (utils.clocks/send last-clock-value)
:show? true}
chat))
(def ^:private transport-keys [:content :content-type :message-type :clock-value :timestamp])
(defn- upsert-and-send [{:keys [chat-id] :as message} {:keys [now] :as cofx}]
@ -374,155 +303,14 @@
:data-store/tx [(messages-store/delete-message-tx message-id)]}
(remove-message-from-group chat-id (get-in db [:chats chat-id :messages message-id]))))
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
(upsert-and-send (prepare-plain-message chat-id params (get-in db [:chats chat-id]) now) cofx))
(defn- prepare-command-message
[identity
{:keys [chat-id last-clock-value] :as chat}
now
{request-params :params
request-command :command
:keys [prefill prefillBotDb]
:as request}
{:keys [params command handler-data content-type]}
chain
currency
prices
tx-hash]
(let [content (if request
{:request-command request-command
;; TODO janherich this is technically not correct, but works for now
:request-command-ref (:ref command)
:params (assoc request-params
:bot-db (:bot-db params)
:fiat-amount (money/fiat-amount-value (:amount request-params)
(-> request-params :asset keyword)
currency
prices)
:currency (name currency))
:prefill prefill
:prefill-bot-db prefillBotDb}
{:params (cond-> params
(= (:name command) constants/command-send)
(assoc :network chain
:fiat-amount (money/fiat-amount-value (:amount params)
(-> params :asset keyword)
currency
prices)
:currency (name currency)
:tx-hash tx-hash))})
content' (assoc content
:command (:name command)
:handler-data handler-data
:type (name (:type command))
:command-scope-bitmask (:scope-bitmask command)
:command-ref (:ref command)
:preview (:preview command)
:short-preview (:short-preview command)
:bot (:owner-id command))]
(add-message-type {:chat-id chat-id
:from identity
:timestamp now
:content content'
:content-type (or content-type
(if request
constants/content-type-command-request
constants/content-type-command))
:clock-value (utils.clocks/send last-clock-value)
:show? true}
chat)))
(defn send-command
[{{:keys [current-public-key chats chain prices] :as db} :db :keys [now] :as cofx} params]
(let [{{:keys [handler-data to-message command] :as content} :command chat-id :chat-id} params
;; We send commands to deleted chats as well, i.e. signed later transactions
chat (or (get chats chat-id) {:chat-id chat-id})
request (:request handler-data)
command-name (:name command)
tx-hash (get-in db [:wallet :send-transaction :tx-hash])
currency (-> (currency-settings/get-user-currency db) name string/upper-case keyword)]
(handlers-macro/merge-fx cofx
(upsert-and-send (prepare-command-message current-public-key chat now request content
chain currency prices tx-hash))
(console-events/console-respond-command-messages command handler-data)
(requests-events/request-answered chat-id to-message))))
(defn invoke-console-command-handler
[{:keys [db] :as cofx} {:keys [command] :as command-params}]
(let [fx-fn (get console-events/console-commands->fx (-> command :command :name))
fx (fx-fn cofx command)]
(let [command (send-command (assoc cofx :db (or (:db fx) db)) command-params)
dn (concat (:dispatch-n fx) (:dispatch-n command))]
;; Make sure `dispatch-n` do not collide
;; TODO (jeluard) Refactor to rely on merge-fx and reduce creation of `dispatch-n`
(merge {:dispatch-n dn} (dissoc fx :dispatch-n) (dissoc command :dispatch-n)))))
(defn invoke-command-handlers
[{{:contacts/keys [contacts] :keys [chain] :as db} :db}
{{:keys [command params id]} :command
:keys [chat-id address]
:as orig-params}]
(let [{:keys [type name scope-bitmask bot owner-id]} command
handler-type (if (= :command type) :commands :responses)
to (get-in contacts [chat-id :address])
identity (or owner-id bot chat-id)
;; TODO what's actually semantic difference between `:parameters` and `:context`
;; and do we have some clear API for both ? seems very messy and unorganized now
jail-params {:parameters params
:context (cond-> {:from address
:to to
:current-account (get db :account/account)
:network chain
:message-id id}
(:async-handler command)
(assoc :orig-params orig-params))}]
{:call-jail [{:jail-id identity
:path [handler-type [name scope-bitmask] :handler]
:params jail-params
:callback-event-creator (fn [jail-response]
(when-not (:async-handler command)
[:command-handler! chat-id orig-params jail-response]))}]}))
(defn process-command
[cofx {:keys [command chat-id] :as params}]
(let [{:keys [command]} command]
(cond
(and (= constants/console-chat-id chat-id)
(console-events/commands-names (:name command)))
(invoke-console-command-handler cofx params)
(:has-handler command)
(invoke-command-handlers cofx params)
:else
(send-command cofx params))))
(defn custom-send-command-message [whisper-id address asset amount]
{:message nil,
:command {:command {:bot "transactor",
:color "#5fc48d",
:ref ["transactor" :command 83 "send"],
:name "send",
:type :command,
:async-handler false,
:icon "money_white",
:scope #{:global :personal-chats :registered :humans},
:has-handler true,
:preview nil,
:short-preview nil,
:scope-bitmask 83,
:owner-id "transactor"},
:params {:asset asset,
:amount amount},
:to-message nil,
:created-at (datetime/timestamp),
:chat-id whisper-id,
:handler-data nil},
:chat-id whisper-id,
:identity whisper-id,
:address address})
(defn send-custom-send-command [{:keys [whisper-identity address asset amount]} cofx]
(when whisper-identity
(send-command cofx (custom-send-command-message whisper-identity address asset amount))))
(defn send-message [{:keys [chat-id] :as message} {:keys [db now] :as cofx}]
(let [{:keys [current-public-key chats]} db
{:keys [last-clock-value] :as chat} (get chats chat-id)
message-data (-> message
(assoc :from current-public-key
:timestamp now
:clock-value (utils.clocks/send
last-clock-value)
:show? true)
(add-message-type chat))]
(upsert-and-send message-data cofx)))

View File

@ -1,5 +1,6 @@
(ns status-im.chat.specs
(:require [cljs.spec.alpha :as s]))
(:require [cljs.spec.alpha :as s]
status-im.chat.commands.specs))
(s/def :chat/chats (s/nilable map?)) ; {id (string) chat (map)} active chats on chat's tab
(s/def :chat/current-chat-id (s/nilable string?)) ; current or last opened chat-id

View File

@ -1,16 +0,0 @@
(ns status-im.chat.styles.input.result-box
(:require [status-im.ui.components.styles :as common]
[status-im.ui.components.colors :as colors]))
(defn root [height bottom]
{:background-color common/color-white
:border-top-color colors/gray-light
:border-top-width 1
:flex-direction :column
:height height
:left 0
:right 0
:elevation 2
:bottom bottom
:position :absolute})

View File

@ -162,195 +162,6 @@
:margin-bottom 4
:font-size 12})
(defn command-request-message-view [outgoing]
{:border-radius 14
:padding-vertical 4
:background-color (if outgoing colors/hawkes-blue styles/color-white)})
(def command-request-from-text
(merge style-sub-text {:margin-bottom 2}))
(defn command-request-image-touchable []
{:position :absolute
:top 0
:right -8
:align-items :center
:justify-content :center
:width 48
:height 48})
(defn command-request-image-view [command scale]
{:width 32
:height 32
:border-radius 16
:background-color (:color command)
:transform [{:scale scale}]})
(def command-image-view
{:position :absolute
:top 0
:right 0
:width 24
:height 24
:align-items :center})
(def command-request-image
{:position :absolute
:top 9
:left 10
:width 12
:height 13})
(def command-request-separator-line
{:background-color colors/gray-light
:height 1
:border-radius 8
:margin-top 10})
(def command-request-button
{:align-items :center
:padding-top 8})
(defn command-request-button-text [answered?]
{:font-size 15
:color (if answered? colors/gray colors/blue)})
(def command-request-text-view
{:margin-top 4
:height 14})
(defn command-request-header-text [outgoing]
{:font-size 12
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(def command-request-network-text
{:color colors/red})
(def command-request-row
{:flex-direction :row
:margin-top 6})
(def command-request-fiat-amount-row
{:margin-top 6})
(def command-request-fiat-amount-text
{:font-size 12
:color colors/black})
(def command-request-timestamp-row
{:margin-top 6})
(defn command-request-timestamp-text [outgoing]
{:font-size 12
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(defstyle command-request-amount-text
{:font-size 22
:ios {:letter-spacing -0.5}
:color colors/black})
(defn command-amount-currency-separator [outgoing]
{:opacity 0
:color (if outgoing colors/hawkes-blue colors/white)})
(defn command-request-currency-text [outgoing]
{:font-size 22
:letter-spacing 1
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(def command-request-recipient-text
{:color colors/blue
:font-size 14
:line-height 18})
(def content-command-view
{:flex-direction :column
:align-items :flex-start})
(def command-container
{:flex-direction :row
:margin-top 4
:margin-right 32})
(def command-image
{:margin-top 9
:width 12
:height 13
:tint-color :#a9a9a9cc})
(def command-text
(merge style-message-text
{:margin-top 8
:margin-horizontal 0}))
(def command-send-message-view
{:flex-direction :column
:align-items :flex-start})
(def command-send-amount-row
{:flex-direction :row
:justify-content :space-between})
(def command-send-amount
{:flex-direction :column
:align-items :flex-end
:max-width 250})
(defstyle command-send-amount-text
{:font-size 22
:color colors/blue
:ios {:letter-spacing -0.5}})
(def command-send-currency
{:flex-direction :column
:align-items :flex-end})
(defn command-send-currency-text [outgoing]
{:font-size 22
:margin-left 4
:letter-spacing 1
:color (if outgoing colors/wild-blue-yonder colors/blue-transparent-40)})
(def command-send-fiat-amount
{:flex-direction :column
:justify-content :flex-end
:margin-top 6})
(def command-send-fiat-amount-text
{:font-size 12
:color colors/black})
(def command-send-recipient-text
{:color colors/blue
:font-size 14
:line-height 18})
(defn command-send-timestamp [outgoing]
{:color (if outgoing colors/wild-blue-yonder colors/gray)
:margin-top 6
:font-size 12})
(def command-send-status-container
{:margin-top 6
:flex-direction :row})
(defn command-send-status-icon [outgoing]
{:background-color (if outgoing
colors/blue-darker
colors/blue-transparent)
:width 24
:height 24
:border-radius 16
:padding-top 4
:padding-left 4})
(defstyle command-send-status-text
{:color colors/blue
:android {:margin-top 3}
:ios {:margin-top 4}
:margin-left 6
:font-size 12})
(def audio-container
{:flex-direction :row
:align-items :center})

View File

@ -3,9 +3,7 @@
[re-frame.core :refer [reg-sub subscribe]]
[status-im.chat.constants :as chat-constants]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.views.input.utils :as input-utils]
[status-im.commands.utils :as commands-utils]
[status-im.chat.commands.core :as commands]
[status-im.utils.datetime :as time]
[status-im.utils.platform :as platform]
[status-im.utils.gfycat.core :as gfycat]
@ -19,6 +17,10 @@
(reg-sub :chat-ui-props :chat-ui-props)
(reg-sub :get-id->command :id->command)
(reg-sub :get-access-scope->command-id :access-scope->command-id)
(reg-sub
:get-current-chat-ui-props
:<- [:chat-ui-props]
@ -63,13 +65,7 @@
:validation-messages
:<- [:get-current-chat-ui-props]
(fn [ui-props]
(some-> ui-props :validation-messages commands-utils/generate-hiccup)))
(reg-sub
:result-box-markup
:<- [:get-current-chat-ui-props]
(fn [ui-props]
(some-> ui-props :result-box :markup commands-utils/generate-hiccup)))
(some-> ui-props :validation-messages)))
(reg-sub
:chat-input-margin
@ -80,18 +76,16 @@
platform/ios? kb-height
:default 0)))
(defn- active-chat? [dev-mode? [_ chat]]
(defn- active-chat? [[_ chat]]
(and (:is-active chat)
(or dev-mode?
(not= const/console-chat-id (:chat-id chat)))))
(not= const/console-chat-id (:chat-id chat))))
(defn active-chats [[chats {:keys [dev-mode?]}]]
(into {} (filter (partial active-chat? dev-mode?) chats)))
(defn active-chats [chats]
(into {} (filter active-chat? chats)))
(reg-sub
:get-active-chats
:<- [:get-chats]
:<- [:get-current-account]
active-chats)
(reg-sub
@ -230,133 +224,83 @@
(reg-sub
:get-commands-for-chat
:<- [:get-commands-responses-by-access-scope]
:<- [:get-current-account]
:<- [:get-id->command]
:<- [:get-access-scope->command-id]
:<- [:get-current-chat]
:<- [:get-contacts]
(fn [[commands-responses account chat contacts]]
(commands-model/commands-responses :command commands-responses account chat contacts)))
(reg-sub
:get-responses-for-chat
:<- [:get-commands-responses-by-access-scope]
:<- [:get-current-account]
:<- [:get-current-chat]
:<- [:get-contacts]
(fn [[commands-responses account {:keys [requests] :as chat} contacts]]
(commands-model/requested-responses commands-responses account chat contacts (vals requests))))
(fn [[id->command access-scope->command-id chat]]
(commands/chat-commands id->command access-scope->command-id chat)))
(def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first)))
(defn- available-commands-responses [[commands-responses {:keys [input-text]}]]
(->> commands-responses
(defn- available-commands [[commands {:keys [input-text]}]]
(->> commands
map->sorted-seq
(filter (fn [item]
(filter (fn [{:keys [type]}]
(when (input-model/starts-as-command? input-text)
(string/includes? (commands-model/command-name item) input-text))))))
(string/includes? (commands/command-name type) input-text))))))
(reg-sub
:get-available-commands
:<- [:get-commands-for-chat]
:<- [:get-current-chat]
available-commands-responses)
available-commands)
(reg-sub
:get-available-responses
:<- [:get-responses-for-chat]
:<- [:get-current-chat]
available-commands-responses)
(reg-sub
:get-available-commands-responses
:get-all-available-commands
:<- [:get-commands-for-chat]
:<- [:get-responses-for-chat]
(fn [[commands responses]]
(map->sorted-seq (merge commands responses))))
(fn [commands]
(map->sorted-seq commands)))
(reg-sub
:selected-chat-command
:<- [:get-current-chat]
:<- [:get-current-chat-ui-prop :selection]
:<- [:get-commands-for-chat]
:<- [:get-responses-for-chat]
(fn [[chat commands responses]]
(input-model/selected-chat-command chat commands responses)))
(fn [[{:keys [input-text]} selection commands]]
(commands/selected-chat-command input-text selection commands)))
(reg-sub
:chat-input-placeholder
:<- [:get-current-chat]
:<- [:selected-chat-command]
(fn [[{:keys [input-text]} command]]
(when (and (string/ends-with? (or input-text "") chat-constants/spacing-char)
(not (get-in command [:command :sequential-params])))
(let [real-args (remove string/blank? (:args command))]
(cond
(and command (empty? real-args))
(get-in command [:command :params 0 :placeholder])
(and command
(= (count real-args) 1))
(get-in command [:command :params 1 :placeholder]))))))
(reg-sub
:current-chat-argument-position
:<- [:selected-chat-command]
:<- [:get-current-chat]
:<- [:get-current-chat-ui-prop :selection]
(fn [[command {:keys [input-text seq-arguments]} selection]]
(input-model/current-chat-argument-position command input-text selection seq-arguments)))
(fn [[{:keys [input-text]} {:keys [params current-param-position]}]]
(when (string/ends-with? (or input-text "") chat-constants/spacing-char)
(get-in params [current-param-position :placeholder]))))
(reg-sub
:chat-parameter-box
:<- [:get-current-chat]
:<- [:selected-chat-command]
:<- [:current-chat-argument-position]
(fn [[current-chat selected-chat-command argument-position]]
(cond
(and selected-chat-command
(not= argument-position input-model/*no-argument-error*))
(get-in current-chat [:parameter-boxes
(get-in selected-chat-command [:command :name])
argument-position])
(not selected-chat-command)
(get-in current-chat [:parameter-boxes :message])
:default
nil)))
(fn [[current-chat {:keys [current-param-position params]}]]
(when (and params current-param-position)
(get-in params [current-param-position :suggestions]))))
(reg-sub
:show-parameter-box?
:<- [:chat-parameter-box]
:<- [:show-suggestions?]
:<- [:get-current-chat]
:<- [:validation-messages]
(fn [[chat-parameter-box show-suggestions? {:keys [input-text]} validation-messages]]
(and (get chat-parameter-box :markup)
(fn [[chat-parameter-box show-suggestions? validation-messages]]
(and chat-parameter-box
(not validation-messages)
(not show-suggestions?))))
(reg-sub
:command-completion
:<- [:selected-chat-command]
input-model/command-completion)
(reg-sub
:show-suggestions-view?
:<- [:get-current-chat-ui-prop :show-suggestions?]
:<- [:get-current-chat]
:<- [:selected-chat-command]
:<- [:get-available-commands-responses]
(fn [[show-suggestions? {:keys [input-text]} selected-command commands-responses]]
(and (or show-suggestions? (input-model/starts-as-command? (string/trim (or input-text ""))))
(seq commands-responses))))
:<- [:get-all-available-commands]
(fn [[show-suggestions? {:keys [input-text]} commands]]
(and (or show-suggestions?
(input-model/starts-as-command? (string/trim (or input-text ""))))
(seq commands))))
(reg-sub
:show-suggestions?
:<- [:show-suggestions-view?]
:<- [:selected-chat-command]
(fn [[show-suggestions-box? selected-command]]
(and show-suggestions-box? (not (:command selected-command)))))
(and show-suggestions-box? (not selected-command))))
(reg-sub
:is-request-answered?

View File

@ -1,41 +0,0 @@
(ns status-im.chat.views.api.choose-asset
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.utils.money :as money]
[status-im.chat.views.api.styles :as styles]))
(defn clean-asset [asset]
(select-keys asset [:name :symbol :decimals :address]))
(defn- render-asset [arg-index bot-db-key]
(fn [{:keys [name symbol amount decimals] :as asset}]
[react/touchable-highlight {:on-press #(re-frame/dispatch
[:set-asset-as-command-argument {:arg-index arg-index
:bot-db-key bot-db-key
:asset (clean-asset asset)}])}
[react/view styles/asset-container
[react/view styles/asset-main
[react/image {:source (-> asset :icon :source)
:style styles/asset-icon}]
[react/text {:style styles/asset-symbol} symbol]
[react/text {:style styles/asset-name} name]]
;;TODO(goranjovic) : temporarily disabled to fix https://github.com/status-im/status-react/issues/4963
;;until the resolution of https://github.com/status-im/status-react/issues/4972
#_[react/text {:style styles/asset-balance}
(str (money/internal->formatted amount symbol decimals))]]]))
(def assets-separator [react/view styles/asset-separator])
(defview choose-asset-view [{arg-index :index
bot-db-key :bot-db-key}]
(letsubs [assets [:wallet/visible-assets-with-amount]]
[react/view
[list/flat-list {:data (filter #(not (:nft? %)) assets)
:key-fn (comp name :symbol)
:render-fn (render-asset arg-index bot-db-key)
:enableEmptySections true
:separator assets-separator
:keyboardShouldPersistTaps :always
:bounces false}]]))

View File

@ -1,33 +0,0 @@
(ns status-im.chat.views.api.choose-contact
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.contact.contact :refer [contact-view]]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]))
(defn- render-contact [arg-index bot-db-key]
(fn [contact]
[contact-view {:contact contact
:on-press #(re-frame/dispatch
[:set-contact-as-command-argument {:arg-index arg-index
:bot-db-key bot-db-key
:contact contact}])}]))
(defview choose-contact-view [{title :title
arg-index :index
bot-db-key :bot-db-key}]
(letsubs [contacts [:get-people-in-current-chat]]
[react/view
[react/text {:style {:font-size 14
:color "rgb(147, 155, 161)"
:padding-top 12
:padding-left 16
:padding-right 16
:padding-bottom 12}}
title]
[list/flat-list {:data contacts
:key-fn :address
:render-fn (render-contact arg-index bot-db-key)
:enableEmptySections true
:keyboardShouldPersistTaps :always
:bounces false}]]))

View File

@ -1,35 +0,0 @@
(ns status-im.chat.views.api.styles
(:require [status-im.ui.components.colors :as colors]))
(def asset-container
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-vertical 11})
(def asset-main
{:flex 1
:flex-direction :row
:align-items :center})
(def asset-icon
{:width 30
:height 30
:margin-left 14
:margin-right 12})
(def asset-symbol
{:color colors/black})
(def asset-name
{:color colors/gray
:padding-left 4})
(def asset-balance
{:color colors/gray
:padding-right 14})
(def asset-separator
{:height 1
:background-color colors/gray-light
:margin-left 56})

View File

@ -6,7 +6,6 @@
[status-im.chat.constants :as constants]
[status-im.chat.styles.input.input :as style]
[status-im.chat.views.input.parameter-box :as parameter-box]
[status-im.chat.views.input.result-box :as result-box]
[status-im.chat.views.input.send-button :as send-button]
[status-im.chat.views.input.suggestions :as suggestions]
[status-im.chat.views.input.validation-messages :as validation-messages]
@ -93,8 +92,8 @@
[input-helper {:width width}]]])))
(defview commands-button []
(letsubs [commands-responses [:get-available-commands-responses]]
(when (seq commands-responses)
(letsubs [commands [:get-all-available-commands]]
(when (seq commands)
[react/touchable-highlight
{:on-press #(do (re-frame/dispatch [:set-chat-input-text constants/command-char])
(re-frame/dispatch [:chat-input-focus :input-ref]))
@ -123,7 +122,6 @@
(defn container []
[react/view
[parameter-box/parameter-box-view]
[result-box/result-box-view]
[suggestions/suggestions-view]
[validation-messages/validation-messages-view]
[input-container]])

View File

@ -2,19 +2,18 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.chat.views.input.animations.expandable :as expandable]
[status-im.chat.styles.input.parameter-box :as style]
[status-im.commands.utils :as command-utils]
[status-im.ui.components.react :as react]
[taoensso.timbre :as log]))
(defview parameter-box-container []
[{:keys [markup]} [:chat-parameter-box]
bot-id-bot-db [:current-bot-db]]
(when markup
[react/view style/root
(command-utils/generate-hiccup markup (first bot-id-bot-db) (second bot-id-bot-db))]))
(letsubs [parameter-box [:chat-parameter-box]]
(when parameter-box
[react/view style/root
[parameter-box]])))
(defview parameter-box-view []
(letsubs [show-parameter-box? [:show-parameter-box?]]
(when show-parameter-box?
(letsubs [show-box? [:show-parameter-box?]]
(when show-box?
[react/view]
[expandable/expandable-view {:key :parameter-box}
[parameter-box-container]])))

View File

@ -1,14 +0,0 @@
(ns status-im.chat.views.input.result-box
(:require-macros [status-im.utils.views :refer [defview]])
(:require [status-im.ui.components.react :as react]
[status-im.chat.views.input.animations.expandable :as expandable]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.styles :as common-styles]))
(defview result-box-view []
[markup [:result-box-markup]]
(when markup
[expandable/expandable-view {:key :result-box}
[react/view common-styles/flex
markup]
[connectivity/error-view]]))

View File

@ -10,7 +10,7 @@
(defn send-button-view-on-update [{:keys [spin-value command-completion]}]
(fn [_]
(let [to-spin-value (if (some #{:complete :no-command} [@command-completion]) 1 0)]
(let [to-spin-value (if (#{:complete :no-command} command-completion) 1 0)]
(animation/start
(animation/timing spin-value {:toValue to-spin-value
:duration 300})))))
@ -20,16 +20,14 @@
(not (or (string/blank? trimmed) (= trimmed "/")))))
(defview send-button-view []
(letsubs [command-completion [:command-completion]
selected-command [:selected-chat-command]
(letsubs [{:keys [command-completion]} [:selected-chat-command]
{:keys [input-text seq-arg-input-text]} [:get-current-chat]
spin-value (animation/create-value 1)
on-update (send-button-view-on-update {:spin-value spin-value
:command-completion command-completion})]
{:component-did-update on-update}
spin-value (animation/create-value 1)]
{:component-did-update (send-button-view-on-update {:spin-value spin-value
:command-completion command-completion})}
(when (and (sendable? input-text)
(or (not selected-command)
(some #{:complete :less-than-needed} [command-completion])))
(or (not command-completion)
(#{:complete :less-than-needed} command-completion)))
[react/touchable-highlight {:on-press #(re-frame/dispatch [:send-current-message])}
(let [spin (.interpolate spin-value (clj->js {:inputRange [0 1]
:outputRange ["0deg" "90deg"]}))]

View File

@ -4,7 +4,7 @@
[status-im.ui.components.react :as react]
[status-im.chat.styles.input.suggestions :as style]
[status-im.chat.views.input.animations.expandable :as expandable]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.commands.core :as commands]
[status-im.i18n :as i18n]
[taoensso.timbre :as log]))
@ -19,19 +19,18 @@
description]]])
(defview suggestions-view []
(letsubs [commands [:get-available-commands]]
(letsubs [available-commands [:get-available-commands]]
[expandable/expandable-view {:key :suggestions}
[react/view
[react/scroll-view {:keyboard-should-persist-taps :always
:bounces false}
(when (seq commands)
(for [[i {:keys [description] :as command}] (map-indexed vector commands)]
^{:key i}
[suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil])
:name (commands-model/command-name command)
:description description
:last? (= i (dec (count commands)))
:accessibility-label (case (:name command)
"send" :send-payment-button
"request" :request-payment-button
nil)}]))]]]))
(when (seq available-commands)
(map-indexed
(fn [i {:keys [type] :as command}]
^{:key i}
[suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil])
:name (commands/command-name type)
:description (commands/command-description type)
:last? (= i (dec (count available-commands)))
:accessibility-label (commands/accessibility-label type)}])
available-commands))]]]))

View File

@ -18,11 +18,12 @@
(defview validation-messages-view []
(letsubs [chat-input-margin [:chat-input-margin]
input-height [:get-current-chat-ui-prop :input-height]
messages [:validation-messages]]
(when messages
[react/view (style/root (+ input-height chat-input-margin))
(if (string? messages)
[messages-list [validation-message {:title (i18n/label :t/error)
:description messages}]]
[messages-list messages])])))
input-height [:get-current-chat-ui-prop :input-height]
validation-result [:validation-messages]]
(when validation-result
(let [message (if (string? validation-result)
{:title (i18n/label :t/error)
:description validation-result}
validation-result)]
[react/view (style/root (+ input-height chat-input-margin))
[messages-list [validation-message message]]]))))

View File

@ -7,12 +7,10 @@
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.action-sheet :as action-sheet]
[status-im.commands.utils :as commands.utils]
[status-im.chat.models.commands :as models.commands]
[status-im.chat.models.message :as models.message]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.chat.styles.message.message :as style]
[status-im.chat.styles.message.command-pill :as pill-style]
[status-im.chat.views.message.request-message :as request-message]
[status-im.chat.views.photos :as photos]
[status-im.constants :as constants]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
@ -22,8 +20,7 @@
[status-im.utils.platform :as platform]
[status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[clojure.string :as string]
[status-im.chat.events.console :as console]))
[clojure.string :as string]))
(defview message-content-status []
(letsubs [{:keys [chat-id group-id name color public-key]} [:get-current-chat]
@ -56,90 +53,11 @@
:font :default}
"03:39"]]])
(defview send-command-status [tx-hash outgoing]
(letsubs [confirmed? [:transaction-confirmed? tx-hash]
tx-exists? [:wallet-transaction-exists? tx-hash]]
[react/touchable-highlight {:on-press #(when tx-exists?
(re-frame/dispatch [:show-transaction-details tx-hash]))}
[react/view style/command-send-status-container
[vector-icons/icon (if confirmed? :icons/check :icons/dots)
{:color colors/blue
:container-style (style/command-send-status-icon outgoing)}]
[react/view
[react/text {:style style/command-send-status-text}
(i18n/label (cond
confirmed? :status-confirmed
tx-exists? :status-pending
:else :status-tx-not-found))]]]]))
(defview message-content-command-send
[{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]]
(let [{{:keys [amount fiat-amount tx-hash asset currency] send-network :network} :params} content
recipient-name (get-in content [:params :bot-db :public :recipient])
amount-text-long? (< 10 (count amount))
network-mismatch? (and (seq send-network) (not= network send-network))]
[react/view style/command-send-message-view
[react/view
[react/view style/command-send-amount-row
[react/view style/command-send-amount
[react/text {:style style/command-send-amount-text
:font :medium}
amount
[react/text {:style (style/command-amount-currency-separator outgoing)}
(if amount-text-long? "\n" ".")]
[react/text {:style (style/command-send-currency-text outgoing)
:font :default}
asset]]]]
(when fiat-amount
[react/view style/command-send-fiat-amount
[react/text {:style style/command-send-fiat-amount-text}
(str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]])
(when (and group-chat
recipient-name)
[react/text {:style style/command-send-recipient-text}
(str
(i18n/label :send-sending-to)
" "
recipient-name)])
[react/view
[react/text {:style (style/command-send-timestamp outgoing)}
(str (i18n/label :sent-at) " " timestamp-str)]]
[send-command-status tx-hash outgoing]
(when network-mismatch?
[react/text send-network])]])))
;; Used for command messages with markup generated on JS side
(defview message-content-command-with-markup
[{:keys [content params]}]
(letsubs [command [:get-command (:command-ref content)]]
(let [preview (:preview content)
{:keys [color] icon-path :icon} command]
[react/view style/content-command-view
(when color
[react/view style/command-container
[react/view (pill-style/pill command)
[react/text {:style pill-style/pill-text
:font :default}
(models.commands/command-name command)]]])
(when icon-path
[react/view style/command-image-view
[react/icon icon-path style/command-image]])
(if (:markup preview)
;; Markup was defined for command in jail, generate hiccup and render it
(commands.utils/generate-hiccup (:markup preview))
;; Display preview if it's defined (as a string), in worst case, render params
[react/text {:style style/command-text
:font :default}
(or preview (str params))])])))
(defn message-content-command
[message]
(let [{{:keys [command preview]} :content} message]
(if (and (= command constants/command-send)
(nil? preview))
[message-content-command-send message]
[message-content-command-with-markup message])))
(defview message-content-command
[command-message]
(letsubs [id->command [:get-id->command]]
(when-let [command (commands-receiving/lookup-command-by-ref command-message id->command)]
(commands/generate-preview command command-message))))
(def rtl-characters-regex #"[^\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]*?[\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]")
@ -156,7 +74,9 @@
[{:keys [timestamp-str outgoing content] :as message} message-content {:keys [justify-timestamp?]}]
[react/view (style/message-view message)
message-content
[message-timestamp timestamp-str justify-timestamp? outgoing (get-in message [:content :command]) content]])
[message-timestamp timestamp-str justify-timestamp? outgoing (or (get content :command-path)
(get content :command-ref))
content]])
(def replacements
{"\\*[^*]+\\*" {:font-weight :bold}
@ -286,11 +206,6 @@
(defmulti message-content (fn [_ message _] (message :content-type)))
(defmethod message-content constants/content-type-command-request
[wrapper message]
[wrapper message
[message-view message [request-message/message-content-command-request message]]])
(defmethod message-content constants/text-content-type
[wrapper message]
[wrapper message [text-message message]])
@ -303,6 +218,13 @@
[_ _]
[message-content-status])
;; TODO(janherich) in the future, `content-type-command-request` will be deprecated
;; as it's the same thing as command
(defmethod message-content constants/content-type-command-request
[wrapper message]
[wrapper message
[message-view message [message-content-command message]]])
(defmethod message-content constants/content-type-command
[wrapper message]
[wrapper message
@ -387,12 +309,7 @@
[{:keys [chat-id message-id current-public-key user-statuses content last-outgoing? outgoing message-type] :as message}]
(let [outgoing-status (or (get-in user-statuses [current-public-key :status]) :not-sent)
delivery-status (get-in user-statuses [chat-id :status])
status (cond (and (= constants/console-chat-id chat-id)
(not (console/commands-with-delivery-status (:command content))))
:seen
:else
(or delivery-status outgoing-status))]
status (or delivery-status outgoing-status)]
(case status
:sending [message-activity-indicator]
:not-sent [message-not-sent-text chat-id message-id]

View File

@ -1,146 +0,0 @@
(ns status-im.chat.views.message.request-message
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [subscribe dispatch]]
[reagent.core :as r]
[status-im.i18n :as i18n]
[status-im.ui.components.react :refer [view
animated-view
text
image
icon
touchable-highlight]]
[status-im.chat.styles.message.message :as st]
[status-im.chat.models.commands :as commands]
[status-im.utils.datetime :as datetime]
[status-im.utils.money :as money]
[status-im.commands.utils :as commands-utils]
[status-im.ui.components.animation :as anim]
[taoensso.timbre :as log]))
(def request-message-icon-scale-delay 600)
(defn set-chat-command [message-id command]
(let [metadata {:to-message-id message-id}]
(dispatch [:select-chat-input-command command metadata])))
(def min-scale 1)
(def max-scale 1.3)
(defn button-animation [val to-value loop? answered?]
(anim/anim-sequence
[(anim/anim-delay
(if (and @loop? (not @answered?))
request-message-icon-scale-delay
0))
(anim/spring val {:toValue to-value
:useNativeDriver true})]))
(defn request-button-animation-logic
[{:keys [to-value val loop? answered?] :as context}]
(anim/start
(button-animation val to-value loop? answered?)
#(if (and @loop? (not @answered?))
(let [new-value (if (= to-value min-scale) max-scale min-scale)
context' (assoc context :to-value new-value)]
(request-button-animation-logic context'))
(anim/start
(button-animation val min-scale loop? answered?)))))
(defn request-button-label
"The request button label will be in the form of `request-the-command-name`"
[command-name]
(keyword (str "request-" (name command-name))))
(defn request-button [message-id _ on-press-handler]
(let [scale-anim-val (anim/create-value min-scale)
answered? (subscribe [:is-request-answered? message-id])
loop? (r/atom true)
context {:to-value max-scale
:val scale-anim-val
:answered? answered?
:loop? loop?}]
(r/create-class
{:display-name "request-button"
:component-did-mount
(if (or (nil? on-press-handler) @answered?) (fn []) #(request-button-animation-logic context))
:component-will-unmount
#(reset! loop? false)
:reagent-render
(fn [message-id {command-icon :icon :as command} on-press-handler]
(when command
[touchable-highlight
{:on-press on-press-handler
:style (st/command-request-image-touchable)
:accessibility-label (request-button-label (:name command))}
[animated-view {:style (st/command-request-image-view command scale-anim-val)}
(when command-icon
[icon command-icon st/command-request-image])]]))})))
(defview message-content-command-request
[{:keys [message-id content outgoing timestamp timestamp-str group-chat]}]
(letsubs [command [:get-command (:request-command-ref content)]
answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?]
network [:network-name]
prices [:prices]]
(let [{:keys [prefill prefill-bot-db prefillBotDb params preview]
text-content :text} content
command (if (and params command)
(merge command {:prefill prefill
:prefill-bot-db (or prefill-bot-db prefillBotDb)})
command)
{:keys [amount asset fiat-amount currency] request-network :network} params
recipient-name (get-in params [:bot-db :public :recipient])
network-mismatch? (and request-network (not= request-network network))
on-press-handler (cond
network-mismatch? nil
(and (not answered?) status-initialized?) #(set-chat-command message-id command))]
[view
[touchable-highlight
{:on-press on-press-handler}
[view (st/command-request-message-view outgoing)
(if (:markup preview)
[view (commands-utils/generate-hiccup (:markup preview))
(when network-mismatch?
[text request-network])]
[view
[view
[text {:style (st/command-request-header-text outgoing)}
(i18n/label :transaction-request)]]
[view st/command-request-row
[text {:style st/command-request-amount-text
:font :medium}
amount
[text {:style (st/command-amount-currency-separator outgoing)}
"."]
[text {:style (st/command-request-currency-text outgoing)
:font :default}
asset]]]
[view st/command-request-fiat-amount-row
[text {:style st/command-request-fiat-amount-text}
(str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]]
(when (and group-chat
recipient-name)
[text {:style st/command-request-recipient-text}
(str
(i18n/label :request-requesting-from)
" "
recipient-name)])
(when network-mismatch?
[text {:style st/command-request-network-text}
(str (i18n/label :on) " " request-network)])
[view st/command-request-timestamp-row
[text {:style (st/command-request-timestamp-text outgoing)}
(str
(datetime/timestamp->mini-date timestamp)
" "
(i18n/label :at)
" "
timestamp-str)]]
(when-not outgoing
[view
[view st/command-request-separator-line]
[view st/command-request-button
[text {:style (st/command-request-button-text answered?)
:on-press on-press-handler}
(i18n/label (if answered? :command-button-sent :command-button-send))]]])])]]])))

View File

@ -1,160 +0,0 @@
(ns status-im.commands.events.loading
(:require [clojure.string :as string]
[clojure.set :as s]
[re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.utils.js-resources :as js-resources]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils]
[status-im.native-module.core :as status]
[status-im.bots.events :as bots-events]
[taoensso.timbre :as log]))
;; FX
(re-frame/reg-fx
::evaluate-jail-n
(fn [jail-data]
(doseq [{:keys [jail-id jail-resource]} jail-data]
(status/parse-jail
jail-id jail-resource
(fn [jail-response]
(let [converted (types/json->clj jail-response)]
(re-frame/dispatch [::proceed-loading jail-id (update converted :result types/json->clj)])))))))
(re-frame/reg-fx
::show-popup
(fn [{:keys [title msg]}]
(utils/show-popup title msg)))
;; Handlers
(defn- valid-network-resource?
[response]
(some-> (.. response -headers)
(get "Content-type")
(string/includes? "application/javascript")))
(defn- evaluate-commands-in-jail
[{:keys [db get-local-storage-data]} commands-resource whisper-identity]
(let [data (get-local-storage-data whisper-identity)
local-storage-snippet (js-resources/local-storage-data data)
network-id (get-in db [:account/account :networks (:network db) :config :NetworkId])
ethereum-id-snippet (js-resources/network-id network-id)
commands-snippet (str local-storage-snippet ethereum-id-snippet commands-resource)]
{::evaluate-jail-n [{:jail-id whisper-identity
:jail-resource commands-snippet}]}))
(defn load-commands-for-bot
"This function takes coeffects, effects, bot contact and adds effects
for loading all commands/responses/subscriptions."
[cofx fx {:keys [whisper-identity bot-url]}]
(if-let [commands-resource (js-resources/get-resource bot-url)]
(merge-with into fx (evaluate-commands-in-jail cofx commands-resource whisper-identity))
(update fx :http-get-n conj {:url bot-url
:response-validator valid-network-resource?
:success-event-creator (fn [commands-resource]
[::evaluate-commands-in-jail commands-resource whisper-identity])
:failure-event-creator (fn [error-response]
[::proceed-loading whisper-identity {:error error-response}])})))
(defn load-commands
"This function takes coeffects and produces effects
for loading all commands/responses/subscriptions for existing bot contacts.
It's currently working only for bots, eq we are not evaluating
dapp resources in jail at all."
[{:keys [db] :as cofx}]
(transduce (comp (map second)
(filter :bot-url))
(completing (partial load-commands-for-bot cofx))
{}
(:contacts/contacts db)))
(defn- add-exclusive-choices [initial-scope exclusive-choices]
(reduce (fn [scopes-set exclusive-choices]
(reduce (fn [scopes-set scope]
(let [exclusive-match (s/intersection scope exclusive-choices)]
(if (seq exclusive-match)
(reduce conj
(disj scopes-set scope)
(map (partial conj (s/difference scope exclusive-match))
exclusive-match))
scopes-set)))
scopes-set
scopes-set))
#{initial-scope}
exclusive-choices))
(defn- create-access-scopes
"Based on command owner and command scope, create set of access-scopes which can be used to directly
look up any commands/subscriptions relevant for actual context (type of chat opened, registered user
or not, etc.)"
[jail-id scope]
(let [member-scope (cond-> scope
(not (scope :global)) (conj jail-id))]
(add-exclusive-choices member-scope [#{:personal-chats :group-chats}
#{:anonymous :registered}
#{:dapps :humans :public-chats}])))
(defn- index-by-access-scope-type
[init jail-id items]
(reduce (fn [acc {:keys [scope name type ref]}]
(let [access-scopes (create-access-scopes jail-id scope)]
(reduce (fn [acc access-scope]
(assoc-in acc [access-scope type name] ref))
acc
access-scopes)))
init
items))
(defn- enrich
[jail-id type [_ {:keys [scope-bitmask scope name] :as props}]]
(-> props
(assoc :scope (into #{} (map keyword) scope)
:owner-id jail-id
:bot jail-id
:type type
:ref [jail-id type scope-bitmask name])))
(defn add-jail-result
"This function add commands/responses/subscriptions from jail-evaluated resource
into the database"
[db jail-id {:keys [commands responses subscriptions]}]
(let [enriched-commands (map (partial enrich jail-id :command) commands)
enriched-responses (map (partial enrich jail-id :response) responses)
new-db (reduce (fn [acc {:keys [ref] :as props}]
(assoc-in acc (into [:contacts/contacts] ref) props))
db
(concat enriched-commands enriched-responses))]
(-> new-db
(update :access-scope->commands-responses (fn [acc]
(-> (or acc {})
(index-by-access-scope-type jail-id enriched-commands)
(index-by-access-scope-type jail-id enriched-responses))))
(update-in [:contacts/contacts jail-id] assoc
:subscriptions (bots-events/transform-bot-subscriptions subscriptions)
:jail-loaded? true))))
(handlers/register-handler-fx
::evaluate-commands-in-jail
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [cofx [commands-resource whisper-identity]]
(evaluate-commands-in-jail cofx commands-resource whisper-identity)))
(handlers/register-handler-fx
::proceed-loading
[re-frame/trim-v]
(fn [{:keys [db]} [jail-id {:keys [error result]}]]
(if error
(let [message (string/join "\n" ["bot.js loading failed"
jail-id
error])]
{::show-popup {:title "Error"
:msg message}})
(let [jail-loaded-events (get-in db [:contacts/contacts jail-id :jail-loaded-events])]
(cond-> {:db (add-jail-result db jail-id result)
:call-jail-function {:chat-id jail-id
:function :init :context
{:from (get-in db [:account/account :address])}}}
(seq jail-loaded-events)
(-> (assoc :dispatch-n jail-loaded-events)
(update-in [:db :contacts/contacts jail-id] dissoc :jail-loaded-events)))))))

View File

@ -1,111 +0,0 @@
(ns status-im.commands.handlers.jail
(:require [clojure.string :as string]
[re-frame.core :refer [after dispatch subscribe trim-v debug]]
[status-im.utils.handlers :as handlers]
[status-im.utils.utils :refer [show-popup]]
[status-im.utils.types :as types]
[status-im.commands.utils :refer [reg-handler]]
[status-im.constants :refer [console-chat-id]]
[status-im.i18n :refer [get-contact-translated]]
[taoensso.timbre :as log]
[status-im.data-store.local-storage :as local-storage-store]))
(defn command-handler!
[_ [chat-id
params
{:keys [result error]}]]
(let [{:keys [returned]} result
{handler-error :error} returned]
(cond
handler-error
(when-let [markup (:markup handler-error)]
(dispatch [:set-chat-ui-props {:validation-messages markup}]))
result
(dispatch [:chat-send-message/send-command chat-id (assoc-in params [:command :handler-data] returned)])
(not (or error handler-error))
(dispatch [:chat-send-message/send-command chat-id params])
:else nil)))
(defn suggestions-handler!
[{:keys [chats] :as db}
[{:keys [chat-id bot-id default-db command parameter-index result]}]]
(let [{:keys [markup] :as returned} (get-in result [:result :returned])
contains-markup? (contains? returned :markup)
current-input (get-in chats [chat-id :input-text])
path (if command
[:chats chat-id :parameter-boxes (:name command) parameter-index]
(when-not (string/blank? current-input)
[:chats chat-id :parameter-boxes :message]))]
(when (and contains-markup? path (not= (get-in db path) markup))
(dispatch [:set-in path returned])
(when default-db
(dispatch [:update-bot-db {:bot bot-id
:db default-db}])))))
(defn suggestions-events-handler!
[{:keys [bot-db]} [bot-id [n & data] val]]
(log/debug "Suggestion event: " n (first data) val)
(case (keyword n)
:set-command-argument
(let [[index value move-to-next?] (first data)]
(dispatch [:set-command-argument [index value move-to-next?]]))
:set-value
(dispatch [:set-chat-input-text (first data)])
:set
(let [opts {:bot bot-id
:path (mapv keyword data)
:value val}]
(dispatch [:set-in-bot-db opts]))
:set-command-argument-from-db
(let [[index arg move-to-next?] (first data)
path (keyword arg)
value (str (get-in bot-db [bot-id path]))]
(dispatch [:set-command-argument [index value move-to-next?]]))
:set-value-from-db
(let [path (keyword (first data))
value (str (get-in bot-db [bot-id path]))]
(dispatch [:set-chat-input-text value]))
:focus-input
(dispatch [:chat-input-focus :input-ref])
nil))
(defn print-error-message! [message]
(fn [_ params]
(when (:error (last params))
(show-popup "Error" (string/join "\n" [message params]))
(log/debug message params))))
;; TODO(alwx): rewrite
(reg-handler :command-handler!
(after (print-error-message! "Error on command handling"))
(handlers/side-effect! command-handler!))
(reg-handler
:suggestions-handler
[(after (print-error-message! "Error on param suggestions"))]
(handlers/side-effect! suggestions-handler!))
(reg-handler
:suggestions-event!
(handlers/side-effect! suggestions-events-handler!))
(reg-handler
:show-suggestions-from-jail
(handlers/side-effect!
(fn [_ [_ {:keys [chat-id markup]}]]
(let [markup' (types/json->clj markup)
result (assoc-in {} [:result :returned :markup] markup')]
(dispatch [:suggestions-handler
{:result result
:chat-id chat-id}])))))
(handlers/register-handler-fx
:set-local-storage
[trim-v]
(fn [_ [{:keys [data chat-id]}]]
{:data-store/tx [(local-storage-store/set-local-storage-data-tx
{:chat-id chat-id
:data data})]}))

View File

@ -1,4 +0,0 @@
(ns status-im.commands.specs
(:require [cljs.spec.alpha :as spec]))
(spec/def :commands/access-scope->commands-responses (spec/nilable map?))

View File

@ -1,10 +0,0 @@
(ns status-im.commands.subs
(:require [re-frame.core :refer [reg-sub]]))
(reg-sub :get-commands-responses-by-access-scope :access-scope->commands-responses)
(reg-sub
:get-command
:<- [:get-contacts]
(fn [contacts [_ ref]]
(some->> ref (get-in contacts))))

View File

@ -1,75 +0,0 @@
(ns status-im.commands.utils
(:require [clojure.set :as set]
[clojure.walk :as w]
[re-frame.core :refer [dispatch trim-v]]
[status-im.ui.components.react :as components]
[status-im.chat.views.input.validation-messages :as chat-validation-messages]
[status-im.chat.views.api.choose-contact :as choose-contact]
[status-im.chat.views.api.choose-asset :as choose-asset]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im.ui.components.chat-preview :as chat-preview]
[status-im.utils.handlers :refer [register-handler]]
[taoensso.timbre :as log]))
(defn json->clj [json]
(when-not (= json "undefined")
(js->clj (.parse js/JSON json) :keywordize-keys true)))
(defn parameter-box-separator []
[components/view {:height 1 :background-color "#c1c7cbb7" :opacity 0.5}])
(def elements
{:view components/view
:text components/text
:text-input components/text-input
:chat-preview-text chat-preview/text
:image components/image
:qr-code qr-code-viewer/qr-code
:linking components/linking
:slider components/slider
:scroll-view components/scroll-view
:web-view components/web-view
:touchable components/touchable-highlight
:activity-indicator components/activity-indicator
:validation-message chat-validation-messages/validation-message
:choose-contact choose-contact/choose-contact-view
:choose-asset choose-asset/choose-asset-view
:separator parameter-box-separator})
(defn get-element [n]
(elements (keyword (.toLowerCase n))))
(def events #{:onPress :onValueChange :onSlidingComplete})
(defn wrap-event [[_ event] bot-id]
#(dispatch [:suggestions-event! bot-id (update event 0 keyword) %]))
(defn check-events [m bot-id]
(let [ks (set (keys m))
evs (set/intersection ks events)]
(reduce #(update %1 %2 wrap-event bot-id) m evs)))
(defn generate-hiccup
([markup]
(generate-hiccup markup nil {}))
([markup bot-id bot-db]
(w/prewalk
(fn [el]
(cond
(and (vector? el) (= "subscribe" (first el)))
(let [path (mapv keyword (second el))]
(get-in bot-db path))
(and (vector? el) (string? (first el)))
(-> el
(update 0 get-element)
(update 1 check-events bot-id))
:else el))
markup)))
(defn reg-handler
([name handler] (reg-handler name nil handler))
([name middleware handler]
(register-handler name [trim-v middleware] handler)))

View File

@ -216,7 +216,7 @@
{:jail-id chat-id
:path path
:params params
:callback (or callback #(dispatch [:chat-received-message/bot-response {:chat-id chat-id} %]))})))
:callback callback})))
(defn set-soft-input-mode [mode]
(when status

View File

@ -10,7 +10,6 @@
status-im.ui.screens.qr-scanner.db
status-im.ui.screens.group.db
status-im.chat.specs
status-im.commands.specs
status-im.ui.screens.profile.db
status-im.ui.screens.network-settings.db
status-im.ui.screens.offline-messaging-settings.db
@ -264,7 +263,8 @@
:chat/last-clock-value
:chat/loaded-chats
:chat/bot-db
:commands/access-scope->commands-responses
:chat/id->command
:chat/access-scope->command-id
:discoveries/discoveries
:discoveries/discover-search-tags
:discoveries/discover-current-dapp

View File

@ -1,8 +1,5 @@
(ns status-im.ui.screens.events
(:require status-im.bots.events
status-im.chat.events
status-im.commands.handlers.jail
status-im.commands.events.loading
(:require status-im.chat.events
status-im.network.events
[status-im.transport.handlers :as transport.handlers]
status-im.protocol.handlers
@ -16,7 +13,6 @@
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.dimensions :as dimensions]
status-im.utils.universal-links.events
[status-im.chat.commands.core :as commands]
status-im.ui.screens.add-new.new-chat.navigation
status-im.ui.screens.network-settings.events
status-im.ui.screens.profile.events
@ -73,11 +69,7 @@
:path path
:params params
:callback (fn [jail-response]
(when-let [event (if callback-event-creator
(callback-event-creator jail-response)
[:chat-received-message/bot-response
{:chat-id chat-id}
jail-response])]
(when-let [event (callback-event-creator jail-response)]
(re-frame/dispatch event)))})))
;;;; COFX
@ -99,28 +91,6 @@
;;;; FX
(re-frame/reg-fx
:call-jail
(fn [args]
(doseq [{:keys [callback-event-creator] :as opts} args]
(status/call-jail
(-> opts
(dissoc :callback-event-creator)
(assoc :callback
(fn [jail-response]
(when-let [event (callback-event-creator jail-response)]
(re-frame/dispatch event)))))))))
(re-frame/reg-fx
:call-jail-function
call-jail-function)
(re-frame/reg-fx
:call-jail-function-n
(fn [opts-seq]
(doseq [opts opts-seq]
(call-jail-function opts))))
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %))
on-error #(re-frame/dispatch (failure-event-creator %))
@ -336,7 +306,6 @@
:initialize-account-db
(fn [{:keys [accounts/accounts accounts/create contacts/contacts networks/networks
network network-status peers-count peers-summary view-id navigation-stack
access-scope->commands-responses
status-module-initialized? status-node-started? device-UUID]
:or [network (get app-db :network)]} [_ address]]
(let [console-contact (get contacts constants/console-chat-id)
@ -344,7 +313,6 @@
account-network-id (get current-account :network network)
account-network (get-in current-account [:networks account-network-id])]
(cond-> (assoc app-db
:access-scope->commands-responses access-scope->commands-responses
:current-public-key (:public-key current-account)
:view-id view-id
:navigation-stack navigation-stack
@ -414,31 +382,6 @@
(fn [_ _]
{::get-fcm-token-fx nil}))
;; Because we send command to jail in params and command `:ref` is a lookup vector with
;; keyword in it (for example `["transactor" :command 51 "send"]`), we lose that keyword
;; information in the process of converting to/from JSON, and we need to restore it
(defn- restore-command-ref-keyword
[orig-params]
(if [(get-in orig-params [:command :command :ref])]
(update-in orig-params [:command :command :ref 1] keyword)
orig-params))
(defn handle-jail-signal [{:keys [chat_id data]}]
(let [{:keys [event data]} (types/json->clj data)]
(case event
"local-storage" [:set-local-storage {:chat-id chat_id
:data data}]
"show-suggestions" [:show-suggestions-from-jail {:chat-id chat_id
:markup data}]
"send-message" [:chat-send-message/from-jail {:chat-id chat_id
:message data}]
"handler-result" (let [orig-params (:origParams data)]
;; TODO(janherich): figure out and fix chat_id from event
[:command-handler! (:chat-id orig-params)
(restore-command-ref-keyword orig-params)
{:result {:returned (dissoc data :origParams)}}])
(log/debug "Unknown jail signal " event))))
(handlers/register-handler-fx
:discovery/summary
(fn [{:keys [db] :as cofx} [_ peers-summary]]
@ -463,7 +406,6 @@
"node.started" [:status-node-started]
"node.stopped" [:status-node-stopped]
"module.initialized" [:status-module-initialized]
"jail.signal" (handle-jail-signal event)
"envelope.sent" [:signals/envelope-status (:hash event) :sent]
"envelope.expired" [:signals/envelope-status (:hash event) :not-sent]
"discovery.summary" [:discovery/summary event]

View File

@ -3,11 +3,12 @@
(:require [re-frame.core :as re-frame]
[clojure.string :as str]
[status-im.constants :as constants]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.receiving :as commands-receiving]
[status-im.ui.components.react :as react]
[status-im.ui.screens.home.styles :as styles]
[status-im.ui.components.styles :as component.styles]
[status-im.utils.core :as utils]
[status-im.commands.utils :as commands-utils]
[status-im.i18n :as i18n]
[status-im.utils.datetime :as time]
[status-im.utils.gfycat.core :as gfycat]
@ -19,18 +20,12 @@
[status-im.ui.components.common.common :as components.common]
[status-im.models.browser :as model]))
(defn command-short-preview
[{:keys [command] {:keys [amount asset]} :params}]
[chat-preview/text {}
(str
(i18n/label (if (= command constants/command-request)
:command-requesting
:command-sending))
(i18n/label-number amount)
" "
asset)])
(defview command-short-preview [message]
(letsubs [id->command [:get-id->command]]
(when-let [command (commands-receiving/lookup-command-by-ref message id->command)]
(commands/generate-short-preview command message))))
(defn message-content-text [{:keys [content] :as message}]
(defn message-content-text [{:keys [content content-type] :as message}]
[react/view styles/last-message-container
(cond
@ -49,11 +44,10 @@
:accessibility-label :chat-message-text}
(:content content)]
(and (:command content) (-> content :short-preview :markup))
(commands-utils/generate-hiccup (-> content :short-preview :markup))
(contains? #{constants/command-request constants/command-send} (:command content))
[command-short-preview content]
(contains? #{constants/content-type-command
constants/content-type-command-request}
content-type)
[command-short-preview message]
:else
[react/text {:style styles/last-message-text

View File

@ -2,11 +2,10 @@
(:require [clojure.spec.alpha :as spec]
[re-frame.core :as re-frame]
[status-im.ui.components.react :refer [show-image-picker]]
[status-im.chat.constants :as chat-const]
[status-im.ui.screens.profile.navigation]
[status-im.ui.screens.accounts.utils :as accounts.utils]
[status-im.chat.events :as chat-events]
[status-im.chat.events.input :as input-events]
[status-im.chat.commands.core :as commands]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.image-processing :refer [img->base64]]
@ -29,11 +28,11 @@
(handlers/register-handler-fx
:profile/send-transaction
[re-frame/trim-v]
(fn [{{:contacts/keys [contacts]} :db :as cofx} [chat-id]]
(let [send-command (get-in contacts chat-const/send-command-ref)]
(fn [{:keys [db] :as cofx} [chat-id]]
(let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])]
(handlers-macro/merge-fx cofx
(chat-events/start-chat chat-id {:navigation-replace? true})
(input-events/select-chat-input-command send-command nil true)))))
(commands/select-chat-input-command send-command nil)))))
(defn valid-name? [name]
(spec/valid? :profile/name name))

View File

@ -2,7 +2,6 @@
(:require [re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.ethereum.core :as ethereum]
status-im.chat.subs
status-im.commands.subs
status-im.ui.screens.accounts.subs
status-im.ui.screens.home.subs
status-im.ui.screens.contacts.subs
@ -17,7 +16,6 @@
status-im.ui.screens.bootnodes-settings.subs
status-im.ui.screens.currency-settings.subs
status-im.ui.screens.browser.subs
status-im.bots.subs
status-im.ui.screens.add-new.new-chat.subs
status-im.ui.screens.profile.subs))

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.wallet.choose-recipient.events
(:require [status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.ui.screens.wallet.send.events :as send.events]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.eip681 :as eip681]
[status-im.utils.handlers :as handlers]
@ -42,7 +41,9 @@
;; NOTE(janherich) - whenever changing assets, we want to clear the previusly set amount/amount-text
(defn changed-asset [{:keys [db] :as fx} old-symbol new-symbol]
(-> fx
(merge (send.events/update-gas-price db))
(merge {:update-gas-price {:web3 (:web3 db)
:success-event :wallet/update-gas-price-success
:edit? false}})
(assoc-in [:db :wallet :send-transaction :amount] nil)
(assoc-in [:db :wallet :send-transaction :amount-text] nil)
(assoc-in [:db :wallet :send-transaction :asset-error]

View File

@ -3,24 +3,24 @@
[status-im.ui.screens.wallet.db :as wallet-db]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.chat.constants :as chat-const]
[status-im.chat.events.input :as input-events]
[status-im.chat.commands.core :as commands]
[status-im.utils.money :as money]))
(handlers/register-handler-fx
::wallet-send-chat-request
[re-frame/trim-v]
(fn [{{:contacts/keys [contacts]} :db :as cofx} [asset amount]]
(fn [{:keys [db] :as cofx} [asset amount]]
(handlers-macro/merge-fx cofx
{:dispatch [:send-current-message]}
(input-events/select-chat-input-command
(assoc (get-in contacts chat-const/request-command-ref) :prefill [asset amount]) nil true))))
(commands/select-chat-input-command
(get-in db [:id->command ["request" #{:personal-chats}]]) [asset amount]))))
(handlers/register-handler-fx
:wallet-send-request
[re-frame/trim-v]
(fn [_ [whisper-identity amount symbol decimals]]
(assert whisper-identity)
;; TODO(janherich) remove this dispatch sequence, there is absolutely no need for that :/
{:dispatch-n [[:navigate-back]
[:navigate-to-clean :home]
[:add-chat-loaded-event whisper-identity

View File

@ -17,6 +17,7 @@
[status-im.utils.utils :as utils]
[status-im.models.wallet :as models.wallet]
[status-im.chat.models.message :as models.message]
[status-im.chat.commands.sending :as commands-sending]
[status-im.constants :as constants]
[status-im.transport.utils :as transport.utils]
[taoensso.timbre :as log]
@ -304,10 +305,11 @@
:send-transaction-message
(concat models.message/send-interceptors
navigation/navigation-interceptors)
(fn [cofx [{:keys [view-id] :as params}]]
(handlers-macro/merge-fx cofx
(models.message/send-custom-send-command params)
(navigation/replace-view view-id))))
(fn [{:keys [db] :as cofx} [chat-id params]]
(when-let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])]
(handlers-macro/merge-fx cofx
(commands-sending/send chat-id send-command params)
(navigation/replace-view :wallet-transaction-sent)))))
(handlers/register-handler-fx
::transaction-completed
@ -331,11 +333,10 @@
(= method constants/web3-send-transaction)
(assoc :dispatch-later [{:ms 400 :dispatch [:navigate-to-modal :wallet-transaction-sent-modal]}]))
{:dispatch [:send-transaction-message {:view-id :wallet-transaction-sent
:whisper-identity whisper-identity
:address to
:asset (name symbol)
:amount amount-text}]}))))))
{:dispatch [:send-transaction-message whisper-identity {:address to
:asset (name symbol)
:amount amount-text
:tx-hash hash}]}))))))
(defn on-transactions-modal-completed [raw-results]
(let [result (types/json->clj raw-results)]

View File

@ -10,7 +10,7 @@
(def ^:private mergable-keys
#{:data-store/tx :data-store/base-tx :chat-received-message/add-fx
:shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post
:confirm-messages-processed :call-jail})
:confirm-messages-processed})
(defn safe-merge [fx new-fx]
(if (:merging-fx-with-common-keys fx)

View File

@ -1,50 +0,0 @@
(ns status-im.test.bots.events
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.bots.events :as bots-events]))
(def ^:private initial-db
{:bot-db {}
:contacts/contacts
{"bot1" {:subscriptions
{:feeExplanation
{:subscriptions {:fee ["sliderValue"]
:tx ["transaction"]}}}}
"bot2" {:subscriptions
{:roundedValue
{:subscriptions {:amount ["input"]}}}}
"bot3" {:subscriptions
{:warning
{:subscriptions {:amount ["input"]}}}}}})
(deftest add-active-bot-subscriptions-test
(testing "That active bot subscriptions are correctly transformed and added to db"
(let [db (update-in initial-db [:contacts/contacts "bot1" :subscriptions]
bots-events/transform-bot-subscriptions)]
(is (= {[:sliderValue] {:feeExplanation {:fee [:sliderValue]
:tx [:transaction]}}
[:transaction] {:feeExplanation {:fee [:sliderValue]
:tx [:transaction]}}}
(get-in db [:contacts/contacts "bot1" :subscriptions]))))))
(defn- fake-subscription-call
[db {:keys [chat-id parameters]}]
(let [{:keys [name subscriptions]} parameters
simulated-jail-response {:result {:returned {:sub-call-arg-map subscriptions}}}]
(bots-events/calculated-subscription db {:bot chat-id
:path [name]
:result simulated-jail-response})))
(deftest set-in-bot-db-test
(let [{:keys [db call-jail-function-n]} (-> initial-db
(update-in [:contacts/contacts "bot1" :subscriptions]
bots-events/transform-bot-subscriptions)
(bots-events/set-in-bot-db {:bot "bot1"
:path [:sliderValue]
:value 2}))
new-db (reduce fake-subscription-call db call-jail-function-n)]
(testing "That setting in value in bot-db correctly updates bot-db"
(is (= 2 (get-in new-db [:bot-db "bot1" :sliderValue]))))
(testing "That subscriptions are fired-off"
(is (= {:sub-call-arg-map {:fee 2
:tx nil}}
(get-in new-db [:bot-db "bot1" :feeExplanation]))))))

View File

@ -7,44 +7,65 @@
[selected-event-creator value]
(selected-event-creator value))
(def test-command-parameters
[{:id :first-param
:type :text
;; pass function as mock-up for suggestions component, so we can
;; just test the correct injection of `:set-command-parameter` event
:suggestions fake-suggestion}
{:id :second-param
:type :text}
{:id :last-param
:type :text
:suggestions fake-suggestion}])
(deftype TestCommand []
protocol/Command
(id [_]
:test-command)
(scope [_]
#{:personal-chats :group-chats :public-chats :requested})
(parameters [_]
[{:id :first-param
:type :text
;; pass function as mock-up for suggestions component, so we can
;; just test the correct injection of `:set-command-parameter` event
:suggestions fake-suggestion}
{:id :second-param
:type :text}
{:id :last-param
:type :text
:suggestions fake-suggestion}])
(id [_] "test-command")
(scope [_] #{:personal-chats :group-chats :public-chats})
(description [_] "Another test command")
(parameters [_] test-command-parameters)
(validate [_ parameters _]
(when-not (every? (comp string? second) parameters)
"Not all parameters are filled and of the correc type"))
(yield-control [_ _ _]
nil)
(on-send [_ _ _ _]
nil)
(on-receive [_ _ _]
nil)
(short-preview [_ command-message _]
"Not all parameters are filled and of the correct type"))
(on-send [_ _ _])
(on-receive [_ _ _])
(short-preview [_ command-message]
[:text (str "Test-command, first-param: "
(get-in command-message [:content :params :first-param]))])
(preview [_ command-message _]
(preview [_ command-message]
[:text (str "Test-command, params: "
(apply str (map [:first-param :second-param :last-param]
(get-in command-message [:content :params]))))]))
(def another-test-command-parameters
[{:id :first-param
:type :text}])
(deftype AnotherTestCommand []
protocol/Command
(id [_] "another-test-command")
(scope [_] #{:public-chats})
(description [_] "Another test command")
(parameters [_] another-test-command-parameters)
(validate [_ parameters _]
(when-not (every? (comp string? second) parameters)
"Not all parameters are filled and of the correct type"))
(on-send [_ _ _])
(on-receive [_ _ _])
(short-preview [_ command-message]
[:text (str "Test-command, first-param: "
(get-in command-message [:content :params :first-param]))])
(preview [_ command-message]
[:text (str "Test-command, params: "
(apply str (map [:first-param]
(get-in command-message [:content :params]))))]))
(def TestCommandInstance (TestCommand.))
(def AnotherTestCommandInstance (AnotherTestCommand.))
(deftest index-commands-test
(let [fx (core/index-commands #{TestCommandInstance} {:db {}})]
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
(testing "Primary composite key index for command is correctly created"
(is (= TestCommandInstance
(get-in fx [:db :id->command
@ -61,12 +82,63 @@
2 :suggestions])
"last-value"))))
(testing "Access scope indexes are correctly created"
(is (= (get-in fx [:db :access-scope->command-id #{:personal-chats :requested}])
(core/command-id TestCommandInstance)))
(is (= (get-in fx [:db :access-scope->command-id #{:group-chats :requested}])
(core/command-id TestCommandInstance)))
(is (= (get-in fx [:db :access-scope->command-id #{:public-chats :requested}])
(core/command-id TestCommandInstance))))))
(is (contains? (get-in fx [:db :access-scope->command-id #{:personal-chats}])
(core/command-id TestCommandInstance)))
(is (not (contains? (get-in fx [:db :access-scope->command-id #{:personal-chats}])
(core/command-id AnotherTestCommandInstance))))
(is (contains? (get-in fx [:db :access-scope->command-id #{:group-chats}])
(core/command-id TestCommandInstance)))
(is (contains? (get-in fx [:db :access-scope->command-id #{:public-chats}])
(core/command-id TestCommandInstance)))
(is (contains? (get-in fx [:db :access-scope->command-id #{:public-chats}])
(core/command-id AnotherTestCommandInstance))))))
(deftest chat-commands-test
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})]
(testing "That relevant commands are looked up for chat"
(is (= #{TestCommandInstance AnotherTestCommandInstance}
(into #{}
(map (comp :type second))
(core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "topic"
:group-chat true
:public? true}))))
(is (= #{TestCommandInstance}
(into #{}
(map (comp :type second))
(core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "group"
:group-chat true}))))
(is (= #{TestCommandInstance}
(into #{}
(map (comp :type second))
(core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})))))))
(deftest selected-chat-command-test
(let [fx (core/index-commands #{TestCommandInstance AnotherTestCommandInstance} {:db {}})
commands (core/chat-commands (get-in fx [:db :id->command])
(get-in fx [:db :access-scope->command-id])
{:chat-id "contact"})]
(testing "Text not beggining with the command special charactes `/` is recognised"
(is (not (core/selected-chat-command "test-command 1" nil commands))))
(testing "Command not matching any available commands is not recognised as well"
(is (not (core/selected-chat-command "/another-test-command" nil commands))))
(testing "Available correctly entered command is recognised"
(is (= TestCommandInstance
(get (core/selected-chat-command "/test-command" nil commands) :type))))
(testing "Command completion and param position are determined as well"
(let [{:keys [current-param-position command-completion]}
(core/selected-chat-command "/test-command 1 " 17 commands)]
(is (= 1 current-param-position))
(is (= :less-then-needed command-completion)))
(let [{:keys [current-param-position command-completion]}
(core/selected-chat-command "/test-command 1 2 3" 20 commands)]
(is (= 2 current-param-position))
(is (= :complete command-completion))))))
(deftest set-command-parameter-test
(testing "Setting command parameter correctly updates the text input"
@ -88,3 +160,19 @@
false 2 "last value"
(create-cofx "/test-command first-value second-value"))
[:db :chats "test" :input-text]))))))
(deftest parse-parameters-test
(testing "testing that parse-parameters work correctly"
(is (= {:first-param "1"
:second-param "2"
:last-param "3"}
(core/parse-parameters test-command-parameters "/test-command 1 2 3")))
(is (= {:first-param "1"
:second-param "2 2"
:last-param "3"}
(core/parse-parameters test-command-parameters "/test-command 1 \"2 2\" 3")))
(is (= {:first-param "1"
:second-param "2"}
(core/parse-parameters test-command-parameters "/test-command 1 2")))
(is (= {}
(core/parse-parameters test-command-parameters "/test-command ")))))

View File

@ -4,13 +4,11 @@
[re-frame.core :as rf]
[day8.re-frame.test :refer [run-test-sync]]
[status-im.constants :as const]
[status-im.chat.console :as console-chat]
[status-im.chat.events :as chat-events]))
(def test-db
{:current-public-key "me"
:chats {const/console-chat-id console-chat/chat
"status" {:public? true
:chats {"status" {:public? true
:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
@ -43,27 +41,6 @@
:whisper-identity "me"
:status :received}}}}}})
(deftest init-console-chat
(testing "initialising console if console is already added to chats, should not modify anything"
(let [fx (chat-events/init-console-chat {:db test-db})]
(is (not fx))))
(testing "initialising console without existing account and console chat not initialisated"
(let [fresh-db {:chats {}}
{:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})]
(is (= (:current-chat-id db)
(:chat-id console-chat/chat)))
(is (= (:current-chat-id db)
const/console-chat-id))))
(testing "initialising console with existing account and console chat not initialisated"
(let [fresh-db {:chats {}}
{:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})]
(is (= (:current-chat-id db)
(:chat-id console-chat/chat)))
(is (= (:current-chat-id db)
const/console-chat-id)))))
(deftest mark-messages-seen
(testing "Marking messages seen correctly marks loaded messages as seen and updates absolute unviewed set"
(let [fx (chat-events/mark-messages-seen "status" {:db test-db})

View File

@ -5,41 +5,6 @@
[status-im.utils.datetime :as datetime]
[status-im.chat.models.input :as input]))
(def fake-db
{:access-scope->commands-responses {#{:global :personal-chats :anonymous :dapps} {:command {"global-command1" ["0x1" :command 0 "global-command1"]}}
#{"0x1" :personal-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 2 "command2"]}}
#{"0x1" :group-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 4 "command2"]}}
#{"0x2" :personal-chats :anonymous :dapps} {:command {"command3" ["0x2" :command 2 "command3"]}}
#{"0x2" :group-chats :anonymous :dapps} {:response {"response1" ["0x2" :response 4 "response1"]}}}
:chats {"test1" {:contacts ["0x1"]
:requests nil
:seq-arguments ["arg1" "arg2"]}
"test2" {:contacts ["0x1" "0x2"]
:group-chat true
:requests {"id1" {:message-id "id1"
:response "response1"}}}
"test3" {:contacts ["0x1"]
:requests {"id1" {:message-id "id1"
:response "request1"}}}
"test4" {:contacts ["0x1" "0x2"]
:group-chat true
:requests {"id2" {:message-id "id2"
:response "response1"}}
:input-metadata {:meta-k "meta-v"}}}
:contacts/contacts {"0x1" {:dapp? true
:command {0 {"global-command1" {:name "global-command1"
:ref ["0x1" :command 0 "global-command1"]}}
2 {"command2" {:name "command2"
:ref ["0x1" :command 2 "command2"]}}
4 {"command2" {:name "command2"
:ref ["0x1" :command 4 "command2"]}}}
:response {}}
"0x2" {:dapp? true
:command {2 {"command3" {:name "command3"
:ref ["0x2" :command 2 "command3"]}}}
:response {4 {"response1" {:name "response1"
:ref ["0x2" :response 4 "response1"]}}}}}})
(deftest text->emoji
(is (nil? (input/text->emoji nil)))
(is (= "" (input/text->emoji "")))
@ -65,111 +30,6 @@
(is (= "" (input/join-command-args [""])))
(is (= "/send 1.0 \"John Doe\"" (input/join-command-args ["/send" "1.0" "John Doe"]))))
(deftest selected-chat-command
(is (= (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test1")
(assoc-in [:chats "test1" :input-text] "/global-command1")))
{:command {:name "global-command1"
:ref ["0x1" :command 0 "global-command1"]}
:metadata nil
:args ["arg1" "arg2"]}))
(is (= (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test2")
(assoc-in [:chats "test2" :input-text] "/command2")))
{:command {:name "command2"
:ref ["0x1" :command 4 "command2"]}
:metadata nil
:args []}))
(is (nil? (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test1")
(assoc-in [:chats "test1" :input-text] "/command3")))))
(is (= (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test1")
(assoc-in [:chats "test1" :input-text] "/command2")))
{:command {:name "command2"
:ref ["0x1" :command 2 "command2"]}
:metadata nil
:args ["arg1" "arg2"]}))
(is (= (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test2")
(assoc-in [:chats "test2" :input-text] "/response1 arg1")))
{:command {:name "response1"
:ref ["0x2" :response 4 "response1"]}
:metadata nil
:args ["arg1"]}))
(is (= (input/selected-chat-command (-> fake-db
(assoc :current-chat-id "test4")
(assoc-in [:chats "test4" :input-text] "/command2 arg1")))
{:command {:name "command2"
:ref ["0x1" :command 4 "command2"]}
:metadata {:meta-k "meta-v"}
:args ["arg1"]})))
(deftest current-chat-argument-position
(is (= (input/current-chat-argument-position
{:name "command1"} "/command1 arg1 arg2 " 0 nil) -1))
(is (= (input/current-chat-argument-position
{:name "command1"} "/command1 argument1 arg2 " 9 nil) -1))
(is (= (input/current-chat-argument-position
{:name "command1"} "/command1 argument1 arg2 " 10 nil) 0))
(is (= (input/current-chat-argument-position
{:name "command1"} "/command1 argument1 arg2 " 19 nil) 0))
(is (= (input/current-chat-argument-position
{:name "command1"} "/command1 argument1 arg2 " 20 nil) 1))
(is (= (input/current-chat-argument-position
{:name "command2"} "/command2 \"a r g u m e n t 1\" argument2" 30 nil) 1))
(is (= (input/current-chat-argument-position
{:name "command3" :command {:sequential-params true}} "/command3" 0 ["test1" "test2"]) 2)))
(deftest argument-position
"Doesn't require a separate test because it simply calls `current-chat-argument-position")
(deftest command-completion
(is (= (input/command-completion {:args ["p1" "p2"]
:command {:params [{:optional false} {:optional false}]}})
:complete))
(is (= (input/command-completion {:args ["p1"]
:command {:params [{:optional false} {:optional false}]}})
:less-than-needed))
(is (= (input/command-completion {:args ["p1" "p2" "p3"]
:command {:params [{:optional false} {:optional false}]}})
:more-than-needed))
(is (= (input/command-completion {:args ["p1" "p2"]
:command {:params [{:optional false} {:optional false} {:optional true}]}})
:complete))
(is (= (input/command-completion {:args ["p1" "p2" "p3"]
:command {:params [{:optional false} {:optional false} {:optional true}]}})
:complete))
(is (= (input/command-completion {:args ["p1" "p2" "p3" "p4"]
:command {:params [{:optional false} {:optional false} {:optional true}]}})
:more-than-needed))
(is (= (input/command-completion {:command {:params [{:optional false}]}})
:less-than-needed))
(is (= (input/command-completion {:command {}})
:complete))
(is (= (input/command-completion nil)
:no-command)))
(deftest args->params
(is (= {} (input/args->params nil)))
(is (= {} (input/args->params {})))
(is (= {} (input/args->params {:args ["1.0"]})))
(is (= {:amount "1.0"}
(input/args->params {:command {:params [{:name "amount"}]}
:args ["1.0"]})))
(is (= {:amount "1.0"}
(input/args->params {:command {:params [{:name "amount"}]}
:args ["1.0" "2.0" "3.0"]})))
(is (= {:amount "1.0"}
(input/args->params {:command {:params [{:name "amount"} {:name "recipient"}]}
:args ["1.0"]})))
(is (= {:amount "1.0" :recipient "John Doe"}
(input/args->params {:command {:params [{:name "amount"} {:name "recipient"}]}
:args ["1.0" "John Doe"]}))))
(deftest modified-db-after-change
"Just a combination of db modifications. Can be skipped now")
(deftest process-cooldown-fx
(let [db {:current-chat-id "chat"
:chats {"chat" {:public? true}}

View File

@ -118,12 +118,7 @@
2 active-chat-2
3 {:is-active false :chat-id 3}
const/console-chat-id console}]
(testing "in normal it returns only chats with is-active, without console"
(testing "it returns only chats with is-active, without console"
(is (= {1 active-chat-1
2 active-chat-2}
(s/active-chats [chats {:dev-mode? false}]))))
(testing "in dev-mode it returns only chats with is-active, keeping console"
(is (= {1 active-chat-1
2 active-chat-2
const/console-chat-id console}
(s/active-chats [chats {:dev-mode? true}]))))))
(s/active-chats chats))))))

View File

@ -10,7 +10,6 @@
[status-im.test.wallet.transactions.subs]
[status-im.test.wallet.transactions.views]
[status-im.test.profile.events]
[status-im.test.bots.events]
[status-im.test.models.mailserver]
[status-im.test.models.bootnode]
[status-im.test.models.account]
@ -73,7 +72,6 @@
'status-im.test.models.contact
'status-im.test.models.network
'status-im.test.models.wallet
'status-im.test.bots.events
'status-im.test.wallet.subs
'status-im.test.wallet.transactions.subs
'status-im.test.wallet.transactions.views