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.Wallet 1.0
|
||||||
import moneroComponents.NetworkType 1.0
|
import moneroComponents.NetworkType 1.0
|
||||||
import moneroComponents.Clipboard 1.0
|
import moneroComponents.Clipboard 1.0
|
||||||
|
import FontAwesome 1.0
|
||||||
|
|
||||||
import "components" as MoneroComponents
|
import "components" as MoneroComponents
|
||||||
import "components/effects/" as MoneroEffects
|
import "components/effects/" as MoneroEffects
|
||||||
|
@ -44,10 +45,13 @@ Rectangle {
|
||||||
property alias unlockedBalanceLabelVisible: unlockedBalanceLabel.visible
|
property alias unlockedBalanceLabelVisible: unlockedBalanceLabel.visible
|
||||||
property alias balanceLabelText: balanceLabel.text
|
property alias balanceLabelText: balanceLabel.text
|
||||||
property alias balanceText: balanceText.text
|
property alias balanceText: balanceText.text
|
||||||
|
property alias balanceTextFiat: balanceTextFiat.text
|
||||||
|
property alias unlockedBalanceTextFiat: unlockedBalanceTextFiat.text
|
||||||
property alias networkStatus : networkStatus
|
property alias networkStatus : networkStatus
|
||||||
property alias progressBar : progressBar
|
property alias progressBar : progressBar
|
||||||
property alias daemonProgressBar : daemonProgressBar
|
property alias daemonProgressBar : daemonProgressBar
|
||||||
property alias minutesToUnlockTxt: unlockedBalanceLabel.text
|
property alias minutesToUnlockTxt: unlockedBalanceLabel.text
|
||||||
|
property bool fiatBalance: false
|
||||||
property int titleBarHeight: 50
|
property int titleBarHeight: 50
|
||||||
property string copyValue: ""
|
property string copyValue: ""
|
||||||
Clipboard { id: clipboard }
|
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 {
|
Item {
|
||||||
|
@ -214,7 +238,7 @@ Rectangle {
|
||||||
width: 50
|
width: 50
|
||||||
|
|
||||||
MoneroComponents.TextPlain {
|
MoneroComponents.TextPlain {
|
||||||
visible: !isMobile
|
visible: !(fiatBalance && persistentSettings.fiatPriceEnabled)
|
||||||
id: balanceText
|
id: balanceText
|
||||||
themeTransition: false
|
themeTransition: false
|
||||||
anchors.left: parent.left
|
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 {
|
MoneroComponents.TextPlain {
|
||||||
id: unlockedBalanceText
|
id: unlockedBalanceText
|
||||||
visible: true
|
visible: !(fiatBalance && persistentSettings.fiatPriceEnabled)
|
||||||
themeTransition: false
|
themeTransition: false
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 20
|
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 {
|
MoneroComponents.Label {
|
||||||
id: unlockedBalanceLabel
|
id: unlockedBalanceLabel
|
||||||
visible: true
|
visible: true
|
||||||
|
|
17
js/Utils.js
17
js/Utils.js
|
@ -154,3 +154,20 @@ function qmlEach(item, properties, ignoredObjectNames, arr){
|
||||||
|
|
||||||
return 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 <QQmlApplicationEngine>
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
#include "qt/utils.h"
|
#include "qt/utils.h"
|
||||||
#include "qt/mime.h"
|
#include "qt/mime.h"
|
||||||
#include "src/qt/KeysFiles.h"
|
#include "src/qt/KeysFiles.h"
|
||||||
|
#include "qt/prices.h"
|
||||||
|
|
||||||
// IOS exclusions
|
// IOS exclusions
|
||||||
#ifndef Q_OS_IOS
|
#ifndef Q_OS_IOS
|
||||||
|
@ -356,6 +358,11 @@ int main(int argc, char *argv[])
|
||||||
builtWithScanner = true;
|
builtWithScanner = true;
|
||||||
#endif
|
#endif
|
||||||
engine.rootContext()->setContextProperty("builtWithScanner", builtWithScanner);
|
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)
|
// Load main window (context properties needs to be defined obove this line)
|
||||||
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
|
||||||
if (engine.rootObjects().isEmpty())
|
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 int appEpoch: Math.floor((new Date).getTime() / 1000)
|
||||||
property bool themeTransition: false
|
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: {
|
property string remoteNodeService: {
|
||||||
// support user-defined remote node aggregators
|
// support user-defined remote node aggregators
|
||||||
if(persistentSettings.remoteNodeService){
|
if(persistentSettings.remoteNodeService){
|
||||||
|
@ -394,6 +414,10 @@ ApplicationWindow {
|
||||||
middlePanel.balanceText = balance;
|
middlePanel.balanceText = balance;
|
||||||
leftPanel.balanceText = balance;
|
leftPanel.balanceText = balance;
|
||||||
|
|
||||||
|
if (persistentSettings.fiatPriceEnabled) {
|
||||||
|
appWindow.fiatApiUpdateBalance(balance, balance_unlocked);
|
||||||
|
}
|
||||||
|
|
||||||
var accountLabel = currentWallet.getSubaddressLabel(currentWallet.currentSubaddressAccount, 0);
|
var accountLabel = currentWallet.getSubaddressLabel(currentWallet.currentSubaddressAccount, 0);
|
||||||
leftPanel.balanceLabelText = qsTr("Balance (#%1%2)").arg(currentWallet.currentSubaddressAccount).arg(accountLabel === "" ? "" : (" – " + accountLabel));
|
leftPanel.balanceLabelText = qsTr("Balance (#%1%2)").arg(currentWallet.currentSubaddressAccount).arg(accountLabel === "" ? "" : (" – " + accountLabel));
|
||||||
}
|
}
|
||||||
|
@ -1102,6 +1126,7 @@ ApplicationWindow {
|
||||||
rootItem.state = "wizard"
|
rootItem.state = "wizard"
|
||||||
// reset balance
|
// reset balance
|
||||||
leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0);
|
leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0);
|
||||||
|
fiatApiUpdateBalance(0, 0);
|
||||||
// disable timers
|
// disable timers
|
||||||
userInActivityTimer.running = false;
|
userInActivityTimer.running = false;
|
||||||
simpleModeConnectionTimer.running = false;
|
simpleModeConnectionTimer.running = false;
|
||||||
|
@ -1126,6 +1151,143 @@ ApplicationWindow {
|
||||||
flags: persistentSettings.customDecorations ? Windows.flagsCustomDecorations : Windows.flags
|
flags: persistentSettings.customDecorations ? Windows.flagsCustomDecorations : Windows.flags
|
||||||
onWidthChanged: x -= 0
|
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: {
|
Component.onCompleted: {
|
||||||
x = (Screen.width - width) / 2
|
x = (Screen.width - width) / 2
|
||||||
y = (Screen.height - maxWindowHeight) / 2
|
y = (Screen.height - maxWindowHeight) / 2
|
||||||
|
@ -1137,6 +1299,7 @@ ApplicationWindow {
|
||||||
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
|
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
|
||||||
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
|
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
|
||||||
IPC.uriHandler.connect(onUriHandler);
|
IPC.uriHandler.connect(onUriHandler);
|
||||||
|
Prices.priceJsonReceived.connect(appWindow.fiatApiJsonReceived);
|
||||||
|
|
||||||
if(typeof daemonManager != "undefined") {
|
if(typeof daemonManager != "undefined") {
|
||||||
daemonManager.daemonStarted.connect(onDaemonStarted);
|
daemonManager.daemonStarted.connect(onDaemonStarted);
|
||||||
|
@ -1144,8 +1307,6 @@ ApplicationWindow {
|
||||||
daemonManager.daemonStopped.connect(onDaemonStopped);
|
daemonManager.daemonStopped.connect(onDaemonStopped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Connect app exit to qml window exit handling
|
// Connect app exit to qml window exit handling
|
||||||
mainApp.closing.connect(appWindow.close);
|
mainApp.closing.connect(appWindow.close);
|
||||||
|
|
||||||
|
@ -1177,6 +1338,11 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUpdates();
|
checkUpdates();
|
||||||
|
|
||||||
|
if(persistentSettings.fiatPriceEnabled){
|
||||||
|
appWindow.fiatApiRefresh();
|
||||||
|
appWindow.fiatTimerStart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
|
@ -1223,6 +1389,10 @@ ApplicationWindow {
|
||||||
property bool showPid: false
|
property bool showPid: false
|
||||||
property bool blackTheme: true
|
property bool blackTheme: true
|
||||||
|
|
||||||
|
property bool fiatPriceEnabled: false
|
||||||
|
property string fiatPriceProvider: "kraken"
|
||||||
|
property string fiatPriceCurrency: "xmrusd"
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
MoneroComponents.Style.blackTheme = persistentSettings.blackTheme
|
MoneroComponents.Style.blackTheme = persistentSettings.blackTheme
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ HEADERS += \
|
||||||
src/qt/mime.h \
|
src/qt/mime.h \
|
||||||
src/qt/KeysFiles.h \
|
src/qt/KeysFiles.h \
|
||||||
src/qt/utils.h
|
src/qt/utils.h
|
||||||
|
src/qt/utils.h \
|
||||||
|
src/qt/prices.h
|
||||||
|
|
||||||
SOURCES += main.cpp \
|
SOURCES += main.cpp \
|
||||||
filter.cpp \
|
filter.cpp \
|
||||||
|
@ -98,6 +100,8 @@ SOURCES += main.cpp \
|
||||||
src/qt/mime.cpp \
|
src/qt/mime.cpp \
|
||||||
src/qt/KeysFiles.cpp \
|
src/qt/KeysFiles.cpp \
|
||||||
src/qt/utils.cpp
|
src/qt/utils.cpp
|
||||||
|
src/qt/utils.cpp \
|
||||||
|
src/qt/prices.cpp
|
||||||
|
|
||||||
CONFIG(DISABLE_PASS_STRENGTH_METER) {
|
CONFIG(DISABLE_PASS_STRENGTH_METER) {
|
||||||
HEADERS -= src/zxcvbn-c/zxcvbn.h
|
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 {
|
MoneroComponents.StandardButton {
|
||||||
visible: !persistentSettings.customDecorations
|
visible: !persistentSettings.customDecorations
|
||||||
Layout.topMargin: 10
|
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: {
|
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');
|
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";
|
accountName = "My monero Account";
|
||||||
return accountName;
|
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);
|
bool fileExists(QString path);
|
||||||
QString getAccountName();
|
QString getAccountName();
|
||||||
const static QRegExp reURI = QRegExp("^\\w+:\\/\\/([\\w+\\-?\\-_\\-=\\-&]+)");
|
const static QRegExp reURI = QRegExp("^\\w+:\\/\\/([\\w+\\-?\\-_\\-=\\-&]+)");
|
||||||
|
QString randomUserAgent();
|
||||||
|
|
||||||
#endif // UTILS_H
|
#endif // UTILS_H
|
||||||
|
|
Loading…
Reference in a new issue