InstaDecrapper

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

当前为 2025-03-22 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         InstaDecrapper
// @version      1.1.2
// @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://greasyfork.org/users/1448662
// ==/UserScript==

function extractSecrets() {
    // Extract CSRF token and App ID from scripts
    let csrfToken = ''
    let appId = ''

    document.querySelectorAll('script').forEach(script => {
        if (csrfToken && appId) {
            return
        }

        const csrfMatch = script.textContent.match(/"csrf_token":"([^"]+)"/)
        const appIdMatch = script.textContent.match(/"app_id":"([^"]+)"/)

        if (csrfMatch && csrfMatch[1]) {
            csrfToken = csrfMatch[1]
            console.log('Found CSRF token:', csrfToken)
        }

        if (appIdMatch && appIdMatch[1]) {
            appId = appIdMatch[1]
            console.log('Found App ID:', appId)
        }
    })

    console.log('csrf_token:', csrfToken)
    console.log('app_id:', appId)

    document.head.innerHTML = ''
    document.body.innerHTML = ''

    return { csrfToken, appId }
}

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 () {
    '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 })
        }
    } else {
        // Block network requests using `beforeload` event (for browsers like Firefox)
        window.addEventListener('beforeload', function (e) {
            if (
                e.target.tagName === 'SCRIPT' ||
                e.target.tagName === 'LINK' ||
                e.target.tagName === 'IMG'
            ) {
                e.preventDefault()
            }
        }, true)

        // Stop resource loading once the DOM is ready
        document.addEventListener('DOMContentLoaded', function () {
            window.stop() // Stop any ongoing network requests

            console.log('Blocked all external resources. Only HTML remains.')

            const secrets = extractSecrets()
            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 })
            }
        })

        console.log("Script loaded")
    }
})()