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:4444'; 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 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 priceChangeFormatted = tokenData.priceChange ? `${formatNumber(tokenData.priceChange)}%` : 'N/A'; const embed = new EmbedBuilder() .setTitle(`${tokenData.name} $${tokenData.symbol} Token Stats`) .setURL(`https://dexscreener.com/solana/${currentToken}`) .setColor('#0099ff') .addFields( { name: 'Market Cap', value: marketCapFormatted, inline: true }, { name: 'Price (USD)', value: `$${tokenData.price || 'N/A'}`, inline: true }, { name: 'Liquidity (USD)', value: liquidityFormatted, inline: true }, { name: 'Status', value: tokenData.profitStatus ? 'Making Money 📈💰' : 'Losing Money 📉😡', inline: false }, { name: 'Price Change (5 Min)', value: priceChangeFormatted, 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 tokenData = { name: pair.baseToken.name, symbol: pair.baseToken.symbol, marketCap: pair.fdv, price: pair.priceUsd, liquidity: pair.liquidity.usd, profitStatus: pair.priceChange.m5 > 0, priceChange: pair.priceChange.m5 }; obsVisibilityState.makingMoney = tokenData.profitStatus; const priceFormatted = tokenData.price ? `$${parseFloat(tokenData.price).toFixed(8)}` : 'N/A'; console.log(`Price Change (Last 5 Minutes): ${pair.priceChange.m5}%`); 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);