InstaDecrapper

Replaces Instagram pages with their decrapped versions (only media & titles)

// ==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)
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址