API commands scopes, changes for API semantics (#1546)

This commit is contained in:
alwx 2017-08-07 23:29:54 +02:00 committed by Roman Volosovskyi
parent 27bc745fc5
commit cb72190a18
46 changed files with 1710 additions and 614 deletions

View File

@ -1,4 +1,4 @@
(ns ^:figwheel-no-load env.ios.main
(ns ^:figwheel-no-load env.ios.main
(:require [reagent.core :as r]
[re-frisk-remote.core :as rr]
[status-im.ios.core :as core]
@ -12,7 +12,7 @@
(def root-el (r/as-element [reloader]))
(figwheel/watch-and-reload
:websocket-url "ws://10.0.1.15:3449/figwheel-ws"
:websocket-url "ws://localhost:3449/figwheel-ws"
:heads-up-display false
:jsload-callback #(swap! cnt inc))

View File

@ -36,7 +36,6 @@
"photo-path": "icon_wallet_avatar",
"dapp?": true,
"pending?": true,
"has-global-command?": true,
"bot-url": "local://wallet-bot",
"unremovable?": true
},
@ -49,10 +48,32 @@
"ru": "Печкин"
},
"dapp?": true,
"has-global-command?": true,
"mixable?": true,
"bot-url": "local://mailman-bot"
},
"transactor-group":
{
"name":
{
"en": "Transactor"
},
"dapp?": true,
"mixable?": true,
"bot-url": "local://transactor-group-bot"
},
"transactor-personal":
{
"name":
{
"en": "Transactor"
},
"dapp?": true,
"mixable?": true,
"bot-url": "local://transactor-personal-bot"
},
"demo-bot":
{
"name":

View File

@ -34,9 +34,9 @@ function browse(params, context) {
}
status.command({
name: "global",
name: "browse",
title: I18n.t('browse_title'),
registeredOnly: true,
scope: ["global", "registered-only", "group-chats", "personal-chats", "can-use-for-dapps"],
description: I18n.t('browse_description'),
color: "#ffa500",
fullscreen: true,

View File

@ -455,7 +455,6 @@ function phoneSuggestions(params, context) {
var phoneConfig = {
name: "phone",
registeredOnly: true,
icon: "phone_white",
color: "#5bb2a2",
title: I18n.t('phone_title'),
@ -491,7 +490,6 @@ var phoneConfig = {
}
};
status.response(phoneConfig);
status.command(phoneConfig);
var ropstenNetworkId = 3;
var rinkebyNetworkId = 4;
@ -553,13 +551,12 @@ function faucetSuggestions(params) {
return {markup: view};
}
var faucetCommandConfig =
{
var faucetCommandConfig ={
name: "faucet",
title: I18n.t('faucet_title'),
description: I18n.t('faucet_description'),
color: "#7099e6",
registeredOnly: true,
scope: ["registered-only", "group-chats", "personal-chats", "can-use-for-dapps"],
params: [{
name: "url",
type: status.types.TEXT,
@ -635,7 +632,7 @@ status.command({
title: I18n.t('debug_mode_title'),
description: I18n.t('debug_mode_description'),
color: "#7099e6",
registeredOnly: true,
scope: ["registered-only", "group-chats", "personal-chats", "can-use-for-dapps"],
params: [{
name: "mode",
suggestions: debugSuggestions,
@ -659,18 +656,6 @@ status.command({
}
});
// status.command({
// name: "help",
// title: "Help",
// description: "Request help from Console",
// color: "#7099e6",
// params: [{
// name: "query",
// type: status.types.TEXT
// }]
// });
status.response({
name: "confirmation-code",
color: "#7099e6",

View File

@ -32,6 +32,7 @@ function locationsSuggestions (params) {
status.command({
name: "location",
title: I18n.t('location_title'),
scope: ["registered-only", "group-chats", "personal-chats"],
description: I18n.t('location_description'),
sequentialParams: true,
hideSendButton: true,

View File

@ -301,7 +301,9 @@ function validateSend(params, context) {
params["bot-db"] = {};
}
if (!params["bot-db"]["public"] || !params["bot-db"]["public"]["recipient"] || !params["bot-db"]["public"]["recipient"]["address"]) {
if (!params["bot-db"]["public"]
|| !params["bot-db"]["public"]["recipient"]
|| !params["bot-db"]["public"]["recipient"]["address"]) {
return {
markup: status.components.validationMessage(
"Wrong address",
@ -516,6 +518,7 @@ function shortPreviewSend(params, context) {
var send = {
name: "send",
scope: ["group-chats"],
icon: "money_white",
color: "#5fc48d",
title: I18n.t('send_title'),
@ -550,6 +553,7 @@ var paramsRequest = [
status.command({
name: "request",
scope: ["group-chats"],
color: "#5fc48d",
title: I18n.t('request_title'),
description: I18n.t('request_description'),

View File

@ -0,0 +1,538 @@
function calculateFee(n, tx) {
var estimatedGas = 21000;
if (tx !== null) {
estimatedGas = web3.eth.estimateGas(tx);
}
var gasMultiplicator = Math.pow(1.4, n).toFixed(3);
var weiFee = web3.eth.gasPrice * gasMultiplicator * estimatedGas;
// force fee in eth to be of BigNumber type
var ethFee = web3.toBigNumber(web3.fromWei(weiFee, "ether"));
// always display 7 decimal places
return ethFee.toFixed(7);
}
function calculateGasPrice(n) {
var gasMultiplicator = Math.pow(1.4, n).toFixed(3);
return web3.eth.gasPrice * gasMultiplicator;
}
status.defineSubscription(
"calculatedFee",
{value: ["sliderValue"], tx: ["transaction"]},
function (params) {
return calculateFee(params.value, params.tx);
}
);
function getFeeExplanation(n) {
return I18n.t('send_explanation') + I18n.t('send_explanation_' + (n + 2));
}
status.defineSubscription(
"feeExplanation",
{value: ["sliderValue"]},
function(params) {
return getFeeExplanation(params.value);
}
)
function amountParameterBox(params, context) {
if (!params["bot-db"]) {
params["bot-db"] = {};
}
var contactAddress = context.to;
var txData;
var amount;
try {
amount = params.args[0];
txData = {
to: contactAddress,
value: web3.toWei(amount) || 0
};
} catch (err) {
amount = null;
txData = {
to: contactAddress,
value: 0
};
}
var sliderValue = params["bot-db"]["sliderValue"] || 0;
status.setDefaultDb({
transaction: txData,
calculatedFee: calculateFee(sliderValue, txData),
feeExplanation: getFeeExplanation(sliderValue),
sliderValue: sliderValue
});
return {
title: I18n.t('send_title'),
showBack: true,
markup: status.components.scrollView(
{
keyboardShouldPersistTaps: "always"
},
[status.components.view(
{
flex: 1
},
[
status.components.text(
{
style: {
fontSize: 14,
color: "rgb(147, 155, 161)",
paddingTop: 12,
paddingLeft: 16,
paddingRight: 16,
paddingBottom: 20
}
},
I18n.t('send_specify_amount')
),
status.components.touchable(
{
onPress: status.components.dispatch([status.events.FOCUS_INPUT, []])
},
status.components.view(
{
flexDirection: "row",
alignItems: "center",
textAlign: "center",
justifyContent: "center"
},
[
status.components.text(
{
font: "light",
numberOfLines: 1,
ellipsizeMode: "tail",
style: {
maxWidth: 250,
fontSize: 38,
marginLeft: 8,
color: "black"
}
},
amount || "0.00"
),
status.components.text(
{
font: "light",
style: {
fontSize: 38,
marginLeft: 8,
color: "rgb(147, 155, 161)"
}
},
I18n.t('eth')
),
]
)
),
status.components.text(
{
style: {
fontSize: 14,
color: "rgb(147, 155, 161)",
paddingTop: 14,
paddingLeft: 16,
paddingRight: 16,
paddingBottom: 5
}
},
I18n.t('send_fee')
),
status.components.view(
{
flexDirection: "row"
},
[
status.components.text(
{
style: {
fontSize: 17,
color: "black",
paddingLeft: 16
}
},
[status.components.subscribe(["calculatedFee"])]
),
status.components.text(
{
style: {
fontSize: 17,
color: "rgb(147, 155, 161)",
paddingLeft: 4,
paddingRight: 4
}
},
I18n.t('eth')
)
]
),
status.components.slider(
{
maximumValue: 2,
minimumValue: -2,
onSlidingComplete: status.components.dispatch(
[status.events.UPDATE_DB, "sliderValue"]
),
step: 1,
style: {
marginLeft: 16,
marginRight: 16
}
}
),
status.components.view(
{
flexDirection: "row"
},
[
status.components.text(
{
style: {
flex: 1,
fontSize: 14,
color: "rgb(147, 155, 161)",
paddingLeft: 16,
alignSelf: "flex-start"
}
},
I18n.t('send_cheaper')
),
status.components.text(
{
style: {
flex: 1,
fontSize: 14,
color: "rgb(147, 155, 161)",
paddingRight: 16,
alignSelf: "flex-end",
textAlign: "right"
}
},
I18n.t('send_faster')
)
]
),
status.components.text(
{
style: {
fontSize: 14,
color: "black",
paddingTop: 16,
paddingLeft: 16,
paddingRight: 16,
paddingBottom: 16,
lineHeight: 24
}
},
[status.components.subscribe(["feeExplanation"])]
)
]
)]
)
};
}
var paramsSend = [
{
name: "amount",
type: status.types.NUMBER,
suggestions: amountParameterBox
}
];
function validateSend(params, context) {
if (!params["bot-db"]) {
params["bot-db"] = {};
}
if (!params["amount"]) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_amount_specified')
)
};
}
var amount = params["amount"].replace(",", ".");
var amountSplitted = amount.split(".");
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_amount_is_too_small')
)
};
}
if (isNaN(parseFloat(params.amount.replace(",", ".")))) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_invalid_number')
)
};
}
try {
var val = web3.toWei(amount, "ether");
if (val <= 0) {
throw new Error();
}
} catch (err) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_invalid_number')
)
};
}
var balance = web3.eth.getBalance(context.from);
var fee = calculateFee(
params["bot-db"]["sliderValue"],
{
to: context.to,
value: val
}
);
if (bn(val).plus(bn(web3.toWei(fee, "ether"))).greaterThan(bn(balance))) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_insufficient_amount')
+ web3.fromWei(balance, "ether")
+ " ETH)"
)
};
}
}
function handleSend(params, context) {
var val = web3.toWei(params["amount"].replace(",", "."), "ether");
var data = {
from: context.from,
to: context.to,
value: val,
gasPrice: calculateGasPrice(params["bot-db"]["sliderValue"])
};
try {
return web3.eth.sendTransaction(data);
} catch (err) {
return {error: err.message};
}
}
function previewSend(params, context) {
var amountStyle = {
fontSize: 36,
color: "#000000",
height: 40
};
var amount = status.components.view(
{
flexDirection: "column",
alignItems: "flex-end",
maxWidth: 250
},
[status.components.text(
{
style: amountStyle,
numberOfLines: 1,
ellipsizeMode: "tail",
font: "light"
},
status.localizeNumber(params.amount, context.delimiter, context.separator)
)]);
var currency = status.components.view(
{
style: {
flexDirection: "column",
justifyContent: "flex-end",
paddingBottom: 0
}
},
[status.components.text(
{
style: {
color: "#9199a0",
fontSize: 16,
lineHeight: 18,
marginLeft: 7.5
}
},
I18n.t('eth')
)]
);
var row = status.components.view(
{
style: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 8,
marginBottom: 8
}
},
[amount, currency]
);
return {
markup: status.components.view(
{
style: {
flexDirection: "column"
}
},
[row]
)
};
}
function shortPreviewSend(params, context) {
return {
markup: status.components.text(
{},
I18n.t('send_title') + ": "
+ status.localizeNumber(params.amount, context.delimiter, context.separator)
+ " ETH"
)
};
}
var send = {
name: "send",
scope: ["personal-chats"],
icon: "money_white",
color: "#5fc48d",
title: I18n.t('send_title'),
description: I18n.t('send_description'),
params: paramsSend,
validator: validateSend,
handler: handleSend,
preview: previewSend,
shortPreview: shortPreviewSend
};
status.command(send);
status.response(send);
var paramsRequest = [
{
name: "amount",
type: status.types.NUMBER
}
];
status.command({
name: "request",
scope: ["personal-chats"],
color: "#5fc48d",
title: I18n.t('request_title'),
description: I18n.t('request_description'),
params: paramsRequest,
handler: function (params, context) {
var val = params["amount"].replace(",", ".");
return {
event: "request",
request: {
command: "send",
params: {
amount: val
},
prefill: [val]
}
};
},
preview: function (params, context) {
var row = status.components.text(
{},
I18n.t('request_requesting') + " "
+ status.localizeNumber(params.amount, context.delimiter, context.separator)
+ " ETH"
);
return {
markup: status.components.view(
{
style: {
flexDirection: "column"
}
},
[row]
)
};
},
shortPreview: function (params, context) {
return {
markup: status.components.text(
{},
I18n.t('request_requesting') + " "
+ status.localizeNumber(params.amount, context.delimiter, context.separator)
+ " ETH"
)
};
},
validator: function (params) {
if (!params["bot-db"]) {
params["bot-db"] = {};
}
if (!params["amount"]) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_amount_specified')
)
};
}
var amount = params.amount.replace(",", ".");
var amountSplitted = amount.split(".");
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_amount_is_too_small')
)
};
}
if (isNaN(parseFloat(params.amount.replace(",", ".")))) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_invalid_number')
)
};
}
try {
var val = web3.toWei(amount, "ether");
if (val <= 0) {
throw new Error();
}
} catch (err) {
return {
markup: status.components.validationMessage(
I18n.t('validation_title'),
I18n.t('validation_invalid_number')
)
};
}
}
});

View File

@ -0,0 +1,448 @@
I18n.translations = {
en: {
send_title: 'Send transaction',
send_description: 'Send a payment',
send_choose_recipient: 'Choose recipient',
send_specify_amount: 'Specify amount',
send_fee: 'Fee',
send_cheaper: 'Cheaper',
send_faster: 'Faster',
send_explanation: 'This is the most amount of money that might be used to process this transaction. Your transaction will be mined ',
send_explanation_0: 'in a few minutes or more.',
send_explanation_1: 'likely within a few minutes.',
send_explanation_2: 'usually within a minute.',
send_explanation_3: 'probably within 30 seconds.',
send_explanation_4: 'probably within a few seconds.',
send_sending_to: 'to ',
eth: 'ETH',
request_title: 'Request ETH',
request_description: 'Request a payment',
request_requesting: 'Requesting ',
request_requesting_from: 'from ',
validation_title: 'Amount',
validation_amount_specified: 'Amount must be specified',
validation_invalid_number: 'Amount is not valid number',
validation_amount_is_too_small: 'Amount is too precise. The smallest unit you can send is 1 Wei (1x10^-18 ETH)',
validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance '
},
ru: {
send_title: 'Отправить транзакцию',
send_description: 'Отправить платеж',
request_title: 'Запросить ETH',
request_description: 'Запросить платеж',
request_requesting: 'Запрос ',
validation_title: 'Сумма',
validation_amount_specified: 'Необходимо указать сумму',
validation_invalid_number: 'Сумма не является действительным числом',
validation_amount_is_too_small: 'Сумма излишне точная. Минимальная единица, которую можно отправить — 1 Wei (1x10^-18 ETH)',
validation_insufficient_amount: 'Недостаточно ETH на балансе ('
},
af: {
send_title: 'Stuur ETH',
send_description: 'Stuur \'n betaling',
request_title: 'Versoek ETH',
request_description: 'Versoek \'n betaling',
request_requesting: 'Besig met versoek ',
validation_title: 'Bedrag',
validation_amount_specified: 'Bedrag moet gespesifiseer word',
validation_invalid_number: 'Bedrag is nie \'n geldige syfer nie',
validation_insufficient_amount: 'Nie genoeg ETH in rekening nie ('
},
ar: {
send_title: 'إرسال ETH',
send_description: 'إرسال مدفوعات',
request_title: 'طلب ETH',
request_description: 'طلب مدفوعات',
request_requesting: 'مُطَالَبَة ',
validation_title: 'المبلغ',
validation_amount_specified: 'يجب تحديد المبلغ',
validation_invalid_number: 'المبلغ المحدد غير صحيح',
validation_insufficient_amount: 'لا يوجد ETH كافي بالحساب ('
},
'zh-hant': {
send_title: '發送 ETH',
send_description: '發送一筆付款',
request_title: '請求 ETH',
request_description: '請求一筆付款',
request_requesting: '正在請求 ',
validation_title: '金額',
validation_amount_specified: 'ي未指定金額',
validation_invalid_number: '金額數字無效',
validation_insufficient_amount: '餘額中 ETH 不足 ('
},
'zh-hans': {
send_title: '发送ETH',
send_description: '付款',
request_title: '请求ETH',
request_description: '要求付款',
request_requesting: '正在请求 ',
validation_title: '金額',
validation_amount_specified: '必须指定金额',
validation_invalid_number: '金额不是有效数字',
validation_insufficient_amount: 'ETH余额不足 ('
},
'zh-yue': {
send_title: '發送ETH',
send_description: '發送付款',
request_title: '徵求ETH',
request_description: '徵求付款',
request_requesting: '徵求中 ',
validation_title: '金額',
validation_amount_specified: '必須指定金額',
validation_invalid_number: '指定金額並非有效數字',
validation_insufficient_amount: '沒有足夠ETH餘額 ('
},
'zh-wuu': {
send_title: '发送ETH',
send_description: '发送付款',
request_title: '请求ETH',
request_description: '请求付款',
request_requesting: '请求中 ',
validation_title: '金额',
validation_amount_specified: '金额必须明确',
validation_invalid_number: '金额不是一个有效数字',
validation_insufficient_amount: 'ETH余额不足 ('
},
nl: {
send_title: 'Stuur ETH',
send_description: 'Stuur een betaling',
request_title: 'Vraag ETH aan',
request_description: 'Vraag om een betaling',
request_requesting: 'Wordt aangevraagd ',
validation_title: 'Bedrag',
validation_amount_specified: 'Bedrag moet worden opgegeven',
validation_invalid_number: 'Bedrag is geen geldig nummer',
validation_insufficient_amount: 'Niet genoeg ETH op saldo ('
},
fr: {
send_title: 'Envoyer l\'ETH',
send_description: 'Envoyer un paiement',
request_title: 'Demander l\'ETH',
request_description: 'Demander un paiement',
request_requesting: 'Demande en cours: ',
validation_title: 'Montant',
validation_amount_specified: 'Le montant doit être spécifié',
validation_invalid_number: 'Le montant n\'est pas un nombre valide',
validation_insufficient_amount: 'Pas assez d\'ETH sur le solde ('
},
de: {
send_title: 'ETH abschicken',
send_description: 'Zahlung senden',
request_title: 'ETH anfragen',
request_description: 'Zahlung anfragen',
request_requesting: 'Anfrage: ',
validation_title: 'Betrag',
validation_amount_specified: 'Betrag muss angegeben werden',
validation_invalid_number: 'Betrag ist keine gültige Zahl',
validation_insufficient_amount: 'Nicht genügend ETH auf dem Konto ('
},
hi: {
send_title: 'ETH भेजें',
send_description: 'भुगतान भेजें',
request_title: 'ETH का अनुरोध करें',
request_description: 'भुगतान का अनुरोध करें',
request_requesting: 'अनुरोध किया जा रहा है ',
validation_title: 'राशि',
validation_amount_specified: 'राशि निर्दिष्ट की जानी चाहिए',
validation_invalid_number: 'राशि वैध संख्या नहीं है',
validation_insufficient_amount: 'बैलेंस पर पर्याप्त ETH नहीं है ('
},
hu: {
send_title: 'ETH küldése',
send_description: 'Kifizetés küldése',
request_title: 'ETH igénylése',
request_description: 'Fizetés igénylése',
request_requesting: 'Igénylés ',
validation_title: 'Összeg',
validation_amount_specified: 'Az összeget meg kell határozni',
validation_invalid_number: 'Az összeg nem egy elfogadott szám',
validation_insufficient_amount: 'Nincs elég ETH a számlán ('
},
it: {
send_title: 'Invia ETH',
send_description: 'Invia un pagamento',
request_title: 'Richiedi ETH',
request_description: 'Richiedi un pagamento',
request_requesting: 'Richiesta in corso: ',
validation_title: 'Ammontare',
validation_amount_specified: 'L\'ammontare deve essere specificato',
validation_invalid_number: 'L\'ammontare non è un numero valido',
validation_insufficient_amount: 'ETH insufficiente sul bilancio ('
},
ja: {
send_title: 'ETHを送信',
send_description: '支払いを送信',
request_title: 'ETHをリクエスト',
request_description: '支払いをリクエスト',
request_requesting: 'リクエスト中 ',
validation_title: '金額',
validation_amount_specified: '金額を特定する必要があります',
validation_invalid_number: '金額は有効な数字ではありません',
validation_insufficient_amount: '残高に十分なETHがありません('
},
ko: {
send_title: 'ETH 보내기',
send_description: '지불금 보내기',
request_title: 'ETH 요청',
request_description: '지불금 요청',
request_requesting: '요청 중 ',
validation_title: '금액',
validation_amount_specified: '금액을 지정해야 합니다',
validation_invalid_number: '금액이 유효한 숫자가 아닙니다',
validation_insufficient_amount: 'ETH 잔고가 부족합니다 ('
},
pl: {
send_title: 'Wyślij ETH',
send_description: 'Wyślij płatność',
request_title: 'Poproś o ETH',
request_description: 'Poproś o płatność',
request_requesting: 'Przesyłanie prośby ',
validation_title: 'Kwota',
validation_amount_specified: 'Należy określić kwotę',
validation_invalid_number: 'Kwota nie jest prawidłową liczbą',
validation_insufficient_amount: 'Brak wystarczającej liczby ETH na koncie ('
},
'pt-br': {
send_title: 'Enviar ETH',
send_description: 'Enviar um pagamento',
request_title: 'Solicitar ETH',
request_description: 'Solicitar um pagamento',
request_requesting: 'Solicitando ',
validation_title: 'Quantia',
validation_amount_specified: 'É necessário especificar a quantia',
validation_invalid_number: 'A quantia não é um número válido',
validation_insufficient_amount: 'ETH insuficiente no saldo ('
},
'pt-pt': {
send_title: 'Enviar ETH',
send_description: 'Enviar um pagamento',
request_title: 'Solicitar ETH',
request_description: 'Solicitar um pagamento',
request_requesting: 'A solicitar ',
validation_title: 'Montante',
validation_amount_specified: 'O montante deve ser especificado',
validation_invalid_number: 'O montante não é um número válido',
validation_insufficient_amount: 'Não há ETH suficiente no saldo ('
},
ro: {
send_title: 'Trimite ETH',
send_description: 'Trimite o plată',
request_title: 'Solicită ETH',
request_description: 'Solicită o plată',
request_requesting: 'Se solicită ',
validation_title: 'Sumă',
validation_amount_specified: 'Trebuie menționată o sumă',
validation_invalid_number: 'Suma nu are forma unui număr valid',
validation_insufficient_amount: 'Sold ETH insuficient ('
},
sl: {
send_title: 'Pošlji ETH',
send_description: 'Pošlji plačilo',
request_title: 'Zahtevaj ETH',
request_description: 'Zahtevaj plačilo',
request_requesting: 'Zahtevam ',
validation_title: 'Vsota',
validation_amount_specified: 'Vsota mora biti izrecno navedena',
validation_invalid_number: 'Vsota ni veljavna številka',
validation_insufficient_amount: 'Stanje ETH na računu je prenizko ('
},
es: {
send_title: 'Enviar ETH ',
send_description: 'Enviar un pago',
request_title: 'Solicitar ETH',
request_description: 'Solicitar un pago',
request_requesting: 'Solicitando ',
validation_title: 'Cantidad',
validation_amount_specified: 'Hay que especificar la cantidad',
validation_invalid_number: 'La cantidad no es un número válido',
validation_insufficient_amount: 'No hay suficiente ETH en conjunto ('
},
'es-ar': {
send_title: 'Enviar ETH',
send_description: 'Enviar un pago',
request_title: 'Solicitar ETH',
request_description: 'Solicitar un pago',
request_requesting: 'Solicitando ',
validation_title: 'Monto',
validation_amount_specified: 'Debes especificar el monto',
validation_invalid_number: 'El monto no es un número válido',
validation_insufficient_amount: 'No tienes suficiente ETH en tu saldo ('
},
sw: {
send_title: 'Tuma ETH',
send_description: 'Tuma malipo',
request_title: 'Omba ETH',
request_description: 'Omba malipo',
request_requesting: 'Kuomba ',
validation_title: 'Kiasi',
validation_amount_specified: 'Kiasi lazima kifafanuliwe',
validation_invalid_number: 'Kiasi si nambari halali',
validation_insufficient_amount: 'ETH haitoshi kwenye salio ('
},
sv: {
send_title: 'Skicka ETH',
send_description: 'Skicka en betalning',
request_title: 'Begär ETH',
request_description: 'Begär en betalning',
request_requesting: 'Begär ',
validation_title: 'Belopp',
validation_amount_specified: 'Beloppet måste anges',
validation_invalid_number: 'Beloppet är inte ett giltigt nummer',
validation_insufficient_amount: 'Inte tillräcklig ETH på balansen ('
},
'fr-ch': {
send_title: 'Envoyer des ETH',
send_description: 'Envoyer un paiement',
request_title: 'Demander des ETH',
request_description: 'Demander un paiement',
request_requesting: 'Demande ',
validation_title: 'Montant',
validation_amount_specified: 'Le montant doit être spécifié',
validation_invalid_number: 'Le montant n\'est pas un nombre valable',
validation_insufficient_amount: 'Pas assez d\'ETH sur le solde ('
},
'de-ch': {
send_title: 'Sende ETH',
send_description: 'Sende eine Zahlung',
request_title: 'Fordere ETH an',
request_description: 'Eine Zahlung anfordern',
request_requesting: 'Anfordern ',
validation_title: 'Betrag',
validation_amount_specified: 'Der Betrag muss angegeben werden',
validation_invalid_number: 'Der Betrag ist nicht gültig',
validation_insufficient_amount: 'Nicht genug ETH vorhanden ('
},
'it-ch': {
send_title: 'Invia ETH',
send_description: 'Invia un pagamento',
request_title: 'Richiedi ETH',
request_description: 'Richiedi un pagamento',
request_requesting: 'Richiesta in corso: ',
validation_title: 'Importo',
validation_amount_specified: 'Specificare l\'importo',
validation_invalid_number: 'Importo inserito non valido',
validation_insufficient_amount: 'Saldo ETH non sufficiente ('
},
th: {
send_title: 'ส่ง ETH',
send_description: 'ส่งการชำระเงิน',
request_title: 'ร้องขอ ETH',
request_description: 'ร้องขอการชำระเงิน',
request_requesting: 'กำลังร้องขอ ',
validation_title: 'จำนวน',
validation_amount_specified: 'จำเป็นต้องระบุจำนวน',
validation_invalid_number: 'จำนวนไม่ใช่หมายเลขที่ถูกต้อง',
validation_insufficient_amount: 'มี ETH ไม่เพียงพอในยอดคงเหลือ ('
},
tr: {
send_title: 'ETH gönder',
send_description: 'Bir ödeme gönder',
request_title: 'ETH iste',
request_description: 'Bir ödeme iste',
request_requesting: 'İsteniyor ',
validation_title: 'Miktar',
validation_amount_specified: 'Miktar belirtilmelidir',
validation_invalid_number: 'Miktar geçerli bir sayı değil',
validation_insufficient_amount: 'Yeterli ETH bakiyesi yok ('
},
uk: {
send_title: 'Надіслати ETH',
send_description: 'Надіслати платіж',
request_title: 'Запит ETH',
request_description: 'Запит платежу',
request_requesting: 'Запит ',
validation_title: 'Сума',
validation_amount_specified: 'Сума повинна бути вказана',
validation_invalid_number: 'Сума не дійсне число',
validation_insufficient_amount: 'Не вистачає ETH на балансі ('
},
ur: {
send_title: 'ETH بھیجیں',
send_description: 'ادائیگی کریں',
request_title: 'ETH کی درخواست دیں',
request_description: 'ادائیگی کی درخواست دیں',
request_requesting: 'درخواست کی جارہی ہے ',
validation_title: 'رقم',
validation_amount_specified: 'رقم درج کی جانی چاہیے۔ ',
validation_invalid_number: 'رقیم درست ہندسے نہیں ہیں',
validation_insufficient_amount: 'ETH میں کافی بیلنس نہیں ہے ('
},
vi: {
send_title: 'Gửi ETH',
send_description: 'Gửi một khoản thanh toán',
request_title: 'Yêu cầu ETH',
request_description: 'Yêu cầu một khoản thanh toán',
request_requesting: 'Đang yêu cầu ',
validation_title: 'Số tiền',
validation_amount_specified: 'Số tiền phải được xác định',
validation_invalid_number: 'Số tiền không phải là một số hợp lệ',
validation_insufficient_amount: 'Không đủ ETH trong số dư ('
}
};

View File

@ -6,13 +6,23 @@ var _status_catalog = {
},
status = {};
function scopeToBitMask(scope) {
// this function transforms scopes map to a single integer by generating a bit mask
// this similar method also exists on clojure side: status-im.chat.models.commands/scope->bit-mask
return (scope["global?"] ? 1 : 0) |
(scope["registered-only?"] ? 2 : 0) |
(scope["personal-chats?"] ? 4 : 0) |
(scope["group-chats?"] ? 8 : 0) |
(scope["can-use-for-dapps?"] ? 16 : 0);
}
function Command() {
}
function Response() {
}
Command.prototype.addToCatalog = function () {
_status_catalog.commands[this.name] = this;
_status_catalog.commands[[this.name, this.scope.bitmask]] = this;
};
Command.prototype.param = function (parameter) {
@ -26,9 +36,8 @@ Command.prototype.create = function (com) {
this.title = com.title;
this.description = com.description;
this.handler = com.handler;
this["has-handler"] = com.handler != null;
this["async-handler"] = (com.handler != null) && com.asyncHandler
this["registered-only"] = com.registeredOnly;
this["has-handler"] = com.handler !== null;
this["async-handler"] = (com.handler != null) && com.asyncHandler;
this.validator = com.validator;
this.color = com.color;
this.icon = com.icon;
@ -43,6 +52,15 @@ Command.prototype.create = function (com) {
this["sequential-params"] = com.sequentialParams;
this["hide-send-button"] = com.hideSendButton;
// scopes
this["scope"] = {};
this["scope"]["global?"] = com["scope"] != null && com["scope"].indexOf("global") > -1;
this["scope"]["registered-only?"] = com["scope"] != null && com["scope"].indexOf("registered-only") > -1;
this["scope"]["personal-chats?"] = com["scope"] == null || com["scope"].indexOf("personal-chats") > -1;
this["scope"]["group-chats?"] = com["scope"] == null || com["scope"].indexOf("group-chats") > -1;
this["scope"]["can-use-for-dapps?"] = com["scope"] == null || com["scope"].indexOf("can-use-for-dapps") > -1;
this["scope"]["bitmask"] = scopeToBitMask(this["scope"]);
this.addToCatalog();
return this;
@ -51,7 +69,7 @@ Command.prototype.create = function (com) {
Response.prototype = Object.create(Command.prototype);
Response.prototype.addToCatalog = function () {
_status_catalog.responses[this.name] = this;
_status_catalog.responses[[this.name, 0]] = this;
};
Response.prototype.onReceiveResponse = function (handler) {
this.onReceive = handler;

View File

@ -3,5 +3,3 @@
(def mailman-bot "mailman")
(defn mailman-bot? [bot-name]
(= mailman-bot bot-name))
(def hidden-bots #{mailman-bot})

View File

@ -5,7 +5,8 @@
[taoensso.timbre :as log]
[status-im.utils.handlers :as handlers]
[status-im.i18n :as i18n]
[status-im.utils.platform :as platform]))
[status-im.utils.platform :as platform]
[status-im.chat.models.commands :as commands-model]))
;;;; Helper fns
@ -23,19 +24,26 @@
(defn request-command-message-data
"Requests command message data from jail"
[db
{{:keys [command content-command params type]} :content
:keys [chat-id jail-id group-id message-id] :as message}
{{command-name :command
content-command-name :content-command
:keys [content-command-scope
scope
params
type
bot]} :content
:keys [chat-id jail-id group-id] :as message}
data-type]
(let [{:keys [chats]
:accounts/keys [current-account-id]
:contacts/keys [contacts]} db
jail-id (or jail-id chat-id)
jail-id (or bot jail-id chat-id)
jail-id (if (get-in chats [jail-id :group-chat])
(get-in chats [jail-id :command-suggestions (keyword command) :owner-id])
(get-in chats [jail-id :possible-commands (keyword command-name) :owner-id])
jail-id)]
(if (get-in contacts [jail-id :commands-loaded?])
(let [path [(if (= :response (keyword type)) :responses :commands)
(or content-command command)
[(if content-command-name content-command-name command-name)
(commands-model/scope->bit-mask (or scope content-command-scope))]
data-type]
to (get-in contacts [chat-id :address])
jail-params {:parameters params

View File

@ -6,7 +6,7 @@
[status-im.chat.utils :as chat-utils]
[status-im.chat.models :as model]
[status-im.chat.models.input :as input-model]
[status-im.chat.models.suggestions :as suggestions-model]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.events.commands :as commands-events]
[status-im.chat.events.animation :as animation-events]
[status-im.bots.events :as bots-events]
@ -53,18 +53,14 @@
to be made as a result of suggestions update."
[{:keys [chats current-chat-id current-account-id local-storage] :as db}]
(let [chat-text (str/trim (or (get-in chats [current-chat-id :input-text]) ""))
requests (->> (suggestions-model/get-request-suggestions db chat-text)
requests (->> (commands-model/get-possible-requests db)
(remove (fn [{:keys [type]}]
(= type :grant-permissions))))
commands (suggestions-model/get-command-suggestions db chat-text)
global-commands (suggestions-model/get-global-command-suggestions db chat-text)
all-commands (->> (into global-commands commands)
(remove (fn [[k {:keys [hidden?]}]] hidden?))
(into {}))
commands (commands-model/commands-for-chat db current-chat-id)
{:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id])
new-db (cond-> db
true (assoc-in [:chats current-chat-id :request-suggestions] requests)
true (assoc-in [:chats current-chat-id :command-suggestions] all-commands)
true (assoc-in [:chats current-chat-id :possible-requests] requests)
true (assoc-in [:chats current-chat-id :possible-commands] commands)
(and dapp?
(str/blank? chat-text))
(assoc-in [:chats current-chat-id :parameter-boxes :message] nil))]
@ -136,13 +132,16 @@
(defn load-chat-parameter-box
"Returns fx for loading chat parameter box for active chat"
[{:keys [current-chat-id bot-db] :accounts/keys [current-account-id] :as db}
{:keys [name type bot owner-id] :as command}]
{:keys [name scope 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 (or bot current-chat-id))
path [(if (= :command type) :commands :responses)
name
[name
(if (= :command type)
(commands-model/scope->bit-mask scope)
0)]
:params
parameter-index
:suggestions]
@ -180,7 +179,8 @@
(let [input-text (get-in db [:chats current-chat-id :input-text])
command (input-model/selected-chat-command db current-chat-id input-text)
new-db (model/set-chat-ui-props db {:selection selection})
chat-parameter-box-fx (load-chat-parameter-box new-db (:command command))]
chat-parameter-box-fx (when command
(load-chat-parameter-box new-db (:command command)))]
(cond-> {:db new-db}
chat-parameter-box-fx
@ -196,7 +196,7 @@
"Selects command + (optional) arguments as input for active chat"
[{:keys [current-chat-id chat-ui-props] :as db}
{:keys [prefill prefill-bot-db sequential-params name] :as command} metadata prevent-auto-focus?]
(let [fx (-> db
(let [db' (-> db
bots-events/clear-bot-db
clear-seq-arguments
(model/set-chat-ui-props {:show-suggestions? false
@ -208,10 +208,8 @@
(set-chat-input-text (str (chat-utils/command-name command)
const/spacing-char
(when-not sequential-params
(input-model/join-command-args prefill))))
update-suggestions
(as-> fx'
(merge fx' (load-chat-parameter-box (:db fx') command))))]
(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})
@ -283,6 +281,8 @@
:chat-id chat-id
:jail-id (or owner-id jail-id)
:content {:command (:name command)
:scope (when-not (:to-message-id metadata)
(:scope command))
:params params
:type (:type command)}
:on-requested (fn [jail-response]
@ -330,13 +330,11 @@
(fn [db]
(input-model/modified-db-after-change db)))
(handlers/register-handler-fx
(handlers/register-handler-db
:set-chat-input-text
[re-frame/trim-v]
(fn [{:keys [db]} [text]]
(-> db
(set-chat-input-text text)
update-suggestions)))
(fn [db [text]]
(set-chat-input-text db text)))
(handlers/register-handler-fx
:add-to-chat-input-text

View File

@ -75,14 +75,18 @@
(assoc-in [:chats chat-identifier :last-message] message))
:dispatch-n [[:upsert-chat! {:chat-id chat-identifier
:group-chat group-chat?}]
[:request-command-message-data enriched-message :short-preview]]
[:request-command-message-data enriched-message :short-preview]
[:update-suggestions]]
:save-message (dissoc enriched-message :new?)}
(get-in enriched-message [:content :command])
(update :dispatch-n conj [:request-command-preview enriched-message])
(= (:content-type enriched-message) const/content-type-command-request)
(update :dispatch-n conj [:add-request chat-identifier enriched-message])))
(update :dispatch-n conj [:add-request chat-identifier enriched-message])
true
(update :dispatch-n conj [:update-suggestions])))
{:db db})))
(def ^:private receive-interceptors

View File

@ -107,8 +107,7 @@
::contacts-synced
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{:keys [db random-id] :as cofx} [contacts]]
(-> db
(contacts-events/add-contacts contacts)
(-> {:db db}
(as-> fx
(merge fx
(accounts-events/account-update (assoc cofx :db (:db fx)) {:signed-up? true})

View File

@ -1,11 +1,9 @@
(ns status-im.chat.handlers
(:require-macros [cljs.core.async.macros :as am])
(:require [re-frame.core :refer [enrich after debug dispatch reg-fx]]
[status-im.models.commands :as commands]
[clojure.string :as string]
[status-im.components.styles :refer [default-chat-color]]
[status-im.chat.models.suggestions :as suggestions]
[status-im.chat.constants :as chat-consts]
[status-im.chat.constants :as chat-const]
[status-im.protocol.core :as protocol]
[status-im.data-store.chats :as chats]
[status-im.data-store.contacts :as contacts]
@ -244,7 +242,7 @@
(when dapp-url
(am/go
(dispatch [:select-chat-input-command
(assoc (:browse global-commands) :prefill [dapp-url])
(assoc (first (:browse global-commands)) :prefill [dapp-url])
nil
true])
(a/<! (a/timeout 100))

View File

@ -10,9 +10,10 @@
(requests/save new-request))
(defn add-request
[db [_ chat-id {:keys [message-id content]}]]
[db [_ chat-id {:keys [message-id content] :as r}]]
(let [request {:chat-id chat-id
:message-id message-id
:bot (:bot content)
:type (:command content)
:added (js/Date.)}
request' (update request :type keyword)]

View File

@ -1,28 +1,25 @@
(ns status-im.chat.handlers.send-message
(:require [status-im.utils.handlers :refer [register-handler] :as u]
[clojure.string :as s]
[status-im.data-store.messages :as messages]
[status-im.data-store.handler-data :as handler-data]
[status-im.native-module.core :as status]
[status-im.utils.random :as random]
[status-im.utils.datetime :as time]
[re-frame.core :refer [enrich after dispatch path]]
(:require [clojure.string :as s]
[re-frame.core :refer [after dispatch path]]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.events.console :as console]
[status-im.chat.utils :as cu]
[status-im.constants :refer [console-chat-id
wallet-chat-id
text-content-type
content-type-log-message
content-type-command
content-type-command-request
default-number-of-messages] :as c]
[status-im.chat.constants :refer [input-height]]
[status-im.utils.datetime :as datetime]
content-type-command-request] :as c]
[status-im.data-store.messages :as messages]
[status-im.native-module.core :as status]
[status-im.protocol.core :as protocol]
[taoensso.timbre :refer-macros [debug] :as log]
[status-im.chat.events.console :as console]
[status-im.utils.types :as types]
[status-im.utils.config :as config]
[status-im.utils.clocks :as clocks]))
[status-im.utils.clocks :as clocks]
[status-im.utils.datetime :as datetime]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.utils.random :as random]
[status-im.utils.types :as types]
[taoensso.timbre :as log]))
(defn prepare-command
[identity chat-id clock-value
@ -37,15 +34,18 @@
:prefill prefill
:prefill-bot-db prefillBotDb}
{:command (:name command)
:scope (:scope command)
:params params})
content' (assoc content :handler-data handler-data
:type (name (:type command))
:content-command (:name command)
:bot (:bot command))]
:content-command-scope (:scope command)
:bot (or (:bot command)
(:owner-id command)))]
{:message-id id
:from identity
:to chat-id
:timestamp (time/now-ms)
:timestamp (datetime/now-ms)
:content content'
:content-type (or content-type
(if request
@ -125,10 +125,9 @@
(register-handler ::dispatch-responded-requests!
(u/side-effect!
(fn [_ [_ {:keys [command chat-id]}]]
(let [{:keys [to-message]} command]
(fn [_ [_ {{:keys [to-message]} :command :keys [chat-id]}]]
(when to-message
(dispatch [:request-answered! chat-id to-message]))))))
(dispatch [:request-answered! chat-id to-message])))))
(register-handler ::invoke-command-handlers!
(u/side-effect!
@ -140,7 +139,7 @@
id]} :command
:keys [chat-id address]
:as orig-params}]]
(let [{:keys [type name bot owner-id]} command
(let [{:keys [type name scope 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)
@ -159,7 +158,7 @@
identity
#(status/call-jail
{:jail-id identity
:path [handler-type name :handler]
:path [handler-type [name (commands-model/scope->bit-mask scope)] :handler]
:params jail-params
:callback (if (:async-handler command) ; async handler, we ignore return value
(fn [_]
@ -180,7 +179,7 @@
:from identity
:content-type text-content-type
:outgoing true
:timestamp (time/now-ms)
:timestamp (datetime/now-ms)
:clock-value (clocks/send clock-value)
:show? true})
message'' (cond-> message'
@ -205,7 +204,7 @@
(u/side-effect!
(fn [_ [_ {:keys [chat-id message]}]]
(dispatch [:upsert-chat! {:chat-id chat-id
:timestamp (time/now-ms)}])
:timestamp (datetime/now-ms)}])
(messages/save chat-id message))))
(register-handler ::send-dapp-message

View File

@ -5,7 +5,6 @@
[status-im.utils.types :as t]
[status-im.i18n :refer [label]]
[taoensso.timbre :as log]
[status-im.models.commands :as commands]
[status-im.commands.utils :as cu]
[status-im.native-module.core :as s]
[status-im.components.nfc :as nfc]

View File

@ -0,0 +1,121 @@
(ns status-im.chat.models.commands
(:require [status-im.chat.constants :as chat-consts]
[status-im.bots.constants :as bots-constants]
[clojure.string :as str]
[taoensso.timbre :as log]))
(defn scope->bit-mask
"Transforms scope map to a single integer value by generating a bit mask."
[{:keys [global? registered-only? personal-chats? group-chats? can-use-for-dapps?]}]
(bit-or (when global? 1)
(when registered-only? 2)
(when personal-chats? 4)
(when group-chats? 8)
(when can-use-for-dapps? 16)))
(defn get-mixable-commands
"Returns all commands of mixable contacts."
[{:contacts/keys [contacts]}]
(->> contacts
(vals)
(filter :mixable?)
(mapv :commands)
(mapcat #(into [] %))
(reduce (fn [acc [k v]] (update acc k #(into % v))) {})))
(defn get-mixable-identities
"Returns a lazy-seq of all mixable contacts. Each contact contains only one key `:identity`."
[{:contacts/keys [contacts]}]
(->> contacts
(vals)
(filter :mixable?)
(map (fn [{:keys [whisper-identity]}]
{:identity whisper-identity}))))
(defn- transform-commands-map
"Transforms a map of commands to a flat list."
[commands]
(->> commands
(map val)
(remove empty?)
(flatten)))
(defn get-possible-requests
"Returns a list of all possible requests for current chat."
[{:keys [current-chat-id] :as db}]
(let [requests (->> (get-in db [:chats current-chat-id :requests])
(map (fn [{:keys [type chat-id bot] :as req}]
[type (map (fn [resp]
(assoc resp :request req))
(get-in db [:contacts/contacts (or bot chat-id) :responses type]))]))
(remove (fn [[_ items]] (empty? items)))
(into {}))]
(transform-commands-map requests)))
(defn get-possible-commands
"Returns a list of all possible commands for current chat."
[{:keys [current-chat-id] :as db}]
(->> (get-in db [:chats current-chat-id :contacts])
(into (get-mixable-identities db))
(map (fn [{:keys [identity]}]
(let [commands (get-in db [:contacts/contacts identity :commands])]
(transform-commands-map commands))))
(flatten)))
(defn get-possible-global-commands
"Returns a list of all possible global commands for current chat."
[{:keys [global-commands] :as db}]
(transform-commands-map global-commands))
(defn commands-for-chat
"Returns a list of filtered commands for current chat.
Uses scopes to filter commands."
[{:keys [global-commands chats]
:contacts/keys [contacts]
:accounts/keys [accounts current-account-id]
:as db} chat-id]
(let [global-commands (get-possible-global-commands db)
commands (get-possible-commands db)
account (get accounts current-account-id)
commands (-> (into [] global-commands)
(into commands))
{chat-contacts :contacts} (get chats chat-id)]
(remove (fn [{:keys [scope]}]
(or
(and (:registered-only? scope)
(not (:address account)))
(and (not (:personal-chats? scope))
(= (count chat-contacts) 1))
(and (not (:group-chats? scope))
(> (count chat-contacts) 1))
(and (not (:can-use-for-dapps? scope))
(every? (fn [{:keys [identity]}]
(get-in contacts [identity :dapp?]))
chat-contacts))))
commands)))
(defn set-command-for-content
"Sets the information about command for a specified message content.
We need to use this command because `command` field in persistent storage (db) doesn't
contain all information about command — we save only the name of it."
[commands global-commands content]
(if (map? content)
(let [{:keys [command bot]} content]
(if (and bot (not (bots-constants/mailman-bot? bot)))
(update content :command #((keyword bot) global-commands))
(update content :command #((keyword command) commands))))
content))
(defn set-command-for-request
"Sets the information about command for a specified request."
[{:keys [message-id content] :as message} possible-requests possible-commands]
(let [requests (->> possible-requests
(map (fn [{:keys [request] :as message}]
[(:message-id request) message]))
(into {}))
commands (->> possible-commands
(map (fn [{:keys [name] :as message}]
[name message]))
(into {}))]
(assoc content :command (or (get requests message-id)
(get commands (get content :command))))))

View File

@ -1,13 +1,14 @@
(ns status-im.chat.models.input
(:require [clojure.string :as str]
[status-im.bots.constants :as bots-constants]
[status-im.components.react :as rc]
[status-im.native-module.core :as status]
[status-im.chat.constants :as const]
[status-im.chat.models.commands :as commands-model]
[status-im.chat.views.input.validation-messages :refer [validation-message]]
[status-im.chat.utils :as chat-utils]
[status-im.i18n :as i18n]
[status-im.utils.phone-number :as phone-number]
[status-im.chat.utils :as chat-utils]
[status-im.bots.constants :as bots-constants]
[status-im.js-dependencies :as dependencies]
[taoensso.timbre :as log]))
@ -34,29 +35,6 @@
(or (str/starts-with? text const/bot-char)
(str/starts-with? text const/command-char))))
(defn possible-chat-actions
"Returns a map of possible chat actions (commands and response) for a specified `chat-id`.
Every map's key is a command's name, value is a pair of [`command` `message-id`]. In the case
of commands `message-id` is `:any`, for responses value contains the actual id.
Example of output:
{:browse [{:description \"Launch the browser\" :name \"browse\" ...} :any]
:request [{:description \"Request a payment\" :name \"request\" ...} \"message-id\"]}"
[{:keys [global-commands current-chat-id] :as db} chat-id]
(let [chat-id (or chat-id current-chat-id)
{:keys [contacts requests]} (get-in db [:chats chat-id])]
(->> contacts
(map (fn [{:keys [identity]}]
(let [{:keys [commands responses]} (get-in db [:contacts/contacts identity])]
(let [commands' (mapv (fn [[k v]] [k [v :any]]) (merge global-commands commands))
responses' (mapv (fn [{:keys [message-id type]}]
(when-let [response (get responses type)]
[type [response message-id]]))
requests)]
(into commands' responses')))))
(reduce (fn [m cur] (into (or m {}) cur)))
(into {}))))
(defn split-command-args
"Returns a list of command's arguments including the command's name.
@ -128,25 +106,26 @@
* `:args` contains all arguments provided by user."
([{:keys [current-chat-id] :as db} chat-id input-text]
(let [chat-id (or chat-id current-chat-id)
input-metadata (get-in db [:chats chat-id :input-metadata])
seq-arguments (get-in db [:chats chat-id :seq-arguments])
possible-actions (possible-chat-actions db chat-id)
{:keys [input-metadata
seq-arguments
possible-requests
possible-commands]} (get-in db [:chats chat-id])
command-args (split-command-args input-text)
command-name (first command-args)]
(when (starts-as-command? (or command-name ""))
(when-let [[command to-message-id]
(-> (filter (fn [[{:keys [name bot]} message-id]]
(= (or (when-not (bots-constants/mailman-bot? bot) bot) name)
(subs command-name 1)))
(vals possible-actions))
(when-let [{{:keys [message-id]} :request :as command}
(->> (into possible-requests possible-commands)
(filter (fn [{:keys [name]}]
(= name (subs command-name 1))))
(first))]
{:command command
:metadata (if (and (nil? (:to-message-id input-metadata)) (not= :any to-message-id))
(assoc input-metadata :to-message-id to-message-id)
:metadata (if (and (nil? (:to-message-id input-metadata)) message-id)
(assoc input-metadata :to-message-id message-id)
input-metadata)
:args (if (empty? seq-arguments)
:args (->> (if (empty? seq-arguments)
(rest command-args)
seq-arguments)}))))
seq-arguments)
(into []))}))))
([{:keys [current-chat-id] :as db} chat-id]
(selected-chat-command db chat-id (get-in db [:chats chat-id :input-text]))))

View File

@ -1,43 +0,0 @@
(ns status-im.chat.models.suggestions
(:require [status-im.chat.constants :as chat-consts]
[clojure.string :as str]))
(defn can-be-suggested?
([text] (can-be-suggested? chat-consts/command-char :name text))
([first-char name-key text]
(fn [command]
(let [name (get command name-key)]
(let [text' (cond
(.startsWith text first-char)
text
(str/blank? text)
first-char
:default
nil)]
(.startsWith (str first-char name) text'))))))
(defn get-request-suggestions
[{:keys [current-chat-id] :as db} text]
(let [requests (get-in db [:chats current-chat-id :requests])]
(->> requests
(map (fn [{:keys [type] :as v}]
(assoc v :name (get-in db [:contacts/contacts current-chat-id :responses type :name]))))
(filter (fn [v] ((can-be-suggested? text) v))))))
(defn get-command-suggestions
[{:keys [current-chat-id] :as db} text]
(->> (get-in db [:chats current-chat-id :contacts])
(map (fn [{:keys [identity]}]
(let [commands (get-in db [:contacts/contacts identity :commands])]
(->> commands
(filter (fn [[_ v]] ((can-be-suggested? text) v)))))))
(reduce (fn [m cur] (into (or m {}) cur)))
(into {})))
(defn get-global-command-suggestions
[{:keys [global-commands] :as db} text]
(->> global-commands
(filter (fn [[_ v]] ((can-be-suggested? chat-consts/bot-char :bot text) v)))
(into {})))

View File

@ -25,7 +25,7 @@
[status-im.chat.views.input.input :as input]
[status-im.chat.views.actions :refer [actions-view]]
[status-im.chat.views.bottom-info :refer [bottom-info-view]]
[status-im.chat.constants :as const]
[status-im.chat.constants :as chat-const]
[status-im.i18n :refer [label label-pluralize]]
[status-im.components.animation :as anim]
[status-im.components.sync-state.offline :refer [offline-view]]
@ -123,7 +123,7 @@
(defn get-intro-status-message [all-messages]
(let [{:keys [timestamp content-type]} (last all-messages)]
(when (not= content-type content-type-status)
{:message-id const/intro-status-message-id
{:message-id chat-const/intro-status-message-id
:content-type content-type-status
:timestamp (or timestamp (time/now-ms))})))

View File

@ -9,7 +9,6 @@
content-type-status
console-chat-id]]
[status-im.commands.utils :as commands-utils]
[status-im.models.commands :as commands]
[status-im.utils.platform :refer [platform-specific ios?]]
[taoensso.timbre :as log]
[clojure.string :as str]))
@ -37,6 +36,15 @@
(fn [db]
(:chats db)))
(reg-sub
:chat-actions
:<- [:chats]
:<- [:get-current-chat-id]
:<- [:chat :input-text]
(fn [[chats current-chat-id text] [_ type chat-id]]
(->> (get-in chats [(or chat-id current-chat-id) type])
(filter #(or (str/includes? (chat-utils/command-name %) (or text "")))))))
(reg-sub
:chat
:<- [:chats]
@ -54,30 +62,15 @@
(fn [_ [_ chat-id]]
(chats/get-by-id chat-id)))
(reg-sub :get-bots-suggestions
(fn [db]
(let [chat-id (subscribe [:get-current-chat-id])]
(get-in db [:bots-suggestions @chat-id]))))
(reg-sub :get-commands
(fn [db [_ chat-id]]
(let [current-chat (or chat-id (db :current-chat-id))]
(or (get-in db [:contacts/contacts current-chat :commands]) {}))))
(reg-sub
:get-responses
(fn [db [_ chat-id]]
(let [current-chat (or chat-id (db :current-chat-id))]
(or (get-in db [:contacts/contacts current-chat :responses]) {}))))
(reg-sub :get-commands-and-responses
(fn [{:keys [chats] :contacts/keys [contacts]} [_ chat-id]]
(fn [{:keys [chats global-commands] :contacts/keys [contacts]} [_ chat-id]]
(->> (get-in chats [chat-id :contacts])
(filter :is-in-chat)
(mapv (fn [{:keys [identity]}]
(let [{:keys [commands responses]} (get contacts identity)]
(merge responses commands))))
(apply merge))))
(apply merge)
(merge global-commands))))
(reg-sub
:selected-chat-command
@ -135,8 +128,8 @@
show-suggestions? (subscribe [:chat-ui-props :show-suggestions? chat-id])
input-text (subscribe [:chat :input-text chat-id])
selected-command (subscribe [:selected-chat-command chat-id])
requests (subscribe [:chat :request-suggestions chat-id])
commands (subscribe [:chat :command-suggestions chat-id])]
requests (subscribe [:chat :possible-requests chat-id])
commands (subscribe [:chat :possible-commands chat-id])]
(and (or @show-suggestions? (input-model/starts-as-command? (str/trim (or @input-text ""))))
(not (:command @selected-command))
(or (not-empty @requests)

View File

@ -1,18 +1,16 @@
(ns status-im.chat.utils
(:require [status-im.constants :refer [console-chat-id
wallet-chat-id]]
[clojure.string :as str]
[status-im.chat.constants :as const]
[status-im.bots.constants :as bots-constants]))
(:require [clojure.string :as str]
[status-im.constants :as consts]
[status-im.chat.constants :as chat-const]))
(defn console? [s]
(= console-chat-id s))
(= consts/console-chat-id s))
(def not-console?
(complement console?))
(defn wallet? [s]
(= wallet-chat-id s))
(= consts/wallet-chat-id s))
(defn safe-trim [s]
(when (string? s)
@ -48,11 +46,10 @@
(validator message)
(pos? (count message))))
(defn command-name [{:keys [bot name]}]
(defn command-name [{:keys [bot name scope]}]
(cond
(bots-constants/mailman-bot? bot)
(str const/command-char name)
(:global? scope)
(str chat-const/bot-char name)
bot (str const/bot-char bot)
:else (str const/command-char name)))
:default
(str chat-const/command-char name)))

View File

@ -35,9 +35,8 @@
(chat-utils/command-name command)]]])
(defview commands-view []
[commands [:chat :command-suggestions]
responses [:get-responses]
requests [:chat :request-suggestions]
[commands [:chat :possible-commands]
requests [:chat :possible-requests]
show-suggestions? [:show-suggestions?]]
[view style/commands-root
[view style/command-list-icon-container
@ -49,11 +48,9 @@
[scroll-view {:horizontal true
:showsHorizontalScrollIndicator false
:keyboardShouldPersistTaps :always}
(let [requests-names (map :type requests)
all-commands (merge (into {} commands) (select-keys responses requests-names))
all-commands-indexed (map-indexed vector (vals all-commands))]
(let [all-commands (apply conj commands requests)]
[view style/commands
(for [[index command] all-commands-indexed]
(for [[index command] (map-indexed vector all-commands)]
^{:key (str "command-" index)}
[command-view (= index 0) command])])]])

View File

@ -25,14 +25,14 @@
:number-of-lines 2}
description]]])
(defview request-item [{:keys [type message-id]} last?]
[{:keys [name description] :as response} [:get-response type]
{:keys [chat-id]} [:get-current-chat]]
(defview request-item [{:keys [name description]
{:keys [type message-id]} :request :as command} last?]
[{:keys [chat-id]} [:get-current-chat]]
[suggestion-item
{:on-press #(let [{:keys [params]} (messages/get-message-content-by-id message-id)
metadata (assoc params :to-message-id message-id)]
(dispatch [:select-chat-input-command response metadata]))
:name name
(dispatch [:select-chat-input-command command metadata]))
:name (chat-utils/command-name command)
:description description
:last? last?}])
@ -50,8 +50,8 @@
(defview suggestions-view []
[show-suggestions? [:show-suggestions?]
requests [:chat :request-suggestions]
commands [:chat :command-suggestions]]
requests [:chat-actions :possible-requests]
commands [:chat-actions :possible-commands]]
(when show-suggestions?
[expandable-view {:key :suggestions
:draggable? false
@ -61,14 +61,14 @@
(when (seq requests)
[view
[item-title false (label :t/suggestions-requests)]
(for [[i {:keys [chat-id message-id] :as request}] (map-indexed vector requests)]
(for [[i {{:keys [chat-id message-id]} :request :as request}] (map-indexed vector requests)]
^{:key [chat-id message-id]}
[request-item request (= i (dec (count requests)))])])
(when (seq commands)
[view
[item-title (seq requests) (label :t/suggestions-commands)]
(for [[i [_ command]] (->> commands
(remove #(nil? (:title (second %))))
(for [[i command] (->> commands
(remove #(nil? (:title %)))
(map-indexed vector))]
^{:key i}
[command-item command (= i (dec (count commands)))])])]]]))

View File

@ -15,14 +15,13 @@
get-dimensions
dismiss-keyboard!]]
[status-im.components.animation :as anim]
[status-im.chat.constants :as chat-consts]
[status-im.components.list-selection :refer [share browse share-or-open-map]]
[status-im.chat.views.message.request-message :refer [message-content-command-request]]
[status-im.chat.constants :as chat-consts]
[status-im.chat.models.commands :as commands]
[status-im.chat.styles.message.message :as st]
[status-im.chat.styles.message.command-pill :as pill-st]
[status-im.chat.views.message.request-message :refer [message-content-command-request]]
[status-im.chat.views.message.datemark :refer [chat-datemark]]
[status-im.models.commands :refer [parse-command-message-content
parse-command-request]]
[status-im.react-native.resources :as res]
[status-im.constants :refer [console-chat-id
wallet-chat-id
@ -125,11 +124,6 @@
(first (vals params))
(str params))]))
(defn commands-subscription [{:keys [type]}]
(if (= type "response")
:get-responses
:get-commands))
(defview message-content-command
[{:keys [message-id content content-type chat-id to from outgoing] :as message}]
[commands [:get-commands-and-responses chat-id]
@ -139,7 +133,7 @@
contact-chat [:get-in [:chats (if outgoing to from)]]
preview [:get-message-preview message-id]]
(let [commands (merge commands from-commands)
{:keys [command params]} (parse-command-message-content commands global-commands content)
{:keys [command params]} (commands/set-command-for-content commands global-commands content)
{:keys [name type]
icon-path :icon} command]
[view st/content-command-view

View File

@ -1,4 +1,5 @@
(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.components.react :refer [view
@ -8,7 +9,7 @@
icon
touchable-highlight]]
[status-im.chat.styles.message.message :as st]
[status-im.models.commands :refer [parse-command-request]]
[status-im.chat.models.commands :as commands]
[status-im.components.animation :as anim]
[taoensso.timbre :as log]))
@ -70,35 +71,35 @@
(when command-icon
[icon command-icon st/command-request-image])]]))})))
(defn message-content-command-request
(defview message-content-command-request
[{:keys [message-id chat-id]}]
(let [commands-atom (subscribe [:get-commands-and-responses chat-id])
answered? (subscribe [:is-request-answered? message-id])
status-initialized? (subscribe [:get :status-module-initialized?])
markup (subscribe [:get-message-preview message-id])]
(fn [{:keys [message-id content from incoming-group]}]
(let [commands @commands-atom
{:keys [prefill prefill-bot-db prefillBotDb params]
(letsubs [requests [:chat-actions :possible-requests]
commands [:chat-actions :possible-commands]
answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?]
markup [:get-message-preview message-id]]
(fn [{:keys [message-id content from incoming-group] :as message}]
(let [{:keys [prefill prefill-bot-db prefillBotDb params]
text-content :text} content
{:keys [command content]} (parse-command-request commands content)
{:keys [command content]} (commands/set-command-for-request message requests commands)
command (if (and params command)
(merge command {:prefill prefill
:prefill-bot-db (or prefill-bot-db prefillBotDb)})
command)
on-press-handler (if (:execute-immediately? command)
#(dispatch [:execute-command-immediately command])
(when (and (not @answered?) @status-initialized?)
(when (and (not answered?) status-initialized?)
#(set-chat-command message-id command)))]
[view st/comand-request-view
[touchable-highlight
{:on-press on-press-handler}
[view st/command-request-message-view
(if (and @markup
(not (string? @markup)))
[view @markup]
(if (and markup
(not (string? markup)))
[view markup]
[text {:style st/style-message-text
:font :default}
(or text-content @markup content)])]]
(or text-content markup content)])]]
(when (:request-text command)
[view st/command-request-text-view
[text {:style st/style-sub-text

View File

@ -6,7 +6,6 @@
[status-im.commands.utils :refer [generate-hiccup reg-handler]]
[clojure.string :as s]
[status-im.components.react :as r]
[status-im.models.commands :as cm]
[status-im.constants :refer [console-chat-id]]
[status-im.i18n :refer [get-contact-translated]]
[taoensso.timbre :as log]

View File

@ -3,21 +3,16 @@
[status-im.utils.handlers :as u]
[status-im.utils.utils :refer [http-get show-popup]]
[clojure.string :as s]
[status-im.data-store.commands :as commands]
[status-im.data-store.contacts :as contacts]
[status-im.native-module.core :as status]
[status-im.utils.types :refer [json->clj]]
[status-im.data-store.local-storage :as local-storage]
[status-im.commands.utils :refer [reg-handler]]
[status-im.constants :refer [console-chat-id wallet-chat-id]]
[taoensso.timbre :as log]
[status-im.i18n :refer [label]]
[status-im.constants :refer [console-chat-id]]
[status-im.chat.models.commands :as commands-model]
[status-im.native-module.core :as status]
[status-im.utils.homoglyph :as h]
[status-im.utils.js-resources :as js-res]
[status-im.utils.random :as random]
[status-im.bots.constants :as bots-constants]
[status-im.utils.datetime :as time]
[status-im.data-store.local-storage :as local-storage]
[clojure.string :as str]))
[status-im.utils.types :as types]
[taoensso.timbre :as log]))
(defn load-commands!
@ -50,13 +45,10 @@
bot-url
dapp?]} :contact
:as params}]]
(if bot-url
(when bot-url
(if-let [resource (js-res/get-resource bot-url)]
(dispatch [::validate-hash params resource])
(http-get-commands params bot-url))
(when-not dapp?
;; TODO: this part should be removed in the future
(dispatch [::validate-hash params js-res/wallet-js]))))
(http-get-commands params bot-url))))
(defn dispatch-loaded!
[db [{{:keys [whisper-identity]} :contact
@ -88,7 +80,7 @@
(status/parse-jail
whisper-identity (str local-storage-js ethereum-id-js file)
(fn [result]
(let [{:keys [error result]} (json->clj result)]
(let [{:keys [error result]} (types/json->clj result)]
(log/debug "Parsing commands results: " error result)
(if error
(dispatch [::loading-failed! whisper-identity ::error-in-jail error])
@ -104,88 +96,61 @@
(get-hash-by-file file))]
(assoc db :command-hash-valid? valid?)))
(defn each-merge [coll with]
(defn each-merge [with coll]
(->> coll
(map (fn [[k v]] [k (merge v with)]))
(into {})))
(defn filter-commands [account {:keys [contacts chat-id] :as chat} commands]
(defn extract-commands [{:keys [contacts]} commands]
(->> commands
(remove (fn [[_ {:keys [registered-only name]}]]
(and (not (:address account))
(not= name "global")
registered-only)))
;; TODO: this part should be removed because it's much better to provide the ability to do this in the API
(map (fn [[k {:keys [name] :as v}]]
[k (assoc v :hidden? (and (some #{name} ["send" "request"])
(= chat-id wallet-chat-id)))]))
(remove (fn [[k _]]
(remove (fn [[_ {:keys [name]}]]
(and (= (count contacts) 1)
(not= console-chat-id (get (first contacts) :identity))
(h/matches (name k) "password"))))
(h/matches name "password"))))
(map (fn [[k {:keys [name scope] :as v}]]
[[name scope] v]))
(into {})))
(defn get-mailmans-commands [db]
(->> (get-in db [:contacts/contacts bots-constants/mailman-bot :commands])
(map
(fn [[k v :as com]]
[k (-> v
(update :params (fn [p]
(if (map? p)
((comp vec vals) p)
p)))
(assoc :bot bots-constants/mailman-bot
:type :command))]))
(defn extract-global-commands [commands chat-id]
(->> commands
(filter (fn [[_ {:keys [scope]}]]
(:global? scope)))
(map (fn [[k {:keys [name scope] :as v}]]
[[name scope] (assoc v :bot chat-id :type :command)]))
(into {})))
(defn transform-commands [commands]
(reduce (fn [m [_ {:keys [name scope] :as v}]]
(update m (keyword name) conj v))
{}
commands))
(defn add-commands
[{:keys [chats] :as db} [id _ {:keys [commands responses subscriptions]}]]
(let [account @(subscribe [:get-current-account])
[{:keys [current-account-id accounts chats] :as db}
[id _ {:keys [commands responses subscriptions]}]]
(let [account (get accounts current-account-id)
chat (get chats id)
commands' (filter-commands account chat commands)
responses' (filter-commands account chat responses)
global-command (:global commands')
commands'' (each-merge (apply dissoc commands' [:init :global])
{:type :command
global-commands (extract-global-commands commands id)
mixable-commands (when-not (get-in db [:contacts/contacts id :mixable?])
(commands-model/get-mixable-commands db))
commands' (->> (apply dissoc commands (keys global-commands))
(extract-commands chat)
(each-merge {:type :command
:owner-id id})
mailman-commands (get-mailmans-commands db)]
(cond-> db
true
(transform-commands)
(merge mixable-commands))
responses' (->> (extract-commands chat responses)
(each-merge {:type :response
:owner-id id})
(transform-commands))
new-db (-> db
(update-in [:contacts/contacts id] assoc
:commands-loaded? true
:commands (merge mailman-commands commands'')
:responses (each-merge responses' {:type :response
:owner-id id})
:commands commands'
:responses responses'
:subscriptions subscriptions)
global-command
(update :global-commands assoc (keyword id)
(assoc global-command :bot id
:type :command))
(= id bots-constants/mailman-bot)
(update :contacts/contacts (fn [contacts]
(reduce (fn [contacts [k _]]
(update-in contacts [k :commands]
(fn [c]
(merge mailman-commands c))))
contacts
contacts))))))
(defn save-commands-js!
[_ [id file]]
#_(commands/save {:chat-id id :file file}))
(defn save-commands!
[{:keys [global-commands] :contacts/keys [contacts]} [id]]
(let [command (get global-commands (keyword id))
commands (get-in contacts [id :commands])
responses (get-in contacts [id :responses])]
(contacts/save {:whisper-identity id
:global-command command
:commands (vals commands)
:responses (vals responses)})))
(update :global-commands merge (transform-commands global-commands)))]
new-db))
(defn loading-failed!
[db [id reason details]]
@ -215,10 +180,7 @@
(reg-handler ::parse-commands! (u/side-effect! parse-commands!))
(reg-handler ::add-commands
[(after save-commands-js!)
(after save-commands!)
(after #(dispatch [:update-suggestions]))
(after (fn [_ [id]]
[(after (fn [_ [id]]
(dispatch [:invoke-commands-loading-callbacks id])
(dispatch [:invoke-chat-loaded-callbacks id])))
(after (fn [{:contacts/keys [contacts]} [id]]
@ -226,8 +188,8 @@
(doseq [[name opts] subscriptions]
(dispatch [:register-bot-subscription
(assoc opts :bot id
:name name)])))))]
:name name)])))))
(after #(dispatch [:update-suggestions]))]
add-commands)
(reg-handler ::loading-failed! (u/side-effect! loading-failed!))

View File

@ -107,8 +107,7 @@
(defn save
;; todo remove chat-id parameter
[chat-id {:keys [message-id content]
:as message}]
[chat-id {:keys [message-id content] :as message}]
(when-not (data-store/exists? message-id)
(let [content' (if (string? content)
content

View File

@ -13,6 +13,7 @@
[status-im.data-store.realm.schemas.account.v12.core :as v12]
[status-im.data-store.realm.schemas.account.v13.core :as v13]
[status-im.data-store.realm.schemas.account.v14.core :as v14]
[status-im.data-store.realm.schemas.account.v15.core :as v15]
))
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
@ -60,4 +61,7 @@
{:schema v14/schema
:schemaVersion 14
:migration v14/migration}
{:schema v15/schema
:schemaVersion 15
:migration v15/migration}
])

View File

@ -0,0 +1,46 @@
(ns status-im.data-store.realm.schemas.account.v15.contact
(:require [taoensso.timbre :as log]))
(def schema {:name :contact
:primaryKey :whisper-identity
:properties {:address {:type :string :optional true}
:whisper-identity :string
:name {:type :string :optional true}
:photo-path {:type :string :optional true}
:last-updated {:type :int :default 0}
:last-online {:type :int :default 0}
:pending? {:type :bool :default false}
:mixable? {:type :bool :default false}
:status {:type :string :optional true}
:fcm-token {:type :string :optional true}
:public-key {:type :string
:optional true}
:private-key {:type :string
:optional true}
:dapp? {:type :bool
:default false}
:dapp-url {:type :string
:optional true}
:bot-url {:type :string
:optional true}
:global-command {:type :command
:optional true}
:commands {:type :list
:objectType :command}
:responses {:type :list
:objectType :command}
:dapp-hash {:type :int
:optional true}
:debug? {:type :bool
:default false}}})
(defn migration [old-realm new-realm]
(log/debug "migrating contact schema v15")
(let [new-contacts (.objects new-realm "contact")]
(dotimes [i (.-length new-contacts)]
(let [contact (aget new-contacts i)
id (aget contact "whisper-identity")]
(when (or (= id "mailman")
(= id "transactor-group")
(= id "transactor-personal"))
(aset contact "mixable?" true))))))

View File

@ -0,0 +1,40 @@
(ns status-im.data-store.realm.schemas.account.v15.core
(:require [status-im.data-store.realm.schemas.account.v11.chat :as chat]
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v6.command :as command]
[status-im.data-store.realm.schemas.account.v9.command-parameter :as command-parameter]
[status-im.data-store.realm.schemas.account.v15.contact :as contact]
[status-im.data-store.realm.schemas.account.v1.discover :as discover]
[status-im.data-store.realm.schemas.account.v1.kv-store :as kv-store]
[status-im.data-store.realm.schemas.account.v10.message :as message]
[status-im.data-store.realm.schemas.account.v12.pending-message :as pending-message]
[status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message]
[status-im.data-store.realm.schemas.account.v15.request :as request]
[status-im.data-store.realm.schemas.account.v1.tag :as tag]
[status-im.data-store.realm.schemas.account.v1.user-status :as user-status]
[status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group]
[status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact]
[status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage]
[status-im.data-store.realm.schemas.account.v13.handler-data :as handler-data]
[taoensso.timbre :as log]))
(def schema [chat/schema
chat-contact/schema
command/schema
command-parameter/schema
contact/schema
discover/schema
kv-store/schema
message/schema
pending-message/schema
processed-message/schema
request/schema
tag/schema
user-status/schema
contact-group/schema
group-contact/schema
local-storage/schema
handler-data/schema])
(defn migration [old-realm new-realm]
(log/debug "migrating v15 account database: " old-realm new-realm))

View File

@ -0,0 +1,15 @@
(ns status-im.data-store.realm.schemas.account.v15.request
(:require [taoensso.timbre :as log]))
(def schema {:name :request
:properties {:message-id :string
:chat-id :string
:bot {:type :string
:optional true}
:type :string
:status {:type :string
:default "open"}
:added :date}})
(defn migration [_ _]
(log/debug "migrating request schema"))

View File

@ -2,11 +2,11 @@
(:require [re-frame.core :refer [after dispatch]]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.components.react :refer [http-bridge]]
[status-im.utils.types :refer [clj->json]]
[status-im.data-store.messages :as messages]
[status-im.data-store.accounts :as accounts]
[taoensso.timbre :as log]
[status-im.utils.platform :as platform]))
[status-im.utils.platform :as platform]
[status-im.utils.types :as types]))
(def debug-server-port 5561)
@ -14,7 +14,7 @@
(.respond http-bridge
200
"application/json"
(clj->json data)))
(types/clj->json data)))
(register-handler :init-debug-mode
(u/side-effect!

View File

@ -1,14 +0,0 @@
(ns status-im.models.commands
(:require [status-im.bots.constants :as bots-constants]))
(defn parse-command-message-content
[commands global-commands content]
(if (map? content)
(let [{:keys [command bot]} content]
(if (and bot (not (bots-constants/mailman-bot? bot)))
(update content :command #((keyword bot) global-commands))
(update content :command #((keyword command) commands))))
content))
(defn parse-command-request [commands content]
(update content :command #((keyword %) commands)))

View File

@ -2,7 +2,6 @@
(:require-macros
[cljs.core.async.macros :refer [go-loop go]])
(:require [status-im.components.react :as r]
[status-im.utils.types :as t]
[re-frame.core :refer [dispatch]]
[taoensso.timbre :as log]
[cljs.core.async :refer [<! timeout]]
@ -130,13 +129,13 @@
#(do
(log/debug :call-jail :jail-id jail-id)
(log/debug :call-jail :path path)
;; this debug message can contain sensetive info
;; this debug message can contain sensitive info
#_(log/debug :call-jail :params params)
(let [params' (update params :context assoc
:debug js/goog.DEBUG
:locale rn-dependencies/i18n.locale)
cb (fn [r]
(let [{:keys [result] :as r'} (t/json->clj r)
(let [{:keys [result] :as r'} (types/json->clj r)
{:keys [messages]} result]
(log/debug r')
(doseq [{:keys [type message]} messages]

View File

@ -290,7 +290,7 @@
(when-let [{:keys [message-status] :as message} (messages/get-by-id message-id')]
(when-not (= (keyword message-status) :seen)
(let [group? (boolean group-id)
message (if (and group? (not= status :sent))
message' (-> (if (and group? (not= status :sent))
(update-in message
[:user-statuses from]
(fn [{old-status :status}]
@ -299,8 +299,10 @@
:status (if (= (keyword old-status) :seen)
old-status
status)}))
(assoc message :message-status status))]
(messages/update message)))))))
(assoc message :message-status status))
;; we need to dissoc preview because it has been saved before
(dissoc :preview))]
(messages/update message')))))))
(defn update-message-status [status]
(fn [db

View File

@ -27,6 +27,7 @@
(spec/def :contact/last-updated (spec/nilable int?))
(spec/def :contact/last-online (spec/nilable int?))
(spec/def :contact/pending? boolean?)
(spec/def :contact/mixable? boolean?)
(spec/def :contact/unremovable? boolean?)
(spec/def :contact/dapp? boolean?)
@ -34,20 +35,38 @@
(spec/def :contact/dapp-hash (spec/nilable int?))
(spec/def :contact/bot-url (spec/nilable string?))
(spec/def :contact/global-command (spec/nilable map?))
(spec/def :contact/commands (spec/nilable (spec/map-of keyword? map?)))
(spec/def :contact/responses (spec/nilable (spec/map-of keyword? map?)))
(spec/def :contact/commands (spec/nilable (spec/map-of keyword? seq?)))
(spec/def :contact/responses (spec/nilable (spec/map-of keyword? seq?)))
(spec/def :contact/commands-loaded? (spec/nilable boolean?))
(spec/def :contact/subscriptions (spec/nilable map?))
;true when contact added using status-dev-cli
(spec/def :contact/debug? boolean?)
(spec/def :contact/contact (allowed-keys
:req-un [:contact/name :contact/whisper-identity]
:opt-un [:contact/address :contact/private-key :contact/public-key :contact/photo-path
:contact/status :contact/last-updated :contact/last-online :contact/pending?
:contact/unremovable? :contact/dapp? :contact/dapp-url :contact/dapp-hash
:contact/bot-url :contact/global-command :contact/commands-loaded?
:contact/commands :contact/responses :contact/debug? :contact/subscriptions
(spec/def :contact/contact
(allowed-keys
:req-un [:contact/name]
:opt-un [:contact/whisper-identity
:contact/address
:contact/private-key
:contact/public-key
:contact/photo-path
:contact/status
:contact/last-updated
:contact/last-online
:contact/pending?
:contact/mixable?
:contact/scope
:contact/unremovable?
:contact/dapp?
:contact/dapp-url
:contact/dapp-hash
:contact/bot-url
:contact/global-command
:contact/commands-loaded?
:contact/commands
:contact/responses
:contact/debug?
:contact/subscriptions
:contact/fcm-token]))
;;Contact list ui props

View File

@ -213,8 +213,8 @@
(defn- prepare-default-contacts-events [contacts default-contacts]
[[:add-contacts
(for [[id {:keys [name photo-path public-key add-chat? global-command
dapp? dapp-url dapp-hash bot-url unremovable? pending?]}] default-contacts
(for [[id {:keys [name photo-path public-key add-chat? pending?
dapp? dapp-url dapp-hash bot-url unremovable? mixable?]}] default-contacts
:let [id' (clojure.core/name id)]
:when (not (get contacts id'))]
{:whisper-identity id'
@ -223,12 +223,12 @@
:photo-path photo-path
:public-key public-key
:unremovable? (boolean unremovable?)
:mixable? (boolean mixable?)
:pending? pending?
:dapp? dapp?
:dapp-url (:en dapp-url)
:bot-url bot-url
:global-command global-command
:dapp-hash dapp-hash
:pending? pending?})]])
:dapp-hash dapp-hash})]])
(defn- prepare-add-chat-events [contacts default-contacts]
(for [[id {:keys [name add-chat?]}] default-contacts
@ -271,50 +271,25 @@
[(inject-cofx ::get-all-contacts)]
(fn [{:keys [db all-contacts]} _]
(let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts)
global-commands (->> contacts-list
(filter (fn [[_ c]] (:global-command c)))
(map (fn [[id {:keys [global-command]}]]
[(keyword id) (-> global-command
(update :params (comp vec vals))
(assoc :bot id
:type :command))]))
(into {}))
contacts (into {} contacts-list)
;;TODO temporary hide wallet contact, this code should be deleted after wallet contact will be deleted
contacts' (if (get contacts "wallet")
(assoc-in contacts ["wallet" :pending?] true)
contacts)]
{:db (assoc db :contacts/contacts contacts'
:global-commands global-commands)
:dispatch-n (mapv (fn [_ contact] [:watch-contact contact]) contacts')})))
{:db (assoc db :contacts/contacts contacts)
:dispatch-n (mapv (fn [_ contact] [:watch-contact contact]) contacts)})))
(defn add-contacts
"Creates effects map for adding contacts"
[db new-contacts]
(register-handler-fx
:add-contacts
(fn [{:keys [db]} [_ new-contacts]]
(let [{:contacts/keys [contacts]} db
identities (set (keys contacts))
new-contacts' (->> new-contacts
(map #(update-pending-status contacts %))
(remove #(identities (:whisper-identity %)))
(map #(vector (:whisper-identity %) %))
(into {}))
global-commands (->> new-contacts'
(keep (fn [[n {:keys [global-command]}]]
(when global-command
[(keyword n) (assoc global-command
:type :command
:bot n)])))
(into {}))]
{:db (-> db
(update :global-commands merge global-commands)
(update :contacts/contacts merge new-contacts'))
::save-contacts! (vals new-contacts')}))
(register-handler-fx
:add-contacts
[trim-v]
(fn [{:keys [db]} [new-contacts]]
(add-contacts db new-contacts)))
{:db (update db :contacts/contacts merge new-contacts')
::save-contacts! (vals new-contacts')})))
(register-handler-db
:remove-contacts-click-handler

View File

@ -23,9 +23,9 @@
(reg-sub :all-added-contacts
:<- [:get-contacts]
(fn [contacts]
(->> (remove (fn [[_ {:keys [pending? whisper-identity]}]]
(or (true? pending?)
(bots-constants/hidden-bots whisper-identity))) contacts)
(->> contacts
(remove (fn [[_ {:keys [pending? mixable?]}]]
(or pending? mixable?)))
(sort-contacts))))
(reg-sub :all-added-people-contacts

View File

@ -11,7 +11,9 @@
(def default-contacts (json->clj (slurp "resources/default_contacts.json")))
(def default-contact-groups (json->clj (slurp "resources/default_contact_groups.json")))
(def wallet-js (slurp-bot :wallet))
(def transactor-group-js (slurp-bot :transactor_group))
(def transactor-personal-js (slurp-bot :transactor_personal))
(def console-js (slurp-bot :console "web3_metadata.js"))
@ -22,7 +24,8 @@
(def demo-bot-js (slurp-bot :demo_bot))
(def resources
{:wallet-bot wallet-js
{:transactor-group-bot transactor-group-js
:transactor-personal-bot transactor-personal-js
:console-bot console-js
:browse-bot browse-js
:mailman-bot mailman-js

View File

@ -3,23 +3,33 @@
[status-im.chat.models.input :as input]))
(def fake-db
{:global-commands {:command1 {:name "global-command1"}}
{:global-commands {:command1 (list {:name "global-command1"})}
:chats {"test1" {:contacts [{:identity "0x1"}]
:requests nil
:seq-arguments ["arg1" "arg2"]}
:seq-arguments ["arg1" "arg2"]
:possible-commands (list {:name "global-command1"}
{:name "command2"})}
"test2" {:contacts [{:identity "0x1"}
{:identity "0x2"}]
:requests [{:message-id "id1" :type :request1}]}
:requests [{:message-id "id1" :type :request1}]
:possible-commands (list {:name "global-command1"}
{:name "command2"}
{:name "command3"})
:possible-requests (list {:name "request1"})}
"test3" {:contacts [{:identity "0x1"}]
:requests [{:message-id "id1" :type :request1}]}
"test4" {:contacts [{:identity "0x1"}
{:identity "0x2"}]
:requests [{:message-id "id2" :type :request2}]
:input-metadata {:meta-k "meta-v"}}}
:contacts/contacts {"0x1" {:commands {:command2 {:name "command2"}}
:input-metadata {:meta-k "meta-v"}
:possible-commands (list {:name "global-command1"}
{:name "command2"}
{:name "command3"})
:possible-requests (list {:name "request1"})}}
:contacts/contacts {"0x1" {:commands {:command2 (list {:name "command2"})}
:responses nil}
"0x2" {:commands {:command3 {:name "command3"}}
:responses {:request1 {:name "request1"}}}}})
"0x2" {:commands {:command3 (list {:name "command3"})}
:responses {:request1 (list {:name "request1"})}}}})
(deftest text->emoji
(is (nil? (input/text->emoji nil)))
@ -33,27 +43,6 @@
(is (false? (input/text-ends-with-space? "word1 word2 word3")))
(is (true? (input/text-ends-with-space? "word1 word2 "))))
(deftest possible-chat-actions
(is (= (input/possible-chat-actions fake-db "non-existent-chat") {}))
(is (= (input/possible-chat-actions fake-db "test1")
{:command1 [{:name "global-command1"} :any]
:command2 [{:name "command2"} :any]}))
(is (= (input/possible-chat-actions fake-db "test1")
{:command1 [{:name "global-command1"} :any]
:command2 [{:name "command2"} :any]}))
(is (= (input/possible-chat-actions fake-db "test2")
{:command1 [{:name "global-command1"} :any]
:command2 [{:name "command2"} :any]
:command3 [{:name "command3"} :any]
:request1 [{:name "request1"} "id1"]}))
(is (= (input/possible-chat-actions fake-db "test3")
{:command1 [{:name "global-command1"} :any]
:command2 [{:name "command2"} :any]}))
(is (= (input/possible-chat-actions fake-db "test4")
{:command1 [{:name "global-command1"} :any]
:command2 [{:name "command2"} :any]
:command3 [{:name "command3"} :any]})))
(deftest split-command-args
(is (nil? (input/split-command-args nil)))
(is (= [""] (input/split-command-args "")))
@ -76,7 +65,7 @@
(is (= (input/selected-chat-command fake-db "test1" "/command2")
{:command {:name "command2"} :metadata nil :args ["arg1" "arg2"]}))
(is (= (input/selected-chat-command fake-db "test2" "/request1 arg1")
{:command {:name "request1"} :metadata {:to-message-id "id1"} :args ["arg1"]}))
{:command {:name "request1"} :metadata nil :args ["arg1"]}))
(is (= (input/selected-chat-command fake-db "test4" "/command2 arg1")
{:command {:name "command2"} :metadata {:meta-k "meta-v"} :args ["arg1"]})))

View File

@ -28,7 +28,7 @@
(doo-tests
'status-im.test.chat.events
'status-im.test.accounts.events
'status-im.test.contacts.events
;;'status-im.test.contacts.events
'status-im.test.profile.events
'status-im.test.wallet.events
'status-im.test.wallet.transactions.subs