From 2025ef0a91811bf7844d4f5a3597478be9f322f7 Mon Sep 17 00:00:00 2001 From: recanman <29310982+recanman@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:54:33 -0700 Subject: [PATCH] feat: improve cache, refactor code, add api key support --- .gitignore | 4 ++ README.md | 12 ++++ coingecko.php | 151 ++++++++++++++++++++++++++++++----------------- secrets.dist.php | 5 ++ 4 files changed, 118 insertions(+), 54 deletions(-) create mode 100644 secrets.dist.php diff --git a/.gitignore b/.gitignore index ef0d844..ee3bb8c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,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..1cf15fb 100644 --- a/README.md +++ b/README.md @@ -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 4f6c677..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); - - $currenciesHttpCode = curl_getinfo($currenciesCh, CURLINFO_HTTP_CODE); - - curl_close($currenciesCh); - - if ($currenciesHttpCode == 200) { - $availableCurrencies = json_decode($availableCurrenciesJson, true); - } else { - $availableCurrencies = array_keys($previousData); - unset($availableCurrencies[array_search('time', $availableCurrencies)]); + // Return cached data if it exists and is less than 24 hours old + if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) { + return fetchJson($cacheFile); } + $apiUrl = getCoinGeckoApiUrl('simple/supported_vs_currencies'); + $data = makeApiRequest($apiUrl); + + if ($data) { + file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT)); + return $data; + } + + return null; +} + +// 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); +} + +$currencyFile = 'coingecko.json'; +$originalFile = 'coingecko-original.json'; + +// Function to process currency data +function processCurrencyData($availableCurrencies, $previousData, $currentTime, $excludedCurrencies) { // Remove excluded currencies $availableCurrencies = array_diff($availableCurrencies, $excludedCurrencies); - $currencies = array_map('strtoupper', $availableCurrencies); // 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'; + $fetchedData = fetchCurrencyData($currencies); - $ch = curl_init($apiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $json = curl_exec($ch); - - $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/secrets.dist.php b/secrets.dist.php new file mode 100644 index 0000000..2b817ab --- /dev/null +++ b/secrets.dist.php @@ -0,0 +1,5 @@ + 'CG-xxxx', + 'coingecko_key_is_demo' => true, +]; \ No newline at end of file