dexscreener-obs/main.js

324 lines
11 KiB
JavaScript

require('dotenv').config();
const axios = require('axios');
const WebSocket = require('ws');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
const Discord = require('discord.js');
const readline = require('readline');
const envPath = path.resolve(__dirname, '.env');
const obsWebSocketUrl = `ws://localhost:${process.env.PORT}`;
const password = process.env.OBS_PASSWORD;
const discordToken = process.env.DISCORD_TOKEN;
const controlChannelId = process.env.controlChannelId;
let currentToken = process.env.tokenAddress;
let statsMessageId = process.env.STATS_MESSAGE_ID || null;
const statsChannelId = process.env.STATS_CHANNEL_ID;
const ws = new WebSocket(obsWebSocketUrl);
const discordClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
let calculationInterval;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', (input) => {
console.log(`Received input: ${input}`);
updateTokenAndRestart(input.trim());
});
function updateTokenAndRestart(newToken) {
currentToken = newToken;
console.log(`Token updated to: ${newToken}`);
updateEnvToken(newToken);
restartCalculations();
}
function restartCalculations() {
if (calculationInterval) {
clearInterval(calculationInterval);
}
calculationInterval = setInterval(checkStatusAndUpdateOBS, 5000);
}
let obsVisibilityState = {
makingMoney: null
};
discordClient.login(discordToken);
discordClient.on('ready', async () => {
console.log(`Logged in as ${discordClient.user.tag}!`);
restartCalculations();
const statsChannel = await discordClient.channels.fetch(statsChannelId);
if (statsChannel) {
try {
const existingMessage = await statsChannel.messages.fetch(process.env.STATS_MESSAGE_ID);
console.log(`Using existing stats message ID: ${process.env.STATS_MESSAGE_ID}`);
} catch (error) {
console.log(`Existing stats message ID is invalid or the message was not found. Sending a new message.`);
const sentMessage = await statsChannel.send({ embeds: [createStatsEmbed()] });
console.log(`New stats message sent, ID: ${sentMessage.id}`);
updateEnvValue('STATS_MESSAGE_ID', sentMessage.id);
}
}
});
function updateEnvValue(key, value) {
fs.readFile(envPath, 'utf8', function(err, data) {
if (err) {
return console.error('Error reading .env file:', err);
}
const envLines = data.split('\n');
const updatedLines = envLines.map(line => line.startsWith(`${key}=`) ? `${key}=${value}` : line);
if (!updatedLines.some(line => line.startsWith(`${key}=`))) {
updatedLines.push(`${key}=${value}`);
}
fs.writeFile(envPath, updatedLines.join('\n'), 'utf8', function(err) {
if (err) {
return console.error('Error writing to .env file:', err);
}
console.log(`${key} updated in .env file.`);
});
});
}
function createStatsEmbed(tokenData = {}) {
const formatNumber = (num) => new Intl.NumberFormat().format(num);
const formatPercentage = (num) => `${num.toFixed(2)}%`;
const marketCapFormatted = tokenData.marketCap ? `$${formatNumber(tokenData.marketCap)}` : 'N/A';
const priceFormatted = tokenData.price ? `$${formatNumber(tokenData.price)}` : 'N/A';
const liquidityFormatted = tokenData.liquidity ? `$${formatNumber(tokenData.liquidity)}` : 'N/A';
const priceChange5mFormatted = tokenData.priceChange5m ? formatPercentage(tokenData.priceChange5m) : 'N/A';
const priceChange1hrFormatted = tokenData.priceChange1hr ? formatPercentage(tokenData.priceChange1hr) : 'N/A';
const priceChange24hrFormatted = tokenData.priceChange24hr ? formatPercentage(tokenData.priceChange24hr) : 'N/A';
const calculateDollarChange = (price, percentageChange) => {
if (price && percentageChange) {
let dollarChange = (parseFloat(percentageChange) / 100) * parseFloat(price);
if (dollarChange < 0) {
return `-$${Math.abs(parseFloat(dollarChange)).toFixed(8)}`;
} else {
return `$${parseFloat(dollarChange).toFixed(8)}`;
}
}
return 'N/A';
};
const dollarChange1hr = calculateDollarChange(tokenData.price, tokenData.priceChange1hr);
const dollarChange24hr = calculateDollarChange(tokenData.price, tokenData.priceChange24hr);
const dollarChange5m = calculateDollarChange(tokenData.price, tokenData.priceChange5m);
const randomColor = Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
const embed = new EmbedBuilder()
.setTitle(`${tokenData.name} $${tokenData.symbol} Token Stats`)
.setURL(`https://dexscreener.com/solana/${currentToken}`)
.setColor(`#${randomColor}`)
.setThumbnail(tokenData.imageUrl)
.addFields(
{ name: 'Market Cap', value: marketCapFormatted, inline: true },
{ name: 'Price (USD)', value: priceFormatted, inline: true },
{ name: 'Liquidity (USD)', value: liquidityFormatted, inline: true },
{ name: 'Status', value: tokenData.profitStatus ? 'Making Money 📈💰' : 'Losing Money 📉😡', inline: false },
{ name: 'Price Change (5 M)', value: `${priceChange5mFormatted} (${dollarChange5m})`, inline: true },
{ name: 'Price Change (1 H)', value: `${priceChange1hrFormatted} (${dollarChange1hr})`, inline: true },
{ name: 'Price Change (24 H)', value: `${priceChange24hrFormatted} (${dollarChange24hr})`, inline: true }
);
return embed;
}
function calculateProfitStatus(tokenData) {
return tokenData.priceChange && tokenData.priceChange.m5 > 0;
}
const updateStatsMessage = async (tokenData) => {
const statsChannel = await discordClient.channels.fetch(statsChannelId);
if (!statsChannel) {
console.log("Stats channel not found.");
return;
}
if (statsMessageId) {
try {
const message = await statsChannel.messages.fetch(statsMessageId);
await message.edit({ embeds: [createStatsEmbed(tokenData)] });
} catch (error) {
console.error('Error updating stats message, sending a new one:', error);
sendNewStatsMessage(statsChannel, tokenData);
}
} else {
sendNewStatsMessage(statsChannel, tokenData);
}
};
const sendNewStatsMessage = async (statsChannel, tokenData) => {
try {
const sentMessage = await statsChannel.send({ embeds: [createStatsEmbed(tokenData)] });
statsMessageId = sentMessage.id;
console.log(`New stats message sent, ID: ${statsMessageId}`);
updateEnvValue('STATS_MESSAGE_ID', statsMessageId);
} catch (error) {
console.error('Error sending new stats message:', error);
}
};
discordClient.on('messageCreate', message => {
if (message.channel.id === controlChannelId) {
if (message.content.startsWith('!token ')) {
const newToken = message.content.split(' ')[1];
if (newToken) {
currentToken = newToken;
updateEnvToken(newToken);
restartCalculations();
message.reply('Token updated successfully. Restarting calculations.');
}
}
}
});
function updateEnvToken(newToken) {
fs.readFile(envPath, 'utf8', (err, data) => {
if (err) {
console.error('Error reading .env file', err);
return;
}
let updatedData = data.split('\n').map(line => {
if (line.startsWith('tokenAddress=')) {
return `tokenAddress=${newToken}`;
}
return line;
}).join('\n');
fs.writeFile(envPath, updatedData, 'utf8', (err) => {
if (err) console.error('Error writing to .env file', err);
});
});
}
ws.on('open', () => {
console.log('Connected to OBS WebSocket server.');
ws.send(JSON.stringify({
'request-type': 'GetAuthRequired',
'message-id': 'authRequired'
}));
});
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message['message-id'] === 'authRequired') {
if (message.authRequired) {
const auth = generateAuth(password, message.salt, message.challenge);
ws.send(JSON.stringify({
'request-type': 'Authenticate',
'message-id': 'authenticate',
'auth': auth
}));
} else {
setInterval(updateOBSTextVisibility, 60000);
}
} else if (message['message-id'] === 'authenticate') {
if (message.status === 'ok') {
setInterval(updateOBSTextVisibility, 5000);
} else {
console.error('Authentication failed:', message.error);
}
} else if (message['message-id'] === 'setText') {
if (message.status === 'ok') {
console.log('Text updated successfully in OBS.');
} else {
console.error('Failed to update text in OBS:', message.error);
}
}
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
const setSourceVisibility = (sourceName, visible) => {
const message = {
'request-type': 'SetSourceRender',
'source': sourceName,
'render': visible,
'message-id': 'setSourceVisibility'
};
ws.send(JSON.stringify(message));
};
const updateOBSTextVisibility = () => {
const makingMoney = obsVisibilityState.makingMoney;
setSourceVisibility("TextGreen", makingMoney);
setSourceVisibility("TextRed", !makingMoney);
setSourceVisibility("Making", makingMoney);
setSourceVisibility("Losing", !makingMoney);
};
const checkStatusAndUpdateOBS = async () => {
try {
const response = await axios.get(`https://api.dexscreener.com/latest/dex/tokens/${currentToken}`);
if (response.data.pairs && response.data.pairs.length > 0) {
const pair = response.data.pairs[0];
const defaultImageUrl = "https://etc.worldhistory.org/wp-content/uploads/2014/12/IMG_3573-888x1024.jpg";
const tokenData = {
name: pair.baseToken.name,
imageUrl: pair.info && pair.info.imageUrl ? pair.info.imageUrl : defaultImageUrl,
symbol: pair.baseToken.symbol,
marketCap: pair.fdv,
price: pair.priceUsd,
liquidity: pair.liquidity.usd,
profitStatus: pair.priceChange.m5 > 0,
priceChange5m: pair.priceChange.m5,
priceChange1hr: pair.priceChange.h1,
priceChange24hr: pair.priceChange.h24
};
obsVisibilityState.makingMoney = tokenData.profitStatus;
const priceFormatted = tokenData.price ? `$${parseFloat(tokenData.price).toFixed(8)}` : 'N/A';
console.log(`Price Change (Last 1 Hour): ${pair.priceChange.h1}%`);
console.log(`Price Change (Last 5 Minutes): ${pair.priceChange.m5}%`);
console.log(`Price Change (Last 24 Hours): ${pair.priceChange.h24}%`);
console.log('Making Money:', tokenData.profitStatus);
setSourceVisibility("TextGreen", tokenData.profitStatus);
setSourceVisibility("TextRed", !tokenData.profitStatus);
updateStatsMessage(tokenData);
updateOBSTextVisibility();
} else {
console.log('No pairs data found for the token address.');
}
} catch (error) {
console.error('Error fetching data:', error);
}
};
function generateAuth(password, salt, challenge) {
const secret = crypto.createHash('sha256').update(password + salt).digest('base64');
const authResponse = crypto.createHash('sha256').update(secret + challenge).digest('base64');
return authResponse;
}
setInterval(checkStatusAndUpdateOBS, 5000);