Skip to content

Instantly share code, notes, and snippets.

@minanagehsalalma
Last active November 22, 2025 20:43
Show Gist options
  • Select an option

  • Save minanagehsalalma/569a536695e40c76cebf956f56dcbf9c to your computer and use it in GitHub Desktop.

Select an option

Save minanagehsalalma/569a536695e40c76cebf956f56dcbf9c to your computer and use it in GitHub Desktop.
Facebook Messenger Audio Downloader (v12.0) - One-click download. Auto-plays/pauses to capture source, converts to real MP3 (with WAV fallback).
// ============================================================================
// AUDIO SNIPPET DOWNLOADER v12.0 (Robust Auto-Pause)
// ============================================================================
// FIX: Uses the 'playing' event listener to ensure pause commands are respected
// by the browser's media engine.
// ============================================================================
(function() {
console.log("Audio Downloader v12: Robust Auto-Pause Active.");
let activeAutoDownloadBtn = null;
// ---------------------------------------------------------
// 1. INJECT LAMEJS (MP3 Library)
// ---------------------------------------------------------
let lameLoaded = false;
const lameScript = document.createElement('script');
lameScript.src = "https://cdn.jsdelivr.net/npm/[email protected]/lame.min.js";
lameScript.crossOrigin = "anonymous";
lameScript.onload = () => { lameLoaded = true; console.log("MP3 Library loaded."); };
lameScript.onerror = () => { lameLoaded = false; console.warn("MP3 Library blocked. Fallback enabled."); };
document.head.appendChild(lameScript);
// ---------------------------------------------------------
// 2. ENCODING LOGIC
// ---------------------------------------------------------
function floatTo16BitPCM(input) {
const output = new Int16Array(input.length);
for (let i = 0; i < input.length; i++) {
const s = Math.max(-1, Math.min(1, input[i]));
output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
}
return output;
}
async function encodeMp3(audioBuffer) {
if (!lameLoaded || !window.lamejs) throw new Error("LameJS missing");
const mp3Encoder = new lamejs.Mp3Encoder(1, audioBuffer.sampleRate, 128);
const samples = floatTo16BitPCM(audioBuffer.getChannelData(0));
const sampleBlockSize = 1152;
const mp3Data = [];
let remaining = samples.length;
let i = 0;
while (remaining >= sampleBlockSize) {
const mp3buf = mp3Encoder.encodeBuffer(samples.subarray(i, i + sampleBlockSize));
if (mp3buf.length > 0) mp3Data.push(mp3buf);
remaining -= sampleBlockSize;
i += sampleBlockSize;
}
const mp3buf = mp3Encoder.flush();
if (mp3buf.length > 0) mp3Data.push(mp3buf);
return new Blob(mp3Data, { type: 'audio/mp3' });
}
function encodeWav(audioBuffer) {
const samples = audioBuffer.getChannelData(0);
const buffer = new ArrayBuffer(44 + samples.length * 2);
const view = new DataView(buffer);
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i));
};
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + samples.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 1, true);
view.setUint32(24, audioBuffer.sampleRate, true);
view.setUint32(28, audioBuffer.sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, samples.length * 2, true);
const output = new Int16Array(buffer, 44);
for (let i = 0; i < samples.length; i++) {
const s = Math.max(-1, Math.min(1, samples[i]));
output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
}
return new Blob([view], { type: 'audio/wav' });
}
// ---------------------------------------------------------
// 3. CORE DOWNLOAD PROCESSOR
// ---------------------------------------------------------
async function processAudioDownload(btn, srcUrl) {
if (!btn) return;
btn.innerText = "⏳ Downloading...";
btn.style.backgroundColor = "#e6b800";
try {
const response = await fetch(srcUrl);
const blob = await response.blob();
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const arrayBuffer = await blob.arrayBuffer();
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
let finalBlob = null;
let ext = "";
if (lameLoaded) {
try {
btn.innerText = "⏳ Encoding MP3...";
finalBlob = await encodeMp3(audioBuffer);
ext = ".mp3";
} catch (e) { console.warn("MP3 failed", e); }
}
if (!finalBlob) {
btn.innerText = "⏳ Encoding WAV...";
finalBlob = encodeWav(audioBuffer);
ext = ".wav";
}
const durationTag = btn.dataset.duration ? `_${btn.dataset.duration.replace(':','m')}` : '';
const filename = `messenger_audio${durationTag}${ext}`;
const a = document.createElement('a');
a.href = URL.createObjectURL(finalBlob);
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
btn.innerText = `✔ SAVED ${ext.toUpperCase()}`;
btn.style.backgroundColor = "#31a24c";
setTimeout(() => {
btn.innerText = "⬇ GET AUDIO";
btn.style.backgroundColor = "#20cef5";
}, 3000);
} catch (err) {
console.error(err);
btn.innerText = "❌ ERROR";
alert("Download failed. See console.");
}
}
// ---------------------------------------------------------
// 4. SPY & AUTOMATION LOGIC
// ---------------------------------------------------------
function getTimeVariations(seconds) {
if (!seconds) return [];
const candidates = [Math.floor(seconds), Math.round(seconds), Math.ceil(seconds)];
return candidates.map(sec => {
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${s.toString().padStart(2, '0')}`;
});
}
const originalAudioPlay = HTMLAudioElement.prototype.play;
// We attach this globally to handle paused events correctly
function attachPauseListener(mediaElement) {
if (mediaElement.dataset.hasPauseListener) return;
mediaElement.addEventListener('playing', function() {
if (activeAutoDownloadBtn) {
console.log("[AutoSpy] Audio started. Pausing immediately.");
mediaElement.pause();
// Process the download
const btn = activeAutoDownloadBtn;
activeAutoDownloadBtn = null;
// Use currentSrc as the most reliable source
const src = mediaElement.currentSrc || mediaElement.src;
processAudioDownload(btn, src);
}
});
mediaElement.dataset.hasPauseListener = "true";
}
function handlePlay(mediaElement) {
attachPauseListener(mediaElement);
// Manual play support
const checkDuration = () => {
const duration = mediaElement.duration;
if (!duration || duration === Infinity) return;
const timeStrings = getTimeVariations(duration);
document.querySelectorAll('.custom-audio-dl-btn').forEach(btn => {
if (timeStrings.includes(btn.dataset.duration) && btn.innerText.includes("GET AUDIO")) {
btn.innerText = "✔ CLICK TO DOWNLOAD";
btn.style.backgroundColor = "#31a24c";
btn.onclick = (e) => {
e.stopPropagation();
processAudioDownload(btn, mediaElement.src || mediaElement.currentSrc);
};
}
});
};
// Run check if metadata already exists, otherwise wait
if (mediaElement.readyState >= 1) checkDuration();
mediaElement.addEventListener('loadedmetadata', checkDuration);
}
HTMLAudioElement.prototype.play = function() {
handlePlay(this);
return originalAudioPlay.apply(this, arguments);
};
// ---------------------------------------------------------
// 5. UI INJECTION
// ---------------------------------------------------------
const style = document.createElement('style');
style.innerHTML = `
.custom-audio-dl-btn {
background-color: #20cef5;
color: white;
border: none;
padding: 6px 10px;
border-radius: 20px;
font-size: 10px;
font-weight: 800;
cursor: pointer;
margin-left: 10px;
z-index: 10000;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
font-family: sans-serif;
transition: all 0.2s;
min-width: 80px;
}
.custom-audio-dl-btn:hover { opacity: 0.9; transform: scale(1.05); }
`;
document.head.appendChild(style);
function addDownloadButtons() {
const playButtons = document.querySelectorAll('[aria-label="Play"]');
playButtons.forEach(playBtn => {
const rowContainer = playBtn.closest('[role="gridcell"]') || playBtn.closest('.html-div');
if (!rowContainer) return;
if (rowContainer.querySelector('.custom-audio-dl-btn')) return;
const timer = rowContainer.querySelector('[role="timer"]');
let durationText = "0:00";
if (timer) {
durationText = timer.innerText;
} else {
const textNodes = rowContainer.innerText.match(/\d+:\d{2}/);
if (textNodes) durationText = textNodes[0];
}
const dlBtn = document.createElement('button');
dlBtn.innerText = "⬇ GET AUDIO";
dlBtn.className = "custom-audio-dl-btn";
dlBtn.dataset.duration = durationText;
// --- AUTOMATION TRIGGER ---
dlBtn.onclick = (e) => {
e.stopPropagation();
activeAutoDownloadBtn = dlBtn;
dlBtn.innerText = "⏳ Connecting...";
playBtn.click();
setTimeout(() => {
if (activeAutoDownloadBtn === dlBtn) {
dlBtn.innerText = "❌ Timeout";
activeAutoDownloadBtn = null;
alert("Could not trigger audio. Try playing it manually once.");
}
}, 5000);
};
const insertionPoint = playBtn.parentElement.parentElement;
if (insertionPoint) {
insertionPoint.appendChild(dlBtn);
} else {
playBtn.parentNode.appendChild(dlBtn);
}
});
}
addDownloadButtons();
setInterval(addDownloadButtons, 2000);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment