Created
November 1, 2024 11:58
-
-
Save Ekiserrepe/021d7230fd2f2ed53ad508eb2afdaf32 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//20241101 10_42 | |
require('dotenv').config(); | |
const { OBSWebSocket } = require('obs-websocket-js'); | |
const xrpl = require('@transia/xrpl'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const ffmpeg = require('fluent-ffmpeg'); | |
const axios = require('axios'); | |
const obs = new OBSWebSocket(); | |
let tracklist = []; | |
let overlayLines = []; | |
const songsFolderPath = `${__dirname}/music_folder`; | |
const maxOverlayLines = 10; | |
const screenHeight = 1080; | |
const fontSize = 24; | |
const overlayHeight = maxOverlayLines * fontSize; | |
let isPlaying = false; | |
let client; | |
let remainingMinutes = 0; | |
let counterInterval; | |
const xummUrl = 'https://xumm.app/api/v1/platform/account-meta/'; | |
const tracklistFilePath = `${__dirname}/tracklist.txt`; | |
//const overlayFilePath = `${__dirname}/overlay.txt`; | |
// Load tracklist and overlay from files on startup | |
function loadPersistentData() { | |
if (fs.existsSync(tracklistFilePath)) { | |
const tracklistData = fs.readFileSync(tracklistFilePath, 'utf8'); | |
tracklist = tracklistData.split('\n').filter(Boolean).map(Number); | |
} | |
} | |
// Save tracklist to file | |
function saveTracklist() { | |
fs.writeFileSync(tracklistFilePath, tracklist.join('\n')); | |
} | |
// Connect to OBS | |
async function connectToOBS() { | |
try { | |
await obs.connect(`ws://${process.env.OBS_ADDRESS}`, process.env.OBS_PASSWORD); | |
console.log('Connected to OBS'); | |
await clearOverlay(); | |
await setupImages(); | |
await setupCounter(); | |
await setupSongCounterOverlay(); | |
} catch (err) { | |
console.error('Error connecting to OBS:', err); | |
} | |
} | |
// Fetch account alias for a given address | |
async function fetchAccountAlias(address) { | |
const url = `${xummUrl}${address}`; | |
const options = { method: 'GET', headers: { accept: 'application/json', 'Content-Type': 'application/json' } }; | |
try { | |
const res = await axios(url, options); | |
const data = res.data; | |
if (data.xummProfile?.accountAlias) return data.xummProfile.accountAlias; | |
if (data.thirdPartyProfiles && data.thirdPartyProfiles.length > 0) { | |
for (const profile of data.thirdPartyProfiles) { | |
if (profile.accountAlias) return profile.accountAlias; | |
} | |
} | |
return address; | |
} catch (error) { | |
console.error('Error fetching account alias:', error); | |
return address; | |
} | |
} | |
// Clear overlay on startup | |
async function clearOverlay() { | |
overlayLines = []; | |
try { | |
await obs.call('SetInputSettings', { | |
inputName: 'OverlayText', | |
inputSettings: { text: '' }, | |
overlay: true | |
}); | |
console.log('Overlay cleared on startup.'); | |
} catch (error) { | |
console.error('Error clearing the overlay:', error); | |
} | |
} | |
// Set up images and overlays in OBS | |
async function setupImages() { | |
const backgroundPath = path.join(__dirname, '/assets/background.png'); | |
const qrPath = path.join(__dirname, '/assets/qr.png'); | |
const infoText = `1 XAH = 1 SONG\nUSE MEMO FIELD TO CHOOSE ONE SONG\nSELECT FROM 1 TO 100 NUMBERS AS MEMO FIELD\nYOU CAN ADD MORE THAN ONE SONG\nSEND ANY AMOUNT\nEX: 10 XAH = 10 SONGS\nANY FRACTIONAL AMOUNT WILL BE SENT BACK\nEX: IF YOU SEND 34.24 XAH, 0.24 XAH WILL BE SENT BACK `; | |
// BackgroundImage configuration | |
if (fs.existsSync(backgroundPath)) { | |
try { | |
await obs.call('GetInputSettings', { inputName: 'BackgroundImage' }); | |
console.log("Background image source already exists. Updating settings."); | |
await obs.call('SetInputSettings', { | |
inputName: 'BackgroundImage', | |
inputSettings: { file: backgroundPath }, | |
overlay: true | |
}); | |
} catch (error) { | |
if (error.code === 601) { | |
console.log("Creating new BackgroundImage source."); | |
await obs.call('CreateInput', { | |
sceneName: 'Scene', | |
inputName: 'BackgroundImage', | |
inputKind: 'image_source', | |
inputSettings: { file: backgroundPath } | |
}); | |
} else { | |
console.error('Error setting up background image:', error); | |
} | |
} | |
const { sceneItemId: bgSceneItemId } = await obs.call('GetSceneItemId', { | |
sceneName: 'Scene', | |
sourceName: 'BackgroundImage' | |
}); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: bgSceneItemId, | |
sceneItemTransform: { | |
positionX: 1300, | |
positionY: screenHeight - 100, | |
alignment: 5 | |
} | |
}); | |
} else { | |
console.error("Background image not found at path:", backgroundPath); | |
} | |
// QRImage configuration | |
if (fs.existsSync(qrPath)) { | |
try { | |
await obs.call('GetInputSettings', { inputName: 'QRImage' }); | |
console.log("QR image source already exists. Updating settings."); | |
await obs.call('SetInputSettings', { | |
inputName: 'QRImage', | |
inputSettings: { file: qrPath }, | |
overlay: true | |
}); | |
} catch (error) { | |
if (error.code === 601) { | |
console.log("Creating new QRImage source."); | |
await obs.call('CreateInput', { | |
sceneName: 'Scene', | |
inputName: 'QRImage', | |
inputKind: 'image_source', | |
inputSettings: { file: qrPath } | |
}); | |
} else { | |
console.error('Error setting up QR image:', error); | |
} | |
} | |
const { sceneItemId: qrSceneItemId } = await obs.call('GetSceneItemId', { | |
sceneName: 'Scene', | |
sourceName: 'QRImage' | |
}); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: qrSceneItemId, | |
sceneItemTransform: { | |
positionX: (1920 - 150) / 2, | |
positionY: screenHeight - 200, | |
alignment: 5 | |
} | |
}); | |
} else { | |
console.error("QR image not found at path:", qrPath); | |
} | |
// Create and position the InfoText overlay | |
try { | |
await obs.call('GetInputSettings', { inputName: 'InfoText' }); | |
console.log("InfoText overlay source already exists. Updating settings."); | |
await obs.call('SetInputSettings', { | |
inputName: 'InfoText', | |
inputSettings: { text: infoText, font: { face: "Arial", size: 30, style:"Bold" } } | |
}); | |
} catch (error) { | |
if (error.code === 601) { | |
console.log("Creating new InfoText overlay source."); | |
await obs.call('CreateInput', { | |
sceneName: 'Scene', | |
inputName: 'InfoText', | |
inputKind: 'text_gdiplus', | |
inputSettings: { text: infoText, font: { face: "Arial", size: 30, style: "Bold" } } | |
}); | |
} else { | |
console.error('Error setting up InfoText overlay:', error); | |
} | |
} | |
const { sceneItemId: infoSceneItemId } = await obs.call('GetSceneItemId', { | |
sceneName: 'Scene', | |
sourceName: 'InfoText' | |
}); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: infoSceneItemId, | |
sceneItemTransform: { | |
positionX: 0, // Positioned to the right of the QR | |
positionY: 575, | |
alignment: 5 | |
} | |
}); | |
console.log('InfoText overlay configured successfully.'); | |
} | |
// Set up dynamic song counter overlay | |
async function setupSongCounterOverlay() { | |
try { | |
await obs.call('GetInputSettings', { inputName: 'SongCounter' }); | |
await obs.call('SetInputSettings', { | |
inputName: 'SongCounter', | |
inputSettings: { text: `Remaining Songs: ${tracklist.length} songs`, font: { face: "Arial", size: 24 } } | |
}); | |
} catch (error) { | |
if (error.code === 601) { | |
await obs.call('CreateInput', { | |
sceneName: 'Scene', | |
inputName: 'SongCounter', | |
inputKind: 'text_gdiplus', | |
inputSettings: { text: `Remaining Songs: ${tracklist.length} songs`, font: { face: "Arial", size: 24 } } | |
}); | |
} else { | |
console.error('Error setting up SongCounter overlay:', error); | |
} | |
} | |
const { sceneItemId: counterSceneItemId } = await obs.call('GetSceneItemId', { | |
sceneName: 'Scene', | |
sourceName: 'SongCounter' | |
}); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: counterSceneItemId, | |
sceneItemTransform: { | |
positionX: 1302, // Position the counter on the screen | |
positionY: 930, // Below the time counter | |
alignment: 5 | |
} | |
}); | |
updateSongCounterOverlay(); | |
} | |
// Update song counter display | |
async function updateSongCounterOverlay() { | |
try { | |
await obs.call('SetInputSettings', { | |
inputName: 'SongCounter', | |
inputSettings: { text: `Remaining Songs: ${tracklist.length} songs` } | |
}); | |
} catch (error) { | |
console.error('Error updating SongCounter overlay:', error); | |
} | |
} | |
// Set up time counter overlay | |
async function setupCounter() { | |
try { | |
await obs.call('GetInputSettings', { inputName: 'TimeCounter' }); | |
console.log("TimeCounter overlay source already exists. Updating settings."); | |
await obs.call('SetInputSettings', { | |
inputName: 'TimeCounter', | |
inputSettings: { text: `Remaining Time: ${remainingMinutes} minutes`, font: { face: "Arial", size: 24 } } | |
}); | |
} catch (error) { | |
if (error.code === 601) { | |
console.log("Creating new TimeCounter overlay source."); | |
await obs.call('CreateInput', { | |
sceneName: 'Scene', | |
inputName: 'TimeCounter', | |
inputKind: 'text_gdiplus', | |
inputSettings: { text: `Remaining Time: ${remainingMinutes} minutes`, font: { face: "Arial", size: 24 } } | |
}); | |
} else { | |
console.error('Error setting up TimeCounter overlay:', error); | |
} | |
} | |
const { sceneItemId: counterSceneItemId } = await obs.call('GetSceneItemId', { | |
sceneName: 'Scene', | |
sourceName: 'TimeCounter' | |
}); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: counterSceneItemId, | |
sceneItemTransform: { | |
positionX: 1302, // Position the counter on the screen | |
positionY: 958, | |
alignment: 5 | |
} | |
}); | |
// Start the counter update interval | |
updateCounter(); | |
counterInterval = setInterval(decrementCounter, 60000); // Update every minute | |
} | |
// Update the counter display | |
async function updateCounter() { | |
try { | |
await obs.call('SetInputSettings', { | |
inputName: 'TimeCounter', | |
inputSettings: { text: `Remaining Time: ${remainingMinutes} minutes` } | |
}); | |
console.log(`Counter updated to: ${remainingMinutes} minutes`); | |
} catch (error) { | |
console.error('Error updating TimeCounter overlay:', error); | |
} | |
} | |
// Decrement the counter by one minute | |
function decrementCounter() { | |
if (remainingMinutes > 0) { | |
remainingMinutes--; | |
updateCounter(); | |
} else { | |
clearInterval(counterInterval); // Stop the interval if no time is remaining | |
} | |
} | |
// Calculate remaining time | |
async function calculateRemainingTime() { | |
remainingMinutes = 0; | |
for (const song of tracklist) { | |
const trackPath = `${songsFolderPath}/${song}.mp3`; | |
const duration = await new Promise((resolve) => { | |
ffmpeg.ffprobe(trackPath, (err, metadata) => { | |
resolve(!err && metadata ? Math.ceil(metadata.format.duration / 60) : 0); | |
}); | |
}); | |
remainingMinutes += duration; | |
} | |
updateCounter(); | |
} | |
// Update and save overlay lines | |
async function updateOverlay(text) { | |
overlayLines.unshift(text); | |
if (overlayLines.length > maxOverlayLines) overlayLines.pop(); | |
//saveOverlay(); | |
try { | |
await obs.call('SetInputSettings', { | |
inputName: 'OverlayText', | |
inputSettings: { text: overlayLines.join('\n'), font: { face: "Arial", size: fontSize }, alignment: 1 }, | |
overlay: true | |
}); | |
const { sceneItemId } = await obs.call('GetSceneItemId', { sceneName: 'Scene', sourceName: 'OverlayText' }); | |
await obs.call('SetSceneItemTransform', { | |
sceneName: 'Scene', | |
sceneItemId: sceneItemId, | |
sceneItemTransform: { positionX: 0, positionY: screenHeight - overlayHeight, alignment: 5 } | |
}); | |
} catch (error) { | |
console.error('Error updating the overlay:', error); | |
} | |
} | |
// Play the song and save progress | |
async function playSong(songNumber) { | |
const trackPath = `${songsFolderPath}/${songNumber}.mp3`; | |
try { | |
ffmpeg.ffprobe(trackPath, (err, metadata) => { | |
if (err) { | |
console.error('Error getting song duration:', err); | |
return; | |
} | |
const durationMs = metadata.format.duration * 1000; | |
obs.call('SetInputSettings', { inputName: 'MusicChannel', inputSettings: { local_file: trackPath }, overlay: true }); | |
console.log(`Playing song ${songNumber} with duration ${Math.round(durationMs / 1000)} seconds`); | |
isPlaying = true; | |
updateOverlay(`Now playing: song ${songNumber}`); | |
//saveOverlay(); | |
setTimeout(() => { | |
isPlaying = false; | |
updateSongCounterOverlay(); | |
}, durationMs); | |
}); | |
} catch (error) { | |
console.error('Error changing song or getting duration:', error); | |
} | |
} | |
// Add song to tracklist and save it | |
async function addSongToTracklist(songNumber, sender) { | |
const alias = await fetchAccountAlias(sender); | |
tracklist.push(songNumber); | |
console.log(`Tracklist updated: ${tracklist.join(', ')}`); | |
updateOverlay(`${alias} added song ${songNumber}`); | |
saveTracklist(); | |
updateSongCounterOverlay(); | |
//calculateRemainingTime(); | |
} | |
// Start listening and manage sequential playback | |
function startTracklistListener() { | |
setInterval(() => { | |
if (!isPlaying && tracklist.length > 0) { | |
const nextSong = tracklist.shift(); | |
playSong(nextSong); | |
saveTracklist(); | |
calculateRemainingTime(); | |
} | |
}, 2000); | |
} | |
// Check and reconnect to Xahau every 30 seconds if disconnected | |
async function checkXahauConnection() { | |
setInterval(async () => { | |
if (!client || !client.isConnected()) { | |
console.log("Client disconnected from Xahau. Attempting to reconnect..."); | |
await connectToXahau(); | |
} else { | |
console.log("Client is connected to Xahau."); | |
} | |
}, 30000); | |
} | |
// Connect to Xahau and subscribe | |
async function connectToXahau(retries = 3) { | |
client = new xrpl.Client(process.env.XRPL_WSS); | |
try { | |
await client.connect(); | |
console.log("Connected to Xahau WebSocket"); | |
updateOverlay("Connected to Xahau"); | |
await subscribeToAccount(); | |
} catch (error) { | |
console.error(`Error connecting to Xahau, retries left: ${retries}`, error); | |
if (retries > 0) { | |
setTimeout(() => connectToXahau(retries - 1), 3000); | |
} else { | |
console.error("Could not connect to Xahau after multiple attempts."); | |
} | |
} | |
} | |
// Subscribe to account on Xahau | |
async function subscribeToAccount() { | |
try { | |
await client.request({ command: 'subscribe', accounts: [process.env.XRPL_ACCOUNT] }); | |
console.log("Successfully subscribed to account:", process.env.XRPL_ACCOUNT); | |
client.on('transaction', async (tx) => { | |
if (tx.transaction?.TransactionType === 'Payment' && tx.transaction?.Destination === process.env.XRPL_ACCOUNT) { | |
handlePayment(tx.transaction); | |
} | |
}); | |
} catch (error) { | |
console.error("Error subscribing to account:", error); | |
setTimeout(subscribeToAccount, 5000); | |
} | |
} | |
// Process incoming Payment | |
async function handlePayment(transaction) { | |
const { Account: sender, Amount, Memos } = transaction; | |
console.log("Transaction received:", transaction); | |
if (typeof Amount === 'string' && parseInt(Amount) >= 1000000) { | |
const memoHex = Memos?.[0]?.Memo?.MemoData; | |
const memoText = memoHex ? Buffer.from(memoHex, 'hex').toString('utf-8') : null; | |
const memoValue = memoText && !isNaN(memoText) ? parseInt(memoText) : null; | |
// Rule for the specific account | |
if (sender === 'rf1NrYAsv92UPDd8nyCG4A3bez7dhYE61r') { | |
if (memoValue && memoValue >= 1 && memoValue <= 100) { | |
console.log(`Adding ${memoValue} songs to the tracklist from specific account ${sender}`); | |
for (let i = 0; i < memoValue; i++) { | |
addSongToTracklist(Math.floor(Math.random() * 100) + 1, sender); | |
} | |
} else { | |
const amountValue = Math.floor(parseInt(Amount) / 1000000); | |
console.log(`Adding ${amountValue} songs based on Amount due to invalid or empty memo`); | |
for (let i = 0; i < amountValue; i++) { | |
addSongToTracklist(Math.floor(Math.random() * 100) + 1, sender); | |
} | |
} | |
} else { | |
// General rule for other accounts | |
if (parseInt(Amount) === 1000000 && memoValue && memoValue >= 1 && memoValue <= 100) { | |
addSongToTracklist(memoValue, sender); | |
} else { | |
const amountValue = Math.floor(parseInt(Amount) / 1000000); | |
const randomSongs = new Set(); | |
while (randomSongs.size < amountValue && randomSongs.size < 100) { | |
const randomSong = Math.floor(Math.random() * 100) + 1; | |
randomSongs.add(randomSong); | |
} | |
randomSongs.forEach(async song => await addSongToTracklist(song, sender)); | |
} | |
} | |
await calculateRemainingTime(); // Actualiza el tiempo después de añadir canciones | |
} | |
} | |
// Initialize connections and tracklist listener | |
(async () => { | |
loadPersistentData(); // Load saved tracklist and overlay | |
await connectToOBS(); | |
await connectToXahau(); | |
startTracklistListener(); | |
checkXahauConnection(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment