diff --git a/.gitignore b/.gitignore index 58bff58..2106659 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,10 @@ # Coingecko export data /coingecko.json /coingecko-original.json +/coingecko-currencies.json + +# Secret files +/secrets.php # Compiled files /js/ diff --git a/README.md b/README.md index 191c011..b644dc5 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ This is a fork of the original project by [nice42q](https://github.com/nice42q/m To convert XMR to a fiat currency, simply visit: ``` -https://monerooo.private.coffee/?in=USD +https://calc.revuo-xmr.com/?in=USD ``` Replace `USD` with your preferred currency code. You can also specify the amount of XMR to convert: ``` -https://monerooo.private.coffee/?in=USD&xmr=1 +https://calc.revuo-xmr.com/?in=USD&xmr=1 ``` The `xmr` parameter specifies the amount of XMR to convert. @@ -55,7 +55,7 @@ The `xmr` parameter specifies the amount of XMR to convert. To convert a fiat currency to XMR, visit: ``` -https://monerooo.private.coffee/?in=USD&fiat=1&direction=1 +https://calc.revuo-xmr.com/?in=USD&fiat=1&direction=1 ``` The `fiat` parameter specifies the amount of fiat currency to convert. The `direction` parameter is set to `1` to indicate conversion from fiat to XMR. @@ -64,7 +64,7 @@ The `fiat` parameter specifies the amount of fiat currency to convert. The `dire 1. Select field A1. 2. Go to `Data` → `Link to external data...`. -3. Input the URL `https://moner.ooo/` and confirm. +3. Input the URL `calc.revuo-xmr.com` and confirm. 4. Confirm the import options and select `HTML_1`. For an example, see [kuno.anne.media](https://kuno.anne.media/donate/onml/). @@ -82,7 +82,7 @@ For an example, see [kuno.anne.media](https://kuno.anne.media/donate/onml/). 1. Clone the repository: ```sh - git clone https://github.com/nice42q/moner.ooo.git + git clone https://github.com/rottenwheel/moner.ooo.git cd moner.ooo ``` @@ -117,6 +117,18 @@ return [ ]; ``` +Create a `secrets.php` file in the root directory to store CoinGecko API keys. Example: + +```php + 'CG-xxxx', + 'coingecko_key_is_demo' => true, +]; +``` + +**Note:** The `secrets.dist.php` file should not be accessible from the web server. + ### Fetching Exchange Rates Exchange rates are fetched from the CoinGecko API. The `coingecko.php` file handles the API requests and attempts to update exchange rates every 5 seconds. Due to the rate limits of the CoinGecko API, actual update intervals may vary and are closer to 60 seconds. diff --git a/coingecko.php b/coingecko.php index 878e9f3..1023e32 100644 --- a/coingecko.php +++ b/coingecko.php @@ -6,78 +6,121 @@ date_default_timezone_set('Europe/Berlin'); // Define currencies that should *not* be included in the list $excludedCurrencies = ['bits', 'sats']; -// Fetch the previously stored data -$previousData = json_decode(file_get_contents("coingecko.json"), true); -$output = $previousData; +// Fetch JSON data from a file and decode it +function fetchJson($filename) { + return json_decode(file_get_contents($filename), true); +} + +// Make an API request and return the JSON response +function makeApiRequest($url) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $json = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode == 200) { + return json_decode($json, true); + } + + return null; +} + +// Get CoinGecko key URL parameter +function getCoinGeckoApiUrl($path, $params = []) { + $secrets = require_once 'secrets.php'; + $key = $secrets['coingecko_api_key']; + $demo = $secrets['coingecko_key_is_demo']; + + $paramName = $demo ? 'x_cg_demo_api_key' : 'x_cg_pro_api_key'; + $baseUrl = $demo ? "https://api.coingecko.com/api/v3/" : "https://pro-api.coingecko.com/api/v3/"; + + $params[$paramName] = $key; + $url = $baseUrl . $path; + + if (!empty($params)) { + $url .= '?' . http_build_query($params); + } + + return $url; +} $currentTime = time(); -// Check if five seconds have passed since the last update -if (($currentTime - $previousData['time']) >= 5) { - // Fetch the available currencies from CoinGecko API - $availableCurrenciesApi = "https://api.coingecko.com/api/v3/simple/supported_vs_currencies"; +// Fetch list of available currencies from CoinGecko API +// Available currencies are cached for 24 hours +function fetchAvailableCurrencies() { + $cacheFile = 'coingecko-currencies.json'; + $cacheTime = 86400; - $currenciesCh = curl_init($availableCurrenciesApi); - curl_setopt($currenciesCh, CURLOPT_RETURNTRANSFER, true); - $availableCurrenciesJson = curl_exec($currenciesCh); + // Return cached data if it exists and is less than 24 hours old + if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) { + return fetchJson($cacheFile); + } - $currenciesHttpCode = curl_getinfo($currenciesCh, CURLINFO_HTTP_CODE); + $apiUrl = getCoinGeckoApiUrl('simple/supported_vs_currencies'); + $data = makeApiRequest($apiUrl); - curl_close($currenciesCh); + if ($data) { + file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT)); + return $data; + } - if ($currenciesHttpCode == 200) { - $availableCurrencies = json_decode($availableCurrenciesJson, true); - } else { - $availableCurrencies = array_keys($previousData); - unset($availableCurrencies[array_search('time', $availableCurrencies)]); - } + return null; +} - // Remove excluded currencies - $availableCurrencies = array_diff($availableCurrencies, $excludedCurrencies); +// Fetch currency data from CoinGecko API +function fetchCurrencyData($currencies) { + $apiUrl = getCoinGeckoApiUrl('simple/price', ['ids' => 'monero', 'vs_currencies' => implode(',', array_map('strtolower', $currencies))]); + return makeApiRequest($apiUrl); +} - $currencies = array_map('strtoupper', $availableCurrencies); +$currencyFile = 'coingecko.json'; +$originalFile = 'coingecko-original.json'; - // Fetch the latest data from CoinGecko API - $apiUrl = 'https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=' . implode('%2C', array_map('strtolower', $currencies)) . '&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&include_last_updated_at=true'; +// Function to process currency data +function processCurrencyData($availableCurrencies, $previousData, $currentTime, $excludedCurrencies) { + // Remove excluded currencies + $availableCurrencies = array_diff($availableCurrencies, $excludedCurrencies); + $currencies = array_map('strtoupper', $availableCurrencies); - $ch = curl_init($apiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $json = curl_exec($ch); + // Fetch the latest data from CoinGecko API + $fetchedData = fetchCurrencyData($currencies); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - // If the request worked and was not rate-limited - if ($httpCode == 200) { - // Decode the fetched data - $fetchedData = json_decode($json, true); + if ($fetchedData) { $moneroData = $fetchedData['monero']; - - // Initialize new data array $newData = ['time' => $currentTime]; - + // Update the data for each currency foreach ($currencies as $currency) { $currencyLower = strtolower($currency); - if (isset($moneroData[$currencyLower])) { - $newData[$currencyLower] = [ - 'lastValue' => $moneroData[$currencyLower], - 'lastDate' => $currentTime - ]; - } else { - $newData[$currencyLower] = [ - 'lastValue' => $previousData[$currencyLower]['lastValue'] ?? null, - 'lastDate' => $previousData[$currencyLower]['lastDate'] ?? null - ]; - } + $newData[$currencyLower] = [ + 'lastValue' => $moneroData[$currencyLower] ?? $previousData[$currencyLower]['lastValue'] ?? null, + 'lastDate' => $currentTime + ]; } - - // Save the new data to JSON files - file_put_contents("coingecko.json", json_encode($newData, JSON_PRETTY_PRINT)); - file_put_contents("coingecko-original.json", json_encode($moneroData, JSON_PRETTY_PRINT)); - - $output = $newData; - } + + return $newData; + } + + return null; +} + +$previousData = fetchJson($currencyFile); +$output = $previousData; + +// Check if five seconds have passed since the last update +if (($currentTime - $previousData['time']) >= 5) { + $availableCurrencies = fetchAvailableCurrencies(); + if ($availableCurrencies !== null) { + $output = processCurrencyData($availableCurrencies, $previousData, $currentTime, $excludedCurrencies); + + // Save the data if the API call was successful + if ($output !== null) { + file_put_contents($currencyFile, json_encode($output, JSON_PRETTY_PRINT)); + file_put_contents($originalFile, json_encode($output, JSON_PRETTY_PRINT)); + } + } } // Output the data diff --git a/config.dist.php b/config.dist.php index bef512a..17341a2 100644 --- a/config.dist.php +++ b/config.dist.php @@ -2,13 +2,13 @@ return [ 'attribution' => '', // Custom attribution HTML to show in the info text 'footer_links' => [ // Custom links to show in the footer - ['text' => 'Clearnet', 'url' => 'https://monerooo.private.coffee/'], - ['text' => 'Tor', 'url' => 'http://monerooo.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion'], - ['text' => 'Private.coffee', 'url' => 'https://private.coffee'] + ['text' => 'Clearnet', 'url' => 'https://calc.revuo-xmr.com'], + ['text' => 'Tor', 'url' => 'http://calc.revuo75joezkbeitqmas4ab6spbrkr4vzbhjmeuv75ovrfqfp47mtjid.onion'], + ['text' => 'Revuo Monero', 'url' => 'https://www.revuo-xmr.com/'] ], 'preferred_currencies' => [ // Currencies that should be displayed at the top of the lists 'usd', 'eur', 'gbp', 'cad', 'btc', 'eth', 'ltc' ], - 'github_url' => 'https://git.private.coffee/kumi/moner.ooo/', // URL to the GitHub repository - replace if you forked the project + 'github_url' => 'https://github.com/rottenwheel/moner.ooo/', // URL to the GitHub repository - replace if you forked the project 'servers_guru' => false, // Show the "Servers Guru" attribution link in the info text - here for upstream compatibility -]; \ No newline at end of file +]; diff --git a/img/apple-touch-icon-114x114.png b/img/apple-touch-icon-114x114.png deleted file mode 100644 index 8db8aa1..0000000 Binary files a/img/apple-touch-icon-114x114.png and /dev/null differ diff --git a/img/apple-touch-icon-120x120.png b/img/apple-touch-icon-120x120.png deleted file mode 100644 index 21c9ff2..0000000 Binary files a/img/apple-touch-icon-120x120.png and /dev/null differ diff --git a/img/apple-touch-icon-144x144.png b/img/apple-touch-icon-144x144.png deleted file mode 100644 index 067948a..0000000 Binary files a/img/apple-touch-icon-144x144.png and /dev/null differ diff --git a/img/apple-touch-icon-152x152.png b/img/apple-touch-icon-152x152.png deleted file mode 100644 index 9c34a26..0000000 Binary files a/img/apple-touch-icon-152x152.png and /dev/null differ diff --git a/img/apple-touch-icon-57x57.png b/img/apple-touch-icon-57x57.png deleted file mode 100644 index 19f17cd..0000000 Binary files a/img/apple-touch-icon-57x57.png and /dev/null differ diff --git a/img/apple-touch-icon-60x60.png b/img/apple-touch-icon-60x60.png deleted file mode 100644 index 0e54ecb..0000000 Binary files a/img/apple-touch-icon-60x60.png and /dev/null differ diff --git a/img/apple-touch-icon-72x72.png b/img/apple-touch-icon-72x72.png deleted file mode 100644 index 6d87299..0000000 Binary files a/img/apple-touch-icon-72x72.png and /dev/null differ diff --git a/img/apple-touch-icon-76x76.png b/img/apple-touch-icon-76x76.png deleted file mode 100644 index 450c6ac..0000000 Binary files a/img/apple-touch-icon-76x76.png and /dev/null differ diff --git a/img/favicon-114x114.png b/img/favicon-114x114.png new file mode 100644 index 0000000..6e2838c Binary files /dev/null and b/img/favicon-114x114.png differ diff --git a/img/favicon-120x120.png b/img/favicon-120x120.png new file mode 100644 index 0000000..14bf9b5 Binary files /dev/null and b/img/favicon-120x120.png differ diff --git a/img/favicon-128.png b/img/favicon-128.png deleted file mode 100644 index 3749e04..0000000 Binary files a/img/favicon-128.png and /dev/null differ diff --git a/img/favicon-128x128.png b/img/favicon-128x128.png new file mode 100644 index 0000000..7f9908b Binary files /dev/null and b/img/favicon-128x128.png differ diff --git a/img/favicon-144x144.png b/img/favicon-144x144.png new file mode 100644 index 0000000..7ec7fe6 Binary files /dev/null and b/img/favicon-144x144.png differ diff --git a/img/favicon-152x152.png b/img/favicon-152x152.png new file mode 100644 index 0000000..84e648f Binary files /dev/null and b/img/favicon-152x152.png differ diff --git a/img/favicon-16x16.png b/img/favicon-16x16.png index 8286439..01dd283 100644 Binary files a/img/favicon-16x16.png and b/img/favicon-16x16.png differ diff --git a/img/favicon-196x196.png b/img/favicon-196x196.png index e47bab7..982838a 100644 Binary files a/img/favicon-196x196.png and b/img/favicon-196x196.png differ diff --git a/img/favicon-32x32.png b/img/favicon-32x32.png index 9b4639d..017bc24 100644 Binary files a/img/favicon-32x32.png and b/img/favicon-32x32.png differ diff --git a/img/favicon-57x57.png b/img/favicon-57x57.png new file mode 100644 index 0000000..bef95fe Binary files /dev/null and b/img/favicon-57x57.png differ diff --git a/img/favicon-60x60.png b/img/favicon-60x60.png new file mode 100644 index 0000000..555ae31 Binary files /dev/null and b/img/favicon-60x60.png differ diff --git a/img/favicon-72x72.png b/img/favicon-72x72.png new file mode 100644 index 0000000..70461c1 Binary files /dev/null and b/img/favicon-72x72.png differ diff --git a/img/favicon-76x76.png b/img/favicon-76x76.png new file mode 100644 index 0000000..d5f643e Binary files /dev/null and b/img/favicon-76x76.png differ diff --git a/img/favicon-96x96.png b/img/favicon-96x96.png index 69847e6..8ec1ca2 100644 Binary files a/img/favicon-96x96.png and b/img/favicon-96x96.png differ diff --git a/img/favicon.ico b/img/favicon.ico deleted file mode 100644 index 6e9491d..0000000 Binary files a/img/favicon.ico and /dev/null differ diff --git a/img/mstile-144x144.png b/img/mstile-144x144.png deleted file mode 100644 index 067948a..0000000 Binary files a/img/mstile-144x144.png and /dev/null differ diff --git a/img/mstile-150x150.png b/img/mstile-150x150.png deleted file mode 100644 index 030af68..0000000 Binary files a/img/mstile-150x150.png and /dev/null differ diff --git a/img/mstile-310x150.png b/img/mstile-310x150.png deleted file mode 100644 index 3f5e3bc..0000000 Binary files a/img/mstile-310x150.png and /dev/null differ diff --git a/img/mstile-310x310.png b/img/mstile-310x310.png deleted file mode 100644 index 9704b73..0000000 Binary files a/img/mstile-310x310.png and /dev/null differ diff --git a/img/mstile-70x70.png b/img/mstile-70x70.png deleted file mode 100644 index 3749e04..0000000 Binary files a/img/mstile-70x70.png and /dev/null differ diff --git a/img/qr.jpg b/img/qr.jpg new file mode 100644 index 0000000..a8f6e34 Binary files /dev/null and b/img/qr.jpg differ diff --git a/index.php b/index.php index 25d63a3..5284a37 100644 --- a/index.php +++ b/index.php @@ -20,13 +20,14 @@ $api_cg = json_decode(file_get_contents('coingecko.json'), true); // Configuration file $config = []; if (file_exists('config.php')) { - $config = require 'config.php'; + $config = require_once 'config.php'; } $display_servers_guru = isset($config['servers_guru']) && $config['servers_guru'] === true; $attribution = isset($config['attribution']) ? $config['attribution'] : ''; $preferred_currencies = isset($config['preferred_currencies']) ? $config['preferred_currencies'] : []; -$github_url = isset($config['github_url']) ? $config['github_url'] : 'https://git.private.coffee/kumi/moner.ooo/'; +$github_url = isset($config['github_url']) ? $config['github_url'] : 'https://github.com/rottenwheel/moner.ooo/'; +$footer_html = isset($config['footer_html']) ? $config['footer_html'] : ''; // Extract the keys $currencies = array_map('strtoupper', array_keys($api_cg)); @@ -77,7 +78,7 @@ foreach ($language_files as $language_file) { // Calculation through GET parameters -$xmr_in = isset($_GET["in"]) ? strtoupper(htmlspecialchars($_GET["in"])) : 'EUR'; +$xmr_in = isset($_GET["in"]) ? strtoupper(htmlspecialchars($_GET["in"])) : 'USD'; $xmr_amount = isset($_GET["xmr"]) ? floatval($_GET["xmr"]) : 1; $fiat_amount = isset($_GET["fiat"]) ? floatval($_GET["fiat"]) : ''; $conversion_direction = isset($_GET["direction"]) ? intval($_GET["direction"]) : 0; @@ -105,5 +106,5 @@ foreach (array_reverse($preferred_currencies) as $currency) { } // Output the HTML -require 'templates/index.php'; -?> \ No newline at end of file +require_once 'templates/index.php'; +?> diff --git a/package-lock.json b/package-lock.json index e4bb07c..70ae82f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,30 +124,10 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "license": "MIT" }, "node_modules/@types/json-schema": { @@ -379,10 +359,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "license": "MIT", "peerDependencies": { "acorn": "^8" @@ -833,9 +813,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -1587,9 +1567,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "license": "ISC" }, "node_modules/pkg-dir": { @@ -1605,9 +1585,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -1626,8 +1606,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1965,9 +1945,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2248,21 +2228,20 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/secrets.dist.php b/secrets.dist.php index f09232a..831b1fe 100644 --- a/secrets.dist.php +++ b/secrets.dist.php @@ -1,7 +1,5 @@ 'CG-xxxx', 'coingecko_key_is_demo' => true, diff --git a/src/css/custom.css b/src/css/custom.css index 702ad95..549d3d0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,9 +1,8 @@ html { - width: 100%; - height: 100%; - background-image: linear-gradient(to bottom right, #013c4a 0, #193e4c 44%, #004b5b 100%) !important; - color: #fff; + background-color: black; + color: #cecece; font-style: normal; + font-family: Arial, sans-serif; } body { @@ -16,7 +15,7 @@ p.fiat-info { p.fiat-info span, a.fiat-tooltip { - color: white; + color: #cecece; } .btn { @@ -48,17 +47,17 @@ input.form-control { .equals-box { text-align: center; - color: #e9ecef; + color: #ff6600; font-weight: 800; font-size: 42px; padding-bottom: 1; } .btn-arrow { - border: 1px solid white; + border: 1px solid #ff6600; border-radius: 10px; font-size: 38px !important; - color: white; + color: #ff6600; padding: 0; cursor: pointer; } @@ -70,13 +69,13 @@ input.form-control { .btn-equals { font-size: 38px !important; - color: white; + color: #ff6600; padding: 0; cursor: default; } .btn-equals:hover { - color: black; + color: #cecece; } .equals-text { @@ -84,7 +83,7 @@ input.form-control { } p { - color: #e9ecef; + color: #cecece; } .gold { @@ -122,6 +121,18 @@ p { .btn { min-width: 38px; } + + #donation-qr { + width: auto; + height: 80vh; + max-width: 250px; + max-height: 250px; + } + + #donation-qr-container { + height: auto; + padding: 20px 0; + } } .bs-tooltip-auto { @@ -136,4 +147,19 @@ p { top: -1px; border-width: 0.4rem 0.4rem 0; border-top-color: #000; +} + +#donation-qr-container { + display: none; + justify-content: center; + align-items: center; +} + +#donation-qr { + width: 100%; + height: auto; +} + +#donation-qr-toggle:checked ~ #donation-qr-container { + display: flex; } \ No newline at end of file diff --git a/src/js/main.js b/src/js/main.js index 7390237..67758ad 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,163 +1,189 @@ import 'bootstrap/dist/css/bootstrap.min.css'; -import 'bootstrap/dist/js/bootstrap.bundle.min'; - import '../css/custom.css'; -import Tooltip from "bootstrap/js/dist/tooltip"; -var tooltipTriggerList = [].slice.call( - document.querySelectorAll('[data-toggle="tooltip"]') -); -var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { - return new Tooltip(tooltipTriggerEl, { placement: "top" }); -}); +import Tooltip from 'bootstrap/js/dist/tooltip'; + +///// +let inactivityTimeout = 30; // in seconds +let fetchInterval = 30; // in seconds +///// + +inactivityTimeout = inactivityTimeout * 1000; +fetchInterval = fetchInterval * 1000; + +const tooltipTriggerList = Array.from(document.querySelectorAll('[data-toggle="tooltip"]')); +const tooltipList = tooltipTriggerList.map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl, { placement: 'top' })); console.log(tooltipList); let lastModifiedField = 'xmr'; +const exchangeRates = {}; -var exchangeRates = {}; +const runConvert = () => + lastModifiedField === 'xmr' ? xmrConvert() : fiatConvert(); -document.addEventListener('DOMContentLoaded', function () { - const copyXMRBtn = document.getElementById('copyXMRBtn'); - const copyFiatBtn = document.getElementById('copyFiatBtn'); - const xmrInput = document.getElementById('xmrInput'); - const fiatInput = document.getElementById('fiatInput'); - const selectBox = document.getElementById('selectBox'); - const convertXMRToFiatBtn = document.getElementById('convertXMRToFiat'); - const convertFiatToXMRBtn = document.getElementById('convertFiatToXMR'); - const fiatButtons = document.querySelectorAll('.fiat-btn'); +let updateInterval +const startFetching = () => updateInterval = setInterval(fetchUpdatedExchangeRates, fetchInterval); +const stopFetching = () => { + clearInterval(updateInterval) + updateInterval = null; +}; - // Add event listeners for the currency buttons - fiatButtons.forEach(button => { - button.addEventListener('click', (e) => { - e.preventDefault(); - selectBox.value = button.textContent; - if (lastModifiedField === 'xmr') { - xmrConvert(); - } else { - fiatConvert(); - } - history.pushState(null, '', `?in=${button.textContent}`); - }); - }); +let lastActivity = Date.now() - // Add event listeners for the copy buttons - copyXMRBtn.addEventListener('click', copyToClipBoardXMR); - copyFiatBtn.addEventListener('click', copyToClipBoardFiat); - - // Add event listeners for the XMR input field - xmrInput.addEventListener('change', () => xmrConvert(xmrInput.value)); - xmrInput.addEventListener('keyup', () => { - xmrInput.value = xmrInput.value.replace(/[^\.^,\d]/g, ''); - xmrInput.value = xmrInput.value.replace(/\,/, '.'); - if (xmrInput.value.split('.').length > 2) { - xmrInput.value = xmrInput.value.slice(0, -1); - } - xmrConvert(xmrInput.value); - }); - xmrInput.addEventListener('input', () => { - lastModifiedField = 'xmr'; - }); - - // Add event listeners for the fiat input field - fiatInput.addEventListener('change', () => fiatConvert(fiatInput.value)); - fiatInput.addEventListener('keyup', () => { - fiatInput.value = fiatInput.value.replace(/[^\.^,\d]/g, ''); - fiatInput.value = fiatInput.value.replace(/\,/, '.'); - if (fiatInput.value.split('.').length > 2) { - fiatInput.value = fiatInput.value.slice(0, -1); - } - fiatConvert(fiatInput.value); - }); - fiatInput.addEventListener('input', () => { - lastModifiedField = 'fiat'; - }); - - // Add event listener for the select box to change the conversion - selectBox.addEventListener('change', () => { - if (lastModifiedField === 'xmr') { - xmrConvert(selectBox.value) +const resetActivity = () => lastActivity = Date.now() +const checkInactivity = () => { + if (Date.now() - lastActivity > inactivityTimeout) { + console.log('Inactivity detected, stopping exchange rate updates'); + stopFetching(); } else { - fiatConvert(selectBox.value) + requestAnimationFrame(checkInactivity); } - }); +} - // Hide the conversion buttons if JavaScript is enabled - convertXMRToFiatBtn.style.display = 'none'; - convertFiatToXMRBtn.style.display = 'none'; +document.addEventListener('focus', () => { + const focused = document.hasFocus(); + console.log(`Page is ${focused ? 'visible' : 'hidden'}`); - // Fetch updated exchange rates immediately, then every 5 seconds - fetchUpdatedExchangeRates(); - setInterval(fetchUpdatedExchangeRates, 5000); + if (focused && !updateInterval) { + console.log('Restarting exchange rate updates'); + startFetching(); + + resetActivity(); + requestAnimationFrame(checkInactivity); + } else { + stopFetching(); + } +}); +window.addEventListener('mousemove', resetActivity); +window.addEventListener('keydown', resetActivity); +window.addEventListener('touchstart', resetActivity); + +requestAnimationFrame(checkInactivity); + +document.addEventListener('DOMContentLoaded', () => { + const copyXMRBtn = document.getElementById('copyXMRBtn'); + const copyFiatBtn = document.getElementById('copyFiatBtn'); + const xmrInput = document.getElementById('xmrInput'); + const fiatInput = document.getElementById('fiatInput'); + const selectBox = document.getElementById('selectBox'); + const convertXMRToFiatBtn = document.getElementById('convertXMRToFiat'); + const convertFiatToXMRBtn = document.getElementById('convertFiatToXMR'); + const fiatButtons = document.querySelectorAll('.fiat-btn'); + + // Add event listeners for the currency buttons + fiatButtons.forEach(button => { + button.addEventListener('click', (e) => { + e.preventDefault(); + selectBox.value = button.textContent; + runConvert(); + history.pushState(null, '', `?in=${button.textContent}`); + }); + }); + + // Add event listeners for the copy buttons + copyXMRBtn.addEventListener('click', copyToClipboardXMR); + copyFiatBtn.addEventListener('click', copyToClipboardFiat); + + // Add event listeners for the XMR input field + xmrInput.addEventListener('change', xmrConvert); + xmrInput.addEventListener('keyup', () => { + xmrInput.value = xmrInput.value.replace(/[^\.^,\d]/g, '').replace(/\,/, '.'); + if (xmrInput.value.split('.').length > 2) { + xmrInput.value = xmrInput.value.slice(0, -1); + } + xmrConvert(); + }); + xmrInput.addEventListener('input', () => lastModifiedField = 'xmr'); + + // Add event listeners for the fiat input field + fiatInput.addEventListener('change', fiatConvert); + fiatInput.addEventListener('keyup', () => { + fiatInput.value = fiatInput.value.replace(/[^\.^,\d]/g, '').replace(/\,/, '.'); + if (fiatInput.value.split('.').length > 2) { + fiatInput.value = fiatInput.value.slice(0, -1); + } + fiatConvert(); + }); + fiatInput.addEventListener('input', () => lastModifiedField = 'fiat'); + + // Add event listener for the select box to change the conversion + selectBox.addEventListener('change', runConvert); + + // Hide the conversion buttons if JavaScript is enabled + convertXMRToFiatBtn.style.display = 'none'; + convertFiatToXMRBtn.style.display = 'none'; + + // Fetch updated exchange rates immediately, then every 5 seconds + fetchUpdatedExchangeRates(true) + startFetching(); }); -function fetchUpdatedExchangeRates() { - fetch('/coingecko.php') +function fetchUpdatedExchangeRates(showAlert = false) { + fetch('/coingecko.php') .then(response => response.json()) .then(data => { - // Update the exchangeRates object with the new values - for (const [currency, value] of Object.entries(data)) { - exchangeRates[currency.toUpperCase()] = value.lastValue; - } - - updateTimeElement(data.time); - - // Re-execute the appropriate conversion function - if (lastModifiedField === 'xmr') { - xmrConvert(); - } else { - fiatConvert(); - } + // Update the exchangeRates object with the new values + for (const [currency, value] of Object.entries(data)) { + exchangeRates[currency.toUpperCase()] = value.lastValue; + } + + updateTimeElement(data.time); + + // Re-execute the appropriate conversion function + runConvert(); }) - .catch(error => console.error('Error fetching exchange rates:', error)); + .catch(e => { + const msg = `Error fetching exchange rates: ${e}`; + showAlert ? alert(msg) : console.error(msg); + }); } function updateTimeElement(unixTimestamp) { - const date = new Date(unixTimestamp * 1000); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - const formattedTime = `${hours}:${minutes}:${seconds}`; - - const u = document.querySelector('u'); - u.textContent = formattedTime; - u.parentElement.innerHTML = u.parentElement.innerHTML.replace('Europe/Berlin', Intl.DateTimeFormat().resolvedOptions().timeZone); + const date = new Date(unixTimestamp * 1000); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + const formattedTime = `${hours}:${minutes}:${seconds}`; + + const u = document.querySelector('u'); + u.textContent = formattedTime; + u.parentElement.innerHTML = u.parentElement.innerHTML.replace('Europe/Berlin', Intl.DateTimeFormat().resolvedOptions().timeZone); } -function copyToClipBoardXMR() { - var content = document.getElementById('xmrInput'); - content.select(); - document.execCommand('copy'); +function copyToClipboardXMR() { + const content = document.getElementById('xmrInput'); + content.select(); + + // Using deprecated execCommand for compatibility with older browsers + document.execCommand('copy'); } -function copyToClipBoardFiat() { - var content = document.getElementById('fiatInput'); - content.select(); - document.execCommand('copy'); +function copyToClipboardFiat() { + const content = document.getElementById('fiatInput'); + content.select(); + + // Using deprecated execCommand for compatibility with older browsers + document.execCommand('copy'); } -function fiatConvert(value) { - let fiatAmount = document.getElementById("fiatInput").value; - let xmrValue = document.getElementById("xmrInput"); - let selectBox = document.getElementById("selectBox").value; - - if (exchangeRates[selectBox]) { - let value = fiatAmount / exchangeRates[selectBox]; - xmrValue.value = value.toFixed(12); - } +function fiatConvert() { + const fiatAmount = document.getElementById('fiatInput').value; + const xmrValue = document.getElementById('xmrInput'); + const selectBox = document.getElementById('selectBox').value; + + if (exchangeRates[selectBox]) { + const value = fiatAmount / exchangeRates[selectBox]; + xmrValue.value = value.toFixed(12); + } } -function xmrConvert(value) { - let xmrAmount = document.getElementById("xmrInput").value; - let fiatValue = document.getElementById("fiatInput"); - let selectBox = document.getElementById("selectBox").value; - - if (exchangeRates[selectBox]) { - let value = xmrAmount * exchangeRates[selectBox]; - fiatValue.value = value.toFixed(selectBox == 'BTC' || selectBox == 'LTC' || selectBox == 'ETH' || selectBox == 'XAG' || selectBox == 'XAU' ? 8 : 2); - } -} - -window.copyToClipBoardXMR = copyToClipBoardXMR; -window.copyToClipBoardFiat = copyToClipBoardFiat; -window.fiatConvert = fiatConvert; -window.xmrConvert = xmrConvert; \ No newline at end of file +function xmrConvert() { + const xmrAmount = document.getElementById('xmrInput').value; + const fiatValue = document.getElementById('fiatInput'); + const selectBox = document.getElementById('selectBox').value; + + if (exchangeRates[selectBox]) { + const value = xmrAmount * exchangeRates[selectBox]; + fiatValue.value = value.toFixed(['BTC', 'LTC', 'ETH', 'XAG', 'XAU'].includes(selectBox) ? 8 : 2); + } +} \ No newline at end of file diff --git a/templates/index.php b/templates/index.php index 0d8723a..9ebe38e 100644 --- a/templates/index.php +++ b/templates/index.php @@ -15,32 +15,45 @@ - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - + - + @@ -123,7 +136,7 @@
@@ -140,6 +153,7 @@ + @@ -149,4 +163,4 @@ - \ No newline at end of file + diff --git a/webpack.config.js b/webpack.config.js index d1a8d91..87eee0d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -29,9 +29,11 @@ module.exports = { }), new PurgeCSSPlugin({ paths: glob.sync([ - path.join(__dirname, 'index.php') + path.join(__dirname, 'index.php'), + path.join(__dirname, 'src/js/*.js'), + path.join(__dirname, 'templates/*.php'), ]), - safelist: ['tooltip', 'fade', 'show', 'bs-tooltip-top', 'tooltip-inner', 'tooltip-arrow', 'btn-equals', 'btn-arrow', 'alert', 'alert-warning'] + safelist: ['tooltip', 'fade', 'show', 'bs-tooltip-top', 'tooltip-inner', 'tooltip-arrow', 'btn-equals', 'btn-arrow', 'alert', 'alert-warning', 'donation-qr', 'donation-qr-toggle', 'donation-qr-container'] }) ] }; \ No newline at end of file