From 7c51964bd200860f698422d34271c7a8610b8aaf Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 3 Oct 2020 11:50:57 +0200 Subject: [PATCH] :sparkles: Add CoinGecko-Node * Add CoinGecko node * CoinGecko improvements and add events * :zap: Small improvements * :zap: Improvements to CoinGecko-Node Co-authored-by: Dokime Co-authored-by: ricardo --- .../nodes/CoinGecko/CoinDescription.ts | 740 ++++++++++++++++++ .../nodes/CoinGecko/CoinGecko.node.ts | 537 +++++++++++++ .../nodes/CoinGecko/EventDescription.ts | 130 +++ .../nodes/CoinGecko/GenericFunctions.ts | 67 ++ .../nodes-base/nodes/CoinGecko/coinGecko.png | Bin 0 -> 3955 bytes packages/nodes-base/package.json | 1 + 6 files changed, 1475 insertions(+) create mode 100644 packages/nodes-base/nodes/CoinGecko/CoinDescription.ts create mode 100644 packages/nodes-base/nodes/CoinGecko/CoinGecko.node.ts create mode 100644 packages/nodes-base/nodes/CoinGecko/EventDescription.ts create mode 100644 packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/CoinGecko/coinGecko.png diff --git a/packages/nodes-base/nodes/CoinGecko/CoinDescription.ts b/packages/nodes-base/nodes/CoinGecko/CoinDescription.ts new file mode 100644 index 0000000000..6b11cea60d --- /dev/null +++ b/packages/nodes-base/nodes/CoinGecko/CoinDescription.ts @@ -0,0 +1,740 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const coinOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'coin', + ], + }, + }, + options: [ + { + name: 'Candlestick', + value: 'candlestick', + description: 'Get a candlestick open-high-low-close chart for the selected currency', + }, + { + name: 'Get', + value: 'get', + description: 'Get current data for a coin', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all coins', + }, + { + name: 'History', + value: 'history', + description: 'Get historical data (name, price, market, stats) at a given date for a coin', + }, + { + name: 'Market', + value: 'market', + description: 'Get prices and market related data for all trading pairs that match the selected currency', + }, + { + name: 'Market Chart', + value: 'marketChart', + description: 'Get historical market data include price, market cap, and 24h volume (granularity auto)', + }, + { + name: 'Price', + value: 'price', + description: 'Get the current price of any cryptocurrencies in any other supported currencies that you need', + }, + { + name: 'Ticker', + value: 'ticker', + description: 'Get coin tickers', + }, + ], + default: 'getAll', + }, +] as INodeProperties[]; + +export const coinFields = [ + { + displayName: 'Search By', + name: 'searchBy', + required: true, + type: 'options', + options: [ + { + name: 'Coin ID', + value: 'coinId', + }, + { + name: 'Contract address', + value: 'contractAddress', + }, + ], + displayOptions: { + show: { + operation: [ + 'get', + 'marketChart', + 'price', + ], + resource: [ + 'coin', + ], + }, + }, + default: 'coinId', + description: 'Search by coin ID or contract address.', + }, + { + displayName: 'Coin ID', + name: 'coinId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCoins', + }, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'coin', + ], + }, + }, + default: '', + placeholder: 'bitcoin', + description: 'Coin ID', + }, + { + displayName: 'Coin ID', + name: 'coinId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCoins', + }, + displayOptions: { + show: { + operation: [ + 'ticker', + 'history', + 'candlestick', + ], + resource: [ + 'coin', + ], + }, + }, + default: '', + placeholder: 'bitcoin', + description: 'Coin ID', + }, + { + displayName: 'Coin IDs', + name: 'coinIds', + required: true, + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getCoins', + }, + displayOptions: { + show: { + operation: [ + 'price', + ], + resource: [ + 'coin', + ], + searchBy: [ + 'coinId' + ], + }, + }, + default: [], + placeholder: 'bitcoin', + description: 'ID of coins, comma-separated. Refers to Coin / GetAll.', + }, + { + displayName: 'Platform ID', + name: 'platformId', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'marketChart', + 'price', + ], + resource: [ + 'coin', + ], + searchBy: [ + 'contractAddress' + ], + }, + }, + type: 'options', + options: [ + { + name: 'Ethereum', + value: 'ethereum', + }, + ], + default: 'ethereum', + description: 'The id of the platform issuing tokens.', + }, + { + displayName: 'Contract address', + name: 'contractAddress', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + 'marketChart', + ], + resource: [ + 'coin', + ], + searchBy: [ + 'contractAddress' + ], + }, + }, + description: 'Token\'s contract address.', + }, + { + displayName: 'Contract addresses', + name: 'contractAddresses', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'price', + ], + resource: [ + 'coin', + ], + searchBy: [ + 'contractAddress' + ], + }, + }, + description: 'The contract address of tokens, comma separated.', + }, + { + displayName: 'Base Currency', + name: 'baseCurrency', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCoins', + }, + displayOptions: { + show: { + operation: [ + 'marketChart', + ], + resource: [ + 'coin', + ], + searchBy: [ + 'coinId' + ], + }, + hide: { + searchBy: [ + 'contractAddress', + ], + }, + }, + default: '', + description: 'The first currency in the pair. For BTC:ETH this is BTC.', + }, + { + displayName: 'Quote Currency', + name: 'quoteCurrency', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + displayOptions: { + show: { + operation: [ + 'market', + 'marketChart', + 'candlestick', + ], + resource: [ + 'coin', + ], + }, + }, + default: '', + description: 'The second currency in the pair. For BTC:ETH this is ETH.', + }, + { + displayName: 'Currencies', + name: 'currencies', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + required: true, + displayOptions: { + show: { + operation: [ + 'price', + ], + resource: [ + 'coin', + ], + }, + }, + default: [], + description: 'Currencies of coin.', + }, + { + displayName: 'Historical Range (days)', + name: 'days', + required: true, + type: 'options', + options: [ + { + name: '1', + value: '1', + }, + { + name: '7', + value: '7', + }, + { + name: '14', + value: '14', + }, + { + name: '30', + value: '30', + }, + { + name: '90', + value: '90', + }, + { + name: '180', + value: '180', + }, + { + name: '365', + value: '365', + }, + { + name: 'Max', + value: 'max', + }, + ], + displayOptions: { + show: { + operation: [ + 'marketChart', + 'candlestick', + ], + resource: [ + 'coin', + ], + }, + }, + default: '', + description: 'Return data for this many days in the past from now.', + }, + { + displayName: 'Date', + name: 'date', + required: true, + type: 'dateTime', + displayOptions: { + show: { + operation: [ + 'history', + ], + resource: [ + 'coin', + ], + }, + }, + default: '', + description: 'The date of data snapshot.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + 'market', + 'ticker', + ], + resource: [ + 'coin', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + 'market', + 'ticker', + ], + resource: [ + 'coin', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'coin', + ], + operation: [ + 'market', + ], + }, + }, + options: [ + { + displayName: 'Coin IDs', + name: 'ids', + type: 'string', + placeholder: 'bitcoin', + default: '', + description: 'Filter results by comma separated list of coin ID.', + }, + { + displayName: 'Category', + name: 'category', + type: 'options', + options: [ + { + name: 'Decentralized Finance Defi', + value: 'decentralized_finance_defi', + }, + ], + default: 'decentralized_finance_defi', + description: 'Filter by coin category.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'Market Cap Desc', + value: 'market_cap_desc', + }, + { + name: 'Gecko Desc', + value: 'gecko_desc', + }, + { + name: 'Gecko Asc', + value: 'gecko_asc', + }, + { + name: 'Market Cap Asc', + value: 'market_cap_asc', + }, + { + name: 'Market Cap Desc', + value: 'market_cap_desc', + }, + { + name: 'Volume Asc', + value: 'volume_asc', + }, + { + name: 'Volume Desc', + value: 'volume_desc', + }, + { + name: 'Id Asc', + value: 'id_asc', + }, + { + name: 'Id Desc', + value: 'id_desc', + } + ], + default: '', + description: 'Sort results by field.', + }, + { + displayName: 'Sparkline', + name: 'sparkline', + type: 'boolean', + default: false, + description: 'Include sparkline 7 days data.', + }, + { + displayName: 'Price Change Percentage', + name: 'price_change_percentage', + type: 'multiOptions', + options: [ + { + name: '1h', + value: '1h', + }, + { + name: '24h', + value: '24h', + }, + { + name: '7d', + value: '7d', + }, + { + name: '14d', + value: '14d', + }, + { + name: '30d', + value: '30d', + }, + { + name: '200d', + value: '200d', + }, + { + name: '1y', + value: '1y', + }, + ], + default: [], + description: 'Include price change percentage for specified times.', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'coin', + ], + operation: [ + 'price', + ], + }, + }, + options: [ + { + displayName: 'Include 24hr Change', + name: 'include_24hr_change', + type: 'boolean', + default: false, + }, + { + displayName: 'Include 24hr Vol', + name: 'include_24hr_vol', + type: 'boolean', + default: false, + }, + { + displayName: 'Include Last Updated At', + name: 'include_last_updated_at', + type: 'boolean', + default: false, + }, + { + displayName: 'Include Market Cap', + name: 'include_market_cap', + type: 'boolean', + default: false, + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'coin', + ], + operation: [ + 'ticker', + ], + }, + }, + options: [ + { + displayName: 'Exchange IDs', + name: 'exchange_ids', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getExchanges', + }, + default: [], + description: 'Filter results by exchange IDs.', + }, + { + displayName: 'Include Exchange Logo', + name: 'include_exchange_logo', + type: 'boolean', + default: false, + description: 'Include exchange logo.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'Trust Score Desc', + value: 'trust_score_desc', + }, + { + name: 'Trust Score Asc', + value: 'trust_score_asc', + }, + { + name: 'Volume Desc', + value: 'volume_desc', + }, + ], + default: 'trust_score_desc', + description: 'Sorts results by the selected rule.', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'coin', + ], + operation: [ + 'history', + ], + }, + }, + options: [ + { + displayName: 'Localization', + name: 'localization', + type: 'boolean', + default: true, + description: 'Set to false to exclude localized languages in response.', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'coin', + ], + }, + }, + options: [ + { + displayName: 'Community data', + name: 'community_data', + type: 'boolean', + default: false, + description: 'Include community data.' + }, + { + displayName: 'Developer data', + name: 'developer_data', + type: 'boolean', + default: false, + description: 'Include developer data.' + }, + { + displayName: 'Localization', + name: 'localization', + type: 'boolean', + default: false, + description: 'Include all localized languages in response.' + }, + { + displayName: 'Market data', + name: 'market_data', + type: 'boolean', + default: false, + description: 'Include market data.' + }, + { + displayName: 'Sparkline', + name: 'sparkline', + type: 'boolean', + default: false, + description: 'Include sparkline 7 days data (eg. true, false).' + }, + { + displayName: 'Tickers', + name: 'tickers', + type: 'boolean', + default: false, + description: 'Include tickers data.' + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/CoinGecko/CoinGecko.node.ts b/packages/nodes-base/nodes/CoinGecko/CoinGecko.node.ts new file mode 100644 index 0000000000..aaf8e537cd --- /dev/null +++ b/packages/nodes-base/nodes/CoinGecko/CoinGecko.node.ts @@ -0,0 +1,537 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + coinFields, + coinOperations, +} from './CoinDescription'; + +import { + eventFields, + eventOperations, +} from './EventDescription'; + +import { + coinGeckoApiRequest, + coinGeckoRequestAllItems, +} from './GenericFunctions'; + +import * as moment from 'moment-timezone'; + +export class CoinGecko implements INodeType { + description: INodeTypeDescription = { + displayName: 'CoinGecko', + name: 'coinGecko', + icon: 'file:coinGecko.png', + group: ['output'], + version: 1, + description: 'Consume CoinGecko API', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'CoinGecko', + color: '#8bc53f', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Coin', + value: 'coin', + }, + { + name: 'Event', + value: 'event', + }, + ], + default: 'coin', + }, + ...coinOperations, + ...coinFields, + ...eventOperations, + ...eventFields, + ], + }; + + methods = { + loadOptions: { + async getCurrencies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const currencies = await coinGeckoApiRequest.call( + this, + 'GET', + '/simple/supported_vs_currencies' + ); + currencies.sort(); + for (const currency of currencies) { + returnData.push({ + name: currency.toUpperCase(), + value: currency, + }); + } + return returnData; + }, + + async getCoins(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const coins = await coinGeckoApiRequest.call( + this, + 'GET', + '/coins/list' + ); + for (const coin of coins) { + returnData.push({ + name: coin.symbol.toUpperCase(), + value: coin.id, + }); + } + returnData.sort((a, b) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + return returnData; + }, + + async getExchanges(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const exchanges = await coinGeckoApiRequest.call( + this, + 'GET', + '/exchanges/list' + ); + for (const exchange of exchanges) { + returnData.push({ + name: exchange.name, + value: exchange.id, + }); + } + return returnData; + }, + + async getEventCountryCodes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const countryCodes = await coinGeckoApiRequest.call( + this, + 'GET', + '/events/countries' + ); + for (const code of countryCodes.data) { + if (!code.code) { + continue; + } + returnData.push({ + name: code.country, + value: code.code, + }); + } + return returnData; + }, + + async getEventTypes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const eventTypes = await coinGeckoApiRequest.call( + this, + 'GET', + '/events/types' + ); + for (const type of eventTypes.data) { + returnData.push({ + name: type, + value: type, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + + if (resource === 'coin') { + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id_ + //https://www.coingecko.com/api/documentations/v3#/contract/get_coins__id__contract__contract_address_ + if (operation === 'get') { + + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.community_data = false; + qs.developer_data = false; + qs.localization = false; + qs.market_data = false; + qs.sparkline = false; + qs.tickers = false; + + Object.assign(qs, options); + + const searchBy = this.getNodeParameter('searchBy', i) as string; + + if (searchBy === 'coinId') { + const coinId = this.getNodeParameter('coinId', i) as string; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${coinId}`, + {}, + qs + ); + } + + if (searchBy === 'contractAddress') { + const platformId = this.getNodeParameter('platformId', i) as string; + const contractAddress = this.getNodeParameter('contractAddress', i) as string; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${platformId}/contract/${contractAddress}`, + {}, + qs + ); + } + } + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins_list + if (operation === 'getAll') { + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + let limit; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + '/coins/list', + {}, + qs + ); + + if (returnAll === false) { + limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + } + + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins_list + if (operation === 'market') { + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const quoteCurrency = this.getNodeParameter('quoteCurrency', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.vs_currency = quoteCurrency; + + Object.assign(qs, options); + + if (options.price_change_percentage) { + qs.price_change_percentage = (options.price_change_percentage as string[]).join(','); + } + + if (returnAll) { + responseData = await coinGeckoRequestAllItems.call( + this, + '', + 'GET', + `/coins/markets`, + {}, + qs + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + + qs.per_page = limit; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/markets`, + {}, + qs + ); + } + } + + //https://www.coingecko.com/api/documentations/v3#/simple/get_simple_price + //https://www.coingecko.com/api/documentations/v3#/simple/get_simple_token_price__id_ + if (operation === 'price') { + + const searchBy = this.getNodeParameter('searchBy', i) as string; + const currencies = this.getNodeParameter('currencies', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.vs_currencies = currencies.join(','); + + Object.assign(qs, options); + + if (searchBy === 'coinId') { + const coinIds = this.getNodeParameter('coinIds', i) as string[]; + + qs.ids = coinIds.join(','); + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + '/simple/price', + {}, + qs + ); + } + + if (searchBy === 'contractAddress') { + const platformId = this.getNodeParameter('platformId', i) as string; + const contractAddresses = this.getNodeParameter('contractAddresses', i) as string; + + qs.contract_addresses = contractAddresses; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/simple/token_price/${platformId}`, + {}, + qs + ); + } + } + + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id__tickers + if (operation === 'ticker') { + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const coinId = this.getNodeParameter('coinId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + Object.assign(qs, options); + + if (options.exchange_ids) { + qs.exchange_ids = (options.exchange_ids as string[]).join(','); + } + + if (returnAll) { + + responseData = await coinGeckoRequestAllItems.call( + this, + 'tickers', + 'GET', + `/coins/${coinId}/tickers`, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${coinId}/tickers`, + {}, + qs + ); + + responseData = responseData.tickers; + responseData = responseData.splice(0, limit); + } + } + + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id__history + if (operation === 'history') { + + const coinId = this.getNodeParameter('coinId', i) as string; + const date = this.getNodeParameter('date', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + Object.assign(qs, options); + + qs.date = moment(date).format('DD-MM-YYYY'); + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${coinId}/history`, + {}, + qs + ); + } + + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id__market_chart + //https://www.coingecko.com/api/documentations/v3#/contract/get_coins__id__contract__contract_address__market_chart_ + if (operation === 'marketChart') { + + let respData; + + const searchBy = this.getNodeParameter('searchBy', i) as string; + const quoteCurrency = this.getNodeParameter('quoteCurrency', i) as string; + const days = this.getNodeParameter('days', i) as string; + + qs.vs_currency = quoteCurrency; + qs.days = days; + + if (searchBy === 'coinId') { + const coinId = this.getNodeParameter('baseCurrency', i) as string; + + respData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${coinId}/market_chart`, + {}, + qs + ); + } + + if (searchBy === 'contractAddress') { + const platformId = this.getNodeParameter('platformId', i) as string; + const contractAddress = this.getNodeParameter('contractAddress', i) as string; + + respData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${platformId}/contract/${contractAddress}/market_chart`, + {}, + qs + ); + } + + responseData = []; + for (let idx = 0; idx < respData.prices.length; idx++) { + const [time, price] = respData.prices[idx]; + const marketCaps = respData.market_caps[idx][1]; + const totalVolume = respData.total_volumes[idx][1]; + responseData.push({ time: moment(time).toISOString(), price, marketCaps, totalVolume } as IDataObject); + } + } + + //https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id__ohlc + if (operation === 'candlestick') { + + const coinId = this.getNodeParameter('coinId', i) as string; + const quoteCurrency = this.getNodeParameter('quoteCurrency', i) as string; + const days = this.getNodeParameter('days', i) as string; + + qs.vs_currency = quoteCurrency; + qs.days = days; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/coins/${coinId}/ohlc`, + {}, + qs + ); + + for (let idx = 0; idx < responseData.length; idx++) { + const [time, open, high, low, close] = responseData[idx]; + responseData[idx] = { time: moment(time).toISOString(), open, high, low, close } as IDataObject; + } + } + } + + if (resource === 'event') { + //https://www.coingecko.com/api/documentations/v3#/events/get_events + if (operation === 'getAll') { + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + + Object.assign(qs, options); + + if (returnAll) { + responseData = await coinGeckoRequestAllItems.call( + this, + 'data', + 'GET', + '/events', + {}, + qs + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + + qs.per_page = limit; + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + '/events', + {}, + qs + ); + responseData = responseData.data; + } + } + } + + if (resource === 'simple') { + //https://www.coingecko.com/api/documentations/v3#/simple/get_simple_price + if (operation === 'price') { + + const ids = this.getNodeParameter('ids', i) as string; + const currencies = this.getNodeParameter('currencies', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.ids = ids, + qs.vs_currencies = currencies.join(','); + + Object.assign(qs, options); + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + '/simple/price', + {}, + qs + ); + } + + //https://www.coingecko.com/api/documentations/v3#/simple/get_simple_token_price__id_ + if (operation === 'tokenPrice') { + + const id = this.getNodeParameter('id', i) as string; + const contractAddresses = this.getNodeParameter('contractAddresses', i) as string; + const currencies = this.getNodeParameter('currencies', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.contract_addresses = contractAddresses; + qs.vs_currencies = currencies.join(','); + + Object.assign(qs, options); + + responseData = await coinGeckoApiRequest.call( + this, + 'GET', + `/simple/token_price/${id}`, + {}, + qs + ); + } + } + + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/CoinGecko/EventDescription.ts b/packages/nodes-base/nodes/CoinGecko/EventDescription.ts new file mode 100644 index 0000000000..4a6f5dc599 --- /dev/null +++ b/packages/nodes-base/nodes/CoinGecko/EventDescription.ts @@ -0,0 +1,130 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const eventOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'event', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all events', + }, + ], + default: 'getAll', + }, +] as INodeProperties[]; + +export const eventFields = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'event', + ], + }, + }, + options: [ + { + displayName: 'Country code', + name: 'country_code', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getEventCountryCodes', + }, + default: '', + description: 'Country code of event.', + }, + { + displayName: 'From date', + name: 'from_date', + type: 'dateTime', + default: '', + description: 'Lists events after this date.', + }, + { + displayName: 'To date', + name: 'to_date', + type: 'dateTime', + default: '', + description: 'Lists events before this date.', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getEventTypes', + }, + default: '', + description: 'Type of event.', + }, + { + displayName: 'Upcoming events only', + name: 'upcoming_events_only', + type: 'boolean', + default: true, + description: 'Lists only upcoming events.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts b/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts new file mode 100644 index 0000000000..d5f3e0b51a --- /dev/null +++ b/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts @@ -0,0 +1,67 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function coinGeckoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, + endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + let options: OptionsWithUri = { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://api.coingecko.com/api/v3${endpoint}`, + json: true, + }; + + options = Object.assign({}, options, option); + + try { + if (Object.keys(body).length === 0) { + delete options.body; + } + + //@ts-ignore + return await this.helpers.request.call(this, options); + + } catch (error) { + + throw error; + } +} + +export async function coinGeckoRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let respData; + query.per_page = 250; + query.page = 1; + + do { + responseData = await coinGeckoApiRequest.call(this, method, endpoint, body, query); + query.page++; + respData = responseData; + if (propertyName !== '') { + respData = responseData[propertyName]; + } + returnData.push.apply(returnData, respData); + } while ( + respData.length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/CoinGecko/coinGecko.png b/packages/nodes-base/nodes/CoinGecko/coinGecko.png new file mode 100644 index 0000000000000000000000000000000000000000..332730847f0e8fde323715e399839633a6498134 GIT binary patch literal 3955 zcmV-(4~+1MP)EX>4Tx04R}tkv&MmKpe$iTcuhmf_4yb$WWauh>D1l zR-p(LLaorMgUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwCr2km7b)?7NufoI2gm(* zckglc4)E8@Of@^k09CV$R3a{9va3Sy72W7V2*8ZQOnpuilkgm0_we!cF2=LG&;2=i zl$^-`pFljzbi*RvAfDQ^bk6(45muBG;&b9rgDyz?$aUG}H_ioz{X8>bq*L?65n`dx z#&R38qM;H`5=RwPqkMnHWrgz=XSG~q&3p0}hI87=GS_JiA%R6KL4*JqRg_SMg&3_G zDJD|1ANTMNI)0H{GPz1%j1(w)-Q(R|?Y;ebrrF;QS6*_QHBDz800006VoOIv05AYm z0LM2KxUm2L010qNS#tmY3ljhU3ljkVnw%H_000McNliruRM=>7Hy3b0g48$ zf;vtbxJA&qje?>;kOrxkq=u8YP2whzC09~viIytMl&r(nL5ZSnUJ}VAmwV36%-av# zT~Xw6xhs)UKJ)?h!|cp^{O5Vz=N`dbqOAuXo${G2&8;)O4>7~w%7K-@eLy2n1Ng_S z8v%NOPT&*Z2yhiJXW|8jX$C2PCg6GC31B&(+{Sd5fZf2)fqj5JUEuE+@K@hnL!ip# z0$v2ZnC2z`77~}RQR(nA%jUytbK+9%XhH?Rlom-VMZ`!js7L74hX|W-lv}rT4QK(r z3-sP8@Y@FbS8uMQy53_4UIJcCiz^bSwFg+_siD>#z;7=>6$&!v>JCu62n5I|mKh;p z#OaBLIUnt%GuDT^IWn3C{SUyv?E)8fAij?R-vO2aXhP+F-#nK1YVq4krk+IcqS%)q zY*h$bB|`IoG?2y+mfnp_o*|a#pkt(uBg36U%tU770PqUXvZc9I&M4qn^E6)h@j5&3 zI`Ef(qM)$4w1K9;TpX(HHswWv;zO}7MmQQ!?6n{?q%nXQZNp3)=WMu#1B30vEj=@6 z7w|_wUoqSkMRK(kcpF$FfqG{Jk5(+fXLC=Z9+eJ1(&hhKYOMuTUPA}Z4QcSCBi-%bL7{cpXFL? zkazoz6SI;4N#Kuxw+aDWDBuTxJwTaTaqwi-N-A8X+&z+@l%keAf;2){@dHE+ot-_$ z=+Q#}W?FgvU@Fj}0M0H2_5)>pt%RqmSK+g}xi=&cwnZrRxtP)aA!Vi5)q8>q@xBb` zKb(rwQ#l0{z)qmdr@DErcFjEmJIWuKI){}whU$F^o9baxbrbcjN&pr3A@FcMH&)5R zT@`o>nCnz*JX^Dx63xkP0!gzUD|Qgg|NE$Fkd0OMQEd+ZIDnr5RYd_#zoA!w^#Z}~ zR<69qoXrKg4>Q_^7TAoYmGimk`*AA{(qR96>%m7wF&th2{0+dmz#^*MfjbaFfe`6m z4waF)cUz$KQ%L^r~pRv=gr+tQjNwxG}Y-?if($Z(r53;#-9pz46!3KRukI^3P=49k55i>ER&9fv7 zMFm~M3P%SOx^%&tO^tk}v5sPVM#XU-YUxJI$cH2%dwApW`@}{)vVR6%9;>x);qct0 zr2_amz;aI=6;5A43lHltJ{&wxd$ceO!b#;Hj>gpG$_pe7K!1eg)?28#oZadTkEc%UMJgOa^ z&4nVA0+m=8#kB&|00DW4GvRJ@Y1n}HXa98G-rX&iZw36?_J;ws0!td*)vWN>Z2JLiZHH+HHc{zX zRj@*gm?O@)#K+ts0hb08cHh1+GTaD^4ML0 z;7p_|KL>RxSno{K2`w!spc-Xx8g$Z01Lh$Uf&!C$L+6Q+|n{Rz0ayfmb z6|CeXgz7~R4l3>0xPAH7gO6sl^a6l@&4Vh$q#AlikL7eA#?zV5&saL}7#lZk#OL$z z&W@e*_xIDda3Rk<_Y4gU^J%^C6B6e2X-%;ACnI=lE~MTGB~73N2->}zkM{yJj5SMD z06xu?chV6fSx|~ZAoyj+U-S6XDMM zfaPdzIlmhhGxVZE!=Yq5NBY{QMQl&@zm4Bn&BdXQZUGCh&|Qs3b7nnxnYs=bXo@m< zcsNy{5G#ViGeNo);=L>1n0Ujm^H6Z%E&G>7FIce&n3SOkF`*p{#w*N^$xk1mp!I1cZ&)ZxB!qsi=buOM z?gkh(qLYfXl#+-UpT1373PQ1p8e{4XhOfFY;~Jj2D4$+YN^^Pr6qY4Ewn~u&d=?;- ziegx1jz|PjmPx`grwy^-AQu9M&!Cia=?k^O%bS%V)rCi z?Q7tn^2Vucv!0F9&u(dM%>q6L7zR=cr(%>jyg4*<5({hE3M;mf7=eZ)gPmTw4pm{s z6@*PsXD~FRp`rPccxqDkYElFj^`VH-;%T#g5vzms1(-&Q-elG@{ABzYKBK^j-uQ4% zz>xx-j$%krO=dL?5o{a+Q^iQQKnfI_fnrY}#KZ&kuzxWtgYyfTe@Ks!kSP>|2^=1u zrrif{DRyI$!OJSW(P{EG?(3vewqnCT`s3G`e& zG*tq0M0)NCcQ;iuQDQ5;VtIV<62@p6>N4=7T)y;JF8>{1bn79`hx>>cNjgUh&UaTy z`Rb`F2^KwWNRM(Pd?9oB_l&kgOv>Smfo=iVJ8+6)p-W?XrS1xORTmEi7ZmTVBH4TW zRA#>)1K%It4KM+4`hl+i-Em9jqnmH*uF+$&mSHRM+yZ^A@>yCwZfACD^@|(b+mX}Xc5a9p(#(5a+?fB+h4f}w# zWyOo~upZ@|zK=4ZydU`bWQ{G8=5*2nUQGkPE9BSQ%r2Q#)R=H8#?QOkNLZ7@hJ<# zJJmuGP*8P&dRD>KYRCQVETwkeHF+xb@B5>DwR#px zY;_iTKuX;OQV8Kp3Y1|5+3ghm^LCt_pI^Hzy#4TNJh|p?`G1-Q`ah-bL>(8!WlI16 N002ovPDHLkV1iClWHSH& literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a703f528dc..c54d6f6a24 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -220,6 +220,7 @@ "dist/nodes/Clockify/ClockifyTrigger.node.js", "dist/nodes/Cockpit/Cockpit.node.js", "dist/nodes/Coda/Coda.node.js", + "dist/nodes/CoinGecko/CoinGecko.node.js", "dist/nodes/Contentful/Contentful.node.js", "dist/nodes/ConvertKit/ConvertKit.node.js", "dist/nodes/ConvertKit/ConvertKitTrigger.node.js",