// ==UserScript== // @name Instagram Comments Exporter // @namespace http://tampermonkey.net/ // @version 1.0 // @description Export Instagram comments from a post/reel with options. // @author realSamy // @downloadURL https://raw.githubusercontent.com/realSamy/instagram-comment-exporter/main/comment-exporter.user.js // @updateURL https://raw.githubusercontent.com/realSamy/instagram-comment-exporter/main/comment-exporter.user.js // @match https://www.instagram.com/p/* // @match https://www.instagram.com/reel/* // @match https://www.instagram.com/*/reel/* // ==/UserScript== (function() { 'use strict'; // Store the original XMLHttpRequest before we hook into it. const OriginalXHR = XMLHttpRequest; let latestDocId = null; let latestMediaId = null; let exportBtn = null; let statusMessage = null; // We'll store the full, successful request body and headers to use as a template let latestRequestBodyTemplate = null; let latestRequestHeadersTemplate = {}; const INSTAGRAM_GRAPHQL_APP_ID = "936619743392459"; // ---- Inject Network Interceptors (Fetch and XHR) ---- // This function hooks into Instagram's network requests to find the // necessary doc_id, media_id, and other dynamic parameters for the comments query. function hookNetwork() { // Intercept XMLHttpRequest const open = XMLHttpRequest.prototype.open; const send = XMLHttpRequest.prototype.send; const setRequestHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.open = function(method, url) { this._url = url; this._requestHeaders = {}; return open.apply(this, arguments); }; XMLHttpRequest.prototype.setRequestHeader = function(header, value) { this._requestHeaders[header] = value; return setRequestHeader.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(body) { try { if (this._url && this._url.includes('/graphql/query')) { if (body) { let bodyStr = typeof body === 'string' ? body : new TextDecoder().decode(body); let bodyObj = new URLSearchParams(bodyStr); let docId = bodyObj.get('doc_id'); let variables = JSON.parse(bodyObj.get('variables')); let mediaId = variables?.media_id; // Check if it's a comment pagination query and store the template if (mediaId) { latestDocId = docId; latestMediaId = mediaId; latestRequestBodyTemplate = bodyStr; latestRequestHeadersTemplate = this._requestHeaders; updateExportButtonState(false); console.log('[Comments Exporter] Captured request template from XHR.'); } } } } catch (err) { console.error('[Comments Exporter] XHR Hook Error:', err); } return send.apply(this, arguments); }; // Intercept Fetch requests const origFetch = window.fetch; window.fetch = async function(...args) { const [input, init] = args; if (typeof input === "string" && input.includes('/graphql/query') && init && init.body) { try { let bodyStr = typeof init.body === 'string' ? init.body : new TextDecoder().decode(init.body); let bodyObj = new URLSearchParams(bodyStr); let docId = bodyObj.get('doc_id'); let variables = JSON.parse(bodyObj.get('variables')); let mediaId = variables?.media_id; // Check if it's a comment pagination query and store the template if (mediaId) { latestDocId = docId; latestMediaId = mediaId; latestRequestBodyTemplate = bodyStr; // Capture all headers from the original request for (let [key, value] of init.headers.entries()) { latestRequestHeadersTemplate[key] = value; } updateExportButtonState(false); console.log('[Comments Exporter] Captured request template from Fetch.'); } } catch (err) { console.error('[Comments Exporter] Fetch Hook Error:', err); } } return origFetch.apply(this, args); }; console.log('%c[Comments Exporter] Network hooks installed', 'color: green; font-weight: bold; font-size: large;'); } // ---- Add Export Button & Status Message ---- function createExportButton() { // Inject a style block for the UI elements const style = document.createElement('style'); style.innerHTML = ` .export-btn { position: fixed; bottom: 20px; right: 20px; padding: 10px 15px; background-color: #3897f0; color: #fff; border: none; border-radius: 5px; font-weight: bold; z-index: 99999; cursor: pointer; transition: background-color 0.2s ease; } .export-btn:hover:enabled { background-color: #2c7bbd; } .export-btn:disabled { cursor: not-allowed; opacity: 0.6; } .status-message { position: fixed; bottom: 60px; right: 20px; padding: 8px 12px; background-color: rgba(0,0,0,0.7); color: #fff; border-radius: 5px; z-index: 99999; display: none; } .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 100000; } .modal-box { background: #fff; padding: 20px; border-radius: 8px; width: 300px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .modal-box * { color: black !important; } .modal-box h3 { margin-top: 0; font-size: 1.2em; border-bottom: 1px solid #eee; padding-bottom: 10px; } .modal-box label { display: block; margin-bottom: 10px; font-size: 0.9em; } .modal-box input[type="number"] { width: 80px; padding: 5px; border: 1px solid #ccc; border-radius: 4px; margin-left: 5px; } .modal-button { padding: 8px 12px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; } .modal-button.primary { background-color: #3897f0; color: #fff; } .modal-button.secondary { background-color: #f0f0f0; color: #333; margin-left: 10px; } .modal-button:hover.primary { background-color: #2c7bbd; } .modal-button:hover.secondary { background-color: #e0e0e0; } `; document.head.appendChild(style); // Create the button using a class exportBtn = document.createElement('button'); exportBtn.innerText = 'Export Comments'; exportBtn.className = 'export-btn'; exportBtn.disabled = true; exportBtn.onclick = showExportModal; document.body.appendChild(exportBtn); // Create the status message element using a class statusMessage = document.createElement('div'); statusMessage.className = 'status-message'; document.body.appendChild(statusMessage); } function updateExportButtonState(disabled = false) { if (exportBtn) { exportBtn.disabled = disabled; } } function showStatus(message, show = true) { if (statusMessage) { statusMessage.innerText = message; statusMessage.style.display = show ? 'block' : 'none'; } } // ---- Modal for options ---- function showExportModal() { const modal = document.createElement('div'); modal.className = 'modal-overlay'; const box = document.createElement('div'); box.className = 'modal-box'; box.innerHTML = `