@cryptodevops/n8n-nodes-santiment
Version:
n8n node for Santiment cryptocurrency API
417 lines (416 loc) • 18.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Santiment = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const axios_1 = __importDefault(require("axios"));
const date_utils_1 = require("../../date-utils");
class Santiment {
constructor() {
this.description = {
displayName: 'Santiment',
name: 'santiment',
icon: 'file:santiment.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Interact with Santiment cryptocurrency data API',
defaults: {
name: 'Santiment',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'santimentApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Execute Custom Query',
value: 'executeCustomQuery',
description: 'Execute a custom GraphQL query',
action: 'Execute custom query',
},
{
name: 'Get Development Activity',
value: 'getDevActivity',
description: 'Get development activity metrics',
action: 'Get development activity',
},
{
name: 'Get On-Chain Data',
value: 'getOnChainData',
description: 'Get on-chain metrics',
action: 'Get on chain data',
},
{
name: 'Get Price Data',
value: 'getPriceData',
description: 'Get price data for cryptocurrencies',
action: 'Get price data',
},
{
name: 'Get Project List',
value: 'getProjectList',
description: 'Get list of available projects',
action: 'Get project list',
},
{
name: 'Get Sentiment Index',
value: 'getSentimentIndex',
description: 'Get sentiment analysis metrics',
action: 'Get sentiment index',
},
{
name: 'Get Social Volume',
value: 'getSocialVolume',
description: 'Get social volume metrics',
action: 'Get social volume',
},
],
default: 'getPriceData',
},
// Paramètres pour l'opération getPriceData
{
displayName: 'Project Slug',
name: 'slug',
type: 'string',
default: 'bitcoin',
required: true,
description: 'Slug of the project (e.g., bitcoin, ethereum)',
displayOptions: {
show: {
operation: ['getPriceData', 'getDevActivity', 'getSocialVolume', 'getOnChainData', 'getSentimentIndex'],
},
},
},
{
displayName: 'From Date',
name: 'fromDate',
type: 'string',
default: 'utc_now-30d',
description: 'Start date (format: YYYY-MM-DD ou utc_now-30d). Les formats simples comme YYYY-MM-DD seront automatiquement convertis au format ISO requis par l\'API.',
displayOptions: {
show: {
operation: ['getPriceData', 'getDevActivity', 'getSocialVolume', 'getOnChainData', 'getSentimentIndex'],
},
},
},
{
displayName: 'To Date',
name: 'toDate',
type: 'string',
default: 'utc_now',
description: 'End date (format: YYYY-MM-DD ou utc_now). Les formats simples comme YYYY-MM-DD seront automatiquement convertis au format ISO requis par l\'API.',
displayOptions: {
show: {
operation: ['getPriceData', 'getDevActivity', 'getSocialVolume', 'getOnChainData', 'getSentimentIndex'],
},
},
},
{
displayName: 'Interval',
name: 'interval',
type: 'options',
options: [
{ name: '1 Day', value: '1d' },
{ name: '1 Hour', value: '1h' },
{ name: '1 Week', value: '1w' },
{ name: '15 Minutes', value: '15m' },
{ name: '30 Minutes', value: '30m' },
{ name: '4 Hours', value: '4h' },
{ name: '5 Minutes', value: '5m' },
],
default: '1d',
description: 'Time interval between data points',
displayOptions: {
show: {
operation: ['getPriceData', 'getDevActivity', 'getSocialVolume', 'getOnChainData', 'getSentimentIndex'],
},
},
},
// Paramètres pour l'opération getSentimentIndex
{
displayName: 'Sentiment Type',
name: 'sentimentType',
type: 'options',
options: [
{ name: 'Sentiment Balance (Total)', value: 'sentiment_balance_total' },
{ name: 'Sentiment Positive (Total)', value: 'sentiment_positive_total' },
{ name: 'Sentiment Negative (Total)', value: 'sentiment_negative_total' },
],
default: 'sentiment_balance_total',
description: 'Type of sentiment metric to retrieve',
displayOptions: {
show: {
operation: ['getSentimentIndex'],
},
},
},
// Paramètres pour l'opération getProjectList
{
displayName: 'Page',
name: 'page',
type: 'number',
default: 1,
description: 'Page number for pagination',
displayOptions: {
show: {
operation: ['getProjectList'],
},
},
},
{
displayName: 'Page Size',
name: 'pageSize',
type: 'number',
default: 20,
description: 'Number of projects per page',
displayOptions: {
show: {
operation: ['getProjectList'],
},
},
},
// Paramètres pour l'opération executeCustomQuery
{
displayName: 'GraphQL Query',
name: 'query',
type: 'string',
typeOptions: {
rows: 10,
},
default: '',
placeholder: `{
getMetric(metric: "price_usd") {
timeseriesDataJson(
slug: "bitcoin"
from: "utc_now-30d"
to: "utc_now"
interval: "1d"
)
}
}`,
description: 'Custom GraphQL query to execute',
required: true,
displayOptions: {
show: {
operation: ['executeCustomQuery'],
},
},
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
const credentials = await this.getCredentials('santimentApi');
const apiKey = credentials.apiKey;
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < items.length; i++) {
try {
let responseData;
let query = '';
let variables = {};
switch (operation) {
case 'getPriceData': {
const slug = this.getNodeParameter('slug', i);
const fromDateRaw = this.getNodeParameter('fromDate', i);
const toDateRaw = this.getNodeParameter('toDate', i);
const interval = this.getNodeParameter('interval', i);
// Convertir les dates au format accepté par Santiment
const fromDate = (0, date_utils_1.convertToSantimentFormat)(fromDateRaw, false);
const toDate = (0, date_utils_1.convertToSantimentFormat)(toDateRaw, true);
query = `{
getMetric(metric: "price_usd") {
timeseriesDataJson(
slug: "${slug}"
from: "${fromDate}"
to: "${toDate}"
interval: "${interval}"
)
}
}`;
break;
}
case 'getDevActivity': {
const slug = this.getNodeParameter('slug', i);
const fromDateRaw = this.getNodeParameter('fromDate', i);
const toDateRaw = this.getNodeParameter('toDate', i);
const interval = this.getNodeParameter('interval', i);
// Convertir les dates au format accepté par Santiment
const fromDate = (0, date_utils_1.convertToSantimentFormat)(fromDateRaw, false);
const toDate = (0, date_utils_1.convertToSantimentFormat)(toDateRaw, true);
query = `{
getMetric(metric: "dev_activity") {
timeseriesDataJson(
slug: "${slug}"
from: "${fromDate}"
to: "${toDate}"
interval: "${interval}"
)
}
}`;
break;
}
case 'getSentimentIndex': {
const slug = this.getNodeParameter('slug', i);
const fromDateRaw = this.getNodeParameter('fromDate', i);
const toDateRaw = this.getNodeParameter('toDate', i);
const interval = this.getNodeParameter('interval', i);
const sentimentType = this.getNodeParameter('sentimentType', i);
// Convertir les dates au format accepté par Santiment
const fromDate = (0, date_utils_1.convertToSantimentFormat)(fromDateRaw, false);
const toDate = (0, date_utils_1.convertToSantimentFormat)(toDateRaw, true);
query = `{
getMetric(metric: "${sentimentType}") {
timeseriesDataJson(
slug: "${slug}"
from: "${fromDate}"
to: "${toDate}"
interval: "${interval}"
)
}
}`;
break;
}
case 'getSocialVolume': {
const slug = this.getNodeParameter('slug', i);
const fromDateRaw = this.getNodeParameter('fromDate', i);
const toDateRaw = this.getNodeParameter('toDate', i);
const interval = this.getNodeParameter('interval', i);
// Convertir les dates au format accepté par Santiment
const fromDate = (0, date_utils_1.convertToSantimentFormat)(fromDateRaw, false);
const toDate = (0, date_utils_1.convertToSantimentFormat)(toDateRaw, true);
query = `{
getMetric(metric: "social_volume_total") {
timeseriesDataJson(
slug: "${slug}"
from: "${fromDate}"
to: "${toDate}"
interval: "${interval}"
)
}
}`;
break;
}
case 'getOnChainData': {
const slug = this.getNodeParameter('slug', i);
const fromDateRaw = this.getNodeParameter('fromDate', i);
const toDateRaw = this.getNodeParameter('toDate', i);
const interval = this.getNodeParameter('interval', i);
// Convertir les dates au format accepté par Santiment
const fromDate = (0, date_utils_1.convertToSantimentFormat)(fromDateRaw, false);
const toDate = (0, date_utils_1.convertToSantimentFormat)(toDateRaw, true);
query = `{
getMetric(metric: "daily_active_addresses") {
timeseriesDataJson(
slug: "${slug}"
from: "${fromDate}"
to: "${toDate}"
interval: "${interval}"
)
}
}`;
break;
}
case 'getProjectList': {
const page = this.getNodeParameter('page', i);
const pageSize = this.getNodeParameter('pageSize', i);
query = `{
allProjects(page: ${page}, pageSize: ${pageSize}) {
slug
name
ticker
description
logoUrl
websiteLink
twitterLink
marketSegments
}
}`;
break;
}
case 'executeCustomQuery': {
query = this.getNodeParameter('query', i);
break;
}
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `L'opération ${operation} n'est pas prise en charge!`);
}
// Exécuter la requête GraphQL
const response = await axios_1.default.post('https://api.santiment.net/graphql', { query, variables }, {
headers: {
'Content-Type': 'application/json',
Authorization: `Apikey ${apiKey}`,
},
});
// Vérifier les erreurs dans la réponse GraphQL
if (response.data.errors) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Erreur GraphQL: ${response.data.errors[0].message}`, { itemIndex: i });
}
responseData = response.data.data;
// Traitement spécifique pour certaines opérations
if (['getPriceData', 'getDevActivity', 'getSocialVolume', 'getOnChainData', 'getSentimentIndex'].includes(operation)) {
// Convertir la chaîne JSON en objet pour les données de séries temporelles
const metricNameMap = {
getPriceData: 'price_usd',
getDevActivity: 'dev_activity',
getSocialVolume: 'social_volume_total',
getOnChainData: 'daily_active_addresses',
getSentimentIndex: this.getNodeParameter('sentimentType', i),
};
const metricName = metricNameMap[operation];
// Vérifier si les données sont vides ou déjà un tableau
let timeseriesData;
if (responseData.getMetric.timeseriesDataJson === '[]' ||
responseData.getMetric.timeseriesDataJson === '' ||
Array.isArray(responseData.getMetric.timeseriesDataJson)) {
// Si c'est un tableau vide ou déjà un objet tableau, l'utiliser directement
timeseriesData = Array.isArray(responseData.getMetric.timeseriesDataJson)
? responseData.getMetric.timeseriesDataJson
: [];
}
else {
try {
timeseriesData = JSON.parse(responseData.getMetric.timeseriesDataJson);
}
catch (parseError) {
// En cas d'erreur de parsing, retourner un tableau vide
console.error('Erreur de parsing JSON:', parseError.message);
timeseriesData = [];
}
}
responseData = {
metric: metricName,
data: timeseriesData,
};
}
// Pour les autres opérations (getProjectList et executeCustomQuery), on utilise directement responseData
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
returnData.push(...executionData);
}
catch (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({ error: error.message }), { itemData: { item: i } });
returnData.push(...executionErrorData);
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.Santiment = Santiment;