// ==UserScript==
// @name InstaDecrapper
// @version 1.2.1
// @description Replaces Instagram pages with their decrapped versions (only media & titles)
// @author GreasyPangolin
// @license MIT
// @match https://www.instagram.com/*
// @match https://instagram.com/*
// @match http://localhost:8000/*
// @run-at document-start
// @grant none
// @namespace https://gf.qytechs.cn/users/1448662
// ==/UserScript==
function extractSecretsAndRemoveScripts(runId) {
// Extract CSRF token and App ID from scripts
let csrfToken = ''
let appId = ''
var scripts = document.querySelectorAll('script')
for (var i = 0; i < scripts.length; i++) {
// scan for the script that contains the CSRF token and App ID
const csrfMatch = scripts[i].textContent.match(/"csrf_token":"([^"]+)"/)
const appIdMatch = scripts[i].textContent.match(/"app_id":"([^"]+)"/)
if (csrfMatch && csrfMatch[1]) {
csrfToken = csrfMatch[1]
console.log(`[Run ${runId}] Found CSRF token: ${csrfToken}`)
}
if (appIdMatch && appIdMatch[1]) {
appId = appIdMatch[1]
console.log(`[Run ${runId}] Found App ID: ${appId}`)
}
// we don't need this script anymore
scripts[i].remove()
if (csrfToken && appId) {
return { csrfToken, appId }
}
}
console.log(`[Run ${runId}] Could not find CSRF token and App ID`)
return null
}
function renderProfileHeader(user) {
const header = document.createElement('div')
header.style.cssText = 'display: flex; align-items: center; padding: 20px;'
const info = document.createElement('div')
info.style.display = 'flex'
info.style.alignItems = 'start'
const profilePic = document.createElement('img')
profilePic.src = user.profilePicUrl
profilePic.width = 64
profilePic.height = 64
profilePic.style.borderRadius = '50%'
profilePic.style.marginRight = '20px'
info.appendChild(profilePic)
const textInfo = document.createElement('div')
const nameContainer = document.createElement('div')
nameContainer.style.display = 'flex'
nameContainer.style.alignItems = 'center'
nameContainer.style.gap = '5px'
const name = document.createElement('h1')
name.textContent = user.fullName
name.style.margin = '0 0 10px 0'
name.style.fontFamily = 'sans-serif'
name.style.fontSize = '18px'
nameContainer.appendChild(name)
if (user.isVerified) {
const checkmark = document.createElement('span')
checkmark.textContent = '✓'
checkmark.style.margin = '0 0 10px'
checkmark.style.color = '#00acff'
checkmark.style.fontSize = '18px'
checkmark.style.fontWeight = 'bold'
nameContainer.appendChild(checkmark)
}
textInfo.appendChild(nameContainer)
if (user.username) {
const username = document.createElement('a')
username.href = '/' + user.username
username.textContent = '@' + user.username
username.style.margin = '0 0 10px 0'
username.style.fontFamily = 'sans-serif'
username.style.fontSize = '14px'
username.style.textDecoration = 'none'
username.style.color = '#00376b'
username.target = '_blank'
textInfo.appendChild(username)
}
if (user.biography) {
const bio = document.createElement('p')
bio.textContent = user.biography
bio.style.margin = '0 0 10px 0'
bio.style.whiteSpace = 'pre-line'
bio.style.fontFamily = 'sans-serif'
bio.style.fontSize = '14px'
textInfo.appendChild(bio)
}
if (user.bioLinks && user.bioLinks.length > 0) {
const links = document.createElement('div')
user.bioLinks.forEach(link => {
const a = document.createElement('a')
a.href = link.url
a.textContent = link.title
a.target = '_blank'
a.style.display = 'block'
a.style.fontFamily = 'sans-serif'
a.style.fontSize = '14px'
links.appendChild(a)
})
textInfo.appendChild(links)
}
info.appendChild(textInfo)
header.appendChild(info)
document.body.appendChild(header)
}
function renderMedia(mediaItems) {
const mediaContainer = document.createElement('div')
mediaContainer.style.display = 'grid'
mediaContainer.style.gridTemplateColumns = 'repeat(auto-fill, minmax(320px, 1fr))'
mediaContainer.style.gap = '20px'
mediaContainer.style.padding = '20px'
mediaItems.forEach(item => {
const mediaDiv = document.createElement('div')
mediaDiv.className = 'media'
mediaDiv.style.display = 'flex'
mediaDiv.style.flexDirection = 'column'
mediaDiv.style.alignItems = 'center'
if (item.isVideo) {
const videoElement = document.createElement('video')
videoElement.controls = true
videoElement.width = 320
const source = document.createElement('source')
source.src = item.videoUrl
source.type = 'video/mp4'
videoElement.appendChild(source)
mediaDiv.appendChild(videoElement)
} else {
const imageElement = document.createElement('img')
imageElement.src = item.imageUrl
imageElement.width = 320
imageElement.style.height = 'auto'
mediaDiv.appendChild(imageElement)
}
const dateContainer = document.createElement('div')
dateContainer.style.display = 'flex'
dateContainer.style.alignItems = 'center'
dateContainer.style.justifyContent = 'center'
dateContainer.style.gap = '10px'
dateContainer.style.width = '320px'
const date = document.createElement('p')
date.textContent = item.date
date.style.fontFamily = 'sans-serif'
date.style.fontSize = '12px'
date.style.margin = '5px 0'
dateContainer.appendChild(date)
if (item.shortcode) {
const postLink = document.createElement('a')
postLink.href = `/p/${item.shortcode}`
postLink.textContent = '[post]'
postLink.style.fontFamily = 'sans-serif'
postLink.style.fontSize = '12px'
postLink.style.color = 'blue'
postLink.style.textDecoration = 'none'
dateContainer.appendChild(postLink)
}
if (item.isVideo) {
const previewLink = document.createElement('a')
previewLink.href = item.imageUrl
previewLink.textContent = '[preview]'
previewLink.style.fontFamily = 'sans-serif'
previewLink.style.fontSize = '12px'
previewLink.style.color = 'blue'
previewLink.style.textDecoration = 'none'
dateContainer.appendChild(previewLink)
}
mediaDiv.appendChild(dateContainer)
const title = document.createElement('p')
title.textContent = item.title
title.style.fontFamily = 'sans-serif'
title.style.fontSize = '12px'
title.style.width = '320px'
title.style.textAlign = 'center'
mediaDiv.appendChild(title)
mediaContainer.appendChild(mediaDiv)
})
document.body.appendChild(mediaContainer)
}
async function loadSinglePost({ csrfToken, appId, shortcode, isDebug }) {
const url = isDebug ?
`http://localhost:8000/post_${shortcode}.json` :
`https://www.instagram.com/graphql/query`
const resp = await fetch(url, {
"method": "POST",
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:131.0) Gecko/20100101 Firefox/131.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/x-www-form-urlencoded",
"X-FB-Friendly-Name": "PolarisPostActionLoadPostQueryQuery",
"X-CSRFToken": csrfToken,
"X-IG-App-ID": appId,
"Origin": "https://www.instagram.com",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
},
"body": new URLSearchParams({
"av": "0",
"hl": "en",
"__d": "www",
"__user": "0",
"__a": "1",
"__req": "a",
"__hs": "20168.HYP:instagram_web_pkg.2.1...0",
"dpr": "2",
"__ccg": "EXCELLENT",
"fb_api_caller_class": "RelayModern",
"fb_api_req_friendly_name": "PolarisPostActionLoadPostQueryQuery",
"variables": JSON.stringify({
"shortcode": shortcode,
"fetch_tagged_user_count": null,
"hoisted_comment_id": null,
"hoisted_reply_id": null
}),
"server_timestamps": "true",
"doc_id": "8845758582119845",
}).toString()
})
const data = await resp.json()
const media = data.data.xdt_shortcode_media
let mediaItems = []
if (media.__typename === 'XDTGraphImage') {
mediaItems.push({
date: new Date(media.taken_at_timestamp * 1000).toISOString().slice(0, 19).replace('T', ' '),
title: media.edge_media_to_caption?.edges[0]?.node.text || "No title",
isVideo: false,
videoUrl: null,
imageUrl: media.display_url
})
}
else if (media.__typename === 'XDTGraphVideo') {
mediaItems.push({
date: new Date(media.taken_at_timestamp * 1000).toISOString().slice(0, 19).replace('T', ' '),
title: media.edge_media_to_caption?.edges[0]?.node.text || "No title",
isVideo: true,
videoUrl: media.video_url,
imageUrl: media.display_url
})
}
else if (media.__typename === 'XDTGraphSidecar') {
media.edge_sidecar_to_children.edges.forEach(edge => {
const child = edge.node
mediaItems.push({
date: new Date(media.taken_at_timestamp * 1000).toISOString().slice(0, 19).replace('T', ' '),
title: media.edge_media_to_caption.edges[0]?.node.text || "No title",
isVideo: child.__typename === 'XDTGraphVideo',
videoUrl: child.__typename === 'XDTGraphVideo' ? child.video_url : null,
imageUrl: child.display_url
})
})
}
renderProfileHeader({
username: media.owner.username,
fullName: media.owner.full_name,
profilePicUrl: media.owner.profile_pic_url,
isVerified: media.owner.is_verified
})
renderMedia(mediaItems)
}
async function loadProfile({ csrfToken, appId, username, isDebug }) {
const url = isDebug ?
`http://localhost:8000/profile.json` :
`https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}&hl=en`
const resp = await fetch(url, {
"credentials": "include",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0",
"Accept": "*/*",
"Accept-Language": "en,en-US;q=0.5",
"X-CSRFToken": csrfToken,
"X-IG-App-ID": appId,
"X-IG-WWW-Claim": "0",
"X-Requested-With": "XMLHttpRequest",
"Alt-Used": "www.instagram.com",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
},
"referrer": `https://www.instagram.com/${username}/?hl=en`,
"method": "GET",
"mode": "cors"
})
const data = await resp.json()
const mediaNodes = [
...data.data.user.edge_felix_video_timeline.edges,
...data.data.user.edge_owner_to_timeline_media.edges
]
const mediaItems = mediaNodes.flatMap(edge => {
const media = edge.node
if (media.__typename === 'GraphSidecar' && media.edge_sidecar_to_children) {
return media.edge_sidecar_to_children.edges.map(child => ({
date: new Date(media.taken_at_timestamp * 1000).toISOString().slice(0, 19).replace('T', ' '),
title: media.edge_media_to_caption.edges[0]?.node.text || "No title",
isVideo: child.node.__typename === 'GraphVideo',
videoUrl: child.node.__typename === 'GraphVideo' ? child.node.video_url : null,
imageUrl: child.node.display_url,
shortcode: child.node.shortcode
}))
} else {
return [{
date: new Date(media.taken_at_timestamp * 1000).toISOString().slice(0, 19).replace('T', ' '),
title: media.edge_media_to_caption.edges[0]?.node.text || "No title",
isVideo: media.is_video,
videoUrl: media.is_video ? media.video_url : null,
imageUrl: media.display_url,
shortcode: media.shortcode
}]
}
})
renderProfileHeader({
fullName: data.data.user.full_name,
biography: data.data.user.biography,
profilePicUrl: data.data.user.profile_pic_url_hd,
bioLinks: data.data.user.bio_links,
isVerified: data.data.user.is_verified,
})
renderMedia(mediaItems)
}
function run(secrets) {
// first, stop the page from loading
window.stop()
document.head.innerHTML = ''
document.body.innerHTML = ''
// and now execute our code
const postID = window.location.pathname.match(/(?:p|reel)\/([^\/]*)/)
if (postID) {
const shortcode = postID[1]
console.log(`Loading post: ${shortcode}`)
loadSinglePost({ shortcode, ...secrets })
} else {
const username = window.location.pathname.split('/')[1]
console.log(`Loading profile: ${username}`)
loadProfile({ username, ...secrets })
}
}
(function () {
'use strict'
const isDebug = window.location.href.includes('localhost:8000')
if (isDebug) {
console.log("Debug mode enabled")
document.body.innerHTML = ""
const shortcode = window.location.pathname.split('/').pop()
if (shortcode) {
loadSinglePost({ isDebug, shortcode })
} else {
loadProfile({ isDebug })
}
return
}
// let's try to stop it from blinking
const style = document.createElement('style')
style.textContent = '#splash-screen { display: none !important; }'
document.head.appendChild(style)
// we try to extract the secrets and run the app right away,
// sometimes it works :)
const secrets = extractSecretsAndRemoveScripts(1)
if (!secrets) {
// but since the user-script injection is kinda unpredictable
// especially across different browsers and extensions,
// we also fallback to a DOMContentLoaded event listener
document.addEventListener('DOMContentLoaded', function () {
window.stop() // we know that the secrets are in the DOM, so we can stop loading all other garbage
const secrets = extractSecretsAndRemoveScripts(2)
if (!secrets) {
console.log("Failed to extract secrets")
return
}
run(secrets)
})
return
}
run(secrets)
})()