Skip to content

Instantly share code, notes, and snippets.

@edwinhu
Last active February 19, 2026 17:54
Show Gist options
  • Select an option

  • Save edwinhu/2d35c66ebe596ea4cc62f7a9f9c0323d to your computer and use it in GitHub Desktop.

Select an option

Save edwinhu/2d35c66ebe596ea4cc62f7a9f9c0323d to your computer and use it in GitHub Desktop.
insta-scroll
// ==UserScript==
// @name Instagram Logged-Out Unlocker
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Removes scroll lock, gray overlay, login prompt, and restores infinite scroll on Instagram without login
// @author You
// @match *://www.instagram.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const DOC_ID = '7950326061742207';
const IG_APP_ID = '936619743392459';
const CELL_W = 206, CELL_H = 275;
const shownCodes = new Set();
const state = { cursor: null, more: true, loading: false, grid: null, nativeRow: null, nativeCell: null, userId: null };
// ── SCROLL UNLOCK ────────────────────────────────────────────────────────────
function patchScrollview() {
const sv = document.querySelector('#scrollview');
if (!sv || sv.__igPatched) return;
sv.__igPatched = true;
let proto = Object.getPrototypeOf(sv), desc = null;
while (proto) { desc = Object.getOwnPropertyDescriptor(proto, 'scrollTop'); if (desc) break; proto = Object.getPrototypeOf(proto); }
if (!desc) return;
Object.defineProperty(sv, 'scrollTop', {
get() { return desc.get.call(this); },
set(val) { if (desc.get.call(this) > 300 && val < 100) return; desc.set.call(this, val); },
configurable: true,
});
if (!window.__igWheel) {
window.__igWheel = e => { const el = document.querySelector('#scrollview'); if (!el) return; e.preventDefault(); el.scrollTop += e.deltaY; };
document.addEventListener('wheel', window.__igWheel, { passive: false, capture: true });
}
}
// ── OVERLAY REMOVAL ──────────────────────────────────────────────────────────
function cleanOverlays() {
document.querySelectorAll('.x1ey2m1c.xtijo5x.x1o0tod.xixxii4.x13vifvy.x1h0vfkc').forEach(el => el.remove());
const svs = [...document.querySelectorAll('#scrollview')];
const main = svs.reduce((best, el) => (!best || el.scrollHeight > best.scrollHeight) ? el : best, null);
svs.forEach(el => { if (el !== main) el.remove(); });
const prompt = [...document.querySelectorAll('*')].find(el => el.textContent?.trim() === 'Continue watching');
if (prompt) {
let c = prompt;
while (c && c.getBoundingClientRect().height < 300) c = c.parentElement;
c?.remove();
}
}
// ── INFINITE SCROLL ──────────────────────────────────────────────────────────
function findGrid() {
const sv = document.querySelector('#scrollview');
if (!sv) return false;
const rows = [...sv.querySelectorAll('._ac7v')];
if (rows.length < 3) return false;
sv.querySelectorAll('._ac7v a').forEach(a => {
const code = a.href?.match(/\\/(p|reel)\\/([^\\/]+)\\//)?.[2];
if (code) shownCodes.add(code);
});
state.nativeRow = rows[0];
state.nativeCell = rows[0].children[0];
state.grid = rows[0].parentElement;
return true;
}
function getUserId() {
for (const s of document.scripts) {
const m = s.textContent.match(/"id":"(\\d{7,15})"/);
if (m) return m[1];
}
return null;
}
function injectEdges(edges) {
const { grid, nativeRow, nativeCell } = state;
if (!grid || !nativeRow || !nativeCell) return 0;
const cells = edges.filter(e => {
const code = e.node?.shortcode;
if (!code || shownCodes.has(code)) return false;
shownCodes.add(code);
return true;
}).map(edge => {
const node = edge.node;
const imgSrc = node.thumbnail_src || node.display_url;
if (!imgSrc) return null;
const cell = nativeCell.cloneNode(true);
cell.style.cssText = `width:${CELL_W}px;height:${CELL_H}px;overflow:hidden;display:block;flex-shrink:0;`;
cell.querySelectorAll('img').forEach(img => { img.src = imgSrc; img.srcset = ''; img.sizes = ''; img.style.cssText = `width:${CELL_W}px;height:${CELL_H}px;object-fit:cover;display:block;`; });
const link = cell.querySelector('a');
if (link) link.href = `https://www.instagram.com/p/${node.shortcode}/`;
return cell;
}).filter(Boolean);
for (let i = 0; i < cells.length; i += 3) {
const row = nativeRow.cloneNode(false);
cells.slice(i, i + 3).forEach(c => row.appendChild(c));
grid.appendChild(row);
}
return cells.length;
}
async function loadNext() {
if (!state.more || state.loading || !state.userId) return 0;
state.loading = true;
try {
const vars = state.cursor ? { id: state.userId, after: state.cursor, first: 12 } : { id: state.userId, first: 12 };
const res = await fetch(`/graphql/query/?doc_id=${DOC_ID}&variables=${encodeURIComponent(JSON.stringify(vars))}`,
{ headers: { 'X-IG-App-ID': IG_APP_ID, 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'include' });
const data = await res.json();
const media = data?.data?.user?.edge_owner_to_timeline_media;
state.cursor = media?.page_info?.end_cursor || null;
state.more = media?.page_info?.has_next_page ?? false;
return injectEdges(media?.edges || []);
} catch (e) {
console.error('[IG Unlocker]', e);
return 0;
} finally {
state.loading = false;
}
}
// ── INIT + NAVIGATION HANDLING ───────────────────────────────────────────────
function reset() {
shownCodes.clear();
Object.assign(state, { cursor: null, more: true, loading: false, grid: null, nativeRow: null, nativeCell: null, userId: null });
if (window.__igPoller) { clearInterval(window.__igPoller); window.__igPoller = null; }
const sv = document.querySelector('#scrollview');
if (sv) sv.__igPatched = false;
if (window.__igWheel) { document.removeEventListener('wheel', window.__igWheel, { capture: true }); window.__igWheel = null; }
}
function start() {
let attempts = 0;
const ready = setInterval(async () => {
if (++attempts > 60) { clearInterval(ready); return; }
cleanOverlays();
patchScrollview();
if (!findGrid()) return;
const userId = getUserId();
if (!userId) return;
clearInterval(ready);
state.userId = userId;
await loadNext();
if (!window.__igPoller) {
window.__igPoller = setInterval(() => {
const sv = document.querySelector('#scrollview');
if (!sv || !state.more || state.loading) return;
if (sv.scrollHeight - sv.scrollTop - sv.clientHeight < 800) loadNext();
}, 800);
}
}, 500);
}
// MutationObserver for overlay removal + scroll patching
new MutationObserver(() => {
cleanOverlays();
patchScrollview();
}).observe(document.documentElement, { childList: true, subtree: true });
// Handle SPA navigation (Instagram uses pushState)
const _pushState = history.pushState.bind(history);
history.pushState = function (...args) {
_pushState(...args);
reset();
start();
};
window.addEventListener('popstate', () => { reset(); start(); });
// Initial page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment