mirror of
https://github.com/monero-project/monero-gui.git
synced 2024-12-23 12:09:57 +00:00
Fiat API
Co-Authored-By: selsta <selsta@sent.at> Co-Authored-By: Gene Peters <gene@telligent-data.com>
This commit is contained in:
parent
3c1fe1da8b
commit
d4be7634cb
10 changed files with 644 additions and 4 deletions
|
@ -32,6 +32,7 @@ import QtGraphicalEffects 1.0
|
|||
import moneroComponents.Wallet 1.0
|
||||
import moneroComponents.NetworkType 1.0
|
||||
import moneroComponents.Clipboard 1.0
|
||||
import FontAwesome 1.0
|
||||
|
||||
import "components" as MoneroComponents
|
||||
import "components/effects/" as MoneroEffects
|
||||
|
@ -44,10 +45,13 @@ Rectangle {
|
|||
property alias unlockedBalanceLabelVisible: unlockedBalanceLabel.visible
|
||||
property alias balanceLabelText: balanceLabel.text
|
||||
property alias balanceText: balanceText.text
|
||||
property alias balanceTextFiat: balanceTextFiat.text
|
||||
property alias unlockedBalanceTextFiat: unlockedBalanceTextFiat.text
|
||||
property alias networkStatus : networkStatus
|
||||
property alias progressBar : progressBar
|
||||
property alias daemonProgressBar : daemonProgressBar
|
||||
property alias minutesToUnlockTxt: unlockedBalanceLabel.text
|
||||
property bool fiatBalance: false
|
||||
property int titleBarHeight: 50
|
||||
property string copyValue: ""
|
||||
Clipboard { id: clipboard }
|
||||
|
@ -202,6 +206,26 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
MoneroComponents.Label {
|
||||
fontSize: 20
|
||||
text: "¥"
|
||||
color: "white"
|
||||
visible: persistentSettings.fiatPriceEnabled
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 45
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 28
|
||||
themeTransition: false
|
||||
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
fiatBalance = !fiatBalance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -214,7 +238,7 @@ Rectangle {
|
|||
width: 50
|
||||
|
||||
MoneroComponents.TextPlain {
|
||||
visible: !isMobile
|
||||
visible: !(fiatBalance && persistentSettings.fiatPriceEnabled)
|
||||
id: balanceText
|
||||
themeTransition: false
|
||||
anchors.left: parent.left
|
||||
|
@ -255,9 +279,39 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
MoneroComponents.TextPlain {
|
||||
visible: !balanceText.visible
|
||||
id: balanceTextFiat
|
||||
themeTransition: false
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 76
|
||||
font.family: "Arial"
|
||||
color: "#FFFFFF"
|
||||
text: "N/A"
|
||||
font.pixelSize: balanceText.font.pixelSize
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
parent.color = MoneroComponents.Style.orange
|
||||
}
|
||||
onExited: {
|
||||
parent.color = MoneroComponents.Style.white
|
||||
}
|
||||
onClicked: {
|
||||
console.log("Copied to clipboard");
|
||||
clipboard.setText(parent.text);
|
||||
appWindow.showStatusMessage(qsTr("Copied to clipboard"),3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MoneroComponents.TextPlain {
|
||||
id: unlockedBalanceText
|
||||
visible: true
|
||||
visible: !(fiatBalance && persistentSettings.fiatPriceEnabled)
|
||||
themeTransition: false
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
|
@ -297,6 +351,36 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
MoneroComponents.TextPlain {
|
||||
id: unlockedBalanceTextFiat
|
||||
themeTransition: false
|
||||
visible: !unlockedBalanceText.visible
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 126
|
||||
font.family: "Arial"
|
||||
color: "#FFFFFF"
|
||||
text: "N/A"
|
||||
font.pixelSize: unlockedBalanceText.font.pixelSize
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
parent.color = MoneroComponents.Style.orange
|
||||
}
|
||||
onExited: {
|
||||
parent.color = MoneroComponents.Style.white
|
||||
}
|
||||
onClicked: {
|
||||
console.log("Copied to clipboard");
|
||||
clipboard.setText(parent.text);
|
||||
appWindow.showStatusMessage(qsTr("Copied to clipboard"),3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MoneroComponents.Label {
|
||||
id: unlockedBalanceLabel
|
||||
visible: true
|
||||
|
|
17
js/Utils.js
17
js/Utils.js
|
@ -154,3 +154,20 @@ function qmlEach(item, properties, ignoredObjectNames, arr){
|
|||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function capitalize(s){
|
||||
if (typeof s !== 'string') return ''
|
||||
return s.charAt(0).toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
function formatMoney(n, c, d, t) {
|
||||
// https://stackoverflow.com/a/149099
|
||||
var c = isNaN(c = Math.abs(c)) ? 2 : c,
|
||||
d = d == undefined ? "." : d,
|
||||
t = t == undefined ? "," : t,
|
||||
s = n < 0 ? "-" : "",
|
||||
i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))),
|
||||
j = (j = i.length) > 3 ? j % 3 : 0;
|
||||
|
||||
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
|
||||
};
|
||||
|
|
7
main.cpp
7
main.cpp
|
@ -30,6 +30,7 @@
|
|||
#include <QQmlApplicationEngine>
|
||||
#include <QtQml>
|
||||
#include <QStandardPaths>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
|
@ -65,6 +66,7 @@
|
|||
#include "qt/utils.h"
|
||||
#include "qt/mime.h"
|
||||
#include "src/qt/KeysFiles.h"
|
||||
#include "qt/prices.h"
|
||||
|
||||
// IOS exclusions
|
||||
#ifndef Q_OS_IOS
|
||||
|
@ -356,6 +358,11 @@ int main(int argc, char *argv[])
|
|||
builtWithScanner = true;
|
||||
#endif
|
||||
engine.rootContext()->setContextProperty("builtWithScanner", builtWithScanner);
|
||||
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager();
|
||||
Prices prices(manager);
|
||||
engine.rootContext()->setContextProperty("Prices", &prices);
|
||||
|
||||
// Load main window (context properties needs to be defined obove this line)
|
||||
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
|
||||
if (engine.rootObjects().isEmpty())
|
||||
|
|
174
main.qml
174
main.qml
|
@ -91,6 +91,26 @@ ApplicationWindow {
|
|||
property int appEpoch: Math.floor((new Date).getTime() / 1000)
|
||||
property bool themeTransition: false
|
||||
|
||||
// fiat price conversion
|
||||
property int fiatPriceXMRUSD: 0
|
||||
property int fiatPriceXMREUR: 0
|
||||
property var fiatPriceAPIs: {
|
||||
return {
|
||||
"kraken": {
|
||||
"xmrusd": "https://api.kraken.com/0/public/Ticker?pair=XMRUSD",
|
||||
"xmreur": "https://api.kraken.com/0/public/Ticker?pair=XMREUR"
|
||||
},
|
||||
"coingecko": {
|
||||
"xmrusd": "https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=usd",
|
||||
"xmreur": "https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=eur"
|
||||
},
|
||||
"cryptocompare": {
|
||||
"xmrusd": "https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=USD",
|
||||
"xmreur": "https://min-api.cryptocompare.com/data/price?fsym=XMR&tsyms=EUR",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property string remoteNodeService: {
|
||||
// support user-defined remote node aggregators
|
||||
if(persistentSettings.remoteNodeService){
|
||||
|
@ -394,6 +414,10 @@ ApplicationWindow {
|
|||
middlePanel.balanceText = balance;
|
||||
leftPanel.balanceText = balance;
|
||||
|
||||
if (persistentSettings.fiatPriceEnabled) {
|
||||
appWindow.fiatApiUpdateBalance(balance, balance_unlocked);
|
||||
}
|
||||
|
||||
var accountLabel = currentWallet.getSubaddressLabel(currentWallet.currentSubaddressAccount, 0);
|
||||
leftPanel.balanceLabelText = qsTr("Balance (#%1%2)").arg(currentWallet.currentSubaddressAccount).arg(accountLabel === "" ? "" : (" – " + accountLabel));
|
||||
}
|
||||
|
@ -1102,6 +1126,7 @@ ApplicationWindow {
|
|||
rootItem.state = "wizard"
|
||||
// reset balance
|
||||
leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0);
|
||||
fiatApiUpdateBalance(0, 0);
|
||||
// disable timers
|
||||
userInActivityTimer.running = false;
|
||||
simpleModeConnectionTimer.running = false;
|
||||
|
@ -1126,6 +1151,143 @@ ApplicationWindow {
|
|||
flags: persistentSettings.customDecorations ? Windows.flagsCustomDecorations : Windows.flags
|
||||
onWidthChanged: x -= 0
|
||||
|
||||
Timer {
|
||||
id: fiatPriceTimer
|
||||
interval: 1000 * 60;
|
||||
running: persistentSettings.fiatPriceEnabled;
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if(persistentSettings.fiatPriceEnabled)
|
||||
appWindow.fiatApiRefresh();
|
||||
}
|
||||
triggeredOnStart: false
|
||||
}
|
||||
|
||||
function fiatApiParseTicker(resp, currency){
|
||||
// parse & validate incoming JSON
|
||||
if(resp._url.startsWith("https://api.kraken.com/0/")){
|
||||
if(resp.hasOwnProperty("error") && resp.error.length > 0 || !resp.hasOwnProperty("result")){
|
||||
appWindow.fiatApiError("Kraken API has error(s)");
|
||||
return;
|
||||
}
|
||||
|
||||
var key = currency === "xmreur" ? "XXMRZEUR" : "XXMRZUSD";
|
||||
var ticker = resp.result[key]["o"];
|
||||
return ticker;
|
||||
} else if(resp._url.startsWith("https://api.coingecko.com/api/v3/")){
|
||||
var key = currency === "xmreur" ? "eur" : "usd";
|
||||
if(!resp.hasOwnProperty("monero") || !resp["monero"].hasOwnProperty(key)){
|
||||
appWindow.fiatApiError("Coingecko API has error(s)");
|
||||
return;
|
||||
}
|
||||
return resp["monero"][key];
|
||||
} else if(resp._url.startsWith("https://min-api.cryptocompare.com/data/")){
|
||||
var key = currency === "xmreur" ? "EUR" : "USD";
|
||||
if(!resp.hasOwnProperty(key)){
|
||||
appWindow.fiatApiError("cryptocompare API has error(s)");
|
||||
return;
|
||||
}
|
||||
return resp[key];
|
||||
}
|
||||
}
|
||||
|
||||
function fiatApiGetCurrency(resp){
|
||||
// map response to `appWindow.fiatPriceAPIs` object
|
||||
if (!resp.hasOwnProperty('_url')){
|
||||
appWindow.fiatApiError("invalid JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
var apis = appWindow.fiatPriceAPIs;
|
||||
for (var api in apis){
|
||||
if (!apis.hasOwnProperty(api))
|
||||
continue;
|
||||
|
||||
for (var cur in apis[api]){
|
||||
if(!apis[api].hasOwnProperty(cur))
|
||||
continue;
|
||||
|
||||
var url = apis[api][cur];
|
||||
if(url === resp._url){
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fiatApiJsonReceived(resp){
|
||||
// handle incoming JSON, set ticker
|
||||
var currency = appWindow.fiatApiGetCurrency(resp);
|
||||
if(typeof currency == "undefined"){
|
||||
appWindow.fiatApiError("could not get currency");
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = appWindow.fiatApiParseTicker(resp, currency);
|
||||
if(ticker <= 0){
|
||||
appWindow.fiatApiError("could not get ticker");
|
||||
return;
|
||||
}
|
||||
|
||||
if(persistentSettings.fiatPriceCurrency === "xmrusd")
|
||||
appWindow.fiatPriceXMRUSD = ticker;
|
||||
else if(persistentSettings.fiatPriceCurrency === "xmreur")
|
||||
appWindow.fiatPriceXMREUR = ticker;
|
||||
|
||||
appWindow.updateBalance();
|
||||
}
|
||||
|
||||
function fiatApiRefresh(){
|
||||
// trigger API call
|
||||
if(!persistentSettings.fiatPriceEnabled)
|
||||
return;
|
||||
|
||||
var userProvider = persistentSettings.fiatPriceProvider;
|
||||
if(!appWindow.fiatPriceAPIs.hasOwnProperty(userProvider)){
|
||||
appWindow.fiatApiError("provider \"" + userProvider + "\" not implemented");
|
||||
return;
|
||||
}
|
||||
|
||||
var provider = appWindow.fiatPriceAPIs[userProvider];
|
||||
var userCurrency = persistentSettings.fiatPriceCurrency;
|
||||
if(!provider.hasOwnProperty(userCurrency)){
|
||||
appWindow.fiatApiError("currency \"" + userCurrency + "\" not implemented");
|
||||
}
|
||||
|
||||
var url = provider[userCurrency];
|
||||
Prices.getJSON(url);
|
||||
}
|
||||
|
||||
function fiatApiUpdateBalance(balance, unlocked_balance){
|
||||
// update balance card
|
||||
var ticker = persistentSettings.fiatPriceCurrency === "xmrusd" ? appWindow.fiatPriceXMRUSD : appWindow.fiatPriceXMREUR;
|
||||
var symbol = persistentSettings.fiatPriceCurrency === "xmrusd" ? "$" : "€"
|
||||
if(ticker <= 0){
|
||||
console.log(fiatApiError("Could not update balance card; invalid ticker value"));
|
||||
leftPanel.unlockedBalanceTextFiat = "N/A";
|
||||
leftPanel.balanceTextFiat = "N/A";
|
||||
return;
|
||||
}
|
||||
|
||||
var uFiat = Utils.formatMoney(unlocked_balance * ticker);
|
||||
var bFiat = Utils.formatMoney(balance * ticker);
|
||||
|
||||
leftPanel.unlockedBalanceTextFiat = symbol + uFiat;
|
||||
leftPanel.balanceTextFiat = symbol + bFiat;
|
||||
}
|
||||
|
||||
function fiatTimerStart(){
|
||||
fiatPriceTimer.start();
|
||||
}
|
||||
|
||||
function fiatTimerStop(){
|
||||
fiatPriceTimer.stop();
|
||||
}
|
||||
|
||||
function fiatApiError(msg){
|
||||
console.log("fiatPriceError: " + msg);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
x = (Screen.width - width) / 2
|
||||
y = (Screen.height - maxWindowHeight) / 2
|
||||
|
@ -1137,6 +1299,7 @@ ApplicationWindow {
|
|||
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
|
||||
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
|
||||
IPC.uriHandler.connect(onUriHandler);
|
||||
Prices.priceJsonReceived.connect(appWindow.fiatApiJsonReceived);
|
||||
|
||||
if(typeof daemonManager != "undefined") {
|
||||
daemonManager.daemonStarted.connect(onDaemonStarted);
|
||||
|
@ -1144,8 +1307,6 @@ ApplicationWindow {
|
|||
daemonManager.daemonStopped.connect(onDaemonStopped);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Connect app exit to qml window exit handling
|
||||
mainApp.closing.connect(appWindow.close);
|
||||
|
||||
|
@ -1177,6 +1338,11 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
checkUpdates();
|
||||
|
||||
if(persistentSettings.fiatPriceEnabled){
|
||||
appWindow.fiatApiRefresh();
|
||||
appWindow.fiatTimerStart();
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
|
@ -1223,6 +1389,10 @@ ApplicationWindow {
|
|||
property bool showPid: false
|
||||
property bool blackTheme: true
|
||||
|
||||
property bool fiatPriceEnabled: false
|
||||
property string fiatPriceProvider: "kraken"
|
||||
property string fiatPriceCurrency: "xmrusd"
|
||||
|
||||
Component.onCompleted: {
|
||||
MoneroComponents.Style.blackTheme = persistentSettings.blackTheme
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ HEADERS += \
|
|||
src/qt/mime.h \
|
||||
src/qt/KeysFiles.h \
|
||||
src/qt/utils.h
|
||||
src/qt/utils.h \
|
||||
src/qt/prices.h
|
||||
|
||||
SOURCES += main.cpp \
|
||||
filter.cpp \
|
||||
|
@ -98,6 +100,8 @@ SOURCES += main.cpp \
|
|||
src/qt/mime.cpp \
|
||||
src/qt/KeysFiles.cpp \
|
||||
src/qt/utils.cpp
|
||||
src/qt/utils.cpp \
|
||||
src/qt/prices.cpp
|
||||
|
||||
CONFIG(DISABLE_PASS_STRENGTH_METER) {
|
||||
HEADERS -= src/zxcvbn-c/zxcvbn.h
|
||||
|
|
|
@ -157,6 +157,106 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
//! Manage pricing
|
||||
RowLayout {
|
||||
MoneroComponents.CheckBox {
|
||||
id: enableConvertCurrency
|
||||
text: qsTr("Enable displaying balance in other currencies") + translationManager.emptyString
|
||||
checked: persistentSettings.fiatPriceEnabled
|
||||
onCheckedChanged: {
|
||||
if (!checked) {
|
||||
console.log("Disabled price conversion");
|
||||
persistentSettings.fiatPriceEnabled = false;
|
||||
appWindow.fiatTimerStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
visible: enableConvertCurrency.checked
|
||||
columns: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 36
|
||||
columnSpacing: 32
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 10
|
||||
Layout.fillWidth: true
|
||||
|
||||
MoneroComponents.Label {
|
||||
Layout.fillWidth: true
|
||||
fontSize: 14
|
||||
text: qsTr("Price source") + translationManager.emptyString
|
||||
}
|
||||
|
||||
MoneroComponents.StandardDropdown {
|
||||
id: fiatPriceProviderDropDown
|
||||
Layout.fillWidth: true
|
||||
dataModel: fiatPriceProvidersModel
|
||||
onChanged: {
|
||||
var obj = dataModel.get(currentIndex);
|
||||
persistentSettings.fiatPriceProvider = obj.data;
|
||||
|
||||
if(persistentSettings.fiatPriceEnabled)
|
||||
appWindow.fiatApiRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 10
|
||||
Layout.fillWidth: true
|
||||
|
||||
MoneroComponents.Label {
|
||||
Layout.fillWidth: true
|
||||
fontSize: 14
|
||||
text: qsTr("Currency") + translationManager.emptyString
|
||||
}
|
||||
|
||||
MoneroComponents.StandardDropdown {
|
||||
id: fiatPriceCurrencyDropdown
|
||||
Layout.fillWidth: true
|
||||
dataModel: fiatPriceCurrencyModel
|
||||
onChanged: {
|
||||
var obj = dataModel.get(currentIndex);
|
||||
persistentSettings.fiatPriceCurrency = obj.data;
|
||||
|
||||
if(persistentSettings.fiatPriceEnabled)
|
||||
appWindow.fiatApiRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
z: parent.z + 1
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
// Feature needs to be double enabled for security purposes (miss-clicks)
|
||||
visible: enableConvertCurrency.checked && !persistentSettings.fiatPriceEnabled
|
||||
spacing: 0
|
||||
Layout.topMargin: 5
|
||||
Layout.leftMargin: 36
|
||||
|
||||
MoneroComponents.WarningBox {
|
||||
text: qsTr("Enabling price conversion exposes your IP address to the selected price source.") + translationManager.emptyString;
|
||||
}
|
||||
|
||||
MoneroComponents.StandardButton {
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
small: true
|
||||
text: qsTr("Confirm and enable") + translationManager.emptyString
|
||||
|
||||
onClicked: {
|
||||
console.log("Enabled price conversion");
|
||||
persistentSettings.fiatPriceEnabled = true;
|
||||
appWindow.fiatApiRefresh();
|
||||
appWindow.fiatTimerStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MoneroComponents.StandardButton {
|
||||
visible: !persistentSettings.customDecorations
|
||||
Layout.topMargin: 10
|
||||
|
@ -177,7 +277,43 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: fiatPriceProvidersModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: fiatPriceCurrencyModel
|
||||
ListElement {
|
||||
data: "xmrusd"
|
||||
column1: "USD"
|
||||
}
|
||||
ListElement {
|
||||
data: "xmreur"
|
||||
column1: "EUR"
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Dynamically fill fiatPrice dropdown based on `appWindow.fiatPriceAPIs`
|
||||
var apis = appWindow.fiatPriceAPIs;
|
||||
fiatPriceProvidersModel.clear();
|
||||
|
||||
var i = 0;
|
||||
for (var api in apis){
|
||||
if (!apis.hasOwnProperty(api))
|
||||
continue;
|
||||
|
||||
fiatPriceProvidersModel.append({"column1": Utils.capitalize(api), "data": api});
|
||||
|
||||
if(api === persistentSettings.fiatPriceProvider)
|
||||
fiatPriceProviderDropDown.currentIndex = i;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
fiatPriceProviderDropDown.update();
|
||||
fiatPriceCurrencyDropdown.currentIndex = persistentSettings.fiatPriceCurrency === "xmrusd" ? 0 : 1;
|
||||
fiatPriceCurrencyDropdown.update();
|
||||
|
||||
console.log('SettingsLayout loaded');
|
||||
}
|
||||
}
|
||||
|
|
110
src/qt/prices.cpp
Normal file
110
src/qt/prices.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2014-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <QtCore>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include "utils.h"
|
||||
#include "prices.h"
|
||||
|
||||
|
||||
Prices::Prices(QNetworkAccessManager *networkAccessManager, QObject *parent)
|
||||
: QObject(parent) {
|
||||
this->m_networkAccessManager = networkAccessManager;
|
||||
}
|
||||
|
||||
void Prices::getJSON(const QString url) {
|
||||
qDebug() << QString("Fetching: %1").arg(url);
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(url));
|
||||
request.setRawHeader("User-Agent", randomUserAgent().toUtf8());
|
||||
request.setRawHeader("Content-Type", "application/json");
|
||||
|
||||
m_reply = this->m_networkAccessManager->get(request);
|
||||
|
||||
connect(m_reply, SIGNAL(finished()), this, SLOT(gotJSON()));
|
||||
}
|
||||
|
||||
void Prices::gotJSON() {
|
||||
// Check connectivity
|
||||
if (!m_reply || m_reply->error() != QNetworkReply::NoError){
|
||||
this->gotError();
|
||||
m_reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check json header
|
||||
QList<QByteArray> headerList = m_reply->rawHeaderList();
|
||||
QByteArray headerJson = m_reply->rawHeader("Content-Type");
|
||||
if(headerJson.length() <= 15){
|
||||
this->gotError("Bad Content-Type");
|
||||
m_reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString headerJsonStr = QTextCodec::codecForMib(106)->toUnicode(headerJson);
|
||||
int _contentType = headerList.indexOf("Content-Type");
|
||||
if (_contentType < 0 || !headerJsonStr.startsWith("application/json")){
|
||||
this->gotError("Bad Content-Type");
|
||||
m_reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check valid json document
|
||||
QByteArray data = m_reply->readAll();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
QString jsonString = doc.toJson(QJsonDocument::Indented);
|
||||
if (jsonString.isEmpty()){
|
||||
this->gotError("Bad JSON");
|
||||
m_reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert source url for later reference
|
||||
QUrl url = m_reply->url();
|
||||
QJsonObject docobj = doc.object();
|
||||
docobj["_url"] = url.toString();
|
||||
doc.setObject(docobj);
|
||||
|
||||
qDebug() << QString("Fetched: %1").arg(url.toString());
|
||||
|
||||
// Emit signal
|
||||
QVariantMap vMap = doc.object().toVariantMap();
|
||||
emit priceJsonReceived(vMap);
|
||||
|
||||
m_reply->deleteLater();
|
||||
}
|
||||
|
||||
void Prices::gotError() {
|
||||
this->gotError("Unknown error");
|
||||
}
|
||||
|
||||
void Prices::gotError(const QString &message) {
|
||||
qCritical() << __FUNCTION__ << ": Error: " << message;
|
||||
emit priceJsonError(message);
|
||||
}
|
30
src/qt/prices.h
Normal file
30
src/qt/prices.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef PRICES_H
|
||||
#define PRICES_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtNetwork>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QDebug>
|
||||
|
||||
class Prices : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Prices(QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
Q_INVOKABLE void getJSON(const QString url);
|
||||
void gotJSON();
|
||||
void gotError();
|
||||
void gotError(const QString &message);
|
||||
signals:
|
||||
void priceJsonReceived(QVariantMap document);
|
||||
void priceJsonError(QString message);
|
||||
|
||||
private:
|
||||
mutable QPointer<QNetworkReply> m_reply;
|
||||
QNetworkAccessManager *m_networkAccessManager;
|
||||
};
|
||||
|
||||
#endif // PRICES_H
|
|
@ -46,3 +46,84 @@ QString getAccountName(){
|
|||
accountName = "My monero Account";
|
||||
return accountName;
|
||||
}
|
||||
|
||||
QString randomUserAgent(){
|
||||
QStringList urand;
|
||||
urand << "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0"
|
||||
<< "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0"
|
||||
<< "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0"
|
||||
<< "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0"
|
||||
<< "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"
|
||||
<< "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0"
|
||||
<< "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0"
|
||||
<< "Mozilla/5.0 (Microsoft Windows NT 6.2.9200.0); rv:22.0) Gecko/20130405 Firefox/22.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:21.0.0) Gecko/20121011 Firefox/21.0.0"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20130514 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0"
|
||||
<< "Mozilla/5.0 (Windows x86; rv:19.0) Gecko/20100101 Firefox/19.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/19.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/18.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux armv7l; rv:17.0) Gecko/20100101 Firefox/17.0"
|
||||
<< "Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
|
||||
<< "Mozilla/5.0 (X11; NetBSD amd64; rv:16.0) Gecko/20121102 Firefox/16.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2"
|
||||
<< "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20120427 Firefox/15.0a1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20120910144328 Firefox/15.0.2"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1"
|
||||
<< "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:15.0) Gecko/20121011 Firefox/15.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:14.0) Gecko/20120405 Firefox/14.0a1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20120405 Firefox/14.0a1"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20120405 Firefox/14.0a1"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:14.0) Gecko/20100101 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (Windows; U; Windows NT 6.1; WOW64; en-US; rv:2.0.4) Gecko/20120718 AskTbAVR-IDW/3.12.5.17700 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.0; rv:14.0) Gecko/20100101 Firefox/14.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/13.0.1"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0"
|
||||
<< "Mozilla/5.0 (Windows NT 6.1; de;rv:12.0) Gecko/20120403211507 Firefox/12.0";
|
||||
|
||||
// @TODO: Qt 5.10 - QRandomGenerator
|
||||
int irand = rand() % urand.length();
|
||||
return urand.at(irand);
|
||||
}
|
||||
|
|
|
@ -35,5 +35,6 @@
|
|||
bool fileExists(QString path);
|
||||
QString getAccountName();
|
||||
const static QRegExp reURI = QRegExp("^\\w+:\\/\\/([\\w+\\-?\\-_\\-=\\-&]+)");
|
||||
QString randomUserAgent();
|
||||
|
||||
#endif // UTILS_H
|
||||
|
|
Loading…
Reference in a new issue