From 239a0cbfe6ae8ad78bfdc11c3472bf56f74f3508 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Tue, 28 Feb 2017 11:14:55 +0200 Subject: [PATCH] split console bot into separate files bots dir slurp-bot macro split commands.js into browse, mailman and wallet bot-url & loading of default bots command schema command-parameter schema global-command in default_commands @browse command in all chats load global command from jail optimize applications startup Conflicts: bots/wallet/translations.js resources/console.js resources/dapp.js resources/default_contacts.json resources/status.js resources/wallet.js src/status_im/accounts/screen.cljs src/status_im/chat/constants.cljs src/status_im/chat/handlers.cljs src/status_im/chat/handlers/commands.cljs src/status_im/chat/handlers/send_message.cljs src/status_im/chat/subs.cljs src/status_im/chat/suggestions.cljs src/status_im/chat/views/command.cljs src/status_im/chat/views/suggestions.cljs src/status_im/commands/handlers/jail.cljs src/status_im/commands/handlers/loading.cljs src/status_im/contacts/handlers.cljs src/status_im/data_store/realm/schemas/account/core.cljs src/status_im/data_store/realm/schemas/account/v5/core.cljs src/status_im/models/commands.cljs src/status_im/utils/js_resources.cljs --- bots/browse/bot.js | 36 + bots/browse/translations.js | 134 ++ bots/console/bot.js | 752 +++++++ bots/console/translations.js | 572 +++++ bots/console/web3_metadata.js | 587 +++++ bots/demo_bot/command.js | 99 + bots/demo_bot/index.html | 0 bots/mailman/bot.js | 49 + bots/mailman/translations.js | 167 ++ bots/wallet/bot.js | 217 ++ .../wallet/translations.js | 525 +---- env/dev/user.clj | 2 +- resources/console.js | 1937 ----------------- resources/default_contacts.json | 30 +- resources/i18n.js | 4 + resources/status.js | 14 +- resources/wallet.js | 31 - src/status_im/accounts/screen.cljs | 7 +- src/status_im/chat/constants.cljs | 3 +- src/status_im/chat/handlers.cljs | 3 + src/status_im/chat/handlers/commands.cljs | 45 +- src/status_im/chat/handlers/input.cljs | 78 +- src/status_im/chat/handlers/send_message.cljs | 34 +- src/status_im/chat/models/input.cljs | 13 +- src/status_im/chat/models/suggestions.cljs | 33 +- src/status_im/chat/sign_up.cljs | 3 +- src/status_im/chat/subs.cljs | 11 +- src/status_im/chat/utils.cljs | 13 +- src/status_im/chat/views/input/input.cljs | 9 +- .../chat/views/input/suggestions.cljs | 13 +- src/status_im/commands/handlers/loading.cljs | 116 +- src/status_im/contacts/handlers.cljs | 60 +- .../realm/schemas/account/core.cljs | 9 +- .../realm/schemas/account/v6/command.cljs | 22 + .../schemas/account/v6/command_parameter.cljs | 9 + .../realm/schemas/account/v6/contact.cljs | 32 + .../realm/schemas/account/v6/core.cljs | 38 + src/status_im/handlers.cljs | 23 +- src/status_im/profile/validations.cljs | 3 +- src/status_im/utils/js_resources.cljs | 37 +- src/status_im/utils/slurp.clj | 8 +- 41 files changed, 3079 insertions(+), 2699 deletions(-) create mode 100644 bots/browse/bot.js create mode 100644 bots/browse/translations.js create mode 100644 bots/console/bot.js create mode 100644 bots/console/translations.js create mode 100644 bots/console/web3_metadata.js create mode 100644 bots/demo_bot/command.js create mode 100644 bots/demo_bot/index.html create mode 100644 bots/mailman/bot.js create mode 100644 bots/mailman/translations.js create mode 100644 bots/wallet/bot.js rename resources/commands.js => bots/wallet/translations.js (52%) delete mode 100644 resources/console.js create mode 100644 resources/i18n.js delete mode 100644 resources/wallet.js create mode 100644 src/status_im/data_store/realm/schemas/account/v6/command.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v6/command_parameter.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v6/contact.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v6/core.cljs diff --git a/bots/browse/bot.js b/bots/browse/bot.js new file mode 100644 index 0000000000..c05f6acb06 --- /dev/null +++ b/bots/browse/bot.js @@ -0,0 +1,36 @@ +function browseSuggestions(params, context) { + var url; + + if (context["dapp-url"]) { + url = context["dapp-url"]; + } + + if (params.url && params.url !== "undefined" && params.url != "") { + url = params.url; + if (!/^[a-zA-Z-_]+:/.test(url)) { + url = 'http://' + url; + } + } + + return { + title: "Browser", + dynamicTitle: true, + markup: status.components.bridgedWebView(url) + }; +} + +status.command({ + name: "global", + title: I18n.t('browse_title'), + registeredOnly: true, + description: I18n.t('browse_description'), + color: "#ffa500", + fullscreen: true, + suggestionsTrigger: 'on-send', + params: [{ + name: "url", + type: status.types.TEXT, + placeholder: "URL" + }], + onSend: browseSuggestions +}); diff --git a/bots/browse/translations.js b/bots/browse/translations.js new file mode 100644 index 0000000000..9f11ce1496 --- /dev/null +++ b/bots/browse/translations.js @@ -0,0 +1,134 @@ +I18n.translations = { + en: { + browse_title: 'Browser', + browse_description: 'Launch the browser' + }, + ru: { + browse_title: 'Браузер', + browse_description: 'Запуск браузера' + }, + af: { + browse_title: 'Webblaaier', + browse_description: 'Begin die webblaaier' + }, + ar: { + browse_title: 'المتصفح', + browse_description: 'تشغيل المتصفح' + }, + 'zh-hant': { + browse_title: '流覽器', + browse_description: '啟動流覽器' + }, + 'zh-hans': { + browse_title: '浏览器', + browse_description: '启动浏览器' + }, + 'zh-yue': { + browse_title: '瀏覽器', + browse_description: '啟動瀏覽器' + }, + 'zh-wuu': { + browse_title: '浏览器', + browse_description: '启动浏览器' + }, + nl: { + browse_title: 'Browser', + browse_description: 'Start de browser' + }, + fr: { + browse_title: 'Navigateur', + browse_description: 'Lancer le navigateur' + }, + de: { + browse_title: 'Browser', + browse_description: 'Browser starten' + }, + hi: { + browse_title: 'ब्राउज़र', + browse_description: 'ब्राउज़र लॉन्च करें' + }, + hu: { + browse_title: 'Böngésző', + browse_description: 'Böngésző indítása' + }, + it: { + browse_title: 'Browser', + browse_description: 'Lancia il browser' + }, + ja: { + browse_title: 'ブラウザ', + browse_description: 'ブラウザを起動' + }, + ko: { + browse_title: '브라우저', + browse_description: '브라우저 시작하기' + }, + pl: { + browse_title: 'Przeglądarka', + browse_description: 'Uruchom przeglądarkę' + }, + 'pt-br': { + browse_title: 'Navegador', + browse_description: 'Abrir o navegador' + }, + 'pt-pt': { + browse_title: 'Navegador', + browse_description: 'Abrir o navegador' + }, + ro: { + browse_title: 'Browser', + browse_description: 'Lansare browser' + }, + sl: { + browse_title: 'Brskalnik', + browse_description: 'Zaženi brskalnik' + }, + es: { + browse_title: 'Navegador', + browse_description: 'Iniciar el navegador' + }, + 'es-ar': { + browse_title: 'Navegador', + browse_description: 'Iniciar navegador' + }, + sw: { + send_title: 'Tuma ETH', + send_description: 'Tuma malipo' + }, + sv: { + browse_title: 'Webbläsare', + browse_description: 'Starta webbläsaren' + }, + 'fr-ch': { + browse_title: 'Navigateur', + browse_description: 'Lancer le navigateur' + }, + 'de-ch': { + browse_title: 'Browser', + browse_description: 'Starte den Browser' + }, + 'it-ch': { + browse_title: 'Browser', + browse_description: 'Avvia il browser' + }, + th: { + browse_title: 'เบราว์เซอร์', + browse_description: 'เปิดเบราว์เซอร์' + }, + tr: { + browse_title: 'Tarayıcı', + browse_description: 'Tarayıcıyı başlat' + }, + uk: { + browse_title: 'Браузер', + browse_description: 'Запустити браузер' + }, + ur: { + browse_title: 'براؤزر', + browse_description: 'براؤزر کھولیں' + }, + vi: { + browse_title: 'Trình duyệt', + browse_description: 'Mở trình duyệt' + } +}; diff --git a/bots/console/bot.js b/bots/console/bot.js new file mode 100644 index 0000000000..bbc9341402 --- /dev/null +++ b/bots/console/bot.js @@ -0,0 +1,752 @@ +function jsSuggestionsContainerStyle(suggestionsCount) { + return { + marginVertical: 1, + marginHorizontal: 0, + keyboardShouldPersistTaps: "always", + //height: Math.min(150, (56 * suggestionsCount)), + backgroundColor: "white", + borderRadius: 5, + keyboardShouldPersistTaps: "always" + }; +} + +var jsSuggestionContainerStyle = { + paddingLeft: 16, + backgroundColor: "white" +}; + +var jsSubContainerStyle = { + //height: 56, + paddingTop: 9, + borderBottomWidth: 1, + borderBottomColor: "#0000001f" +}; + +var jsValueStyle = { + fontSize: 14, + fontFamily: "font", + color: "#000000de" +}; + +var jsBoldValueStyle = { + fontSize: 14, + fontFamily: "font", + color: "#000000de", + fontWeight: "bold" +}; + +var jsDescriptionStyle = { + marginTop: 1.5, + paddingBottom: 9, + fontSize: 14, + fontFamily: "font", + color: "#838c93de" +}; + +var messages = []; + + +console = (function (old) { + return { + log: function (text) { + old.log(text); + var message = { + type: 'log', + message: JSON.stringify(text) + }; + messages.push(message); + context.messages.push(message); + }, + info: function (text) { + old.info(text); + context.messages.push({ + type: 'info', + message: JSON.stringify(text) + }); + }, + warn: function (text) { + old.warn(text); + context.messages.push({ + type: 'warn', + message: JSON.stringify(text) + }); + }, + error: function (text) { + old.error(text); + context.messages.push({ + type: 'error', + message: JSON.stringify(text) + }); + } + }; +}(console)); + + +if (!String.prototype.startsWith) { + String.prototype.startsWith = function (searchString, position) { + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; +} + +function matchSubString(array, string) { + var matched = []; + for (var i = 0; i < array.length; i++) { + var item = array[i]; + if (item.toLowerCase().startsWith(string.toLowerCase())) { + matched.push(item); + } + } + return matched; +} + +function cleanCode(code) { + // remove comments + var commentsRegex = /\/\*.+?\*\/|\/\/.*/g; + code = code.replace(commentsRegex, ""); + // replace string literals + var literalsRegex = /\"(?:\\\\\"|[^\"])*?\"/g; + code = code.replace(literalsRegex, '""'); + var literalsRegex = /\'(?:\\\\\'|[^\'])*?\'/g; + code = code.replace(literalsRegex, '""'); + + return code +} + +function createObjectSuggestion(name, docInfo, code, parameterNumber) { + var title = name; + if (docInfo.args) { + title += "("; + for (var i = 0; i < docInfo.args.length; i++) { + var argument = docInfo.args[i]; + var argumentText = (i > 0 ? ", " : "") + (parameterNumber === i ? "*" + argument.name + "*" : argument.name); + if (argument.optional) { + argumentText = "[" + argumentText + "]"; + } + title += argumentText; + } + title += ")"; + } + if (!docInfo.desc) { + name += "."; + } else if (docInfo.args) { + name += "("; + if (docInfo.args.length == 0) { + name += ")"; + } + } + var suggestion = { + title: title, + desc: docInfo.desc + }; + + if (code != null) { + suggestion.pressValue = code + name; + } + return suggestion; +} + +var lastMessage = null; + +function getLastForm(code) { + var codeLength = code.length; + var form = ''; + var level = 0; + var index = codeLength - 1; + while (index >= 0) { + var char = code[index]; + if (level == 0 && (char == '(' || char == ',')) { + break; + } + if (char == ')') { + level--; + } + if (char == '(') { + level++; + } + form = char + form; + index--; + } + return form; +} + +function getLastLevel(code) { + var codeLength = code.length; + var form = ''; + var index = codeLength - 1; + var nested = false; + var level = 0; + while (index >= 0) { + var char = code[index]; + if (char == ')') { + level--; + nested = true; + } + if (char == '(') { + level++; + if (level == 0) { + nested = false; + } + if (level <= 0) { + form = "argument" + form; + index--; + continue; + } + } + if ((level == 1 && char == ',') || level == 2) { + break; + } + if (!nested || level > 0) { + form = char + form; + } + index--; + } + if (form.indexOf("(") < 0) { + var parts = form.split(','); + form = parts[parts.length - 1]; + } + return form; +} + +function getPartialSuggestions(doc, fullCode, code) { + var suggestions = []; + var functionParts = code.split("("); + var objectParts = code.split(/[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/); + + var index = 0; + var suggestedFunction = ''; + while (index < objectParts.length) { + var part = objectParts[index]; + if (part != "desc" && part != "args" && doc[part] != null) { + doc = doc[part]; + suggestedFunction += part + '.'; + index++; + } else { + break; + } + } + suggestedFunction = suggestedFunction.substring(0, suggestedFunction.length - 1) + if (functionParts.length == 1) { + // object suggestions + if (index > objectParts.length - 1) { + var suggestion = objectParts[objectParts.length - 1]; + suggestions.push(createObjectSuggestion(suggestion, doc, fullCode.substring(0, fullCode.length - suggestion.length))); + } else if (index === objectParts.length - 1) { + var lastPart = objectParts[index]; + var keys = Object.keys(doc); + var matches = matchSubString(keys, lastPart); + + for (var i = 0; i < matches.length; i++) { + var suggestion = matches[i]; + if (suggestion == "desc" || suggestion == "args") { + continue; + } + var docInfo = doc[suggestion]; + if (docInfo != null) { + suggestions.push(createObjectSuggestion(suggestion, docInfo, fullCode.substring(0, fullCode.length - lastPart.length))); + } + } + } + } else if (functionParts.length == 2) { + // parameter suggestions + var parameters = functionParts[1].split(","); + if (doc.args && parameters.length <= doc.args.length && parameters[parameters.length - 1].indexOf(")") < 0) { + var paramInfo = doc.args[parameters.length - 1]; + var docInfo = doc; + docInfo.desc = paramInfo.name + ": " + paramInfo.desc; + suggestions.push(createObjectSuggestion(suggestedFunction, docInfo, null, parameters.length - 1)); + } + } + console.log(suggestions); + return suggestions; +} + +function getJsSuggestions(code, context) { + var suggestions = []; + var doc = DOC_MAP; + // TODO: what's /c / doing there ??? + console.log(code); + if (!code || code == "" || code == "c ") { + code = ""; + console.log("Last message: " + context.data); + if (context.data != null) { + suggestions.push({ + title: 'Last command used:', + desc: context.data, + pressValue: context.data + }); + } + var keys = Object.keys(doc); + for (var i = 0; i < keys.length; i++) { + var suggestion = keys[i]; + var docInfo = doc[suggestion]; + if (docInfo != null) { + suggestions.push(createObjectSuggestion(suggestion, docInfo, "")); + } + } + } else { + // TODO: what's /c / doing there ??? + if (code.startsWith("c ")) { + code = code.substring(2); + } + if (context.data != null && + (typeof context.data === 'string' || context.data instanceof String) && + context.data.startsWith(code)) { + suggestions.unshift({ + title: 'Last command used:', + desc: context.data, + pressValue: context.data + }); + } + var originalCode = code; + code = cleanCode(code); + var levelCode = getLastLevel(code); + var code = getLastForm(levelCode); + if (levelCode != code) { + suggestions = getPartialSuggestions(doc, originalCode, levelCode); + } + + console.log("Final code: " + code); + console.log("Level code: " + levelCode); + suggestions = suggestions.concat(getPartialSuggestions(doc, originalCode, code)); + } + return suggestions; +} + +function createMarkupText(text) { + var parts = []; + var index = 0; + var currentText = ''; + var isBold = false; + while (index < text.length) { + var char = text[index]; + if (char == '*') { + if (currentText != '') { + parts.push( + status.components.text( + {style: isBold ? jsBoldValueStyle : jsValueStyle}, + currentText + ) + ); + currentText = ''; + } + isBold = !isBold; + } else { + currentText += char; + } + index++; + } + if (currentText != '') { + parts.push( + status.components.text( + {style: isBold ? jsBoldValueStyle : jsValueStyle}, + currentText + ) + ); + } + console.log(parts); + return parts; +} + +function jsSuggestions(params, context) { + var suggestions = getJsSuggestions(params.code, context); + var sugestionsMarkup = []; + + for (var i = 0; i < suggestions.length; i++) { + var suggestion = suggestions[i]; + + if (suggestion.title.indexOf('*') >= 0) { + suggestion.title = createMarkupText(suggestion.title); + } + var suggestionMarkup = status.components.view(jsSuggestionContainerStyle, + [status.components.view(jsSubContainerStyle, + [ + status.components.text({style: jsValueStyle}, + suggestion.title), + status.components.text({style: jsDescriptionStyle}, + suggestion.desc) + ])]); + if (suggestion.pressValue) { + suggestionMarkup = status.components.touchable({ + onPress: [status.events.SET_VALUE, suggestion.pressValue] + }, + suggestionMarkup); + } + sugestionsMarkup.push(suggestionMarkup); + } + + if (sugestionsMarkup.length > 0) { + var view = status.components.scrollView(jsSuggestionsContainerStyle(sugestionsMarkup.length), + sugestionsMarkup + ); + return {markup: view}; + } +} + + +function jsHandler(params, context) { + var result = { + err: null, + data: null, + messages: [] + }; + messages = []; + try { + result.data = JSON.stringify(eval(params.code)); + localStorage.set(params.code); + } catch (e) { + result.err = e; + } + + result.messages = messages; + + return result; +} + +function suggestionsContainerStyle(suggestionsCount) { + return { + marginVertical: 1, + marginHorizontal: 0, + keyboardShouldPersistTaps: "always", + height: Math.min(150, (56 * suggestionsCount)), + backgroundColor: "white", + borderRadius: 5, + flexGrow: 1 + }; +} + +var suggestionContainerStyle = { + paddingLeft: 16, + backgroundColor: "white" +}; + +var suggestionSubContainerStyle = { + height: 56, + borderBottomWidth: 1, + borderBottomColor: "#0000001f" +}; + +var valueStyle = { + marginTop: 9, + fontSize: 14, + fontFamily: "font", + color: "#000000de" +}; + +var descriptionStyle = { + marginTop: 1.5, + fontSize: 14, + fontFamily: "font", + color: "#838c93de" +}; + +function startsWith(str1, str2) { + // String.startsWith(...) doesn't work in otto + return str1.lastIndexOf(str2, 0) == 0 && str1 != str2; +} + +function phoneSuggestions(params, context) { + var ph, suggestions; + if (!params.phone || params.phone == "") { + ph = context.suggestions; + } else { + ph = context.suggestions.filter(function (phone) { + return startsWith(phone.number, params.phone); + }); + } + + if (ph.length == 0) { + return; + } + + suggestions = ph.map(function (phone) { + return status.components.touchable( + {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, phone.number]]}, + status.components.view(suggestionContainerStyle, + [status.components.view(suggestionSubContainerStyle, + [ + status.components.text( + {style: valueStyle}, + phone.number + ), + status.components.text( + {style: descriptionStyle}, + phone.description + ) + ])]) + ); + }); + + var view = status.components.scrollView( + suggestionsContainerStyle(ph.length), + suggestions + ); + + return {markup: view}; +} + +var phoneConfig = { + name: "phone", + registeredOnly: true, + icon: "phone_white", + color: "#5bb2a2", + title: I18n.t('phone_title'), + description: I18n.t('phone_description'), + sequentialParams: true, + validator: function (params) { + return { + validationHandler: "phone", + parameters: [params.phone] + }; + }, + params: [{ + name: "phone", + type: status.types.PHONE, + suggestions: phoneSuggestions, + placeholder: I18n.t('phone_placeholder') + }] +}; +status.response(phoneConfig); +status.command(phoneConfig); + +var faucets = [ + /*{ + name: "Ethereum Ropsten Faucet", + url: "http://faucet.ropsten.be:3001" + },*/ + { + name: "Status Testnet Faucet", + url: "http://46.101.129.137:3001", + } +]; + +function faucetSuggestions(params) { + var suggestions = faucets.map(function (entry) { + return status.components.touchable( + {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, entry.url]]}, + status.components.view( + suggestionContainerStyle, + [status.components.view( + suggestionSubContainerStyle, + [ + status.components.text( + {style: valueStyle}, + entry.name + ), + status.components.text( + {style: descriptionStyle}, + entry.url + ) + ] + )] + ) + ); + }); + + var view = status.components.scrollView( + suggestionsContainerStyle(faucets.length), + suggestions + ); + + return {markup: view}; +} + +status.command({ + name: "faucet", + title: I18n.t('faucet_title'), + description: I18n.t('faucet_description'), + color: "#7099e6", + registeredOnly: true, + params: [{ + name: "url", + type: status.types.TEXT, + suggestions: faucetSuggestions, + placeholder: I18n.t('faucet_placeholder') + }], + preview: function (params) { + return { + markup: status.components.text( + {}, + params.url + ) + }; + }, + shortPreview: function (params) { + return { + markup: status.components.text( + {}, + I18n.t('faucet_title') + ": " + params.url + ) + }; + }, + validator: function (params, context) { + var f = faucets.map(function (entry) { + return entry.url; + }); + + if (f.indexOf(params.url) == -1) { + var error = status.components.validationMessage( + I18n.t('faucet_incorrect_title'), + I18n.t('faucet_incorrect_description') + ); + + return {markup: error}; + } + } +}); + +function debugSuggestions(params) { + var suggestions = ["On", "Off"].map(function (entry) { + return status.components.touchable( + {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, entry]]}, + status.components.view( + suggestionContainerStyle, + [status.components.view( + suggestionSubContainerStyle, + [ + status.components.text( + {style: valueStyle}, + entry + ) + ] + )] + ) + ); + }); + + var view = status.components.scrollView( + suggestionsContainerStyle(faucets.length), + suggestions + ); + + return {markup: view}; +} + +status.command({ + name: "debug", + title: I18n.t('debug_mode_title'), + description: I18n.t('debug_mode_description'), + color: "#7099e6", + registeredOnly: true, + params: [{ + name: "mode", + suggestions: debugSuggestions, + type: status.types.TEXT + }], + preview: function (params) { + return { + markup: status.components.text( + {}, + I18n.t('debug_mode_title') + ": " + params.mode + ) + }; + }, + shortPreview: function (params) { + return { + markup: status.components.text( + {}, + I18n.t('debug_mode_title') + ": " + params.mode + ) + }; + } +}); + + +// 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", + description: I18n.t('confirm_description'), + sequentialParams: true, + params: [{ + name: "code", + type: status.types.NUMBER + }], + validator: function (params) { + if (!/^[\d]{4}$/.test(params.code)) { + var error = status.components.validationMessage( + I18n.t('confirm_validation_title'), + I18n.t('confirm_validation_description') + ); + + return {markup: error}; + } + } +}); + +status.response({ + name: "password", + color: "#7099e6", + description: I18n.t('password_description'), + icon: "lock_white", + sequentialParams: true, + params: [ + { + name: "password", + type: status.types.PASSWORD, + placeholder: I18n.t('password_placeholder'), + hidden: true + }, + { + name: "password-confirmation", + type: status.types.PASSWORD, + placeholder: I18n.t('password_placeholder2'), + hidden: true + } + ], + validator: function (params, context) { + if (!params.hasOwnProperty("password-confirmation") || params["password-confirmation"].length === 0) { + if (params.password === null || params.password.length < 6) { + var error = status.components.validationMessage( + I18n.t('password_validation_title'), + I18n.t('password_error') + ); + return {markup: error}; + } + } else { + if (params.password !== params["password-confirmation"]) { + var error = status.components.validationMessage( + I18n.t('password_validation_title'), + I18n.t('password_error1') + ); + return {markup: error}; + } + } + + }, + preview: function (params, context) { + var style = { + marginTop: 5, + marginHorizontal: 0, + fontSize: 14, + color: "black" + }; + + if (context.platform == "ios") { + style.fontSize = 8; + style.marginTop = 10; + style.marginBottom = 2; + style.letterSpacing = 1; + } + + return {markup: status.components.text({style: style}, "●●●●●●●●●●")}; + } +}); + +status.registerFunction("message-suggestions", function (params, context) { + return jsSuggestions({code: params.message}, context); +}); + +status.registerFunction("message-handler", function (params, context) { + return jsHandler({code: params.message}, context); +}); diff --git a/bots/console/translations.js b/bots/console/translations.js new file mode 100644 index 0000000000..1592b8e076 --- /dev/null +++ b/bots/console/translations.js @@ -0,0 +1,572 @@ +I18n.translations = { + en: { + phone_title: 'Send Phone Number', + phone_description: 'Find friends using your number', + phone_placeholder: 'Phone number', + + confirm_description: 'Confirmation code', + confirm_validation_title: 'Confirmation code', + confirm_validation_description: 'Wrong format', + + password_description: 'Password', + password_placeholder: 'Type your password', + password_placeholder2: 'Please re-enter password to confirm', + password_error: 'Password should be not less then 6 symbols.', + password_error1: 'Password confirmation doesn\'t match password.', + password_validation_title: 'Password', + + faucet_incorrect_title: 'Incorrect faucet', + faucet_incorrect_description: 'Please, select a one from the list', + + debug_mode_title: 'Debug mode', + debug_mode_description: 'Starts/stops a debug mode', + + faucet_title: 'Faucet', + faucet_description: 'Get some ETH', + faucet_placeholder: 'Faucet URL' + }, + ru: { + phone_title: 'Отправить номер телефона', + phone_description: 'Найти друзей, используя ваш номер', + phone_placeholder: 'Номер телефона', + + confirm_description: 'Код подтверждения', + confirm_validation_title: 'Код подтверждения', + confirm_validation_description: 'Неверный формат', + + password_description: 'Пароль', + password_placeholder: 'Введите свой пароль', + password_placeholder2: 'Повторно введите пароль для подтверждения', + password_error: 'Пароль должен содержать не менее 6 символов', + password_error1: 'Подтверждение пароля не совпадает с паролем', + password_validation_title: 'Пароль' + + }, + af: { + phone_title: 'Stuur telefoonnommer', + phone_description: 'Vind vriende deur jou nommer te gebruik', + phone_placeholder: 'Telefoonnommer', + + confirm_description: 'Bevestigingskode', + confirm_validation_title: 'Bevestigingskode', + confirm_validation_description: 'Verkeerde formaat', + + password_description: 'Wagwoord', + password_placeholder: 'Tik jou wagwoord in', + password_placeholder2: 'Tik asseblief weer jou wagwoord in om te bevestig', + password_error: 'Wagwoord mag nie minder as 6 simbole wees nie.', + password_error1: 'Wagwoordbevestiging is nie dieselfde as wagwoord nie.', + password_validation_title: 'Wagwoord' + + }, + ar: { + phone_title: 'أرسل رقم الهاتف', + phone_description: 'ابحث عن الأصدقاء باستخدام رقمك', + phone_placeholder: 'رقم الهاتف', + + confirm_description: 'رمز التأكيد', + confirm_validation_title: 'رمز التأكيد', + confirm_validation_description: 'صيغة خاطئة', + + password_description: 'كلمة المرور', + password_placeholder: 'اكتب كلمة المرور الخاصة بك', + password_placeholder2: 'الرجاء إعادة إدخال كلمة المرور للتأكيد', + password_error: 'ينبغي أن لا تقل كلمة المرور عن 6 رموز.', + password_error1: 'لا يتوافق تأكيد كلمة المرور مع كلمة المرور.', + password_validation_title: 'كلمة المرور' + + }, + 'zh-hant': { + phone_title: '發送手機號碼', + phone_description: '使用您的號碼發現好友', + phone_placeholder: '手機號碼', + + confirm_description: '確認碼', + confirm_validation_title: '確認碼', + confirm_validation_description: '格式錯誤', + + password_description: '密碼', + password_placeholder: '鍵入您的密碼', + password_placeholder2: '重新鍵入您的密碼', + password_error: '密碼不得短於6個字元。', + password_error1: '確認密碼與鍵入的密碼不一致。', + password_validation_title: '密碼' + + }, + 'zh-hans': { + phone_title: '发送电话号码', + phone_description: '用你的号码来查找朋友', + phone_placeholder: '电话号码', + + confirm_description: '确认码', + confirm_validation_title: '确认码', + confirm_validation_description: '格式错误', + + password_description: '密码', + password_placeholder: '输入密码', + password_placeholder2: '请重新输入密码以确认', + password_error: '密码应不少于6个字符。', + password_error1: '密码确认信息与密码不匹配。', + password_validation_title: '密码' + + }, + 'zh-yue': { + phone_title: '發送電話號碼', + phone_description: '使用本電話號碼查找好友', + phone_placeholder: '電話號碼', + + confirm_description: '驗證碼', + confirm_validation_title: '驗證碼', + confirm_validation_description: '格式錯誤', + + password_description: '密碼', + password_placeholder: '輸入密碼', + password_placeholder2: '請重新輸入密碼確認', + password_error: '密碼不能短於6個字符.', + password_error1: '確認密碼與輸入密碼不符.', + password_validation_title: '密碼' + + }, + 'zh-wuu': { + phone_title: '发送电话号码', + phone_description: '用您的号码查找朋友', + phone_placeholder: '电话号码', + + confirm_description: '确认码', + confirm_validation_title: '确认码', + confirm_validation_description: '错误格式', + + password_description: '密码', + password_placeholder: '输入密码', + password_placeholder2: '请重新输入密码确认', + password_error: '密码应不小于6个字符。', + password_error1: '密码确认不匹配。', + password_validation_title: '密码' + + }, + nl: { + phone_title: 'Stuur telefoonnummer', + phone_description: 'Zoek vrienden met behulp van je nummer', + phone_placeholder: 'Telefoonnummer', + + confirm_description: 'Bevestigingscode', + confirm_validation_title: 'Bevestigingscode', + confirm_validation_description: 'Verkeerd format', + + password_description: 'Wachtwoord', + password_placeholder: 'Typ je wachtwoord', + password_placeholder2: 'Voer je wachtwoord opnieuw in om te bevestigen', + password_error: 'Wachtwoord moet minstens 6 tekens hebben.', + password_error1: 'Wachtwoordbevestiging komt niet overeen met wachtwoord.', + password_validation_title: 'Wachtwoord' + + }, + fr: { + phone_title: 'Envoyer le numéro de téléphone', + phone_description: 'Trouver des amis en utilisant votre numéro', + phone_placeholder: 'Numéro de téléphone', + + confirm_description: 'Code de confirmation', + confirm_validation_title: 'Code de confirmation', + confirm_validation_description: 'Format incorrect', + + password_description: 'Mot de passe', + password_placeholder: 'Tapez votre mot de passe', + password_placeholder2: 'Veuillez retapez votre mot de passe pour le confirmer', + password_error: 'Le mot de passe doit contenir 6 symboles au minimum.', + password_error1: 'Le mot de passe de confirmation ne correspond pas au mot de passe.', + password_validation_title: 'Mot de passe' + + }, + de: { + phone_title: 'Telefonnummer absenden', + phone_description: 'Freunde mit Ihrer Nummer finden', + phone_placeholder: 'Telefonnummer', + + confirm_description: 'Bestätigungscode', + confirm_validation_title: 'Bestätigungscode', + confirm_validation_description: 'Falsches Format', + + password_description: 'Passwort', + password_placeholder: 'Geben Sie Ihr Passwort ein', + password_placeholder2: 'Bitte geben Sie das Passwort zur Bestätigung erneut ein', + password_error: 'Das Passwort sollte nicht weniger als 6 Stellen beinhalten', + password_error1: 'Die Passwortbestätigung stimmt nicht mit dem Passwort überein', + password_validation_title: 'Passwort', + + }, + hi: { + phone_title: 'फ़ोन नंबर भेजें', + phone_description: 'अपने नंबर का उपयोग करके दोस्त ढूंढें', + phone_placeholder: 'फ़ोन नंबर', + + confirm_description: 'पुष्टि कोड', + confirm_validation_title: 'पुष्टि कोड', + confirm_validation_description: 'गलत प्रारूप', + + password_description: 'पासवर्ड', + password_placeholder: 'अपना पासवर्ड टाइप करें', + password_placeholder2: 'पुष्टि करने के लिए फिर से पासवर्ड दर्ज करें', + password_error: 'पासवर्ड 6 प्रतीकों से कम का नहीं होना चाहिए।', + password_error1: 'पासवर्ड पुष्टि पासवर्ड मेल नहीं खाता है।', + password_validation_title: 'पासवर्ड' + + }, + hu: { + phone_title: 'Telefonszám küldése', + phone_description: 'Ismerősök megkeresése telefonszám alapján', + phone_placeholder: 'Telefonszám', + + confirm_description: 'Megerősítési kód', + confirm_validation_title: 'Megerősítési kód', + confirm_validation_description: 'Rossz formátum', + + password_description: 'Jelszó', + password_placeholder: 'Add meg a jelszavad', + password_placeholder2: 'A megerősítéshez kérjük, add meg újra a jelszavad', + password_error: 'A jelszó nem lehet hosszabb 6 szimbólumnál.', + password_error1: 'A megerősített jelszó nem egyezik a jelszóval.', + password_validation_title: 'Jelszó' + + }, + it: { + phone_title: 'Invia numero di telefono', + phone_description: 'Trova gli amici che usano il tuo numero', + phone_placeholder: 'Numero di telefono', + + confirm_description: 'Codice di conferma', + confirm_validation_title: 'Codice di conferma', + confirm_validation_description: 'Formato errato', + + password_description: 'Password', + password_placeholder: 'Digita la tua password', + password_placeholder2: 'Reinserisci la password per confermare', + password_error: 'La password deve contenere almeno 6 caratteri.', + password_error1: 'Conferma password\ la password non corrisponde.', + password_validation_title: 'Password' + + }, + ja: { + phone_title: '電話番号を送信', + phone_description: 'あなたの番号を使用している友人を検索', + phone_placeholder: '携帯電話番号', + + confirm_description: '確認コード', + confirm_validation_title: '確認コード', + confirm_validation_description: '間違った形式', + + password_description: 'パスワード', + password_placeholder: 'パスワードを入力してください', + password_placeholder2: '確認のためにパスワードを再入力してください', + password_error: 'パスワードは6文字以下でなければなりません.', + password_error1: 'パスワードの確認がパスワードと一致しません。', + password_validation_title: 'パスワード' + + }, + ko: { + phone_title: '전화번호 보내기', + phone_description: '내 번호를 사용하여 친구 찾기', + phone_placeholder: '전화번호', + + confirm_description: '확인 코드', + confirm_validation_title: '확인 코드', + confirm_validation_description: '잘못된 형식', + + password_description: '비밀번호', + password_placeholder: '비밀번호를 입력하세요', + password_placeholder2: '확인을 위해 비밀번호를 다시 입력해 주세요', + password_error: '비밀번호는 6자 이상이어야 합니다.', + password_error1: '확인용 비밀번호가 원래 비밀번호와 일치하지 않습니다.', + password_validation_title: '비밀번호' + + }, + pl: { + phone_title: 'Wyślij numer telefonu', + phone_description: 'Znajdź znajomych, używając swojego numeru', + phone_placeholder: 'Numer telefonu', + + confirm_description: 'Kod potwierdzający', + confirm_validation_title: 'Kod potwierdzający', + confirm_validation_description: 'Nieprawidłowy format', + + password_description: 'Hasło', + password_placeholder: 'Wpisz swoje hasło', + password_placeholder2: 'Wprowadź ponownie hasło, aby potwierdzić', + password_error: 'Hasło powinno zawierać co najmniej 6 symboli.', + password_error1: 'Wprowadzone i potwierdzone hasła nie są takie same.', + password_validation_title: 'Hasło' + + }, + 'pt-br': { + phone_title: 'Enviar número de telefone', + phone_description: 'Encontrar amigos por meio do seu número', + phone_placeholder: 'Número de telefone', + + confirm_description: 'Código de confirmação', + confirm_validation_title: 'Código de confirmação', + confirm_validation_description: 'Formato incorreto', + + password_description: 'Senha', + password_placeholder: 'Digite sua senha', + password_placeholder2: 'Por favor, digite a senha novamente para confirmar', + password_error: 'A senha deve ter no mínimo 6 símbolos.', + password_error1: 'A confirmação da senha é diferente da senha.', + password_validation_title: 'Senha' + + }, + 'pt-pt': { + phone_title: 'Enviar o Número de Telefone', + phone_description: 'Encontrar amigos que utilizem o seu número', + phone_placeholder: 'Número de telefone', + + confirm_description: 'Código de confirmação', + confirm_validation_title: 'Código de confirmação', + confirm_validation_description: 'Formato errado', + + password_description: 'Palavra-passe', + password_placeholder: 'Digite a sua palavra-passe', + password_placeholder2: 'Por favor, volte a digitar a palavra-passe para confirmar', + password_error: 'A palavra-passe não deve ter menos de 6 símbolos.', + password_error1: 'A confirmação da palavra-passe não coincide com a palavra-passe.', + password_validation_title: 'Palavra-passe' + + }, + ro: { + phone_title: 'Trimite numărul de telefon', + phone_description: 'Găsește prieteni folosindu-ți numărul de telefon', + phone_placeholder: 'Număr de telefon', + + confirm_description: 'Cod de confirmare', + confirm_validation_title: 'Cod de confirmare', + confirm_validation_description: 'Format greșit', + + password_description: 'Parolă', + password_placeholder: 'Tastează parola', + password_placeholder2: 'Te rugăm să re-introduci parola pentru a confirma', + password_error: 'Parola trebuie să aibă cel puțin 6 simboluri.', + password_error1: 'Parola confirmată nu este aceeași cu parola introdusă.', + password_validation_title: 'Parolă' + + }, + sl: { + phone_title: 'Pošlji telefonsko številko', + phone_description: 'Iskanje prijateljev z uporabo tvoje telefonske številke', + phone_placeholder: 'Telefonska številka', + + confirm_description: 'Potrditvena koda', + confirm_validation_title: 'Potrditvena koda', + confirm_validation_description: 'Neveljaven format', + + password_description: 'Geslo', + password_placeholder: 'Vnesi svoje geslo', + password_placeholder2: 'Prosimo, ponovno vnesi geslo za potrditev', + password_error: 'Geslo mora vsebovati vsaj 6 simbolov.', + password_error1: 'Potrditev gesla se ne ujema z geslom.', + password_validation_title: 'Geslo' + + }, + es: { + phone_title: 'Enviar número de teléfono', + phone_description: 'Encontrar amigos que estén utilizando tu número', + phone_placeholder: 'Número de teléfono', + + confirm_description: 'Código de confirmación', + confirm_validation_title: 'Código de confirmación', + confirm_validation_description: 'Formato erróneo', + + password_description: 'Contraseña', + password_placeholder: 'Escribe tu contraseña', + password_placeholder2: 'Por favor, vuelve a escribir la contraseña para confirmar', + password_error: 'La contraseña no debe ser inferior a 6 símbolos.', + password_error1: 'La confirmación de contraseña no coincide con la contraseña.', + password_validation_title: 'Contraseña' + + }, + 'es-ar': { + phone_title: 'Envia un número telefónico', + phone_description: 'Encuentra amigos utilizando tu número', + phone_placeholder: 'Número telefónico', + + confirm_description: 'Código de confirmación', + confirm_validation_title: 'Código de confirmación', + confirm_validation_description: 'Formato incorrecto', + + password_description: 'Contraseña', + password_placeholder: 'Ingresa tu contraseña', + password_placeholder2: 'Ingresa tu contraseña para confirmar', + password_error: 'Las contraseñas deben contener no menos de 6 símbolos.', + password_error1: 'La confirmación de la contraseña no coincide con la contraseña.', + password_validation_title: 'Contraseña' + + }, + sw: { + phone_title: 'Tuma Namba ya Simu', + phone_description: 'Pata marafiki kwa kutumia namba yako', + phone_placeholder: 'Namba ya simu', + + confirm_description: 'Kificho cha uthibitisho', + confirm_validation_title: 'Kificho cha uthibitisho', + confirm_validation_description: 'Muundo hafifu', + + password_description: 'Nenosiri', + password_placeholder: 'Andika nenosiri lako', + password_placeholder2: 'Tafadhali ingiza tena nenosiri kuthibitisha', + password_error: 'Nenosiri lisiwe chini ya alama 6.', + password_error1: 'Uthibitisho wa nenosiri haulingani na nenosiri.', + password_validation_title: 'Nenosiri' + + }, + sv: { + phone_title: 'Skicka telefonnummer', + phone_description: 'Hitta vänner som använder ditt nummer', + phone_placeholder: 'Telefonnummer', + + confirm_description: 'Bekräftelsekod', + confirm_validation_title: 'Bekräftelsekod', + confirm_validation_description: 'Fel format', + + password_description: 'Lösenord', + password_placeholder: 'Skriv ditt lösenord', + password_placeholder2: 'Var god ange ditt lösenord igen för att bekräfta', + password_error: 'Lösenordet bör inte vara mindre än 6 symboler.', + password_error1: 'Lösenordsbekräftelsen matcharinte lösenordet.', + password_validation_title: 'Lösenord' + + }, + 'fr-ch': { + phone_title: 'Envoyer numéro de téléphone', + phone_description: 'Trouvez des amis en utilisant votre numéro', + phone_placeholder: 'Numéro de téléphone', + + confirm_description: 'Code de confirmation', + confirm_validation_title: 'Code de confirmation', + confirm_validation_description: 'Mauvais format', + + password_description: 'Mot de passe', + password_placeholder: 'Tapez votre mot de passe', + password_placeholder2: 'Veuillez saisir à nouveau le mot de passe pour confirmer', + password_error: 'Le mot de passe doit avoir au moins 6 caractères.', + password_error1: 'La confirmation du mot de passe ne correspond pas au premier mot de passe.', + password_validation_title: 'Mot de passe' + + }, + 'de-ch': { + phone_title: 'Sende Telefonnummer', + phone_description: 'Finde Freunde mittels deiner Telefonnummer', + phone_placeholder: 'Telefonnummer', + + confirm_description: 'Konfirmationscode', + confirm_validation_title: 'Konfirmationscode', + confirm_validation_description: 'Falsches Format', + + password_description: 'Passwort', + password_placeholder: 'Gib dein Passwort ein', + password_placeholder2: 'Bitte gib das Passwort zur Bestätigung erneut ein', + password_error: 'Passwort sollte nicht kleiner als 6 Symbole sein.', + password_error1: 'Passwort Bestätigung stimmt mit Passwort nicht überein.', + password_validation_title: 'Passwort' + + }, + 'it-ch': { + phone_title: 'Invia numero di telefono', + phone_description: 'Trova amici che utilizzano il tuo numero', + phone_placeholder: 'Numero di telefono', + + confirm_description: 'Codice di conferma', + confirm_validation_title: 'Codice di conferma', + confirm_validation_description: 'Formato errato', + + password_description: 'Password', + password_placeholder: 'Digita la tua password', + password_placeholder2: 'Inserisci nuovamente la password per confermare', + password_error: 'La password non può contenere meno di 6 caratteri.', + password_error1: 'La password di conferma non corrisponde alla password.', + password_validation_title: 'Password' + + }, + th: { + phone_title: 'ส่งหมายเลขโทรศัพท์', + phone_description: 'ค้นหาเพื่อนโดยใช้หมายเลขของคุณ ', + phone_placeholder: 'หมายเลขโทรศัพท์', + + confirm_description: 'รหัสยืนยัน', + confirm_validation_title: 'รหัสยืนยัน', + confirm_validation_description: 'รูปแบบผิด', + + password_description: 'รหัสผ่าน', + password_placeholder: 'พิมพ์รหัสผ่านของคุณ', + password_placeholder2: 'โปรดกรอกรหัสผ่านอีกครั้งเพื่อยืนยัน', + password_error: 'รหัสผ่านควรมีสัญลักษณ์ไม่น้อยกว่า 6 ตัว', + password_error1: 'การยืนยันรหัสผ่านไม่ตรงกับรหัสผ่าน', + password_validation_title: 'รหัสผ่าน' + + }, + tr: { + phone_title: 'Telefon Numarasını Gönder', + phone_description: 'Telefon numaranı kullanarak arkadaşlarınızı bulun', + phone_placeholder: 'Telefon numarası', + + confirm_description: 'Onay kodu', + confirm_validation_title: 'Onay kodu', + confirm_validation_description: 'Hatalı format', + + password_description: 'Şifre', + password_placeholder: 'Şifrenizi girin', + password_placeholder2: 'Onaylamak için lütfen parolanızı yeniden girin', + password_error: 'Şifre 6 simgeden daha kısa olmamalıdır.', + password_error1: 'Şifre onayı, şifre ile eşleşmiyor.', + password_validation_title: 'Şifre' + + }, + uk: { + phone_title: 'Надіслати номер телефону', + phone_description: 'Знайдіть друзів, використовуючи свій номер', + phone_placeholder: 'Номер телефону', + + confirm_description: 'Код підтвердження', + confirm_validation_title: 'Код підтвердження', + confirm_validation_description: 'Неправильний формат', + + password_description: 'Пароль', + password_placeholder: 'Введіть свій пароль', + password_placeholder2: 'Будь ласка, введіть пароль ще раз для підтвердження', + password_error: 'Пароль повинен бути не менше 6 символів.', + password_error1: 'Підтвердження паролю не співпадає з паролем.', + password_validation_title: 'Пароль' + + }, + ur: { + phone_title: 'فون نمبر بھیجیں', + phone_description: 'فون نمبر استعمال کرتے ہوئے دوستوں کو تلاش کریں', + phone_placeholder: 'فون نمبر', + + confirm_description: 'تصدیقی کوڈ', + confirm_validation_title: 'تصدیقی کوڈ', + confirm_validation_description: 'غلط فارمیٹ', + + password_description: 'پاسورڈ', + password_placeholder: 'اپنا پاسورڈ لکھیں', + password_placeholder2: 'برائے مہربانی تصدیق کے لیے اپنا پاسورڈ دوبارہ لکھیں', + password_error: 'پاسورڈ 6 اعداد سے چھوٹا نہیں ہونا چاہیے۔', + password_error1: 'تصدیقی پاسورڈ پاسورڈ سے مماثل نہیں', + password_validation_title: 'پاسورڈ' + + }, + vi: { + phone_title: 'Gửi số điện thoại', + phone_description: 'Tìm bạn bè bằng các sử dụng số điện thoại của bạn', + phone_placeholder: 'Số điện thoại', + + confirm_description: 'Mã xác nhận', + confirm_validation_title: 'Mã xác nhận', + confirm_validation_description: 'Sai định dạng', + + password_description: 'Mật khẩu', + password_placeholder: 'Gõ mật khẩu của bạn', + password_placeholder2: 'Vui lòng nhập lại mật khẩu để xác nhận', + password_error: 'Mật khẩu không được ít hơn 6 ký tự.', + password_error1: 'Xác nhận mật khẩu không khớp với mật khẩu.', + password_validation_title: 'Mật khẩu' + + } +}; diff --git a/bots/console/web3_metadata.js b/bots/console/web3_metadata.js new file mode 100644 index 0000000000..13c24644d6 --- /dev/null +++ b/bots/console/web3_metadata.js @@ -0,0 +1,587 @@ +var WEB3_UNIT = [ + 'kwei/ada', + 'mwei/babbage', + 'gwei/shannon', + 'szabo', + 'finney', + 'ether', + 'kether/grand/einstein', + 'mether', + 'gether', + 'tether' +]; + +// because web3 doesn't provide params or docs +var DOC_MAP = { + console: { + log : { + desc: 'Outputs a message to chat context.', + args: [{ + name: 'text', + type: 'String', + desc: 'message to output to chat context' + }] + } + }, + web3: { + // setProvider : ['provider'], // TODO + version: { + api: { + desc: 'The ethereum js api version.' + }, + node: { + desc: 'The client/node version.' + }, + network: { + desc: 'The network protocol version.' + }, + ethereum: { + desc: 'The ethereum protocol version.' + }, + whisper: { + desc: 'The whisper protocol version.' + }, + }, + isConnected: { + desc: 'Check if a connection to a node exists.', + args: [] + }, + currentProvider: { + desc: 'Will contain the current provider, if one is set. This can be used to check if mist etc. set already a provider.' + }, + reset: { + desc: 'Should be called to reset state of web3. Resets everything except manager. Uninstalls all filters. Stops polling.', + args: [{ + name: 'keepIsSyncing', + type: 'Boolean', + desc: 'If true it will uninstall all filters, but will keep the web3.eth.isSyncing() polls' + }] + }, + sha3: { + desc: 'Returns the Keccak-256 SHA3 of the given data.', + args: [{ + name: 'string', + type: 'String', + desc: 'The string to hash using the Keccak-256 SHA3 algorithm' + }, { + name: 'options', + type: 'Object', + optional: true, + desc: 'Set encoding to hex if the string to hash is encoded in hex. A leading 0x will be automatically ignored.' + }] + }, + toHex: { + desc: 'Converts any value into HEX', + args: [{ + name: 'mixed', + type: 'String|Number|Object|Array|BigNumber', + desc: 'The value to parse to HEX. If its an object or array it will be JSON.stringify first. If its a BigNumber it will make it the HEX value of a number.' + }] + }, + toAscii: { + desc: 'Converts a HEX string into a ASCII string.', + args: [{ + name: 'hexString', + type: 'String', + desc: 'A HEX string to be converted to ascii.' + }] + }, + fromAscii: { + desc: 'Converts any ASCII string to a HEX string.', + args: [{ + name: 'string', + type: 'String', + desc: 'An ASCII string to be converted to HEX.' + }, { + name: 'padding', + type: 'Number', + desc: 'The number of bytes the returned HEX string should have. ' + }] + }, + toDecimal: { + desc: 'Converts a HEX string to its number representation.', + args: [{ + name: 'hexString', + type: 'String', + desc: 'An HEX string to be converted to a number.' + }] + }, + fromDecimal: { + desc: 'Converts a number or number string to its HEX representation.', + args: [{ + name: 'number', + type: 'Number', + desc: 'A number to be converted to a HEX string.' + }] + }, + fromWei: { + desc: 'Converts a number of wei into an ethereum unit', + args: [{ + name: 'number', + type: 'Number|String|BigNumber', + desc: 'A number or BigNumber instance.' + }, { + name: 'unit', + type: 'string', + desc: 'One of the ether units' + }] + }, + toWei: { + desc: 'Converts an ethereum unit into wei', + args: [{ + name: 'number', + type: 'Number|String|BigNumber', + desc: 'A number or BigNumber instance.' + }, { + name: 'unit', + type: 'string', + desc: 'One of the ether units' + }] + }, + toBigNumber: { + desc: 'Converts a given number into a BigNumber instance', + args: [{ + name: 'numberOrHexString', + type: 'Number|String', + desc: 'A number, number string or HEX string of a number.' + }] + }, + net: { + listening: { + desc: 'Is node actively listening for network connections?' + }, + peerCount: { + desc: 'Returns the number of connected peers' + } + }, + isAddress: { + desc: '', + args: [{ + name: '', + type: 'string', + desc: 'hex string' + }], // TODO not in docs + }, + eth: { + defaultAccount: { + desc: 'The currently set default address' + }, + defaultBlock: { + desc: 'The default block number to use when querying a state.' + }, + syncing: { + desc: 'Returns the either a sync object, when the node is syncing or false.' + }, + isSyncing: { + desc: 'This convenience function calls the callback everytime a sync starts, updates and stops.', + args: [{ + name: 'callback', + type: 'Function', + desc: 'The callback will be fired with true when the syncing starts and with false when it stopped. While syncing it will return the syncing object: {startingBlock, currentBlock, highestBlock}' + }] + }, + coinbase: { + desc: 'Returns the coinbase address were the mining rewards go to.' + }, + mining: { + desc: 'Says whether the node is mining or not.' + }, + hashrate: { + desc: 'Returns the number of hashes per second that the node is mining with.' + }, + gasPrice: { + desc: 'Returns the current gas price. The gas price is determined by the x latest blocks median gas price' + }, + accounts: { + desc: 'Returns a list of accounts the node controls' + }, + blockNumber: { + desc: 'Returns the current block number' + }, + getBalance: { + desc: 'Get the balance of an address at a given block.', + args: [{ + name: 'addressHexString', + type: 'String', + desc: 'The address to get the balance of' + }, { + name: 'defaultBlock', + type: 'Number|String', + optional: true, + desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getStorageAt: { + desc: 'Get the storage at a specific position of an address.', + args: [{ + name: 'addressHexString', + type: 'String', + desc: 'The address to get the storage from.' + }, { + name: 'position', + type: 'Number', + desc: 'The index position of the storage.' + }, { + name: 'defaultBlock', + type: 'Number|String', + optional: true, + desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getCode: { + desc: 'Get the code at a specific address.', + args: [{ + name: 'addressHexString', + type: 'String', + desc: 'The address to get the code from.' + }, { + name: 'defaultBlock', + type: 'Number|String', + optional: true, + desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getBlock: { + desc: 'Returns a block matching the block number or block hash.', + args: [{ + name: 'blockHashOrBlockNumber', + type: 'String|Number', + desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' + }, { + name: 'returnTransactionObjects', + type: 'Boolean', + optional: true, + desc: '(default false) If true, the returned block will contain all transactions as objects, if false it will only contains the transaction hashes.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getBlockTransactionCount: { + desc: 'Returns the number of transaction in a given block.', + args: [{ + name: 'hashStringOrBlockNumber', + type: 'String|Number', + desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getUncle: { + desc: 'Returns a blocks uncle by a given uncle index position', + args: [{ + name: 'blockHashStringOrNumber', + type: 'String|Number', + desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' + }, { + name: 'uncleNumber', + type: 'Number', + desc: 'The index position of the uncle.' + }, { + name: 'returnTransactionObjects', + type: 'Boolean', + desc: '(default false) If true, the returned block will contain all transactions as objects, if false it will only contains the transaction hashes.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getBlockUncleCount: { + desc: '', // TODO missing from docs + }, + getTransaction: { + desc: 'Returns a transaction matching the given transaction hash.', + args: [{ + name: 'transactionHash', + type: 'String', + desc: 'The transaction hash.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getTransactionFromBlock: { + desc: 'Returns a transaction based on a block hash or number and the transactions index position.', + args: [{ + name: 'hashStringOrBlockNumber', + type: 'String|Number', + desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' + }, { + name: 'indexNumber', + type: 'Number', + desc: 'The transactions index position.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getTransactionReceipt: { + desc: 'Returns the receipt of a transaction by transaction hash.', + args: [{ + name: 'hashString', + type: 'String', + desc: 'The transaction hash.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + getTransactionCount: { + desc: 'Get the numbers of transactions sent from this address.', + args: [{ + name: 'addressHexString', + type: 'String', + desc: 'The address to get the numbers of transactions from.' + }, { + name: 'defaultBlock', + type: 'String|Number', + desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + sendTransaction: { + desc: 'Sends a transaction to the network.', + args: [{ + name: 'transactionObject', + type: 'Object', + desc: 'The transaction object to send: {from[, to][, value][, gas][, gasPrice][, data][, nonce]}' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + sendRawTransaction: { + desc: 'Sends an already signed transaction.', + args: [{ + name: 'signedTransactionData', + type: 'String', + desc: 'Signed transaction data in HEX format' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + sign: { + desc: 'Signs data from a specific account. This account needs to be unlocked.', + args: [{ + name: 'address', + type: 'String', + desc: 'Address to sign with.' + }, { + name: 'dataToSign', + type: 'String', + desc: 'Data to sign.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + call: { + desc: 'Executes a message call transaction, which is directly executed in the VM of the node, but never mined into the blockchain.', + args: [{ + name: 'callObject', + type: 'String', + desc: 'Address to sign with.' + }, { + name: 'defaultBlock', + type: 'String', + optional: true, + desc: 'Data to sign.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + estimateGas: { + desc: 'Executes a message call or transaction, which is directly executed in the VM of the node, but never mined into the blockchain and returns the amount of the gas used.', + args: [{ + name: 'callObject', + type: 'Object', + desc: 'The transaction object to send: {[from][, to][, value][, gas][, gasPrice][, data][, nonce]}' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + filter: { + // TODO: add description + desc: '', + args: [{ + name: 'options', + type: 'String|Object', + desc: 'The string "latest" or "pending" to watch for changes in the latest block or pending transactions respectively. Or a filter options object as follows: {fromBlock: Number|String, toBlock: Number|String, address: String, topics: StringArray}' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'Watch callback' + }] + }, + // TODO filters + // watch : ['callback'], + // stopWatching : ['callback'], + contract: { + desc: 'Creates a contract object for a solidity contract, which can be used to initiate contracts on an address.', + args: [{ + name: 'abiArray', + type: 'Array', + desc: 'ABI array with descriptions of functions and events of the contract.' + }] + }, + getCompilers: { + desc: 'Gets a list of available compilers.', + args: [{ + name: 'callback', + type: 'Function', + optional: true, + desc: 'If you pass a callback the HTTP request is made asynchronous.' + }] + }, + compile: { // TODO we should auto hide these depending on output from getCompilers + lll: { + desc: 'Compiles LLL source code.', + args: [{ + name: 'sourceString', + type: 'String', + desc: 'The LLL source code.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'Watch callback' + }] + }, + solidity: { + desc: 'Compiles solidity source code', + args: [{ + name: 'sourceString', + type: 'String', + desc: 'The solidity source code.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'Watch callback' + }], + }, + serpent: { + desc: 'Compiles serpent source code', + args: [{ + name: 'sourceString', + type: 'String', + desc: 'The serpent source code.' + }, { + name: 'callback', + type: 'Function', + optional: true, + desc: 'Watch callback' + }] + } + }, + namereg: { + desc: 'Returns GlobalRegistrar object.' + } + }, + + db: { + putString: { + desc: 'Store a string in the local leveldb database.', + args: [{ + name: 'db', + type: 'String', + desc: 'The database to store to.' + }, { + name: 'key', + type: 'String', + desc: 'The name of the store.' + }, { + name: 'value', + type: 'String', + desc: 'The string value to store.' + }] + }, + getString: { + desc: 'Retrieve a string from the local leveldb database. (db, key)', + args: [{ + name: 'db', + type: 'String', + desc: 'The database string name to retrieve from.' + }, { + name: 'key', + type: 'String', + desc: 'The name of the store.' + }] + }, + putHex: { + desc: 'Store binary data in the local leveldb database. (db, key, value)', + args: [{ + name: 'db', + type: 'String', + desc: 'The database to store to.' + }, { + name: 'key', + type: 'String', + desc: 'The name of the store.' + }, { + name: 'value', + type: 'String', + desc: 'The HEX string to store.' + }] + }, + getHex: { + desc: 'Retrieve binary data from the local leveldb database. (db, key)', + args: [{ + name: 'db', + type: 'String', + desc: 'The database string name to retrieve from.' + }, { + name: 'key', + type: 'String', + desc: 'The name of the store.' + }] + } + } + } +}; diff --git a/bots/demo_bot/command.js b/bots/demo_bot/command.js new file mode 100644 index 0000000000..acc0e475cb --- /dev/null +++ b/bots/demo_bot/command.js @@ -0,0 +1,99 @@ +function round(n) { + return Math.round(n * 100) / 100; +} + +function doubledValueLabel(params) { + var value = round(params.value); + return "sliderValue = " + value + + "; (2 * sliderValue) = " + (2 * value); +} + +status.defineSubscription( + // the name of subscription and the name of the value in bot-db + // associated with this subscription + "doubledValue", + // the map of values on which subscription depends: keys are arbitrary names + // and values are db paths to another value + {value: ["sliderValue"]}, + // the function which will be called as reaction on changes of values above, + // should be pure. Returned result will be associated with subscription in bot-db + doubledValueLabel +); + +status.defineSubscription( + "roundedValue", + {value: ["sliderValue"]}, + function (params) { + return round(params.value); + } +) + +function superSuggestion(params, context) { + var balance = parseFloat(web3.fromWei(web3.eth.getBalance(context.from), "ether")); + var defaultSliderValue = balance / 2; + + var view = ["view", {}, + ["text", {}, "Balance " + balance + " ETH"], + ["text", {}, ["subscribe", ["doubledValue"]]], + ["slider", { + maximumValue: ["subscribe", ["balance"]], + value: defaultSliderValue, + minimumValue: 0, + onSlidingComplete: ["dispatch", ["set", "sliderValue"]], + step: 0.05 + }], + ['touchable', + {onPress: ['dispatch', ["set-value-from-db", "roundedValue"]]}, + ["view", {}, ["text", {}, "Set value"]] + ], + ["text", {style: {color: "red"}}, ["subscribe", ["validationText"]]] + ]; + + status.setDefaultDb({ + sliderValue: defaultSliderValue, + doubledValue: doubledValueLabel({value: defaultSliderValue}) + }); + + var validationText = ""; + + if (isNaN(params.message)) { + validationText = "That's not a float number!"; + } else if (parseFloat(params.message) > balance) { + validationText = + "Input value is too big!" + + " You have only " + balance + " ETH on your balance!"; + } + + status.updateDb({ + balance: balance, + validationText: validationText + }); + + status.setSuggestions(view); +}; + +status.on("text-change", superSuggestion); +status.on("message", function (params, context) { + if (isNaN(params.message)) { + status.sendMessage("Seems that you don't want to send money :("); + return; + } + + var balance = web3.eth.getBalance(context.from); + var value = parseFloat(params.message); + var weiValue = web3.toWei(value, "ether"); + if (bn(weiValue).greaterThan(bn(balance))) { + status.sendMessage("No way man, you don't have enough money! :)"); + return; + } + try { + web3.eth.sendTransaction({ + from: context.from, + to: context.from, + value: weiValue + }); + status.sendMessage("You are the hero, you sent " + value + " ETH to yourself!"); + } catch (err) { + status.sendMessage("Something went wrong :(") + } +}); diff --git a/bots/demo_bot/index.html b/bots/demo_bot/index.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bots/mailman/bot.js b/bots/mailman/bot.js new file mode 100644 index 0000000000..a1ef5625a6 --- /dev/null +++ b/bots/mailman/bot.js @@ -0,0 +1,49 @@ +status.command({ + name: "location", + icon: "location", + title: I18n.t('location_title'), + description: I18n.t('location_description'), + color: "#a187d5", + sequentialParams: true, + preview: function (params) { + var text = status.components.text( + { + style: { + marginTop: 5, + marginHorizontal: 0, + fontSize: 14, + fontFamily: "font", + color: "black" + } + }, params.address); + var uri = "https://maps.googleapis.com/maps/api/staticmap?center=" + + params.address + + "&size=100x100&maptype=roadmap&key=AIzaSyBNsj1qoQEYPb3IllmWMAscuXW0eeuYqAA&language=en" + + "&markers=size:mid%7Ccolor:0xff0000%7Clabel:%7C" + + params.address; + + var image = status.components.image( + { + source: {uri: uri}, + style: { + width: 100, + height: 100 + } + } + ); + + return {markup: status.components.view({}, [text, image])}; + }, + shortPreview: function (params) { + return { + markup: status.components.text( + {}, + I18n.t('location_title') + ": " + params.address + ) + }; + } +}).param({ + name: "address", + type: status.types.TEXT, + placeholder: I18n.t('location_address') +}); diff --git a/bots/mailman/translations.js b/bots/mailman/translations.js new file mode 100644 index 0000000000..3ebb67c6f0 --- /dev/null +++ b/bots/mailman/translations.js @@ -0,0 +1,167 @@ +I18n.translations = { + en: { + location_title: 'Location', + location_description: 'Share your location', + location_address: 'Address' + }, + ru: { + location_title: 'Местоположение', + location_description: 'Поделитесь своим местоположением', + location_address: 'Адрес' + }, + af: { + location_title: 'Ligging', + location_description: 'Deel jou ligging', + location_address: 'Addres' + }, + ar: { + location_title: 'الموقع', + location_description: 'شارك موقعك', + location_address: 'العنوان' + }, + 'zh-hant': { + location_title: '位置', + location_description: '分享您的位置', + location_address: '地址' + }, + 'zh-hans': { + location_title: '位置', + location_description: '分享你的位置', + location_address: '地址' + }, + 'zh-yue': { + location_title: '所在位置', + location_description: '分享所在位置', + location_address: '地址' + }, + 'zh-wuu': { + location_title: '位置', + location_description: '分享您的位置', + location_address: '地址' + }, + nl: { + location_title: 'Locatie', + location_description: 'Deel je locatie', + location_address: 'Adres' + }, + fr: { + location_title: 'Emplacement', + location_description: 'Partager votre emplacement', + location_address: 'Adresse' + }, + de: { + location_title: 'Ort', + location_description: 'Teilen Sie Ihren Ort', + location_address: 'Adresse' + }, + hi: { + location_title: 'स्थान', + location_description: 'अपना स्थान साझा करें', + location_address: 'पता' + }, + hu: { + location_title: 'Helyszín', + location_description: 'Helyszín megosztása', + location_address: 'Cím' + }, + it: { + location_title: 'Posizione', + location_description: 'Condividi la tua posizione', + location_address: 'Indirizzo' + }, + ja: { + location_title: '位置', + location_description: '位置情報を共有', + location_address: 'アドレス' + }, + ko: { + location_title: '위치', + location_description: '내 위치 공유하기', + location_address: '주소' + }, + pl: { + location_title: 'Lokalizacja', + location_description: 'Udostępnij swoją lokalizację', + location_address: 'Adres' + }, + 'pt-br': { + location_title: 'Localização', + location_description: 'Compartilhar sua localização', + location_address: 'Endereço' + }, + 'pt-pt': { + location_title: 'Location', + location_description: 'Partilhar a sua localização', + location_address: 'Endereço' + }, + ro: { + location_title: 'Locație', + location_description: "Partajează locația", + location_address: 'Adresă' + }, + sl: { + location_title: 'Lokacija', + location_description: 'Deli svojo lokacijo', + location_address: 'Naslov' + }, + es: { + location_title: 'Ubicación', + location_description: 'Comparte tu ubicación', + location_address: 'Dirección' + }, + 'es-ar': { + location_title: 'Ubicación', + location_description: 'Comparte tu ubicación', + location_address: 'Dirección' + }, + sw: { + location_title: 'Eneo', + location_description: 'Shiriki eneo lako', + location_address: 'Anwani' + }, + sv: { + location_title: 'Plats', + location_description: 'Dela din plats', + location_address: 'Adress' + }, + 'fr-ch': { + location_title: 'Emplacement', + location_description: 'Partagez votre emplacement', + location_address: 'Adresse' + }, + 'de-ch': { + location_title: 'Standort', + location_description: 'Teile deinen Standort', + location_address: 'Adresse' + }, + 'it-ch': { + location_title: 'Posizione', + location_description: 'Condividi la tua posizione', + location_address: 'Indirizzo' + }, + th: { + location_title: 'ตำแหน่ง', + location_description: 'แชร์ตำแหน่งของคุณ', + location_address: 'ที่อยู่' + }, + tr: { + location_title: 'Konum', + location_description: 'Konumunuzu paylaşın', + location_address: 'Adres' + }, + uk: { + location_title: 'Місцезнаходження', + location_description: 'Поділіться своїм місцезнаходженням', + location_address: 'Адреса' + }, + ur: { + location_title: 'مقام', + location_description: 'اپنا مقام بتائیں', + location_address: 'پتہ' + }, + vi: { + location_title: 'Vị trí', + location_description: 'Chia sẻ vị trí của bạn', + location_address: 'Địa chỉ' + } +}; diff --git a/bots/wallet/bot.js b/bots/wallet/bot.js new file mode 100644 index 0000000000..1e0b11b5ea --- /dev/null +++ b/bots/wallet/bot.js @@ -0,0 +1,217 @@ +function validateSend(params, context) { + if (!context.to) { + return { + markup: status.components.validationMessage( + "Wrong address", + "Recipient address must be specified" + ) + }; + } + 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') + ) + }; + } + + 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 estimatedGas = web3.eth.estimateGas({ + from: context.from, + to: context.to, + value: val + }); + + if (bn(val).plus(bn(estimatedGas)).greaterThan(bn(balance))) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_insufficient_amount') + + web3.fromWei(balance, "ether") + + " ETH)" + ) + }; + } +} + +function sendTransaction(params, context) { + var data = { + from: context.from, + to: context.to, + value: web3.toWei(params.amount.replace(",", "."), "ether") + }; + + try { + return web3.eth.sendTransaction(data); + } catch (err) { + return {error: err.message}; + } +} + +var send = { + name: "send", + icon: "money_white", + color: "#5fc48d", + title: I18n.t('send_title'), + description: I18n.t('send_description'), + sequentialParams: true, + params: [{ + name: "amount", + type: status.types.NUMBER + }], + preview: function (params, context) { + var amountStyle = { + fontSize: 36, + color: "#000000", + height: 40 + }; + + var amount = status.components.view( + { + flexDirection: "column", + alignItems: "flex-end", + }, + [status.components.text( + { + style: amountStyle, + 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 + } + }, + "ETH" + )] + ); + + return { + markup: status.components.view( + { + style: { + flexDirection: "row", + justifyContent: "space-between", + marginTop: 8, + marginBottom: 8 + } + }, + [amount, currency] + ) + }; + }, + shortPreview: function (params, context) { + return { + markup: status.components.text( + {}, + I18n.t('send_title') + ": " + + status.localizeNumber(params.amount, context.delimiter, context.separator) + + " ETH" + ) + }; + }, + handler: sendTransaction, + validator: validateSend +}; + +status.command(send); +status.response(send); + +status.command({ + name: "request", + color: "#5fc48d", + title: I18n.t('request_title'), + description: I18n.t('request_description'), + sequentialParams: true, + params: [{ + name: "amount", + type: status.types.NUMBER + }], + handler: function (params) { + return { + event: "request", + params: [params.amount], + request: { + command: "send", + params: { + amount: params.amount + } + } + }; + }, + preview: function (params, context) { + return { + markup: status.components.text( + {}, + I18n.t('request_requesting') + " " + + status.localizeNumber(params.amount, context.delimiter, context.separator) + + " ETH" + ) + }; + }, + 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) { + try { + var val = web3.toWei(params.amount.replace(",", "."), "ether"); + if (val <= 0) { + throw new Error(); + } + } catch (err) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_invalid_number') + ) + }; + } + } +}); diff --git a/resources/commands.js b/bots/wallet/translations.js similarity index 52% rename from resources/commands.js rename to bots/wallet/translations.js index a34d8706a1..1e0c28f858 100644 --- a/resources/commands.js +++ b/bots/wallet/translations.js @@ -1,12 +1,5 @@ I18n.translations = { en: { - location_title: 'Location', - location_description: 'Send location', - location_address: 'Address', - - browse_title: 'Browser', - browse_description: 'Open web browser', - send_title: 'Send ETH', send_description: 'Send a payment', @@ -21,13 +14,6 @@ I18n.translations = { validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance ' }, ru: { - location_title: 'Местоположение', - location_description: 'Поделитесь своим местоположением', - location_address: 'Адрес', - - browse_title: 'Браузер', - browse_description: 'Запуск браузера', - send_title: 'Отправить ETH', send_description: 'Отправить платеж', @@ -42,13 +28,6 @@ I18n.translations = { validation_insufficient_amount: 'Недостаточно ETH на балансе (' }, af: { - location_title: 'Ligging', - location_description: 'Deel jou ligging', - location_address: 'Addres', - - browse_title: 'Webblaaier', - browse_description: 'Begin die webblaaier', - send_title: 'Stuur ETH', send_description: 'Stuur \'n betaling', @@ -62,13 +41,6 @@ I18n.translations = { validation_insufficient_amount: 'Nie genoeg ETH in rekening nie (' }, ar: { - location_title: 'الموقع', - location_description: 'شارك موقعك', - location_address: 'العنوان', - - browse_title: 'المتصفح', - browse_description: 'تشغيل المتصفح', - send_title: 'إرسال ETH', send_description: 'إرسال مدفوعات', @@ -82,13 +54,6 @@ I18n.translations = { validation_insufficient_amount: 'لا يوجد ETH كافي بالحساب (' }, 'zh-hant': { - location_title: '位置', - location_description: '分享您的位置', - location_address: '地址', - - browse_title: '流覽器', - browse_description: '啟動流覽器', - send_title: '發送 ETH', send_description: '發送一筆付款', @@ -102,13 +67,6 @@ I18n.translations = { validation_insufficient_amount: '餘額中 ETH 不足 (' }, 'zh-hans': { - location_title: '位置', - location_description: '分享你的位置', - location_address: '地址', - - browse_title: '浏览器', - browse_description: '启动浏览器', - send_title: '发送ETH', send_description: '付款', @@ -122,13 +80,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH余额不足 (' }, 'zh-yue': { - location_title: '所在位置', - location_description: '分享所在位置', - location_address: '地址', - - browse_title: '瀏覽器', - browse_description: '啟動瀏覽器', - send_title: '發送ETH', send_description: '發送付款', @@ -142,13 +93,6 @@ I18n.translations = { validation_insufficient_amount: '沒有足夠ETH餘額 (' }, 'zh-wuu': { - location_title: '位置', - location_description: '分享您的位置', - location_address: '地址', - - browse_title: '浏览器', - browse_description: '启动浏览器', - send_title: '发送ETH', send_description: '发送付款', @@ -162,13 +106,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH余额不足 (' }, nl: { - location_title: 'Locatie', - location_description: 'Deel je locatie', - location_address: 'Adres', - - browse_title: 'Browser', - browse_description: 'Start de browser', - send_title: 'Stuur ETH', send_description: 'Stuur een betaling', @@ -182,13 +119,6 @@ I18n.translations = { validation_insufficient_amount: 'Niet genoeg ETH op saldo (' }, fr: { - location_title: 'Emplacement', - location_description: 'Partager votre emplacement', - location_address: 'Adresse', - - browse_title: 'Navigateur', - browse_description: 'Lancer le navigateur', - send_title: 'Envoyer l\'ETH', send_description: 'Envoyer un paiement', @@ -202,13 +132,6 @@ I18n.translations = { validation_insufficient_amount: 'Pas assez d\'ETH sur le solde (' }, de: { - location_title: 'Ort', - location_description: 'Teilen Sie Ihren Ort', - location_address: 'Adresse', - - browse_title: 'Browser', - browse_description: 'Browser starten', - send_title: 'ETH abschicken', send_description: 'Zahlung senden', @@ -222,13 +145,6 @@ I18n.translations = { validation_insufficient_amount: 'Nicht genügend ETH auf dem Konto (' }, hi: { - location_title: 'स्थान', - location_description: 'अपना स्थान साझा करें', - location_address: 'पता', - - browse_title: 'ब्राउज़र', - browse_description: 'ब्राउज़र लॉन्च करें', - send_title: 'ETH भेजें', send_description: 'भुगतान भेजें', @@ -242,13 +158,6 @@ I18n.translations = { validation_insufficient_amount: 'बैलेंस पर पर्याप्त ETH नहीं है (' }, hu: { - location_title: 'Helyszín', - location_description: 'Helyszín megosztása', - location_address: 'Cím', - - browse_title: 'Böngésző', - browse_description: 'Böngésző indítása', - send_title: 'ETH küldése', send_description: 'Kifizetés küldése', @@ -262,13 +171,6 @@ I18n.translations = { validation_insufficient_amount: 'Nincs elég ETH a számlán (' }, it: { - location_title: 'Posizione', - location_description: 'Condividi la tua posizione', - location_address: 'Indirizzo', - - browse_title: 'Browser', - browse_description: 'Lancia il browser', - send_title: 'Invia ETH', send_description: 'Invia un pagamento', @@ -282,13 +184,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH insufficiente sul bilancio (' }, ja: { - location_title: '位置', - location_description: '位置情報を共有', - location_address: 'アドレス', - - browse_title: 'ブラウザ', - browse_description: 'ブラウザを起動', - send_title: 'ETHを送信', send_description: '支払いを送信', @@ -302,13 +197,6 @@ I18n.translations = { validation_insufficient_amount: '残高に十分なETHがありません(' }, ko: { - location_title: '위치', - location_description: '내 위치 공유하기', - location_address: '주소', - - browse_title: '브라우저', - browse_description: '브라우저 시작하기', - send_title: 'ETH 보내기', send_description: '지불금 보내기', @@ -322,13 +210,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH 잔고가 부족합니다 (' }, pl: { - location_title: 'Lokalizacja', - location_description: 'Udostępnij swoją lokalizację', - location_address: 'Adres', - - browse_title: 'Przeglądarka', - browse_description: 'Uruchom przeglądarkę', - send_title: 'Wyślij ETH', send_description: 'Wyślij płatność', @@ -342,13 +223,6 @@ I18n.translations = { validation_insufficient_amount: 'Brak wystarczającej liczby ETH na koncie (' }, 'pt-br': { - location_title: 'Localização', - location_description: 'Compartilhar sua localização', - location_address: 'Endereço', - - browse_title: 'Navegador', - browse_description: 'Abrir o navegador', - send_title: 'Enviar ETH', send_description: 'Enviar um pagamento', @@ -362,13 +236,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH insuficiente no saldo (' }, 'pt-pt': { - location_title: 'Location', - location_description: 'Partilhar a sua localização', - location_address: 'Endereço', - - browse_title: 'Navegador', - browse_description: 'Abrir o navegador', - send_title: 'Enviar ETH', send_description: 'Enviar um pagamento', @@ -382,13 +249,6 @@ I18n.translations = { validation_insufficient_amount: 'Não há ETH suficiente no saldo (' }, ro: { - location_title: 'Locație', - location_description: "Partajează locația", - location_address: 'Adresă', - - browse_title: 'Browser', - browse_description: 'Lansare browser', - send_title: 'Trimite ETH', send_description: 'Trimite o plată', @@ -402,13 +262,6 @@ I18n.translations = { validation_insufficient_amount: 'Sold ETH insuficient (' }, sl: { - location_title: 'Lokacija', - location_description: 'Deli svojo lokacijo', - location_address: 'Naslov', - - browse_title: 'Brskalnik', - browse_description: 'Zaženi brskalnik', - send_title: 'Pošlji ETH', send_description: 'Pošlji plačilo', @@ -422,13 +275,6 @@ I18n.translations = { validation_insufficient_amount: 'Stanje ETH na računu je prenizko (' }, es: { - location_title: 'Ubicación', - location_description: 'Comparte tu ubicación', - location_address: 'Dirección', - - browse_title: 'Navegador', - browse_description: 'Iniciar el navegador', - send_title: 'Enviar ETH ', send_description: 'Enviar un pago', @@ -442,13 +288,6 @@ I18n.translations = { validation_insufficient_amount: 'No hay suficiente ETH en conjunto (' }, 'es-ar': { - location_title: 'Ubicación', - location_description: 'Comparte tu ubicación', - location_address: 'Dirección', - - browse_title: 'Navegador', - browse_description: 'Iniciar navegador', - send_title: 'Enviar ETH', send_description: 'Enviar un pago', @@ -462,13 +301,6 @@ I18n.translations = { validation_insufficient_amount: 'No tienes suficiente ETH en tu saldo (' }, sw: { - location_title: 'Eneo', - location_description: 'Shiriki eneo lako', - location_address: 'Anwani', - - browse_title: 'Programu ya utafutaji', - browse_description: 'Zindua programu ya utafutaji', - send_title: 'Tuma ETH', send_description: 'Tuma malipo', @@ -482,13 +314,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH haitoshi kwenye salio (' }, sv: { - location_title: 'Plats', - location_description: 'Dela din plats', - location_address: 'Adress', - - browse_title: 'Webbläsare', - browse_description: 'Starta webbläsaren', - send_title: 'Skicka ETH', send_description: 'Skicka en betalning', @@ -502,13 +327,6 @@ I18n.translations = { validation_insufficient_amount: 'Inte tillräcklig ETH på balansen (' }, 'fr-ch': { - location_title: 'Emplacement', - location_description: 'Partagez votre emplacement', - location_address: 'Adresse', - - browse_title: 'Navigateur', - browse_description: 'Lancer le navigateur', - send_title: 'Envoyer des ETH', send_description: 'Envoyer un paiement', @@ -522,13 +340,6 @@ I18n.translations = { validation_insufficient_amount: 'Pas assez d\'ETH sur le solde (' }, 'de-ch': { - location_title: 'Standort', - location_description: 'Teile deinen Standort', - location_address: 'Adresse', - - browse_title: 'Browser', - browse_description: 'Starte den Browser', - send_title: 'Sende ETH', send_description: 'Sende eine Zahlung', @@ -542,13 +353,6 @@ I18n.translations = { validation_insufficient_amount: 'Nicht genug ETH vorhanden (' }, 'it-ch': { - location_title: 'Posizione', - location_description: 'Condividi la tua posizione', - location_address: 'Indirizzo', - - browse_title: 'Browser', - browse_description: 'Avvia il browser', - send_title: 'Invia ETH', send_description: 'Invia un pagamento', @@ -562,13 +366,6 @@ I18n.translations = { validation_insufficient_amount: 'Saldo ETH non sufficiente (' }, th: { - location_title: 'ตำแหน่ง', - location_description: 'แชร์ตำแหน่งของคุณ', - location_address: 'ที่อยู่', - - browse_title: 'เบราว์เซอร์', - browse_description: 'เปิดเบราว์เซอร์', - send_title: 'ส่ง ETH', send_description: 'ส่งการชำระเงิน', @@ -582,13 +379,6 @@ I18n.translations = { validation_insufficient_amount: 'มี ETH ไม่เพียงพอในยอดคงเหลือ (' }, tr: { - location_title: 'Konum', - location_description: 'Konumunuzu paylaşın', - location_address: 'Adres', - - browse_title: 'Tarayıcı', - browse_description: 'Tarayıcıyı başlat', - send_title: 'ETH gönder', send_description: 'Bir ödeme gönder', @@ -601,14 +391,7 @@ I18n.translations = { validation_invalid_number: 'Miktar geçerli bir sayı değil', validation_insufficient_amount: 'Yeterli ETH bakiyesi yok (' }, - uk: { - location_title: 'Місцезнаходження', - location_description: 'Поділіться своїм місцезнаходженням', - location_address: 'Адреса', - - browse_title: 'Браузер', - browse_description: 'Запустити браузер', - + uk: { send_title: 'Надіслати ETH', send_description: 'Надіслати платіж', @@ -622,13 +405,6 @@ I18n.translations = { validation_insufficient_amount: 'Не вистачає ETH на балансі (' }, ur: { - location_title: 'مقام', - location_description: 'اپنا مقام بتائیں', - location_address: 'پتہ', - - browse_title: 'براؤزر', - browse_description: 'براؤزر کھولیں', - send_title: 'ETH بھیجیں', send_description: 'ادائیگی کریں', @@ -642,13 +418,6 @@ I18n.translations = { validation_insufficient_amount: 'ETH میں کافی بیلنس نہیں ہے (' }, vi: { - location_title: 'Vị trí', - location_description: 'Chia sẻ vị trí của bạn', - location_address: 'Địa chỉ', - - browse_title: 'Trình duyệt', - browse_description: 'Mở trình duyệt', - send_title: 'Gửi ETH', send_description: 'Gửi một khoản thanh toán', @@ -662,295 +431,3 @@ I18n.translations = { validation_insufficient_amount: 'Không đủ ETH trong số dư (' } }; - -status.command({ - name: "location", - icon: "location", - title: I18n.t('location_title'), - description: I18n.t('location_description'), - color: "#a187d5", - sequentialParams: true, - preview: function (params) { - var text = status.components.text( - { - style: { - marginTop: 5, - marginHorizontal: 0, - fontSize: 14, - fontFamily: "font", - color: "black" - } - }, params.address); - var uri = "https://maps.googleapis.com/maps/api/staticmap?center=" - + params.address - + "&size=100x100&maptype=roadmap&key=AIzaSyBNsj1qoQEYPb3IllmWMAscuXW0eeuYqAA&language=en" - + "&markers=size:mid%7Ccolor:0xff0000%7Clabel:%7C" - + params.address; - - var image = status.components.image( - { - source: {uri: uri}, - style: { - width: 100, - height: 100 - } - } - ); - - return {markup: status.components.view({}, [text, image])}; - }, - shortPreview: function (params) { - return { - markup: status.components.text( - {}, - I18n.t('location_title') + ": " + params.address - ) - }; - } -}).param({ - name: "address", - type: status.types.TEXT, - placeholder: I18n.t('location_address') -}); - -status.command({ - name: "browse", - fullscreen: true, - title: I18n.t('browse_title'), - description: I18n.t('browse_description'), - params: [{ - name: "url", - placeholder: "URL", - type: status.types.TEXT - }], - onSend: function (params, context) { - var url = params.url; - if (!/^[a-zA-Z-_]+:/.test(url)) { - url = 'http://' + url; - } - - return { - title: "Browser", - dynamicTitle: true, - markup: status.components.bridgedWebView(url) - }; - } -}); - -function validateSend(params, context) { - if (!context.to) { - return { - markup: status.components.validationMessage( - "Wrong address", - "Recipient address must be specified" - ) - }; - } - 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') - ) - }; - } - - 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 estimatedGas = web3.eth.estimateGas({ - from: context.from, - to: context.to, - value: val - }); - - if (bn(val).plus(bn(estimatedGas)).greaterThan(bn(balance))) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_insufficient_amount') - + web3.fromWei(balance, "ether") - + " ETH)" - ) - }; - } -} - -function sendTransaction(params, context) { - var data = { - from: context.from, - to: context.to, - value: web3.toWei(params.amount.replace(",", "."), "ether") - }; - - try { - return web3.eth.sendTransaction(data); - } catch (err) { - return {error: err.message}; - } -} - -var send = { - name: "send", - icon: "money_white", - color: "#5fc48d", - title: I18n.t('send_title'), - description: I18n.t('send_description'), - sequentialParams: true, - params: [{ - name: "amount", - type: status.types.NUMBER - }], - preview: function (params, context) { - var amountStyle = { - fontSize: 36, - color: "#000000", - height: 40 - }; - - var amount = status.components.view( - { - flexDirection: "column", - alignItems: "flex-end", - }, - [status.components.text( - { - style: amountStyle, - 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 - } - }, - "ETH" - )] - ); - - return { - markup: status.components.view( - { - style: { - flexDirection: "row", - justifyContent: "space-between", - marginTop: 8, - marginBottom: 8 - } - }, - [amount, currency] - ) - }; - }, - shortPreview: function (params, context) { - return { - markup: status.components.text( - {}, - I18n.t('send_title') + ": " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ) - }; - }, - handler: sendTransaction, - validator: validateSend -}; - -status.command(send); -status.response(send); - -status.command({ - name: "request", - color: "#5fc48d", - title: I18n.t('request_title'), - description: I18n.t('request_description'), - sequentialParams: true, - params: [{ - name: "amount", - type: status.types.NUMBER - }], - handler: function (params) { - return { - event: "request", - params: [params.amount], - request: { - command: "send", - params: { - amount: params.amount - } - } - }; - }, - preview: function (params, context) { - return { - markup: status.components.text( - {}, - I18n.t('request_requesting') + " " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ) - }; - }, - 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) { - try { - var val = web3.toWei(params.amount.replace(",", "."), "ether"); - if (val <= 0) { - throw new Error(); - } - } catch (err) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - } -}); diff --git a/env/dev/user.clj b/env/dev/user.clj index 0e6e6c3c40..c52065d4cc 100644 --- a/env/dev/user.clj +++ b/env/dev/user.clj @@ -42,7 +42,7 @@ [] (ra/stop-figwheel!)) -(hawk/watch! [{:paths ["resources"] +(hawk/watch! [{:paths ["resources" "bots"] :handler (fn [ctx e] (let [path "src/status_im/utils/js_resources.cljs" js-resourced (slurp path)] diff --git a/resources/console.js b/resources/console.js deleted file mode 100644 index e8f4f75424..0000000000 --- a/resources/console.js +++ /dev/null @@ -1,1937 +0,0 @@ -I18n.translations = { - en: { - phone_title: 'Send Phone Number', - phone_description: 'Find friends using your number', - phone_placeholder: 'Phone number', - - confirm_description: 'Confirmation code', - confirm_validation_title: 'Confirmation code', - confirm_validation_description: 'Wrong format', - - password_description: 'Password', - password_placeholder: 'Type your password', - password_placeholder2: 'Confirm', - password_error: 'Password should be not less then 6 symbols.', - password_error1: 'Password confirmation doesn\'t match password.', - password_validation_title: 'Password', - - faucet_incorrect_title: 'Incorrect faucet', - faucet_incorrect_description: 'Please, select a one from the list', - - debug_mode_title: 'Debug mode', - debug_mode_description: 'Starts/stops a debug mode', - - faucet_title: 'Faucet', - faucet_description: 'Get some ETH', - faucet_placeholder: 'Faucet URL' - }, - ru: { - phone_title: 'Отправить номер телефона', - phone_description: 'Найти друзей, используя ваш номер', - phone_placeholder: 'Номер телефона', - - confirm_description: 'Код подтверждения', - confirm_validation_title: 'Код подтверждения', - confirm_validation_description: 'Неверный формат', - - password_description: 'Пароль', - password_placeholder: 'Введите свой пароль', - password_placeholder2: 'Повторно введите пароль для подтверждения', - password_error: 'Пароль должен содержать не менее 6 символов', - password_error1: 'Подтверждение пароля не совпадает с паролем', - password_validation_title: 'Пароль' - - }, - af: { - phone_title: 'Stuur telefoonnommer', - phone_description: 'Vind vriende deur jou nommer te gebruik', - phone_placeholder: 'Telefoonnommer', - - confirm_description: 'Bevestigingskode', - confirm_validation_title: 'Bevestigingskode', - confirm_validation_description: 'Verkeerde formaat', - - password_description: 'Wagwoord', - password_placeholder: 'Tik jou wagwoord in', - password_placeholder2: 'Tik asseblief weer jou wagwoord in om te bevestig', - password_error: 'Wagwoord mag nie minder as 6 simbole wees nie.', - password_error1: 'Wagwoordbevestiging is nie dieselfde as wagwoord nie.', - password_validation_title: 'Wagwoord' - - }, - ar: { - phone_title: 'أرسل رقم الهاتف', - phone_description: 'ابحث عن الأصدقاء باستخدام رقمك', - phone_placeholder: 'رقم الهاتف', - - confirm_description: 'رمز التأكيد', - confirm_validation_title: 'رمز التأكيد', - confirm_validation_description: 'صيغة خاطئة', - - password_description: 'كلمة المرور', - password_placeholder: 'اكتب كلمة المرور الخاصة بك', - password_placeholder2: 'الرجاء إعادة إدخال كلمة المرور للتأكيد', - password_error: 'ينبغي أن لا تقل كلمة المرور عن 6 رموز.', - password_error1: 'لا يتوافق تأكيد كلمة المرور مع كلمة المرور.', - password_validation_title: 'كلمة المرور' - - }, - 'zh-hant': { - phone_title: '發送手機號碼', - phone_description: '使用您的號碼發現好友', - phone_placeholder: '手機號碼', - - confirm_description: '確認碼', - confirm_validation_title: '確認碼', - confirm_validation_description: '格式錯誤', - - password_description: '密碼', - password_placeholder: '鍵入您的密碼', - password_placeholder2: '重新鍵入您的密碼', - password_error: '密碼不得短於6個字元。', - password_error1: '確認密碼與鍵入的密碼不一致。', - password_validation_title: '密碼' - - }, - 'zh-hans': { - phone_title: '发送电话号码', - phone_description: '用你的号码来查找朋友', - phone_placeholder: '电话号码', - - confirm_description: '确认码', - confirm_validation_title: '确认码', - confirm_validation_description: '格式错误', - - password_description: '密码', - password_placeholder: '输入密码', - password_placeholder2: '请重新输入密码以确认', - password_error: '密码应不少于6个字符。', - password_error1: '密码确认信息与密码不匹配。', - password_validation_title: '密码' - - }, - 'zh-yue': { - phone_title: '發送電話號碼', - phone_description: '使用本電話號碼查找好友', - phone_placeholder: '電話號碼', - - confirm_description: '驗證碼', - confirm_validation_title: '驗證碼', - confirm_validation_description: '格式錯誤', - - password_description: '密碼', - password_placeholder: '輸入密碼', - password_placeholder2: '請重新輸入密碼確認', - password_error: '密碼不能短於6個字符.', - password_error1: '確認密碼與輸入密碼不符.', - password_validation_title: '密碼' - - }, - 'zh-wuu': { - phone_title: '发送电话号码', - phone_description: '用您的号码查找朋友', - phone_placeholder: '电话号码', - - confirm_description: '确认码', - confirm_validation_title: '确认码', - confirm_validation_description: '错误格式', - - password_description: '密码', - password_placeholder: '输入密码', - password_placeholder2: '请重新输入密码确认', - password_error: '密码应不小于6个字符。', - password_error1: '密码确认不匹配。', - password_validation_title: '密码' - - }, - nl: { - phone_title: 'Stuur telefoonnummer', - phone_description: 'Zoek vrienden met behulp van je nummer', - phone_placeholder: 'Telefoonnummer', - - confirm_description: 'Bevestigingscode', - confirm_validation_title: 'Bevestigingscode', - confirm_validation_description: 'Verkeerd format', - - password_description: 'Wachtwoord', - password_placeholder: 'Typ je wachtwoord', - password_placeholder2: 'Voer je wachtwoord opnieuw in om te bevestigen', - password_error: 'Wachtwoord moet minstens 6 tekens hebben.', - password_error1: 'Wachtwoordbevestiging komt niet overeen met wachtwoord.', - password_validation_title: 'Wachtwoord' - - }, - fr: { - phone_title: 'Envoyer le numéro de téléphone', - phone_description: 'Trouver des amis en utilisant votre numéro', - phone_placeholder: 'Numéro de téléphone', - - confirm_description: 'Code de confirmation', - confirm_validation_title: 'Code de confirmation', - confirm_validation_description: 'Format incorrect', - - password_description: 'Mot de passe', - password_placeholder: 'Tapez votre mot de passe', - password_placeholder2: 'Veuillez retapez votre mot de passe pour le confirmer', - password_error: 'Le mot de passe doit contenir 6 symboles au minimum.', - password_error1: 'Le mot de passe de confirmation ne correspond pas au mot de passe.', - password_validation_title: 'Mot de passe' - - }, - de: { - phone_title: 'Telefonnummer absenden', - phone_description: 'Freunde mit Ihrer Nummer finden', - phone_placeholder: 'Telefonnummer', - - confirm_description: 'Bestätigungscode', - confirm_validation_title: 'Bestätigungscode', - confirm_validation_description: 'Falsches Format', - - password_description: 'Passwort', - password_placeholder: 'Geben Sie Ihr Passwort ein', - password_placeholder2: 'Bitte geben Sie das Passwort zur Bestätigung erneut ein', - password_error: 'Das Passwort sollte nicht weniger als 6 Stellen beinhalten', - password_error1: 'Die Passwortbestätigung stimmt nicht mit dem Passwort überein', - password_validation_title: 'Passwort', - - }, - hi: { - phone_title: 'फ़ोन नंबर भेजें', - phone_description: 'अपने नंबर का उपयोग करके दोस्त ढूंढें', - phone_placeholder: 'फ़ोन नंबर', - - confirm_description: 'पुष्टि कोड', - confirm_validation_title: 'पुष्टि कोड', - confirm_validation_description: 'गलत प्रारूप', - - password_description: 'पासवर्ड', - password_placeholder: 'अपना पासवर्ड टाइप करें', - password_placeholder2: 'पुष्टि करने के लिए फिर से पासवर्ड दर्ज करें', - password_error: 'पासवर्ड 6 प्रतीकों से कम का नहीं होना चाहिए।', - password_error1: 'पासवर्ड पुष्टि पासवर्ड मेल नहीं खाता है।', - password_validation_title: 'पासवर्ड' - - }, - hu: { - phone_title: 'Telefonszám küldése', - phone_description: 'Ismerősök megkeresése telefonszám alapján', - phone_placeholder: 'Telefonszám', - - confirm_description: 'Megerősítési kód', - confirm_validation_title: 'Megerősítési kód', - confirm_validation_description: 'Rossz formátum', - - password_description: 'Jelszó', - password_placeholder: 'Add meg a jelszavad', - password_placeholder2: 'A megerősítéshez kérjük, add meg újra a jelszavad', - password_error: 'A jelszó nem lehet hosszabb 6 szimbólumnál.', - password_error1: 'A megerősített jelszó nem egyezik a jelszóval.', - password_validation_title: 'Jelszó' - - }, - it: { - phone_title: 'Invia numero di telefono', - phone_description: 'Trova gli amici che usano il tuo numero', - phone_placeholder: 'Numero di telefono', - - confirm_description: 'Codice di conferma', - confirm_validation_title: 'Codice di conferma', - confirm_validation_description: 'Formato errato', - - password_description: 'Password', - password_placeholder: 'Digita la tua password', - password_placeholder2: 'Reinserisci la password per confermare', - password_error: 'La password deve contenere almeno 6 caratteri.', - password_error1: 'Conferma password\ la password non corrisponde.', - password_validation_title: 'Password' - - }, - ja: { - phone_title: '電話番号を送信', - phone_description: 'あなたの番号を使用している友人を検索', - phone_placeholder: '携帯電話番号', - - confirm_description: '確認コード', - confirm_validation_title: '確認コード', - confirm_validation_description: '間違った形式', - - password_description: 'パスワード', - password_placeholder: 'パスワードを入力してください', - password_placeholder2: '確認のためにパスワードを再入力してください', - password_error: 'パスワードは6文字以下でなければなりません.', - password_error1: 'パスワードの確認がパスワードと一致しません。', - password_validation_title: 'パスワード' - - }, - ko: { - phone_title: '전화번호 보내기', - phone_description: '내 번호를 사용하여 친구 찾기', - phone_placeholder: '전화번호', - - confirm_description: '확인 코드', - confirm_validation_title: '확인 코드', - confirm_validation_description: '잘못된 형식', - - password_description: '비밀번호', - password_placeholder: '비밀번호를 입력하세요', - password_placeholder2: '확인을 위해 비밀번호를 다시 입력해 주세요', - password_error: '비밀번호는 6자 이상이어야 합니다.', - password_error1: '확인용 비밀번호가 원래 비밀번호와 일치하지 않습니다.', - password_validation_title: '비밀번호' - - }, - pl: { - phone_title: 'Wyślij numer telefonu', - phone_description: 'Znajdź znajomych, używając swojego numeru', - phone_placeholder: 'Numer telefonu', - - confirm_description: 'Kod potwierdzający', - confirm_validation_title: 'Kod potwierdzający', - confirm_validation_description: 'Nieprawidłowy format', - - password_description: 'Hasło', - password_placeholder: 'Wpisz swoje hasło', - password_placeholder2: 'Wprowadź ponownie hasło, aby potwierdzić', - password_error: 'Hasło powinno zawierać co najmniej 6 symboli.', - password_error1: 'Wprowadzone i potwierdzone hasła nie są takie same.', - password_validation_title: 'Hasło' - - }, - 'pt-br': { - phone_title: 'Enviar número de telefone', - phone_description: 'Encontrar amigos por meio do seu número', - phone_placeholder: 'Número de telefone', - - confirm_description: 'Código de confirmação', - confirm_validation_title: 'Código de confirmação', - confirm_validation_description: 'Formato incorreto', - - password_description: 'Senha', - password_placeholder: 'Digite sua senha', - password_placeholder2: 'Por favor, digite a senha novamente para confirmar', - password_error: 'A senha deve ter no mínimo 6 símbolos.', - password_error1: 'A confirmação da senha é diferente da senha.', - password_validation_title: 'Senha' - - }, - 'pt-pt': { - phone_title: 'Enviar o Número de Telefone', - phone_description: 'Encontrar amigos que utilizem o seu número', - phone_placeholder: 'Número de telefone', - - confirm_description: 'Código de confirmação', - confirm_validation_title: 'Código de confirmação', - confirm_validation_description: 'Formato errado', - - password_description: 'Palavra-passe', - password_placeholder: 'Digite a sua palavra-passe', - password_placeholder2: 'Por favor, volte a digitar a palavra-passe para confirmar', - password_error: 'A palavra-passe não deve ter menos de 6 símbolos.', - password_error1: 'A confirmação da palavra-passe não coincide com a palavra-passe.', - password_validation_title: 'Palavra-passe' - - }, - ro: { - phone_title: 'Trimite numărul de telefon', - phone_description: 'Găsește prieteni folosindu-ți numărul de telefon', - phone_placeholder: 'Număr de telefon', - - confirm_description: 'Cod de confirmare', - confirm_validation_title: 'Cod de confirmare', - confirm_validation_description: 'Format greșit', - - password_description: 'Parolă', - password_placeholder: 'Tastează parola', - password_placeholder2: 'Te rugăm să re-introduci parola pentru a confirma', - password_error: 'Parola trebuie să aibă cel puțin 6 simboluri.', - password_error1: 'Parola confirmată nu este aceeași cu parola introdusă.', - password_validation_title: 'Parolă' - - }, - sl: { - phone_title: 'Pošlji telefonsko številko', - phone_description: 'Iskanje prijateljev z uporabo tvoje telefonske številke', - phone_placeholder: 'Telefonska številka', - - confirm_description: 'Potrditvena koda', - confirm_validation_title: 'Potrditvena koda', - confirm_validation_description: 'Neveljaven format', - - password_description: 'Geslo', - password_placeholder: 'Vnesi svoje geslo', - password_placeholder2: 'Prosimo, ponovno vnesi geslo za potrditev', - password_error: 'Geslo mora vsebovati vsaj 6 simbolov.', - password_error1: 'Potrditev gesla se ne ujema z geslom.', - password_validation_title: 'Geslo' - - }, - es: { - phone_title: 'Enviar número de teléfono', - phone_description: 'Encontrar amigos que estén utilizando tu número', - phone_placeholder: 'Número de teléfono', - - confirm_description: 'Código de confirmación', - confirm_validation_title: 'Código de confirmación', - confirm_validation_description: 'Formato erróneo', - - password_description: 'Contraseña', - password_placeholder: 'Escribe tu contraseña', - password_placeholder2: 'Por favor, vuelve a escribir la contraseña para confirmar', - password_error: 'La contraseña no debe ser inferior a 6 símbolos.', - password_error1: 'La confirmación de contraseña no coincide con la contraseña.', - password_validation_title: 'Contraseña' - - }, - 'es-ar': { - phone_title: 'Envia un número telefónico', - phone_description: 'Encuentra amigos utilizando tu número', - phone_placeholder: 'Número telefónico', - - confirm_description: 'Código de confirmación', - confirm_validation_title: 'Código de confirmación', - confirm_validation_description: 'Formato incorrecto', - - password_description: 'Contraseña', - password_placeholder: 'Ingresa tu contraseña', - password_placeholder2: 'Ingresa tu contraseña para confirmar', - password_error: 'Las contraseñas deben contener no menos de 6 símbolos.', - password_error1: 'La confirmación de la contraseña no coincide con la contraseña.', - password_validation_title: 'Contraseña' - - }, - sw: { - phone_title: 'Tuma Namba ya Simu', - phone_description: 'Pata marafiki kwa kutumia namba yako', - phone_placeholder: 'Namba ya simu', - - confirm_description: 'Kificho cha uthibitisho', - confirm_validation_title: 'Kificho cha uthibitisho', - confirm_validation_description: 'Muundo hafifu', - - password_description: 'Nenosiri', - password_placeholder: 'Andika nenosiri lako', - password_placeholder2: 'Tafadhali ingiza tena nenosiri kuthibitisha', - password_error: 'Nenosiri lisiwe chini ya alama 6.', - password_error1: 'Uthibitisho wa nenosiri haulingani na nenosiri.', - password_validation_title: 'Nenosiri' - - }, - sv: { - phone_title: 'Skicka telefonnummer', - phone_description: 'Hitta vänner som använder ditt nummer', - phone_placeholder: 'Telefonnummer', - - confirm_description: 'Bekräftelsekod', - confirm_validation_title: 'Bekräftelsekod', - confirm_validation_description: 'Fel format', - - password_description: 'Lösenord', - password_placeholder: 'Skriv ditt lösenord', - password_placeholder2: 'Var god ange ditt lösenord igen för att bekräfta', - password_error: 'Lösenordet bör inte vara mindre än 6 symboler.', - password_error1: 'Lösenordsbekräftelsen matcharinte lösenordet.', - password_validation_title: 'Lösenord' - - }, - 'fr-ch': { - phone_title: 'Envoyer numéro de téléphone', - phone_description: 'Trouvez des amis en utilisant votre numéro', - phone_placeholder: 'Numéro de téléphone', - - confirm_description: 'Code de confirmation', - confirm_validation_title: 'Code de confirmation', - confirm_validation_description: 'Mauvais format', - - password_description: 'Mot de passe', - password_placeholder: 'Tapez votre mot de passe', - password_placeholder2: 'Veuillez saisir à nouveau le mot de passe pour confirmer', - password_error: 'Le mot de passe doit avoir au moins 6 caractères.', - password_error1: 'La confirmation du mot de passe ne correspond pas au premier mot de passe.', - password_validation_title: 'Mot de passe' - - }, - 'de-ch': { - phone_title: 'Sende Telefonnummer', - phone_description: 'Finde Freunde mittels deiner Telefonnummer', - phone_placeholder: 'Telefonnummer', - - confirm_description: 'Konfirmationscode', - confirm_validation_title: 'Konfirmationscode', - confirm_validation_description: 'Falsches Format', - - password_description: 'Passwort', - password_placeholder: 'Gib dein Passwort ein', - password_placeholder2: 'Bitte gib das Passwort zur Bestätigung erneut ein', - password_error: 'Passwort sollte nicht kleiner als 6 Symbole sein.', - password_error1: 'Passwort Bestätigung stimmt mit Passwort nicht überein.', - password_validation_title: 'Passwort' - - }, - 'it-ch': { - phone_title: 'Invia numero di telefono', - phone_description: 'Trova amici che utilizzano il tuo numero', - phone_placeholder: 'Numero di telefono', - - confirm_description: 'Codice di conferma', - confirm_validation_title: 'Codice di conferma', - confirm_validation_description: 'Formato errato', - - password_description: 'Password', - password_placeholder: 'Digita la tua password', - password_placeholder2: 'Inserisci nuovamente la password per confermare', - password_error: 'La password non può contenere meno di 6 caratteri.', - password_error1: 'La password di conferma non corrisponde alla password.', - password_validation_title: 'Password' - - }, - th: { - phone_title: 'ส่งหมายเลขโทรศัพท์', - phone_description: 'ค้นหาเพื่อนโดยใช้หมายเลขของคุณ ', - phone_placeholder: 'หมายเลขโทรศัพท์', - - confirm_description: 'รหัสยืนยัน', - confirm_validation_title: 'รหัสยืนยัน', - confirm_validation_description: 'รูปแบบผิด', - - password_description: 'รหัสผ่าน', - password_placeholder: 'พิมพ์รหัสผ่านของคุณ', - password_placeholder2: 'โปรดกรอกรหัสผ่านอีกครั้งเพื่อยืนยัน', - password_error: 'รหัสผ่านควรมีสัญลักษณ์ไม่น้อยกว่า 6 ตัว', - password_error1: 'การยืนยันรหัสผ่านไม่ตรงกับรหัสผ่าน', - password_validation_title: 'รหัสผ่าน' - - }, - tr: { - phone_title: 'Telefon Numarasını Gönder', - phone_description: 'Telefon numaranı kullanarak arkadaşlarınızı bulun', - phone_placeholder: 'Telefon numarası', - - confirm_description: 'Onay kodu', - confirm_validation_title: 'Onay kodu', - confirm_validation_description: 'Hatalı format', - - password_description: 'Şifre', - password_placeholder: 'Şifrenizi girin', - password_placeholder2: 'Onaylamak için lütfen parolanızı yeniden girin', - password_error: 'Şifre 6 simgeden daha kısa olmamalıdır.', - password_error1: 'Şifre onayı, şifre ile eşleşmiyor.', - password_validation_title: 'Şifre' - - }, - uk: { - phone_title: 'Надіслати номер телефону', - phone_description: 'Знайдіть друзів, використовуючи свій номер', - phone_placeholder: 'Номер телефону', - - confirm_description: 'Код підтвердження', - confirm_validation_title: 'Код підтвердження', - confirm_validation_description: 'Неправильний формат', - - password_description: 'Пароль', - password_placeholder: 'Введіть свій пароль', - password_placeholder2: 'Будь ласка, введіть пароль ще раз для підтвердження', - password_error: 'Пароль повинен бути не менше 6 символів.', - password_error1: 'Підтвердження паролю не співпадає з паролем.', - password_validation_title: 'Пароль' - - }, - ur: { - phone_title: 'فون نمبر بھیجیں', - phone_description: 'فون نمبر استعمال کرتے ہوئے دوستوں کو تلاش کریں', - phone_placeholder: 'فون نمبر', - - confirm_description: 'تصدیقی کوڈ', - confirm_validation_title: 'تصدیقی کوڈ', - confirm_validation_description: 'غلط فارمیٹ', - - password_description: 'پاسورڈ', - password_placeholder: 'اپنا پاسورڈ لکھیں', - password_placeholder2: 'برائے مہربانی تصدیق کے لیے اپنا پاسورڈ دوبارہ لکھیں', - password_error: 'پاسورڈ 6 اعداد سے چھوٹا نہیں ہونا چاہیے۔', - password_error1: 'تصدیقی پاسورڈ پاسورڈ سے مماثل نہیں', - password_validation_title: 'پاسورڈ' - - }, - vi: { - phone_title: 'Gửi số điện thoại', - phone_description: 'Tìm bạn bè bằng các sử dụng số điện thoại của bạn', - phone_placeholder: 'Số điện thoại', - - confirm_description: 'Mã xác nhận', - confirm_validation_title: 'Mã xác nhận', - confirm_validation_description: 'Sai định dạng', - - password_description: 'Mật khẩu', - password_placeholder: 'Gõ mật khẩu của bạn', - password_placeholder2: 'Vui lòng nhập lại mật khẩu để xác nhận', - password_error: 'Mật khẩu không được ít hơn 6 ký tự.', - password_error1: 'Xác nhận mật khẩu không khớp với mật khẩu.', - password_validation_title: 'Mật khẩu' - - } -}; - -var WEB3_UNIT = [ - 'kwei/ada', - 'mwei/babbage', - 'gwei/shannon', - 'szabo', - 'finney', - 'ether', - 'kether/grand/einstein', - 'mether', - 'gether', - 'tether' -]; - -// because web3 doesn't provide params or docs -var DOC_MAP = { - console: { - log: { - desc: 'Outputs a message to chat context.', - args: [{ - name: 'text', - type: 'String', - desc: 'message to output to chat context' - }] - } - }, - web3: { - // setProvider : ['provider'], // TODO - version: { - api: { - desc: 'The ethereum js api version.' - }, - node: { - desc: 'The client/node version.' - }, - network: { - desc: 'The network protocol version.' - }, - ethereum: { - desc: 'The ethereum protocol version.' - }, - whisper: { - desc: 'The whisper protocol version.' - }, - }, - isConnected: { - desc: 'Check if a connection to a node exists.', - args: [] - }, - currentProvider: { - desc: 'Will contain the current provider, if one is set. This can be used to check if mist etc. set already a provider.' - }, - reset: { - desc: 'Should be called to reset state of web3. Resets everything except manager. Uninstalls all filters. Stops polling.', - args: [{ - name: 'keepIsSyncing', - type: 'Boolean', - desc: 'If true it will uninstall all filters, but will keep the web3.eth.isSyncing() polls' - }] - }, - sha3: { - desc: 'Returns the Keccak-256 SHA3 of the given data.', - args: [{ - name: 'string', - type: 'String', - desc: 'The string to hash using the Keccak-256 SHA3 algorithm' - }, { - name: 'options', - type: 'Object', - optional: true, - desc: 'Set encoding to hex if the string to hash is encoded in hex. A leading 0x will be automatically ignored.' - }] - }, - toHex: { - desc: 'Converts any value into HEX', - args: [{ - name: 'mixed', - type: 'String|Number|Object|Array|BigNumber', - desc: 'The value to parse to HEX. If its an object or array it will be JSON.stringify first. If its a BigNumber it will make it the HEX value of a number.' - }] - }, - toAscii: { - desc: 'Converts a HEX string into a ASCII string.', - args: [{ - name: 'hexString', - type: 'String', - desc: 'A HEX string to be converted to ascii.' - }] - }, - fromAscii: { - desc: 'Converts any ASCII string to a HEX string.', - args: [{ - name: 'string', - type: 'String', - desc: 'An ASCII string to be converted to HEX.' - }, { - name: 'padding', - type: 'Number', - desc: 'The number of bytes the returned HEX string should have. ' - }] - }, - toDecimal: { - desc: 'Converts a HEX string to its number representation.', - args: [{ - name: 'hexString', - type: 'String', - desc: 'An HEX string to be converted to a number.' - }] - }, - fromDecimal: { - desc: 'Converts a number or number string to its HEX representation.', - args: [{ - name: 'number', - type: 'Number', - desc: 'A number to be converted to a HEX string.' - }] - }, - fromWei: { - desc: 'Converts a number of wei into an ethereum unit', - args: [{ - name: 'number', - type: 'Number|String|BigNumber', - desc: 'A number or BigNumber instance.' - }, { - name: 'unit', - type: 'string', - desc: 'One of the ether units' - }] - }, - toWei: { - desc: 'Converts an ethereum unit into wei', - args: [{ - name: 'number', - type: 'Number|String|BigNumber', - desc: 'A number or BigNumber instance.' - }, { - name: 'unit', - type: 'string', - desc: 'One of the ether units' - }] - }, - toBigNumber: { - desc: 'Converts a given number into a BigNumber instance', - args: [{ - name: 'numberOrHexString', - type: 'Number|String', - desc: 'A number, number string or HEX string of a number.' - }] - }, - net: { - listening: { - desc: 'Is node actively listening for network connections?' - }, - peerCount: { - desc: 'Returns the number of connected peers' - } - }, - isAddress: { - desc: '', - args: [{ - name: '', - type: 'string', - desc: 'hex string' - }], // TODO not in docs - }, - eth: { - defaultAccount: { - desc: 'The currently set default address' - }, - defaultBlock: { - desc: 'The default block number to use when querying a state.' - }, - syncing: { - desc: 'Returns the either a sync object, when the node is syncing or false.' - }, - isSyncing: { - desc: 'This convenience function calls the callback everytime a sync starts, updates and stops.', - args: [{ - name: 'callback', - type: 'Function', - desc: 'The callback will be fired with true when the syncing starts and with false when it stopped. While syncing it will return the syncing object: {startingBlock, currentBlock, highestBlock}' - }] - }, - coinbase: { - desc: 'Returns the coinbase address were the mining rewards go to.' - }, - mining: { - desc: 'Says whether the node is mining or not.' - }, - hashrate: { - desc: 'Returns the number of hashes per second that the node is mining with.' - }, - gasPrice: { - desc: 'Returns the current gas price. The gas price is determined by the x latest blocks median gas price' - }, - accounts: { - desc: 'Returns a list of accounts the node controls' - }, - blockNumber: { - desc: 'Returns the current block number' - }, - getBalance: { - desc: 'Get the balance of an address at a given block.', - args: [{ - name: 'addressHexString', - type: 'String', - desc: 'The address to get the balance of' - }, { - name: 'defaultBlock', - type: 'Number|String', - optional: true, - desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getStorageAt: { - desc: 'Get the storage at a specific position of an address.', - args: [{ - name: 'addressHexString', - type: 'String', - desc: 'The address to get the storage from.' - }, { - name: 'position', - type: 'Number', - desc: 'The index position of the storage.' - }, { - name: 'defaultBlock', - type: 'Number|String', - optional: true, - desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getCode: { - desc: 'Get the code at a specific address.', - args: [{ - name: 'addressHexString', - type: 'String', - desc: 'The address to get the code from.' - }, { - name: 'defaultBlock', - type: 'Number|String', - optional: true, - desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getBlock: { - desc: 'Returns a block matching the block number or block hash.', - args: [{ - name: 'blockHashOrBlockNumber', - type: 'String|Number', - desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' - }, { - name: 'returnTransactionObjects', - type: 'Boolean', - optional: true, - desc: '(default false) If true, the returned block will contain all transactions as objects, if false it will only contains the transaction hashes.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getBlockTransactionCount: { - desc: 'Returns the number of transaction in a given block.', - args: [{ - name: 'hashStringOrBlockNumber', - type: 'String|Number', - desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getUncle: { - desc: 'Returns a blocks uncle by a given uncle index position', - args: [{ - name: 'blockHashStringOrNumber', - type: 'String|Number', - desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' - }, { - name: 'uncleNumber', - type: 'Number', - desc: 'The index position of the uncle.' - }, { - name: 'returnTransactionObjects', - type: 'Boolean', - desc: '(default false) If true, the returned block will contain all transactions as objects, if false it will only contains the transaction hashes.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getBlockUncleCount: { - desc: '', // TODO missing from docs - }, - getTransaction: { - desc: 'Returns a transaction matching the given transaction hash.', - args: [{ - name: 'transactionHash', - type: 'String', - desc: 'The transaction hash.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getTransactionFromBlock: { - desc: 'Returns a transaction based on a block hash or number and the transactions index position.', - args: [{ - name: 'hashStringOrBlockNumber', - type: 'String|Number', - desc: 'The block number or hash. Or the string "earliest", "latest" or "pending"' - }, { - name: 'indexNumber', - type: 'Number', - desc: 'The transactions index position.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getTransactionReceipt: { - desc: 'Returns the receipt of a transaction by transaction hash.', - args: [{ - name: 'hashString', - type: 'String', - desc: 'The transaction hash.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - getTransactionCount: { - desc: 'Get the numbers of transactions sent from this address.', - args: [{ - name: 'addressHexString', - type: 'String', - desc: 'The address to get the numbers of transactions from.' - }, { - name: 'defaultBlock', - type: 'String|Number', - desc: 'If you pass this parameter it will not use the default block set with web3.eth.defaultBlock.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - sendTransaction: { - desc: 'Sends a transaction to the network.', - args: [{ - name: 'transactionObject', - type: 'Object', - desc: 'The transaction object to send: {from[, to][, value][, gas][, gasPrice][, data][, nonce]}' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - sendRawTransaction: { - desc: 'Sends an already signed transaction.', - args: [{ - name: 'signedTransactionData', - type: 'String', - desc: 'Signed transaction data in HEX format' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - sign: { - desc: 'Signs data from a specific account. This account needs to be unlocked.', - args: [{ - name: 'address', - type: 'String', - desc: 'Address to sign with.' - }, { - name: 'dataToSign', - type: 'String', - desc: 'Data to sign.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - call: { - desc: 'Executes a message call transaction, which is directly executed in the VM of the node, but never mined into the blockchain.', - args: [{ - name: 'callObject', - type: 'String', - desc: 'Address to sign with.' - }, { - name: 'defaultBlock', - type: 'String', - optional: true, - desc: 'Data to sign.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - estimateGas: { - desc: 'Executes a message call or transaction, which is directly executed in the VM of the node, but never mined into the blockchain and returns the amount of the gas used.', - args: [{ - name: 'callObject', - type: 'Object', - desc: 'The transaction object to send: {[from][, to][, value][, gas][, gasPrice][, data][, nonce]}' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - filter: { - // TODO: add description - desc: '', - args: [{ - name: 'options', - type: 'String|Object', - desc: 'The string "latest" or "pending" to watch for changes in the latest block or pending transactions respectively. Or a filter options object as follows: {fromBlock: Number|String, toBlock: Number|String, address: String, topics: StringArray}' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'Watch callback' - }] - }, - // TODO filters - // watch : ['callback'], - // stopWatching : ['callback'], - contract: { - desc: 'Creates a contract object for a solidity contract, which can be used to initiate contracts on an address.', - args: [{ - name: 'abiArray', - type: 'Array', - desc: 'ABI array with descriptions of functions and events of the contract.' - }] - }, - getCompilers: { - desc: 'Gets a list of available compilers.', - args: [{ - name: 'callback', - type: 'Function', - optional: true, - desc: 'If you pass a callback the HTTP request is made asynchronous.' - }] - }, - compile: { // TODO we should auto hide these depending on output from getCompilers - lll: { - desc: 'Compiles LLL source code.', - args: [{ - name: 'sourceString', - type: 'String', - desc: 'The LLL source code.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'Watch callback' - }] - }, - solidity: { - desc: 'Compiles solidity source code', - args: [{ - name: 'sourceString', - type: 'String', - desc: 'The solidity source code.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'Watch callback' - }], - }, - serpent: { - desc: 'Compiles serpent source code', - args: [{ - name: 'sourceString', - type: 'String', - desc: 'The serpent source code.' - }, { - name: 'callback', - type: 'Function', - optional: true, - desc: 'Watch callback' - }] - } - }, - namereg: { - desc: 'Returns GlobalRegistrar object.' - } - }, - - db: { - putString: { - desc: 'Store a string in the local leveldb database.', - args: [{ - name: 'db', - type: 'String', - desc: 'The database to store to.' - }, { - name: 'key', - type: 'String', - desc: 'The name of the store.' - }, { - name: 'value', - type: 'String', - desc: 'The string value to store.' - }] - }, - getString: { - desc: 'Retrieve a string from the local leveldb database. (db, key)', - args: [{ - name: 'db', - type: 'String', - desc: 'The database string name to retrieve from.' - }, { - name: 'key', - type: 'String', - desc: 'The name of the store.' - }] - }, - putHex: { - desc: 'Store binary data in the local leveldb database. (db, key, value)', - args: [{ - name: 'db', - type: 'String', - desc: 'The database to store to.' - }, { - name: 'key', - type: 'String', - desc: 'The name of the store.' - }, { - name: 'value', - type: 'String', - desc: 'The HEX string to store.' - }] - }, - getHex: { - desc: 'Retrieve binary data from the local leveldb database. (db, key)', - args: [{ - name: 'db', - type: 'String', - desc: 'The database string name to retrieve from.' - }, { - name: 'key', - type: 'String', - desc: 'The name of the store.' - }] - } - } - } -}; - -function jsSuggestionsContainerStyle(suggestionsCount) { - return { - marginVertical: 1, - marginHorizontal: 0, - keyboardShouldPersistTaps: "always", - //height: Math.min(150, (56 * suggestionsCount)), - backgroundColor: "white", - borderRadius: 5, - keyboardShouldPersistTaps: "always" - }; -} - -var jsSuggestionContainerStyle = { - paddingLeft: 16, - backgroundColor: "white" -}; - -var jsSubContainerStyle = { - //height: 56, - paddingTop: 9, - borderBottomWidth: 1, - borderBottomColor: "#0000001f" -}; - -var jsValueStyle = { - fontSize: 14, - fontFamily: "font", - color: "#000000de" -}; - -var jsBoldValueStyle = { - fontSize: 14, - fontFamily: "font", - color: "#000000de", - fontWeight: "bold" -}; - -var jsDescriptionStyle = { - marginTop: 1.5, - paddingBottom: 9, - fontSize: 14, - fontFamily: "font", - color: "#838c93de" -}; - -var messages = []; - - -console = (function (old) { - return { - log: function (text) { - old.log(text); - var message = { - type: 'log', - message: JSON.stringify(text) - }; - messages.push(message); - context.messages.push(message); - }, - info: function (text) { - old.info(text); - context.messages.push({ - type: 'info', - message: JSON.stringify(text) - }); - }, - warn: function (text) { - old.warn(text); - context.messages.push({ - type: 'warn', - message: JSON.stringify(text) - }); - }, - error: function (text) { - old.error(text); - context.messages.push({ - type: 'error', - message: JSON.stringify(text) - }); - } - }; -}(console)); - - -if (!String.prototype.startsWith) { - String.prototype.startsWith = function (searchString, position) { - position = position || 0; - return this.substr(position, searchString.length) === searchString; - }; -} - -function matchSubString(array, string) { - var matched = []; - for (var i = 0; i < array.length; i++) { - var item = array[i]; - if (item.toLowerCase().startsWith(string.toLowerCase())) { - matched.push(item); - } - } - return matched; -} - -function cleanCode(code) { - // remove comments - var commentsRegex = /\/\*.+?\*\/|\/\/.*/g; - code = code.replace(commentsRegex, ""); - // replace string literals - var literalsRegex = /\"(?:\\\\\"|[^\"])*?\"/g; - code = code.replace(literalsRegex, '""'); - var literalsRegex = /\'(?:\\\\\'|[^\'])*?\'/g; - code = code.replace(literalsRegex, '""'); - - return code -} - -function createObjectSuggestion(name, docInfo, code, parameterNumber) { - var title = name; - if (docInfo.args) { - title += "("; - for (var i = 0; i < docInfo.args.length; i++) { - var argument = docInfo.args[i]; - var argumentText = (i > 0 ? ", " : "") + (parameterNumber === i ? "*" + argument.name + "*" : argument.name); - if (argument.optional) { - argumentText = "[" + argumentText + "]"; - } - title += argumentText; - } - title += ")"; - } - if (!docInfo.desc) { - name += "."; - } else if (docInfo.args) { - name += "("; - if (docInfo.args.length == 0) { - name += ")"; - } - } - var suggestion = { - title: title, - desc: docInfo.desc - }; - - if (code != null) { - suggestion.pressValue = code + name; - } - return suggestion; -} - -var lastMessage = null; - -function getLastForm(code) { - var codeLength = code.length; - var form = ''; - var level = 0; - var index = codeLength - 1; - while (index >= 0) { - var char = code[index]; - if (level == 0 && (char == '(' || char == ',')) { - break; - } - if (char == ')') { - level--; - } - if (char == '(') { - level++; - } - form = char + form; - index--; - } - return form; -} - -function getLastLevel(code) { - var codeLength = code.length; - var form = ''; - var index = codeLength - 1; - var nested = false; - var level = 0; - while (index >= 0) { - var char = code[index]; - if (char == ')') { - level--; - nested = true; - } - if (char == '(') { - level++; - if (level == 0) { - nested = false; - } - if (level <= 0) { - form = "argument" + form; - index--; - continue; - } - } - if ((level == 1 && char == ',') || level == 2) { - break; - } - if (!nested || level > 0) { - form = char + form; - } - index--; - } - if (form.indexOf("(") < 0) { - var parts = form.split(','); - form = parts[parts.length - 1]; - } - return form; -} - -function getPartialSuggestions(doc, fullCode, code) { - var suggestions = []; - var functionParts = code.split("("); - var objectParts = code.split(/[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/); - - var index = 0; - var suggestedFunction = ''; - while (index < objectParts.length) { - var part = objectParts[index]; - if (part != "desc" && part != "args" && doc[part] != null) { - doc = doc[part]; - suggestedFunction += part + '.'; - index++; - } else { - break; - } - } - suggestedFunction = suggestedFunction.substring(0, suggestedFunction.length - 1) - if (functionParts.length == 1) { - // object suggestions - if (index > objectParts.length - 1) { - var suggestion = objectParts[objectParts.length - 1]; - suggestions.push(createObjectSuggestion(suggestion, doc, fullCode.substring(0, fullCode.length - suggestion.length))); - } else if (index === objectParts.length - 1) { - var lastPart = objectParts[index]; - var keys = Object.keys(doc); - var matches = matchSubString(keys, lastPart); - - for (var i = 0; i < matches.length; i++) { - var suggestion = matches[i]; - if (suggestion == "desc" || suggestion == "args") { - continue; - } - var docInfo = doc[suggestion]; - if (docInfo != null) { - suggestions.push(createObjectSuggestion(suggestion, docInfo, fullCode.substring(0, fullCode.length - lastPart.length))); - } - } - } - } else if (functionParts.length == 2) { - // parameter suggestions - var parameters = functionParts[1].split(","); - if (doc.args && parameters.length <= doc.args.length && parameters[parameters.length - 1].indexOf(")") < 0) { - var paramInfo = doc.args[parameters.length - 1]; - var docInfo = doc; - docInfo.desc = paramInfo.name + ": " + paramInfo.desc; - suggestions.push(createObjectSuggestion(suggestedFunction, docInfo, null, parameters.length - 1)); - } - } - console.log(suggestions); - return suggestions; -} - -function getJsSuggestions(code, context) { - var suggestions = []; - var doc = DOC_MAP; - // TODO: what's /c / doing there ??? - console.log(code); - if (!code || code == "" || code == "c ") { - code = ""; - console.log("Last message: " + context.data); - if (context.data != null) { - suggestions.push({ - title: 'Last command used:', - desc: context.data, - pressValue: context.data - }); - } - var keys = Object.keys(doc); - for (var i = 0; i < keys.length; i++) { - var suggestion = keys[i]; - var docInfo = doc[suggestion]; - if (docInfo != null) { - suggestions.push(createObjectSuggestion(suggestion, docInfo, "")); - } - } - } else { - // TODO: what's /c / doing there ??? - if (code.startsWith("c ")) { - code = code.substring(2); - } - if (context.data != null && - (typeof context.data === 'string' || context.data instanceof String) && - context.data.startsWith(code)) { - suggestions.unshift({ - title: 'Last command used:', - desc: context.data, - pressValue: context.data - }); - } - var originalCode = code; - code = cleanCode(code); - var levelCode = getLastLevel(code); - var code = getLastForm(levelCode); - if (levelCode != code) { - suggestions = getPartialSuggestions(doc, originalCode, levelCode); - } - - console.log("Final code: " + code); - console.log("Level code: " + levelCode); - suggestions = suggestions.concat(getPartialSuggestions(doc, originalCode, code)); - } - return suggestions; -} - -function createMarkupText(text) { - var parts = []; - var index = 0; - var currentText = ''; - var isBold = false; - while (index < text.length) { - var char = text[index]; - if (char == '*') { - if (currentText != '') { - parts.push( - status.components.text( - {style: isBold ? jsBoldValueStyle : jsValueStyle}, - currentText - ) - ); - currentText = ''; - } - isBold = !isBold; - } else { - currentText += char; - } - index++; - } - if (currentText != '') { - parts.push( - status.components.text( - {style: isBold ? jsBoldValueStyle : jsValueStyle}, - currentText - ) - ); - } - console.log(parts); - return parts; -} - -function jsSuggestions(params, context) { - var suggestions = getJsSuggestions(params.code, context); - var sugestionsMarkup = []; - - for (var i = 0; i < suggestions.length; i++) { - var suggestion = suggestions[i]; - - if (suggestion.title.indexOf('*') >= 0) { - suggestion.title = createMarkupText(suggestion.title); - } - var suggestionMarkup = status.components.view(jsSuggestionContainerStyle, - [status.components.view(jsSubContainerStyle, - [ - status.components.text({style: jsValueStyle}, - suggestion.title), - status.components.text({style: jsDescriptionStyle}, - suggestion.desc) - ])]); - if (suggestion.pressValue) { - suggestionMarkup = status.components.touchable({ - onPress: [status.events.SET_VALUE, suggestion.pressValue] - }, - suggestionMarkup); - } - sugestionsMarkup.push(suggestionMarkup); - } - - if (sugestionsMarkup.length > 0) { - var view = status.components.scrollView(jsSuggestionsContainerStyle(sugestionsMarkup.length), - sugestionsMarkup - ); - return {markup: view}; - } -} - - -function jsHandler(params, context) { - var result = { - err: null, - data: null, - messages: [] - }; - messages = []; - try { - result.data = JSON.stringify(eval(params.code)); - localStorage.set(params.code); - } catch (e) { - result.err = e; - } - - result.messages = messages; - - return result; -} - -function suggestionsContainerStyle(suggestionsCount) { - return { - marginVertical: 1, - marginHorizontal: 0, - keyboardShouldPersistTaps: "always", - height: Math.min(150, (56 * suggestionsCount)), - backgroundColor: "white", - borderRadius: 5, - flexGrow: 1 - }; -} - -var suggestionContainerStyle = { - paddingLeft: 16, - backgroundColor: "white" -}; - -var suggestionSubContainerStyle = { - height: 56, - borderBottomWidth: 1, - borderBottomColor: "#0000001f" -}; - -var valueStyle = { - marginTop: 9, - fontSize: 14, - fontFamily: "font", - color: "#000000de" -}; - -var descriptionStyle = { - marginTop: 1.5, - fontSize: 14, - fontFamily: "font", - color: "#838c93de" -}; - -function startsWith(str1, str2) { - // String.startsWith(...) doesn't work in otto - return str1.lastIndexOf(str2, 0) == 0 && str1 != str2; -} - -function phoneSuggestions(params, context) { - var ph, suggestions; - if (!params.phone || params.phone == "") { - ph = context.suggestions; - } else { - ph = context.suggestions.filter(function (phone) { - return startsWith(phone.number, params.phone); - }); - } - - if (ph.length == 0) { - return; - } - - suggestions = ph.map(function (phone) { - return status.components.touchable( - {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, phone.number]]}, - status.components.view(suggestionContainerStyle, - [status.components.view(suggestionSubContainerStyle, - [ - status.components.text( - {style: valueStyle}, - phone.number - ), - status.components.text( - {style: descriptionStyle}, - phone.description - ) - ])]) - ); - }); - - var view = status.components.scrollView( - suggestionsContainerStyle(ph.length), - suggestions - ); - - return {markup: view}; -} - -var phoneConfig = { - name: "phone", - registeredOnly: true, - icon: "phone_white", - color: "#5bb2a2", - title: I18n.t('phone_title'), - description: I18n.t('phone_description'), - sequentialParams: true, - validator: function (params) { - return { - validationHandler: "phone", - parameters: [params.phone] - }; - }, - params: [{ - name: "phone", - type: status.types.PHONE, - suggestions: phoneSuggestions, - placeholder: I18n.t('phone_placeholder') - }] -}; -status.response(phoneConfig); -status.command(phoneConfig); - -var faucets = [ - /*{ - name: "Ethereum Ropsten Faucet", - url: "http://faucet.ropsten.be:3001" - },*/ - { - name: "Status Testnet Faucet", - url: "http://46.101.129.137:3001", - } -]; - -function faucetSuggestions(params) { - var suggestions = faucets.map(function (entry) { - return status.components.touchable( - {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, entry.url]]}, - status.components.view( - suggestionContainerStyle, - [status.components.view( - suggestionSubContainerStyle, - [ - status.components.text( - {style: valueStyle}, - entry.name - ), - status.components.text( - {style: descriptionStyle}, - entry.url - ) - ] - )] - ) - ); - }); - - var view = status.components.scrollView( - suggestionsContainerStyle(faucets.length), - suggestions - ); - - return {markup: view}; -} - -status.command({ - name: "faucet", - title: I18n.t('faucet_title'), - description: I18n.t('faucet_description'), - color: "#7099e6", - registeredOnly: true, - params: [{ - name: "url", - type: status.types.TEXT, - suggestions: faucetSuggestions, - placeholder: I18n.t('faucet_placeholder') - }], - preview: function (params) { - return { - markup: status.components.text( - {}, - params.url - ) - }; - }, - shortPreview: function (params) { - return { - markup: status.components.text( - {}, - I18n.t('faucet_title') + ": " + params.url - ) - }; - }, - validator: function (params, context) { - var f = faucets.map(function (entry) { - return entry.url; - }); - - if (f.indexOf(params.url) == -1) { - var error = status.components.validationMessage( - I18n.t('faucet_incorrect_title'), - I18n.t('faucet_incorrect_description') - ); - - return {markup: error}; - } - } -}); - -function debugSuggestions(params) { - var suggestions = ["On", "Off"].map(function (entry) { - return status.components.touchable( - {onPress: [status.events.SET_COMMAND_ARGUMENT, [0, entry]]}, - status.components.view( - suggestionContainerStyle, - [status.components.view( - suggestionSubContainerStyle, - [ - status.components.text( - {style: valueStyle}, - entry - ) - ] - )] - ) - ); - }); - - var view = status.components.scrollView( - suggestionsContainerStyle(faucets.length), - suggestions - ); - - return {markup: view}; -} - -status.command({ - name: "debug", - title: I18n.t('debug_mode_title'), - description: I18n.t('debug_mode_description'), - color: "#7099e6", - registeredOnly: true, - params: [{ - name: "mode", - suggestions: debugSuggestions, - type: status.types.TEXT - }], - preview: function (params) { - return { - markup: status.components.text( - {}, - I18n.t('debug_mode_title') + ": " + params.mode - ) - }; - }, - shortPreview: function (params) { - return { - markup: status.components.text( - {}, - I18n.t('debug_mode_title') + ": " + params.mode - ) - }; - } -}); - -status.command({ - name: "browse", - title: "Browser", - registeredOnly: true, - description: "Open web browser", - params: [{ - name: "url", - placeholder: "URL", - type: status.types.TEXT - }], - onSend: function (params, context) { - var url = params.url; - if (!/^[a-zA-Z-_]+:/.test(url)) { - url = 'http://' + url; - } - - return { - title: "Browser", - dynamicTitle: true, - markup: status.components.bridgedWebView(url) - }; - } -}); - - -// 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", - description: I18n.t('confirm_description'), - sequentialParams: true, - params: [{ - name: "code", - type: status.types.NUMBER - }], - validator: function (params) { - if (!/^[\d]{4}$/.test(params.code)) { - var error = status.components.validationMessage( - I18n.t('confirm_validation_title'), - I18n.t('confirm_validation_description') - ); - - return {markup: error}; - } - } -}); - -status.response({ - name: "password", - color: "#7099e6", - description: I18n.t('password_description'), - icon: "lock_white", - sequentialParams: true, - params: [ - { - name: "password", - type: status.types.PASSWORD, - placeholder: I18n.t('password_placeholder'), - hidden: true - }, - { - name: "password-confirmation", - type: status.types.PASSWORD, - placeholder: I18n.t('password_placeholder2'), - hidden: true - } - ], - validator: function (params, context) { - if (!params.hasOwnProperty("password-confirmation") || params["password-confirmation"].length === 0) { - if (params.password === null || params.password.length < 6) { - var error = status.components.validationMessage( - I18n.t('password_validation_title'), - I18n.t('password_error') - ); - return {markup: error}; - } - } else { - if (params.password !== params["password-confirmation"]) { - var error = status.components.validationMessage( - I18n.t('password_validation_title'), - I18n.t('password_error1') - ); - return {markup: error}; - } - } - - }, - preview: function (params, context) { - var style = { - marginTop: 5, - marginHorizontal: 0, - fontSize: 14, - color: "black" - }; - - if (context.platform == "ios") { - style.fontSize = 8; - style.marginTop = 10; - style.marginBottom = 2; - style.letterSpacing = 1; - } - - return {markup: status.components.text({style: style}, "●●●●●●●●●●")}; - } -}); - -status.registerFunction("message-suggestions", function (params, context) { - return jsSuggestions({code: params.message}, context); -}); - -status.registerFunction("message-handler", function (params, context) { - return jsHandler({code: params.message}, context); -}); diff --git a/resources/default_contacts.json b/resources/default_contacts.json index 7a912085c0..07edb3f802 100644 --- a/resources/default_contacts.json +++ b/resources/default_contacts.json @@ -11,7 +11,35 @@ "photo-path": "icon_wallet_avatar", "add-chat?": true, "dapp?": true, - "groups": ["dapps"] + "groups": ["dapps"], + "has-global-command?": true, + "dapp-url": + { + "en": "https://status.im/dapps/wallet/" + }, + "bot-url": "local://wallet-bot" + }, + + "mailman": + { + "name": + { + "en": "Mailman", + "ru": "Печкин" + }, + "dapp?": true, + "has-global-command?": true, + "bot-url": "local://mailman-bot" + }, + + "browse": + { + "name": + { + "en": "Browse" + }, + "dapp?": true, + "bot-url": "local://browse-bot" }, "0x0428c9d6c1aaaa8369a7c63819684f30e34396dc0907d49afeac85a0a774ccb919b3482097d992e66bcc538e7a0c6acf874c77748f396f53c0a102e10d1a37765b": diff --git a/resources/i18n.js b/resources/i18n.js new file mode 100644 index 0000000000..842710a9b6 --- /dev/null +++ b/resources/i18n.js @@ -0,0 +1,4 @@ +// i18n.js 3.0.0.rc14 +!function(a){if("undefined"!=typeof module&&module.exports)module.exports=a(this);else if("function"==typeof define&&define.amd){var b=this;define("i18n",function(){return a(b)})}else this.I18n=a(this)}(function(a){"use strict";var b=a&&a.I18n||{},c=Array.prototype.slice,d=function(a){return("0"+a.toString()).substr(-2)},e=function(a,b){return h("round",a,-b).toFixed(b)},f=function(a){var b=typeof a;return"function"===b||"object"===b&&!!a},g=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)},h=function(a,b,c){return"undefined"==typeof c||0===+c?Math[a](b):(b=+b,c=+c,isNaN(b)||"number"!=typeof c||c%1!==0?NaN:(b=b.toString().split("e"),b=Math[a](+(b[0]+"e"+(b[1]?+b[1]-c:-c))),b=b.toString().split("e"),+(b[0]+"e"+(b[1]?+b[1]+c:c))))},i=function(a,b){var c,d;for(c in b)b.hasOwnProperty(c)&&(d=b[c],"[object String]"===Object.prototype.toString.call(d)?a[c]=d:(null==a[c]&&(a[c]={}),i(a[c],d)));return a},j={day_names:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbr_day_names:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],month_names:[null,"January","February","March","April","May","June","July","August","September","October","November","December"],abbr_month_names:[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],meridian:["AM","PM"]},k={precision:3,separator:".",delimiter:",",strip_insignificant_zeros:!1},l={unit:"$",precision:2,format:"%u%n",sign_first:!0,delimiter:",",separator:"."},m={unit:"%",precision:3,format:"%n%u",separator:".",delimiter:""},n=[null,"kb","mb","gb","tb"],o={defaultLocale:"en",locale:"en",defaultSeparator:".",placeholder:/(?:\{\{|%\{)(.*?)(?:\}\}?)/gm,fallbacks:!1,translations:{},missingBehaviour:"message",missingTranslationPrefix:""};return b.reset=function(){this.defaultLocale=o.defaultLocale,this.locale=o.locale,this.defaultSeparator=o.defaultSeparator,this.placeholder=o.placeholder,this.fallbacks=o.fallbacks,this.translations=o.translations,this.missingBehaviour=o.missingBehaviour,this.missingTranslationPrefix=o.missingTranslationPrefix},b.initializeOptions=function(){"undefined"==typeof this.defaultLocale&&null!==this.defaultLocale&&(this.defaultLocale=o.defaultLocale),"undefined"==typeof this.locale&&null!==this.locale&&(this.locale=o.locale),"undefined"==typeof this.defaultSeparator&&null!==this.defaultSeparator&&(this.defaultSeparator=o.defaultSeparator),"undefined"==typeof this.placeholder&&null!==this.placeholder&&(this.placeholder=o.placeholder),"undefined"==typeof this.fallbacks&&null!==this.fallbacks&&(this.fallbacks=o.fallbacks),"undefined"==typeof this.translations&&null!==this.translations&&(this.translations=o.translations),"undefined"==typeof this.missingBehaviour&&null!==this.missingBehaviour&&(this.missingBehaviour=o.missingBehaviour),"undefined"==typeof this.missingTranslationPrefix&&null!==this.missingTranslationPrefix&&(this.missingTranslationPrefix=o.missingTranslationPrefix)},b.initializeOptions(),b.locales={},b.locales.get=function(a){var c=this[a]||this[b.locale]||this.default;return"function"==typeof c&&(c=c(a)),g(c)===!1&&(c=[c]),c},b.locales.default=function(a){var e,c=[],d=[];return a&&c.push(a),!a&&b.locale&&c.push(b.locale),b.fallbacks&&b.defaultLocale&&c.push(b.defaultLocale),c.forEach(function(a){e=a.split("-")[0],~d.indexOf(a)||d.push(a),b.fallbacks&&e&&e!==a&&!~d.indexOf(e)&&d.push(e)}),c.length||c.push("en"),d},b.pluralization={},b.pluralization.get=function(a){return this[a]||this[b.locale]||this.default},b.pluralization.default=function(a){switch(a){case 0:return["zero","other"];case 1:return["one"];default:return["other"]}},b.currentLocale=function(){return this.locale||this.defaultLocale},b.isSet=function(a){return void 0!==a&&null!==a},b.lookup=function(a,b){b=this.prepareOptions(b);var e,f,g,c=this.locales.get(b.locale).slice();c[0];for(a=this.getFullScope(a,b);c.length;)if(e=c.shift(),f=a.split(this.defaultSeparator),g=this.translations[e]){for(;f.length&&(g=g[f.shift()],void 0!==g&&null!==g););if(void 0!==g&&null!==g)return g}if(this.isSet(b.defaultValue))return b.defaultValue},b.meridian=function(){var a=this.lookup("time"),b=this.lookup("date");return a&&a.am&&a.pm?[a.am,a.pm]:b&&b.meridian?b.meridian:j.meridian},b.prepareOptions=function(){for(var d,a=c.call(arguments),b={};a.length;)if(d=a.shift(),"object"==typeof d)for(var e in d)d.hasOwnProperty(e)&&(this.isSet(b[e])||(b[e]=d[e]));return b},b.createTranslationOptions=function(a,b){var c=[{scope:a}];return this.isSet(b.defaults)&&(c=c.concat(b.defaults)),this.isSet(b.defaultValue)&&(c.push({message:b.defaultValue}),delete b.defaultValue),c},b.translate=function(a,b){b=this.prepareOptions(b);var d,c=this.createTranslationOptions(a,b),e=c.some(function(a){if(this.isSet(a.scope)?d=this.lookup(a.scope,b):this.isSet(a.message)&&(d=a.message),void 0!==d&&null!==d)return!0},this);return e?("string"==typeof d?d=this.interpolate(d,b):f(d)&&this.isSet(b.count)&&(d=this.pluralize(b.count,d,b)),d):this.missingTranslation(a,b)},b.interpolate=function(a,b){b=this.prepareOptions(b);var d,e,f,g,c=a.match(this.placeholder);if(!c)return a;for(var e;c.length;)d=c.shift(),f=d.replace(this.placeholder,"$1"),e=this.isSet(b[f])?b[f].toString().replace(/\$/gm,"_#$#_"):f in b?this.nullPlaceholder(d,a,b):this.missingPlaceholder(d,a,b),g=new RegExp(d.replace(/\{/gm,"\\{").replace(/\}/gm,"\\}")),a=a.replace(g,e);return a.replace(/_#\$#_/g,"$")},b.pluralize=function(a,b,c){c=this.prepareOptions(c);var d,e,g,h,i;if(d=f(b)?b:this.lookup(b,c),!d)return this.missingTranslation(b,c);for(e=this.pluralization.get(c.locale),g=e(a);g.length;)if(h=g.shift(),this.isSet(d[h])){i=d[h];break}return c.count=String(a),this.interpolate(i,c)},b.missingTranslation=function(a,b){if("guess"==this.missingBehaviour){var c=a.split(".").slice(-1)[0];return(this.missingTranslationPrefix.length>0?this.missingTranslationPrefix:"")+c.replace("_"," ").replace(/([a-z])([A-Z])/g,function(a,b,c){return b+" "+c.toLowerCase()})}var d=null!=b&&null!=b.locale?b.locale:this.currentLocale(),e=this.getFullScope(a,b),f=[d,e].join(this.defaultSeparator);return'[missing "'+f+'" translation]'},b.missingPlaceholder=function(a,b,c){return"[missing "+a+" value]"},b.nullPlaceholder=function(){return b.missingPlaceholder.apply(b,arguments)},b.toNumber=function(a,b){b=this.prepareOptions(b,this.lookup("number.format"),k);var g,i,c=a<0,d=e(Math.abs(a),b.precision).toString(),f=d.split("."),h=[],j=b.format||"%n",l=c?"-":"";for(a=f[0],g=f[1];a.length>0;)h.unshift(a.substr(Math.max(0,a.length-3),3)),a=a.substr(0,a.length-3);return i=h.join(b.delimiter),b.strip_insignificant_zeros&&g&&(g=g.replace(/0+$/,"")),b.precision>0&&g&&(i+=b.separator+g),j=b.sign_first?"%s"+j:j.replace("%n","%s%n"),i=j.replace("%u",b.unit).replace("%n",i).replace("%s",l)},b.toCurrency=function(a,b){return b=this.prepareOptions(b,this.lookup("number.currency.format"),this.lookup("number.format"),l),this.toNumber(a,b)},b.localize=function(a,b,c){switch(c||(c={}),a){case"currency":return this.toCurrency(b);case"number":return a=this.lookup("number.format"),this.toNumber(b,a);case"percentage":return this.toPercentage(b);default:var d;return d=a.match(/^(date|time)/)?this.toTime(a,b):b.toString(),this.interpolate(d,c)}},b.parseDate=function(a){var b,c,d;if("object"==typeof a)return a;if(b=a.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/)){for(var e=1;e<=6;e++)b[e]=parseInt(b[e],10)||0;b[2]-=1,d=b[7]?1e3*("0"+b[7]):null,c=b[8]?new Date(Date.UTC(b[1],b[2],b[3],b[4],b[5],b[6],d)):new Date(b[1],b[2],b[3],b[4],b[5],b[6],d)}else"number"==typeof a?(c=new Date,c.setTime(a)):a.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)?(c=new Date,c.setTime(Date.parse([RegExp.$1,RegExp.$2,RegExp.$3,RegExp.$6,RegExp.$4,RegExp.$5].join(" ")))):a.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)?(c=new Date,c.setTime(Date.parse(a))):(c=new Date,c.setTime(Date.parse(a)));return c},b.strftime=function(a,c){var e=this.lookup("date"),f=b.meridian();if(e||(e={}),e=this.prepareOptions(e,j),isNaN(a.getTime()))throw new Error("I18n.strftime() requires a valid date object, but received an invalid date.");var g=a.getDay(),h=a.getDate(),i=a.getFullYear(),k=a.getMonth()+1,l=a.getHours(),m=l,n=l>11?1:0,o=a.getSeconds(),p=a.getMinutes(),q=a.getTimezoneOffset(),r=Math.floor(Math.abs(q/60)),s=Math.abs(q)-60*r,t=(q>0?"-":"+")+(r.toString().length<2?"0"+r:r)+(s.toString().length<2?"0"+s:s);return m>12?m-=12:0===m&&(m=12),c=c.replace("%a",e.abbr_day_names[g]),c=c.replace("%A",e.day_names[g]),c=c.replace("%b",e.abbr_month_names[k]),c=c.replace("%B",e.month_names[k]),c=c.replace("%d",d(h)),c=c.replace("%e",h),c=c.replace("%-d",h),c=c.replace("%H",d(l)),c=c.replace("%-H",l),c=c.replace("%I",d(m)),c=c.replace("%-I",m),c=c.replace("%m",d(k)),c=c.replace("%-m",k),c=c.replace("%M",d(p)),c=c.replace("%-M",p),c=c.replace("%p",f[n]),c=c.replace("%S",d(o)),c=c.replace("%-S",o),c=c.replace("%w",g),c=c.replace("%y",d(i)),c=c.replace("%-y",d(i).replace(/^0+/,"")),c=c.replace("%Y",i),c=c.replace("%z",t)},b.toTime=function(a,b){var c=this.parseDate(b),d=this.lookup(a);return c.toString().match(/invalid/i)?c.toString():d?this.strftime(c,d):c.toString()},b.toPercentage=function(a,b){return b=this.prepareOptions(b,this.lookup("number.percentage.format"),this.lookup("number.format"),m),this.toNumber(a,b)},b.toHumanSize=function(a,b){for(var f,g,c=1024,d=a,e=0;d>=c&&e<4;)d/=c,e+=1;return 0===e?(f=this.t("number.human.storage_units.units.byte",{count:d}),g=0):(f=this.t("number.human.storage_units.units."+n[e]),g=d-Math.floor(d)===0?0:1),b=this.prepareOptions(b,{unit:f,precision:g,format:"%n%u",delimiter:""}),this.toNumber(d,b)},b.getFullScope=function(a,b){return b=this.prepareOptions(b),a.constructor===Array&&(a=a.join(this.defaultSeparator)),b.scope&&(a=[b.scope,a].join(this.defaultSeparator)),a},b.extend=function(a,b){return"undefined"==typeof a&&"undefined"==typeof b?{}:i(a,b)},b.t=b.translate,b.l=b.localize,b.p=b.pluralize,b}); +I18n.defaultLocale = "en"; +I18n.fallbacks = true; diff --git a/resources/status.js b/resources/status.js index 5cda037319..97d0d9b023 100644 --- a/resources/status.js +++ b/resources/status.js @@ -147,7 +147,7 @@ var status = { var response = new Response(); return response.create(h); }, - registerFunction: function (name, fn){ + registerFunction: function (name, fn) { _status_catalog.functions[name] = fn; }, autorun: function (commandName) { @@ -156,7 +156,12 @@ var status = { localizeNumber: function (num, del, sep) { return I18n.toNumber( num.replace(",", "."), - {precision: 10, strip_insignificant_zeros: true, delimiter: del, separator: sep}); + { + precision: 10, + strip_insignificant_zeros: true, + delimiter: del, + separator: sep + }); }, types: { TEXT: 'text', @@ -179,8 +184,3 @@ var status = { bridgedWebView: bridgedWebView } }; - -// i18n.js 3.0.0.rc14 -!function(a){if("undefined"!=typeof module&&module.exports)module.exports=a(this);else if("function"==typeof define&&define.amd){var b=this;define("i18n",function(){return a(b)})}else this.I18n=a(this)}(function(a){"use strict";var b=a&&a.I18n||{},c=Array.prototype.slice,d=function(a){return("0"+a.toString()).substr(-2)},e=function(a,b){return h("round",a,-b).toFixed(b)},f=function(a){var b=typeof a;return"function"===b||"object"===b&&!!a},g=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)},h=function(a,b,c){return"undefined"==typeof c||0===+c?Math[a](b):(b=+b,c=+c,isNaN(b)||"number"!=typeof c||c%1!==0?NaN:(b=b.toString().split("e"),b=Math[a](+(b[0]+"e"+(b[1]?+b[1]-c:-c))),b=b.toString().split("e"),+(b[0]+"e"+(b[1]?+b[1]+c:c))))},i=function(a,b){var c,d;for(c in b)b.hasOwnProperty(c)&&(d=b[c],"[object String]"===Object.prototype.toString.call(d)?a[c]=d:(null==a[c]&&(a[c]={}),i(a[c],d)));return a},j={day_names:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbr_day_names:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],month_names:[null,"January","February","March","April","May","June","July","August","September","October","November","December"],abbr_month_names:[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],meridian:["AM","PM"]},k={precision:3,separator:".",delimiter:",",strip_insignificant_zeros:!1},l={unit:"$",precision:2,format:"%u%n",sign_first:!0,delimiter:",",separator:"."},m={unit:"%",precision:3,format:"%n%u",separator:".",delimiter:""},n=[null,"kb","mb","gb","tb"],o={defaultLocale:"en",locale:"en",defaultSeparator:".",placeholder:/(?:\{\{|%\{)(.*?)(?:\}\}?)/gm,fallbacks:!1,translations:{},missingBehaviour:"message",missingTranslationPrefix:""};return b.reset=function(){this.defaultLocale=o.defaultLocale,this.locale=o.locale,this.defaultSeparator=o.defaultSeparator,this.placeholder=o.placeholder,this.fallbacks=o.fallbacks,this.translations=o.translations,this.missingBehaviour=o.missingBehaviour,this.missingTranslationPrefix=o.missingTranslationPrefix},b.initializeOptions=function(){"undefined"==typeof this.defaultLocale&&null!==this.defaultLocale&&(this.defaultLocale=o.defaultLocale),"undefined"==typeof this.locale&&null!==this.locale&&(this.locale=o.locale),"undefined"==typeof this.defaultSeparator&&null!==this.defaultSeparator&&(this.defaultSeparator=o.defaultSeparator),"undefined"==typeof this.placeholder&&null!==this.placeholder&&(this.placeholder=o.placeholder),"undefined"==typeof this.fallbacks&&null!==this.fallbacks&&(this.fallbacks=o.fallbacks),"undefined"==typeof this.translations&&null!==this.translations&&(this.translations=o.translations),"undefined"==typeof this.missingBehaviour&&null!==this.missingBehaviour&&(this.missingBehaviour=o.missingBehaviour),"undefined"==typeof this.missingTranslationPrefix&&null!==this.missingTranslationPrefix&&(this.missingTranslationPrefix=o.missingTranslationPrefix)},b.initializeOptions(),b.locales={},b.locales.get=function(a){var c=this[a]||this[b.locale]||this.default;return"function"==typeof c&&(c=c(a)),g(c)===!1&&(c=[c]),c},b.locales.default=function(a){var e,c=[],d=[];return a&&c.push(a),!a&&b.locale&&c.push(b.locale),b.fallbacks&&b.defaultLocale&&c.push(b.defaultLocale),c.forEach(function(a){e=a.split("-")[0],~d.indexOf(a)||d.push(a),b.fallbacks&&e&&e!==a&&!~d.indexOf(e)&&d.push(e)}),c.length||c.push("en"),d},b.pluralization={},b.pluralization.get=function(a){return this[a]||this[b.locale]||this.default},b.pluralization.default=function(a){switch(a){case 0:return["zero","other"];case 1:return["one"];default:return["other"]}},b.currentLocale=function(){return this.locale||this.defaultLocale},b.isSet=function(a){return void 0!==a&&null!==a},b.lookup=function(a,b){b=this.prepareOptions(b);var e,f,g,c=this.locales.get(b.locale).slice();c[0];for(a=this.getFullScope(a,b);c.length;)if(e=c.shift(),f=a.split(this.defaultSeparator),g=this.translations[e]){for(;f.length&&(g=g[f.shift()],void 0!==g&&null!==g););if(void 0!==g&&null!==g)return g}if(this.isSet(b.defaultValue))return b.defaultValue},b.meridian=function(){var a=this.lookup("time"),b=this.lookup("date");return a&&a.am&&a.pm?[a.am,a.pm]:b&&b.meridian?b.meridian:j.meridian},b.prepareOptions=function(){for(var d,a=c.call(arguments),b={};a.length;)if(d=a.shift(),"object"==typeof d)for(var e in d)d.hasOwnProperty(e)&&(this.isSet(b[e])||(b[e]=d[e]));return b},b.createTranslationOptions=function(a,b){var c=[{scope:a}];return this.isSet(b.defaults)&&(c=c.concat(b.defaults)),this.isSet(b.defaultValue)&&(c.push({message:b.defaultValue}),delete b.defaultValue),c},b.translate=function(a,b){b=this.prepareOptions(b);var d,c=this.createTranslationOptions(a,b),e=c.some(function(a){if(this.isSet(a.scope)?d=this.lookup(a.scope,b):this.isSet(a.message)&&(d=a.message),void 0!==d&&null!==d)return!0},this);return e?("string"==typeof d?d=this.interpolate(d,b):f(d)&&this.isSet(b.count)&&(d=this.pluralize(b.count,d,b)),d):this.missingTranslation(a,b)},b.interpolate=function(a,b){b=this.prepareOptions(b);var d,e,f,g,c=a.match(this.placeholder);if(!c)return a;for(var e;c.length;)d=c.shift(),f=d.replace(this.placeholder,"$1"),e=this.isSet(b[f])?b[f].toString().replace(/\$/gm,"_#$#_"):f in b?this.nullPlaceholder(d,a,b):this.missingPlaceholder(d,a,b),g=new RegExp(d.replace(/\{/gm,"\\{").replace(/\}/gm,"\\}")),a=a.replace(g,e);return a.replace(/_#\$#_/g,"$")},b.pluralize=function(a,b,c){c=this.prepareOptions(c);var d,e,g,h,i;if(d=f(b)?b:this.lookup(b,c),!d)return this.missingTranslation(b,c);for(e=this.pluralization.get(c.locale),g=e(a);g.length;)if(h=g.shift(),this.isSet(d[h])){i=d[h];break}return c.count=String(a),this.interpolate(i,c)},b.missingTranslation=function(a,b){if("guess"==this.missingBehaviour){var c=a.split(".").slice(-1)[0];return(this.missingTranslationPrefix.length>0?this.missingTranslationPrefix:"")+c.replace("_"," ").replace(/([a-z])([A-Z])/g,function(a,b,c){return b+" "+c.toLowerCase()})}var d=null!=b&&null!=b.locale?b.locale:this.currentLocale(),e=this.getFullScope(a,b),f=[d,e].join(this.defaultSeparator);return'[missing "'+f+'" translation]'},b.missingPlaceholder=function(a,b,c){return"[missing "+a+" value]"},b.nullPlaceholder=function(){return b.missingPlaceholder.apply(b,arguments)},b.toNumber=function(a,b){b=this.prepareOptions(b,this.lookup("number.format"),k);var g,i,c=a<0,d=e(Math.abs(a),b.precision).toString(),f=d.split("."),h=[],j=b.format||"%n",l=c?"-":"";for(a=f[0],g=f[1];a.length>0;)h.unshift(a.substr(Math.max(0,a.length-3),3)),a=a.substr(0,a.length-3);return i=h.join(b.delimiter),b.strip_insignificant_zeros&&g&&(g=g.replace(/0+$/,"")),b.precision>0&&g&&(i+=b.separator+g),j=b.sign_first?"%s"+j:j.replace("%n","%s%n"),i=j.replace("%u",b.unit).replace("%n",i).replace("%s",l)},b.toCurrency=function(a,b){return b=this.prepareOptions(b,this.lookup("number.currency.format"),this.lookup("number.format"),l),this.toNumber(a,b)},b.localize=function(a,b,c){switch(c||(c={}),a){case"currency":return this.toCurrency(b);case"number":return a=this.lookup("number.format"),this.toNumber(b,a);case"percentage":return this.toPercentage(b);default:var d;return d=a.match(/^(date|time)/)?this.toTime(a,b):b.toString(),this.interpolate(d,c)}},b.parseDate=function(a){var b,c,d;if("object"==typeof a)return a;if(b=a.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/)){for(var e=1;e<=6;e++)b[e]=parseInt(b[e],10)||0;b[2]-=1,d=b[7]?1e3*("0"+b[7]):null,c=b[8]?new Date(Date.UTC(b[1],b[2],b[3],b[4],b[5],b[6],d)):new Date(b[1],b[2],b[3],b[4],b[5],b[6],d)}else"number"==typeof a?(c=new Date,c.setTime(a)):a.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)?(c=new Date,c.setTime(Date.parse([RegExp.$1,RegExp.$2,RegExp.$3,RegExp.$6,RegExp.$4,RegExp.$5].join(" ")))):a.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)?(c=new Date,c.setTime(Date.parse(a))):(c=new Date,c.setTime(Date.parse(a)));return c},b.strftime=function(a,c){var e=this.lookup("date"),f=b.meridian();if(e||(e={}),e=this.prepareOptions(e,j),isNaN(a.getTime()))throw new Error("I18n.strftime() requires a valid date object, but received an invalid date.");var g=a.getDay(),h=a.getDate(),i=a.getFullYear(),k=a.getMonth()+1,l=a.getHours(),m=l,n=l>11?1:0,o=a.getSeconds(),p=a.getMinutes(),q=a.getTimezoneOffset(),r=Math.floor(Math.abs(q/60)),s=Math.abs(q)-60*r,t=(q>0?"-":"+")+(r.toString().length<2?"0"+r:r)+(s.toString().length<2?"0"+s:s);return m>12?m-=12:0===m&&(m=12),c=c.replace("%a",e.abbr_day_names[g]),c=c.replace("%A",e.day_names[g]),c=c.replace("%b",e.abbr_month_names[k]),c=c.replace("%B",e.month_names[k]),c=c.replace("%d",d(h)),c=c.replace("%e",h),c=c.replace("%-d",h),c=c.replace("%H",d(l)),c=c.replace("%-H",l),c=c.replace("%I",d(m)),c=c.replace("%-I",m),c=c.replace("%m",d(k)),c=c.replace("%-m",k),c=c.replace("%M",d(p)),c=c.replace("%-M",p),c=c.replace("%p",f[n]),c=c.replace("%S",d(o)),c=c.replace("%-S",o),c=c.replace("%w",g),c=c.replace("%y",d(i)),c=c.replace("%-y",d(i).replace(/^0+/,"")),c=c.replace("%Y",i),c=c.replace("%z",t)},b.toTime=function(a,b){var c=this.parseDate(b),d=this.lookup(a);return c.toString().match(/invalid/i)?c.toString():d?this.strftime(c,d):c.toString()},b.toPercentage=function(a,b){return b=this.prepareOptions(b,this.lookup("number.percentage.format"),this.lookup("number.format"),m),this.toNumber(a,b)},b.toHumanSize=function(a,b){for(var f,g,c=1024,d=a,e=0;d>=c&&e<4;)d/=c,e+=1;return 0===e?(f=this.t("number.human.storage_units.units.byte",{count:d}),g=0):(f=this.t("number.human.storage_units.units."+n[e]),g=d-Math.floor(d)===0?0:1),b=this.prepareOptions(b,{unit:f,precision:g,format:"%n%u",delimiter:""}),this.toNumber(d,b)},b.getFullScope=function(a,b){return b=this.prepareOptions(b),a.constructor===Array&&(a=a.join(this.defaultSeparator)),b.scope&&(a=[b.scope,a].join(this.defaultSeparator)),a},b.extend=function(a,b){return"undefined"==typeof a&&"undefined"==typeof b?{}:i(a,b)},b.t=b.translate,b.l=b.localize,b.p=b.pluralize,b}); -I18n.defaultLocale = "en"; -I18n.fallbacks = true; diff --git a/resources/wallet.js b/resources/wallet.js deleted file mode 100644 index 45f72bd4e5..0000000000 --- a/resources/wallet.js +++ /dev/null @@ -1,31 +0,0 @@ -status.command({ - name: "browse", - title: I18n.t('browse_title'), - description: I18n.t('browse_description'), - color: "#ffa500", - fullscreen: true, - onSend: function (params, context) { - var url = 'https://status.im/dapps/wallet'; - if (context.debug) { - url = 'http://127.0.0.1:3450'; - } - - return { - title: "Wallet", - dynamicTitle: false, - markup: status.components.bridgedWebView(url) - }; - } -}); - -status.autorun("browse"); - -status.registerFunction("send", function (params, context) { - var data = { - from: context.from, - to: params.address, - value: web3.toWei(params.amount, "ether") - }; - - web3.eth.sendTransaction(data); -}) diff --git a/src/status_im/accounts/screen.cljs b/src/status_im/accounts/screen.cljs index 6a8202ef75..8bd35ecd9c 100644 --- a/src/status_im/accounts/screen.cljs +++ b/src/status_im/accounts/screen.cljs @@ -33,9 +33,8 @@ [view st/account-view [account-bage address photo-path name]]]]) -(defn- create-account [_] - (dispatch-sync [:reset-app]) - (dispatch [:navigate-to :chat console-chat-id])) +(defn create-account [_] + (dispatch [:reset-app #(dispatch [:navigate-to :chat console-chat-id])])) (defview accounts [] [accounts [:get :accounts]] @@ -58,4 +57,4 @@ [action-button (i18n/label :t/recover-access) :dots_horizontal_white #(dispatch [:navigate-to :recover]) - st/accounts-action-button]]]) \ No newline at end of file + st/accounts-action-button]]]) diff --git a/src/status_im/chat/constants.cljs b/src/status_im/chat/constants.cljs index 50f91d4072..a810f96803 100644 --- a/src/status_im/chat/constants.cljs +++ b/src/status_im/chat/constants.cljs @@ -4,6 +4,7 @@ (def spacing-char " ") (def arg-wrapping-char "\"") (def masking-char "*") +(def bot-char "@") (def input-height 56) (def max-input-height 66) @@ -12,4 +13,4 @@ (def crazy-math-message-id "crazy-math-message") (def passphrase-message-id "passphraze-message") (def intro-status-message-id "intro-status") -(def intro-message1-id "intro-message1") \ No newline at end of file +(def intro-message1-id "intro-message1") diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index a0aed5a99f..8829ec5a14 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -176,6 +176,9 @@ (doseq [{:keys [content] :as message} messages] (when (and (:command content) (not (:content content))) + ;; todo rewrite it so that commands defined outside chat's context + ;; (bots' commands in group chats and global commands in all chats) + ;; could be rendered properly (dispatch [:request-command-data (assoc message :chat-id current-chat-id)]))) (assoc db :messages messages)))) diff --git a/src/status_im/chat/handlers/commands.cljs b/src/status_im/chat/handlers/commands.cljs index 68c2516a1b..70a64e8191 100644 --- a/src/status_im/chat/handlers/commands.cljs +++ b/src/status_im/chat/handlers/commands.cljs @@ -12,25 +12,26 @@ (handlers/side-effect! (fn [{:keys [chats current-account-id] :as db} [_ {{:keys [command params content-command type]} :content - :keys [message-id chat-id on-requested] :as message} data-type]] - (if-not (get-in chats [chat-id :commands-loaded]) - (do (dispatch [:add-commands-loading-callback - chat-id - #(dispatch [:request-command-data message data-type])]) - (dispatch [:load-commands! chat-id])) - (let [path [(if (= :response (keyword type)) :responses :commands) - (if content-command content-command command) - data-type] - to (get-in db [:contacts chat-id :address]) - params {:parameters params - :context (merge {:platform platform/platform - :from current-account-id - :to to} - i18n/delimeters)} - callback #(let [result (get-in % [:result :returned]) - result (if (:markup result) - (update result :markup cu/generate-hiccup) - result)] - (dispatch [:set-in [:message-data data-type message-id] result]) - (when on-requested (on-requested result)))] - (status/call-jail chat-id path params callback)))))) + :keys [message-id chat-id on-requested jail-id] :as message} data-type]] + (let [jail-id (or jail-id chat-id)] + (if-not (get-in chats [jail-id :commands-loaded]) + (do (dispatch [:add-commands-loading-callback + jail-id + #(dispatch [:request-command-data message data-type])]) + (dispatch [:load-commands! jail-id])) + (let [path [(if (= :response (keyword type)) :responses :commands) + (if content-command content-command command) + data-type] + to (get-in db [:contacts chat-id :address]) + params {:parameters params + :context (merge {:platform platform/platform + :from current-account-id + :to to} + i18n/delimeters)} + callback #(let [result (get-in % [:result :returned]) + result (if (:markup result) + (update result :markup cu/generate-hiccup) + result)] + (dispatch [:set-in [:message-data data-type message-id] result]) + (when on-requested (on-requested result)))] + (status/call-jail jail-id path params callback))))))) diff --git a/src/status_im/chat/handlers/input.cljs b/src/status_im/chat/handlers/input.cljs index 1c682eb950..aabda46943 100644 --- a/src/status_im/chat/handlers/input.cljs +++ b/src/status_im/chat/handlers/input.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :refer [enrich after dispatch]] [taoensso.timbre :as log] [status-im.chat.constants :as const] + [status-im.chat.utils :as chat-utils] [status-im.chat.models.input :as input-model] [status-im.chat.models.suggestions :as suggestions] [status-im.components.react :as react-comp] @@ -38,9 +39,8 @@ :select-chat-input-command (handlers/side-effect! (fn [{:keys [current-chat-id chat-ui-props] :as db} - [_ {:keys [name prefill sequential-params] :as command} metadata]] - (dispatch [:set-chat-input-text (str const/command-char - name + [_ {:keys [prefill sequential-params] :as command} metadata]] + (dispatch [:set-chat-input-text (str (chat-utils/command-name command) const/spacing-char (when-not sequential-params (input-model/join-command-args prefill)))]) @@ -66,10 +66,10 @@ :set-command-argument (handlers/side-effect! (fn [{:keys [current-chat-id] :as db} [_ [index arg]]] - (let [command (-> (get-in db [:chats current-chat-id :input-text]) - (input-model/split-command-args)) - seq-params? (-> (input-model/selected-chat-command db current-chat-id) - (get-in [:command :sequential-params]))] + (let [command (-> (get-in db [:chats current-chat-id :input-text]) + (input-model/split-command-args)) + seq-params? (-> (input-model/selected-chat-command db current-chat-id) + (get-in [:command :sequential-params]))] (if seq-params? (dispatch [:set-chat-seq-arg-input-text arg]) (let [command-name (first command) @@ -95,16 +95,17 @@ (handlers/register-handler :update-suggestions (fn [{:keys [current-chat-id] :as db} [_ chat-id text]] - (let [chat-id (or chat-id current-chat-id) - chat-text (or text (get-in db [:chats chat-id :input-text]) "") - requests (suggestions/get-request-suggestions db chat-text) - suggestions (suggestions/get-command-suggestions db chat-text) + (let [chat-id (or chat-id current-chat-id) + chat-text (or text (get-in db [:chats chat-id :input-text]) "") + requests (suggestions/get-request-suggestions db chat-text) + suggestions (suggestions/get-command-suggestions db chat-text) + global-commands (suggestions/get-global-command-suggestions db chat-text) {:keys [dapp?]} (get-in db [:contacts chat-id])] - (when (and dapp? (empty? (into requests suggestions))) + (when (and dapp? (every? empty? [requests suggestions global-commands])) (dispatch [::check-dapp-suggestions chat-id chat-text])) (-> db (assoc-in [:chats chat-id :request-suggestions] requests) - (assoc-in [:chats chat-id :command-suggestions] suggestions))))) + (assoc-in [:chats chat-id :command-suggestions] (into suggestions global-commands)))))) (handlers/register-handler :load-chat-parameter-box @@ -137,12 +138,12 @@ ::send-message (handlers/side-effect! (fn [{:keys [current-public-key current-account-id] :as db} [_ command-message chat-id]] - (let [text (get-in db [:chats chat-id :input-text]) - data {:message text - :command command-message - :chat-id chat-id - :identity current-public-key - :address current-account-id}] + (let [text (get-in db [:chats chat-id :input-text]) + data {:message text + :command command-message + :chat-id chat-id + :identity current-public-key + :address current-account-id}] (dispatch [:set-chat-input-text nil chat-id]) (dispatch [:set-chat-input-metadata nil chat-id]) (dispatch [:set-chat-ui-props {:sending-in-progress? false}]) @@ -156,18 +157,28 @@ :proceed-command (handlers/side-effect! (fn [db [_ command chat-id]] - (let [after-validation #(dispatch [::request-command-data - {:command command - :chat-id chat-id - :data-type :on-send - :after (fn [_ res] - (dispatch [::send-command res command chat-id]))}])] - (dispatch [::request-command-data - {:command command - :chat-id chat-id - :data-type :validator - :after #(dispatch [::proceed-validation-messages - command chat-id %2 after-validation])}]))))) + (let [jail-id (or (get-in command [:command :bot]) chat-id)] + ;:check-and-load-commands! + (let [params + {:command command + :chat-id jail-id} + + on-send-params + (merge params + {:data-type :on-send + :after (fn [_ res] + (dispatch [::send-command res command chat-id]))}) + + after-validation + #(dispatch [::request-command-data on-send-params]) + + validation-params + (merge params + {:data-type :validator + :after #(dispatch [::proceed-validation-messages + command chat-id %2 after-validation])})] + + (dispatch [::request-command-data validation-params])))))) (handlers/register-handler ::proceed-validation-messages @@ -215,7 +226,7 @@ (handlers/side-effect! (fn [{:keys [contacts] :as db} [_ {{:keys [command metadata args] :as c} :command - :keys [message-id chat-id data-type after]}]] + :keys [message-id chat-id data-type after]}]] (let [{:keys [dapp? dapp-url name]} (get contacts chat-id) message-id (random/id) metadata (merge metadata @@ -228,7 +239,8 @@ :to-message (:to-message-id metadata) :created-at (time/now-ms) :id message-id - :chat-id chat-id} + :chat-id chat-id + :jail-id (or (:bot command) chat-id)} request-data {:message-id message-id :chat-id chat-id :content {:command (:name command) diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs index 337fbb4f30..b995e40614 100644 --- a/src/status_im/chat/handlers/send_message.cljs +++ b/src/status_im/chat/handlers/send_message.cljs @@ -127,21 +127,25 @@ (fn [db [_ {:keys [chat-id address command-message] :as parameters}]] (let [{:keys [id command params]} command-message - {:keys [type name]} command - path [(if (= :command type) :commands :responses) - name - :handler] - to (get-in db [:contacts chat-id :address]) - params {:parameters params - :context {:from address - :to to - :message-id id}}] - (status/call-jail - chat-id - path - params - (fn [result] - (dispatch [:command-handler! chat-id parameters result]))))))) + {:keys [type name bot]} command + path [(if (= :command type) :commands :responses) + name + :handler] + to (get-in db [:contacts chat-id :address]) + params {:parameters params + :context {:from address + :to to + :message-id id}} + identity (or bot chat-id)] + (dispatch + [:check-and-load-commands! + identity + #(status/call-jail + identity + path + params + (fn [res] + (dispatch [:command-handler! chat-id parameters res])))]))))) (register-handler :prepare-message (u/side-effect! diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 42abf6a183..246555747b 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -5,17 +5,18 @@ [status-im.chat.views.input.validation-messages :refer [validation-message]] [status-im.i18n :as i18n] [status-im.utils.phone-number :as phone-number] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.chat.utils :as chat-utils])) (defn text-ends-with-space? [text] (when text (= (str/last-index-of text const/spacing-char) (dec (count text))))) -(defn possible-chat-actions [db chat-id] +(defn possible-chat-actions [{:keys [global-commands] :as db} chat-id] (let [{:keys [commands requests responses]} (get-in db [:chats chat-id]) - commands' (into {} (map (fn [[k v]] [k [v :any]]) commands)) + commands' (into {} (map (fn [[k v]] [k [v :any]]) (merge global-commands commands))) responses' (into {} (map (fn [{:keys [message-id type]}] [type [(get responses type) message-id]]) requests))] @@ -60,9 +61,9 @@ possible-actions (possible-chat-actions db chat-id) command-args (split-command-args input-text) command-name (first command-args)] - (when (.startsWith (or command-name "") const/command-char) - (when-let [[command to-message-id] (-> (filter (fn [[{:keys [name]} message-id]] - (= name (subs command-name 1))) + (when (chat-utils/starts-as-command? (or command-name "")) + (when-let [[command to-message-id] (-> (filter (fn [[{:keys [name bot]} message-id]] + (= (or bot name) (subs command-name 1))) possible-actions) (first))] {:command command diff --git a/src/status_im/chat/models/suggestions.cljs b/src/status_im/chat/models/suggestions.cljs index 556ef6cf9a..8b1e194492 100644 --- a/src/status_im/chat/models/suggestions.cljs +++ b/src/status_im/chat/models/suggestions.cljs @@ -2,21 +2,21 @@ (:require [status-im.chat.constants :as chat-consts] [clojure.string :as str])) -(defn suggestion? [text] - (= (get text 0) chat-consts/command-char)) +(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 -(defn can-be-suggested? [text] - (fn [{:keys [name]}] - (let [text' (cond - (.startsWith text chat-consts/command-char) - text + (str/blank? text) + first-char - (str/blank? text) - chat-consts/command-char - - :default - nil)] - (.startsWith (str chat-consts/command-char name) text')))) + :default + nil)] + (.startsWith (str first-char name) text')))))) (defn get-request-suggestions [{:keys [current-chat-id] :as db} text] @@ -29,4 +29,9 @@ (defn get-command-suggestions [{:keys [current-chat-id] :as db} text] (let [commands (get-in db [:chats current-chat-id :commands])] - (filter (fn [[_ v]] ((can-be-suggested? text) v)) commands))) \ No newline at end of file + (filter (fn [[_ v]] ((can-be-suggested? text) v)) commands))) + +(defn get-global-command-suggestions + [{:keys [global-commands] :as db} text] + (filter (fn [[_ v]] ((can-be-suggested? chat-consts/bot-char :bot text) v)) + global-commands)) diff --git a/src/status_im/chat/sign_up.cljs b/src/status_im/chat/sign_up.cljs index dca4c6f8e2..bed1ef6256 100644 --- a/src/status_im/chat/sign_up.cljs +++ b/src/status_im/chat/sign_up.cljs @@ -183,6 +183,5 @@ :name (s/capitalize console-chat-id) :photo-path console-chat-id :dapp? true - ; todo remove/change dapp config fot console - :dapp-url "http://localhost:8185/resources" + :bot-url "local://console-bot" :dapp-hash 858845357}) diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index de85229ef7..5a02514541 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -54,8 +54,12 @@ (fn [_ [_ chat-id]] (reaction (chats/get-by-id chat-id)))) -(register-sub - :get-commands +(register-sub :get-bots-suggestions + (fn [db _] + (let [chat-id (subscribe [:get-current-chat-id])] + (reaction (get-in @db [:bots-suggestions @chat-id]))))) + +(register-sub :get-commands (fn [db [_ chat-id]] (let [current-chat (or chat-id (@db :current-chat-id))] (reaction (or (get-in @db [:chats current-chat :commands]) {}))))) @@ -138,8 +142,7 @@ requests (subscribe [:chat :request-suggestions chat-id]) commands (subscribe [:chat :command-suggestions chat-id])] (reaction - (and (or @show-suggestions? - (.startsWith (or @input-text "") const/command-char)) + (and (or @show-suggestions? (chat-utils/starts-as-command? @input-text)) (not (:command @selected-command)) (or (not-empty @requests) (not-empty @commands))))))) diff --git a/src/status_im/chat/utils.cljs b/src/status_im/chat/utils.cljs index 08fbe36ab2..e6eb9a5296 100644 --- a/src/status_im/chat/utils.cljs +++ b/src/status_im/chat/utils.cljs @@ -1,7 +1,8 @@ (ns status-im.chat.utils (:require [status-im.constants :refer [console-chat-id wallet-chat-id]] - [clojure.string :as str])) + [clojure.string :as str] + [status-im.chat.constants :as const])) (defn console? [s] (= console-chat-id s)) @@ -45,3 +46,13 @@ (if validator (validator message) (pos? (count message)))) + +(defn command-name [{:keys [bot name]}] + (if bot + (str const/bot-char bot) + (str const/command-char name))) + +(defn starts-as-command? [text] + (and (not (nil? text)) + (or (str/starts-with? text const/bot-char) + (str/starts-with? text const/command-char)))) diff --git a/src/status_im/chat/views/input/input.cljs b/src/status_im/chat/views/input/input.cljs index ba5150401d..cb45ffa024 100644 --- a/src/status_im/chat/views/input/input.cljs +++ b/src/status_im/chat/views/input/input.cljs @@ -24,14 +24,15 @@ [status-im.chat.constants :as const] [status-im.components.animation :as anim] [status-im.i18n :as i18n] - [status-im.utils.platform :as platform])) + [status-im.utils.platform :as platform] + [status-im.chat.utils :as chat-utils])) -(defn command-view [first? {command-name :name :as command}] +(defn command-view [first? command] [touchable-highlight {:on-press #(dispatch [:select-chat-input-command command nil])} [view [text {:style (style/command first?) :font :roboto-mono} - (str const/command-char) command-name]]]) + (chat-utils/command-name command)]]]) (defview commands-view [] [commands [:chat :command-suggestions] @@ -117,7 +118,7 @@ (when-not (get-in command [:command :sequential-params]) (let [real-args (remove str/blank? (:args command))] (when-let [placeholder (cond - (= @input-text const/command-char) + (#{const/command-char const/bot-char} @input-text) (i18n/label :t/type-a-command) (and command (empty? real-args)) diff --git a/src/status_im/chat/views/input/suggestions.cljs b/src/status_im/chat/views/input/suggestions.cljs index 2ffca3c0fa..c2553f542e 100644 --- a/src/status_im/chat/views/input/suggestions.cljs +++ b/src/status_im/chat/views/input/suggestions.cljs @@ -12,15 +12,15 @@ [status-im.chat.views.input.animations.expandable :refer [expandable-view]] [status-im.chat.views.input.utils :as input-utils] [status-im.i18n :refer [label]] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.chat.utils :as chat-utils])) (defn suggestion-item [{:keys [on-press name description last?]}] [touchable-highlight {:on-press on-press} [view (style/item-suggestion-container last?) [view {:style style/item-suggestion-name} [text {:style style/item-suggestion-name-text - :font :roboto-mono} - const/command-char name]] + :font :roboto-mono} name]] [text {:style style/item-suggestion-description :number-of-lines 2} description]]]) @@ -36,11 +36,10 @@ :description description :last? last?}]) -(defview command-item [{:keys [name description] :as command} last?] - [] +(defn command-item [{:keys [name description bot] :as command} last?] [suggestion-item {:on-press #(dispatch [:select-chat-input-command command nil]) - :name name + :name (chat-utils/command-name command) :description description :last? last?}]) @@ -72,4 +71,4 @@ (remove #(nil? (:title (second %)))) (map-indexed vector))] ^{:key i} - [command-item command (= i (dec (count commands)))])])]]])) \ No newline at end of file + [command-item command (= i (dec (count commands)))])])]]])) diff --git a/src/status_im/commands/handlers/loading.cljs b/src/status_im/commands/handlers/loading.cljs index 8cc292bfc3..6a757e2834 100644 --- a/src/status_im/commands/handlers/loading.cljs +++ b/src/status_im/commands/handlers/loading.cljs @@ -4,6 +4,7 @@ [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.components.status :as status] [status-im.utils.types :refer [json->clj]] [status-im.commands.utils :refer [reg-handler]] @@ -12,33 +13,33 @@ [status-im.i18n :refer [label]] [status-im.utils.homoglyph :as h] [status-im.utils.js-resources :as js-res] - [status-im.utils.random :as random])) + [status-im.utils.random :as random] + [status-im.chat.sign-up :as sign-up])) (def commands-js "commands.js") (defn load-commands! - [{:keys [current-chat-id contacts]} [identity]] - (let [identity (or identity current-chat-id) - contact (or (get contacts identity) - {:whisper-identity identity})] - (when identity - (dispatch [::fetch-commands! contact]))) + [{:keys [current-chat-id contacts]} [identity callback]] + (let [identity' (or identity current-chat-id) + contact (or (get contacts identity') + sign-up/console-contact)] + (when identity' + (dispatch [::fetch-commands! {:contact contact + :callback callback}]))) ;; todo uncomment #_(if-let [{:keys [file]} (commands/get-by-chat-id identity)] (dispatch [::parse-commands! identity file]) (dispatch [::fetch-commands! identity]))) (defn fetch-commands! - [_ [{:keys [whisper-identity dapp? dapp-url]}]] + [_ [{{:keys [dapp? dapp-url bot-url whisper-identity]} :contact + :as params}]] (cond - (= console-chat-id whisper-identity) - (dispatch [::validate-hash whisper-identity js-res/console-js]) - - (= wallet-chat-id whisper-identity) - (dispatch [::validate-hash whisper-identity js-res/wallet-js]) + (js-res/local-resource? bot-url) + (dispatch [::validate-hash params (js-res/get-resource bot-url)]) (and dapp? dapp-url) - (http-get (s/join "/" [dapp-url commands-js]) + (http-get (s/join "/" [dapp-url "commands.js"]) (fn [response] (and (string? (.text response)) @@ -48,13 +49,14 @@ #(dispatch [::validate-hash whisper-identity js-res/dapp-js])) :else - (dispatch [::validate-hash whisper-identity js-res/commands-js]))) + (dispatch [::validate-hash params js-res/commands-js]))) (defn dispatch-loaded! - [db [identity file]] + [db [{{:keys [whisper-identity]} :contact + :as params} file]] (if (::valid-hash db) - (dispatch [::parse-commands! identity file]) - (dispatch [::loading-failed! identity ::wrong-hash]))) + (dispatch [::parse-commands! params file]) + (dispatch [::loading-failed! whisper-identity ::wrong-hash]))) (defn get-hash-by-identity [db identity] @@ -65,19 +67,23 @@ ;; todo tbd hashing algorithm (hash file)) -(defn parse-commands! [_ [identity file]] - (status/parse-jail identity file - (fn [result] - (let [{:keys [error result]} (json->clj result)] - (log/debug "Error parsing commands: " error result) - (if error - (dispatch [::loading-failed! identity ::error-in-jail error]) - (if identity - (dispatch [::add-commands identity file result]) - (dispatch [::add-all-commands result]))))))) +(defn parse-commands! + [_ [{{:keys [whisper-identity]} :contact + :keys [callback]} + file]] + (status/parse-jail + whisper-identity file + (fn [result] + (let [{:keys [error result]} (json->clj result)] + (log/debug "Parsing commands results: " error result) + (if error + (dispatch [::loading-failed! whisper-identity ::error-in-jail error]) + (do + (dispatch [::add-commands whisper-identity file result]) + (when callback (callback)))))))) (defn validate-hash - [db [identity file]] + [db [_ file]] (let [valid? true ;; todo check #_(= (get-hash-by-identity db identity) @@ -102,18 +108,36 @@ (defn add-commands [db [id _ {:keys [commands responses autorun]}]] - (let [account @(subscribe [:get-current-account]) - commands' (filter-forbidden-names account id commands) - responses' (filter-forbidden-names account id responses)] - (-> db - (assoc-in [id :commands] (mark-as :command commands')) - (assoc-in [id :responses] (mark-as :response responses')) - (assoc-in [id :commands-loaded] true) - (assoc-in [id :autorun] autorun)))) + (let [account @(subscribe [:get-current-account]) + commands' (filter-forbidden-names account id commands) + global-command (:global commands') + commands'' (apply dissoc commands' [:init :global]) + responses' (filter-forbidden-names account id responses)] + (cond-> db + + (get-in db [:chats id]) + (update-in [:chats id] assoc + :commands (mark-as :command commands'') + :responses (mark-as :response responses') + :commands-loaded true + :autorun autorun + :global-command global-command) + + global-command + (update :global-commands assoc (keyword id) + (assoc global-command :bot id + :type :command))))) (defn save-commands-js! [_ [id file]] - (commands/save {:chat-id id :file file})) + #_(commands/save {:chat-id id :file file})) + +(defn save-global-command! + [{:keys [global-commands]} [id]] + (let [command (get global-commands (keyword id))] + (when command + (contacts/save {:whisper-identity id + :global-command command})))) (defn loading-failed! [db [id reason details]] @@ -126,6 +150,13 @@ (show-popup "Error" m) (log/debug m)))) +(reg-handler :check-and-load-commands! + (u/side-effect! + (fn [{:keys [chats]} [identity callback]] + (if (get-in chats [identity :commands-loaded]) + (callback) + (dispatch [:load-commands! identity callback]))))) + (reg-handler :load-commands! (u/side-effect! load-commands!)) (reg-handler ::fetch-commands! (u/side-effect! fetch-commands!)) @@ -136,8 +167,8 @@ (reg-handler ::parse-commands! (u/side-effect! parse-commands!)) (reg-handler ::add-commands - [(path :chats) - (after save-commands-js!) + [(after save-commands-js!) + (after save-global-command!) (after #(dispatch [:check-autorun])) (after #(dispatch [:update-suggestions])) (after (fn [_ [id]] @@ -145,11 +176,6 @@ (dispatch [:invoke-chat-loaded-callbacks id])))] add-commands) -(reg-handler ::add-all-commands - (fn [db [{:keys [commands responses]}]] - (assoc db :all-commands {:commands (mark-as :command commands) - :responses (mark-as :response responses)}))) - (reg-handler ::loading-failed! (u/side-effect! loading-failed!)) (reg-handler :add-commands-loading-callback diff --git a/src/status_im/contacts/handlers.cljs b/src/status_im/contacts/handlers.cljs index f8986e4e53..99a3049e05 100644 --- a/src/status_im/contacts/handlers.cljs +++ b/src/status_im/contacts/handlers.cljs @@ -103,13 +103,22 @@ db))) (defn load-contacts! [db _] - (let [contacts (->> (contacts/get-all) - (map (fn [{:keys [whisper-identity] :as contact}] - [whisper-identity contact])) - (into {}))] + (let [contacts-list (->> (contacts/get-all) + (map (fn [{:keys [whisper-identity] :as contact}] + [whisper-identity contact]))) + global-commands (->> contacts-list + (filter (fn [[_ c]] (:global-command c))) + (map (fn [[id {:keys [global-command]}]] + [(keyword id) (-> global-command + (update :params vals) + (assoc :bot id + :type :command))])) + (into {})) + contacts (into {} contacts-list)] (doseq [[_ contact] contacts] (dispatch [:watch-contact contact])) - (assoc db :contacts contacts))) + (assoc db :contacts contacts + :global-commands global-commands))) (register-handler :load-contacts load-contacts!) @@ -216,34 +225,39 @@ (u/side-effect! (fn [{:keys [chats groups]}] (let [default-contacts js-res/default-contacts - default-groups js-res/default-contact-groups] + default-groups js-res/default-contact-groups] (dispatch [:add-groups (mapv (fn [[id {:keys [name contacts]}]] - {:group-id (clojure.core/name id) + {:group-id (clojure.core/name id) :name (:en name) :order 0 :timestamp (random/timestamp) :contacts (mapv #(hash-map :identity %) contacts)}) default-groups)]) - (doseq [[id {:keys [name photo-path public-key add-chat? - dapp? dapp-url dapp-hash]}] default-contacts] + (doseq [[id {:keys [name photo-path public-key add-chat? has-global-command? + dapp? dapp-url dapp-hash bot-url]}] default-contacts] (let [id' (clojure.core/name id)] (when-not (chats id') (when add-chat? (dispatch [:add-chat id' {:name (:en name)}])) - (dispatch [:add-contacts [{:whisper-identity id' - :address (public-key->address id') - :name (:en name) - :photo-path photo-path - :public-key public-key - :dapp? dapp? - :dapp-url (:en dapp-url) - :dapp-hash dapp-hash}]])))))))) + (dispatch [:add-contacts [{:whisper-identity id' + :address (public-key->address id') + :name (:en name) + :photo-path photo-path + :public-key public-key + :dapp? dapp? + :dapp-url (:en dapp-url) + :bot-url bot-url + :has-global-command? has-global-command? + :dapp-hash dapp-hash}]]) + (when bot-url + (dispatch [:load-commands! id']))))))))) + (register-handler :add-contacts - (-> add-new-contacts - ((after save-contacts!)) - ((after add-contacts-to-groups)))) + [(after save-contacts!) + (after add-contacts-to-groups)] + add-new-contacts) (defn add-new-contact [db [_ {:keys [whisper-identity] :as contact}]] (-> db @@ -270,9 +284,9 @@ (register-handler :add-pending-contact (u/side-effect! (fn [{:keys [chats contacts]} [_ chat-id]] - (let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])] - (read-string contact-info) - (assoc (get contacts chat-id) :pending? false)) + (let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])] + (read-string contact-info) + (assoc (get contacts chat-id) :pending? false)) contact' (assoc contact :address (public-key->address chat-id))] (dispatch [::prepare-contact contact']) (dispatch [:watch-contact contact']) diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 121b62930b..4a376fc055 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -3,8 +3,8 @@ [status-im.data-store.realm.schemas.account.v2.core :as v2] [status-im.data-store.realm.schemas.account.v3.core :as v3] [status-im.data-store.realm.schemas.account.v4.core :as v4] - [status-im.data-store.realm.schemas.account.v5.core :as v5])) - + [status-im.data-store.realm.schemas.account.v5.core :as v5] + [status-im.data-store.realm.schemas.account.v6.core :as v6])) ; put schemas ordered by version (def schemas [{:schema v1/schema @@ -21,4 +21,7 @@ :migration v4/migration} {:schema v5/schema :schemaVersion 5 - :migration v5/migration}]) + :migration v5/migration} + {:schema v6/schema + :schemaVersion 6 + :migration v6/migration}]) diff --git a/src/status_im/data_store/realm/schemas/account/v6/command.cljs b/src/status_im/data_store/realm/schemas/account/v6/command.cljs new file mode 100644 index 0000000000..a8ee573671 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v6/command.cljs @@ -0,0 +1,22 @@ +(ns status-im.data-store.realm.schemas.account.v6.command + (:require [taoensso.timbre :as log])) + +(def schema {:name :command + :properties {:description {:type :string + :optional true} + :color {:type :string + :optional true} + :name {:type :string} + :params {:type :list + :objectType :command-parameter} + :title {:type :string + :optional true} + :has-handler {:type :bool + :default true} + :fullscreen {:type :bool + :default true} + :suggestions-trigger {:type :string + :default "on-change"}}}) + +(defn migration [_ _] + (log/debug "migrating chat-contact schema")) diff --git a/src/status_im/data_store/realm/schemas/account/v6/command_parameter.cljs b/src/status_im/data_store/realm/schemas/account/v6/command_parameter.cljs new file mode 100644 index 0000000000..0c360aa892 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v6/command_parameter.cljs @@ -0,0 +1,9 @@ +(ns status-im.data-store.realm.schemas.account.v6.command-parameter + (:require [taoensso.timbre :as log])) + +(def schema {:name :command-parameter + :properties {:name {:type :string} + :type {:type :string}}}) + +(defn migration [_ _] + (log/debug "migrating command-parameter schema")) diff --git a/src/status_im/data_store/realm/schemas/account/v6/contact.cljs b/src/status_im/data_store/realm/schemas/account/v6/contact.cljs new file mode 100644 index 0000000000..cff3d69b0c --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v6/contact.cljs @@ -0,0 +1,32 @@ +(ns status-im.data-store.realm.schemas.account.v6.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} + :status {: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} + :dapp-hash {:type :int + :optional true} + :debug? {:type :bool + :default false}}}) + +(defn migration [old-realm new-realm] + (log/debug "migrating contact schema v6")) diff --git a/src/status_im/data_store/realm/schemas/account/v6/core.cljs b/src/status_im/data_store/realm/schemas/account/v6/core.cljs new file mode 100644 index 0000000000..e6739bc979 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v6/core.cljs @@ -0,0 +1,38 @@ +(ns status-im.data-store.realm.schemas.account.v6.core + (:require [status-im.data-store.realm.schemas.account.v4.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.v6.command-parameter :as command-parameter] + [status-im.data-store.realm.schemas.account.v6.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.v4.message :as message] + [status-im.data-store.realm.schemas.account.v1.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.v1.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] + [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]) + +(defn migration [old-realm new-realm] + (log/debug "migrating v5 account database: " old-realm new-realm) + (chat/migration old-realm new-realm) + (contact/migration old-realm new-realm)) diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index 19e6a9feb2..1968daad4d 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -44,13 +44,14 @@ (register-handler :initialize-db (fn [{:keys [status-module-initialized? status-node-started? - network-status network]} _] + network-status network first-run]} _] (data-store/init) (assoc app-db :current-account-id nil :network-status network-status :status-module-initialized? (or p/ios? js/goog.DEBUG status-module-initialized?) :status-node-started? status-node-started? - :network (or network :testnet)))) + :network (or network :testnet) + :first-run (or (nil? first-run) first-run)))) (register-handler :initialize-account-db (fn [db _] @@ -81,12 +82,22 @@ (register-handler :reset-app (u/side-effect! - (fn [_ _] + (fn [{:keys [first-run] :as db} [_ callback]] (dispatch [:initialize-db]) (dispatch [:load-accounts]) - (dispatch [:init-console-chat]) - (dispatch [:load-default-contacts!]) - (dispatch [:load-commands!])))) + + (dispatch [::init-chats! callback])))) + +(register-handler ::init-chats! + (u/side-effect! + (fn [{:keys [first-run accounts] :as db} [_ callback]] + (when first-run + (dispatch [:set :first-run false])) + (when (or (not first-run) (empty? accounts)) + (dispatch [:init-console-chat]) + (dispatch [:load-default-contacts!]) + (dispatch [:load-commands!]) + (when callback (callback)))))) (def ecc (js/require "eccjs")) diff --git a/src/status_im/profile/validations.cljs b/src/status_im/profile/validations.cljs index fc65d1d0e1..d3aea1b3fc 100644 --- a/src/status_im/profile/validations.cljs +++ b/src/status_im/profile/validations.cljs @@ -11,7 +11,8 @@ [(str/blank? username) (h/matches username console-chat-id) (h/matches username wallet-chat-id) - (str/includes? username chat-consts/command-char)]))) + (str/includes? username chat-consts/command-char) + (str/includes? username chat-consts/bot-char)]))) (defn correct-email? [email] (let [pattern #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"] diff --git a/src/status_im/utils/js_resources.cljs b/src/status_im/utils/js_resources.cljs index a5d17ab8fa..15e762d19e 100644 --- a/src/status_im/utils/js_resources.cljs +++ b/src/status_im/utils/js_resources.cljs @@ -1,14 +1,39 @@ (ns status-im.utils.js-resources - (:require-macros [status-im.utils.slurp :refer [slurp]]) - (:require [status-im.utils.types :refer [json->clj]])) + (:require-macros [status-im.utils.slurp :refer [slurp slurp-bot]]) + (:require [status-im.utils.types :refer [json->clj]] + [clojure.string :as s])) + +(def local-protocol "local://") + +(defn local-resource? [url] + (and (string? url) (s/starts-with? url local-protocol))) (def default-contacts (json->clj (slurp "resources/default_contacts.json"))) (def default-contact-groups (json->clj (slurp "resources/default_contact_groups.json"))) -(def commands-js (slurp "resources/commands.js")) -(def console-js (slurp "resources/console.js")) -(def status-js (slurp "resources/status.js")) -(def wallet-js (slurp "resources/wallet.js")) +(def wallet-js (slurp-bot :wallet)) + +(def console-js (slurp-bot :console "web3_metadata.js")) + +(def browse-js (slurp-bot :browse)) + +(def mailman-js (slurp-bot :mailman )) + +(def commands-js wallet-js) + +(def resources + {:wallet-bot wallet-js + :console-bot console-js + :browse-bot browse-js + :mailman-bot mailman-js}) + +(defn get-resource [url] + (let [resource-name (keyword (subs url (count local-protocol)))] + (resources resource-name))) + +(def status-js (str (slurp "resources/status.js") + (slurp "resources/i18n.js"))) + (def dapp-js (str (slurp "resources/dapp.js"))) (def webview-js (slurp "resources/webview.js")) diff --git a/src/status_im/utils/slurp.clj b/src/status_im/utils/slurp.clj index 194b1384b5..e590b00040 100644 --- a/src/status_im/utils/slurp.clj +++ b/src/status_im/utils/slurp.clj @@ -1,5 +1,11 @@ (ns status-im.utils.slurp - (:refer-clojure :exclude [slurp])) + (:refer-clojure :exclude [slurp]) + (:require [clojure.string :as s])) (defmacro slurp [file] (clojure.core/slurp file)) + +(defmacro slurp-bot [bot-name & files] + (->> (concat files ["translations.js" "bot.js"]) + (map #(clojure.core/slurp (s/join "/" ["bots" (name bot-name) %]))) + (apply str)))