Instagram Post Date and Location

Add Date and Location in instagram Posts by sniffing background requests

当前为 2025-09-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         Instagram Post Date and Location
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Add Date and Location in instagram Posts by sniffing background requests
// @author       SH3LL
// @match        https://www.instagram.com/*
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .post-date-label {
            position: absolute;
            top: 10px;
            left: 10px;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 5px;
            border-radius: 5px;
            font-size: 12px;
            z-index: 99999;
        }
        .post-location-label {
            position: absolute;
            top: 10px;
            right: 10px;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 5px;
            border-radius: 5px;
            font-size: 12px;
            z-index: 99999;
        }
    `);

    const interceptedJsons = [];
    const postIndex = {}; // map: code -> node

    // Intercept XHR GraphQL requests and index nodes containing "code"
    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function() {
        this.addEventListener('readystatechange', function() {
            try {
                if (this.readyState === 4 && this.responseURL && this.responseURL.includes('/graphql/query')) {
                    console.log('[DEBUG] Intercepted GraphQL call:', this.responseURL);
                    let json;
                    try {
                        json = JSON.parse(this.responseText);
                    } catch (err) {
                        console.error('[DEBUG] Error parsing JSON from responseText', err);
                        return;
                    }
                    console.log('[DEBUG] JSON root keys:', Object.keys(json || {}));
                    interceptedJsons.push(json);
                    const indexed = indexJsonNodes(json);
                    const n = Object.keys(indexed).length;
                    console.log(`[DEBUG] Indexed ${n} media nodes from this GraphQL response`);
                    Object.assign(postIndex, indexed);
                }
            } catch (outerErr) {
                console.error('[DEBUG] Error in XHR event listener', outerErr);
            }
        });
        return originalSend.apply(this, arguments);
    };

    // Recursively scan JSON and collect media objects (objects with "code" + timestamp)
    function indexJsonNodes(json) {
        const map = {};
        function walk(obj) {
            if (!obj || typeof obj !== 'object') return;
            if (Array.isArray(obj)) {
                obj.forEach(walk);
                return;
            }
            // heuristic: media object has "code" and "taken_at" (or similar)
            if (obj.code && (obj.taken_at || obj.taken_at_timestamp || obj.taken_at_ms)) {
                map[obj.code] = obj;
            }
            for (const k in obj) {
                if (!obj.hasOwnProperty || obj.hasOwnProperty(k)) walk(obj[k]);
            }
        }
        walk(json && json.data ? json.data : json);
        return map;
    }

    // Robust extraction of post code from href
    function extractPostCodeFromHref(href) {
        if (!href) return null;
        href = String(href);
        // regex: /p/<code>/  or /reel/<code>/  or /tv/<code>/
        const regex = /\/(?:p|reel|tv)\/([^\/?#]+)/i;
        const m = href.match(regex);
        if (m && m[1]) {
            console.log('[DEBUG] Regex extracted postCode:', m[1], 'from href:', href);
            return m[1];
        }
        // fallback: split and look for 'p'/'reel'/'tv' and take the next segment
        const parts = href.split('/').filter(Boolean);
        console.log('[DEBUG] href parts fallback:', parts);
        for (let i = 0; i < parts.length - 1; i++) {
            const seg = parts[i].toLowerCase();
            if (seg === 'p' || seg === 'reel' || seg === 'tv') {
                console.log('[DEBUG] Fallback extracted postCode:', parts[i+1], 'from parts:', parts);
                return parts[i+1];
            }
        }
        console.log('[DEBUG] No postCode extracted from href:', href);
        return null;
    }

    function addDateLabel(postElement, dateSeconds) {
        if (!postElement.querySelector('.post-date-label')) {
            console.log('[DEBUG] Adding date label for timestamp:', dateSeconds);
            const label = document.createElement('div');
            label.classList.add('post-date-label');
            label.textContent = "📅 " + formatDate(dateSeconds * 1000);
            postElement.style.position = 'relative';
            postElement.appendChild(label);
        } else {
            console.log('[DEBUG] Date label already exists for this element');
        }
    }

    function addLocationLabel(postElement, locationName, locationPk) {
        if (locationName && !postElement.querySelector('.post-location-label')) {
            console.log('[DEBUG] Adding location label:', locationName, 'pk:', locationPk);
            const label = document.createElement('a');
            label.classList.add('post-location-label');
            const truncated = locationName.length > 20 ? locationName.substring(0, 20) + "..." : locationName;
            label.textContent = `📌 ${truncated}`;
            label.href = `https://www.instagram.com/explore/locations/${locationPk}`;
            label.target = "_blank";
            postElement.style.position = 'relative';
            postElement.appendChild(label);
        }
    }

    function formatDate(ms) {
        const date = new Date(ms);
        const options = { year: 'numeric', month: 'short', day: 'numeric' };
        return date.toLocaleDateString('en-US', options);
    }

    function processPosts() {
        try {
            // image wrapper selector (more stable)
            const postElements = document.querySelectorAll('._aagu');
            console.log('[DEBUG] processPosts → postElements found:', postElements.length);
            postElements.forEach(postElement => {
                try {
                    const linkElement = postElement.closest('a[href*="/p/"], a[href*="/reel/"], a[href*="/tv/"]');
                    if (!linkElement) {
                        console.log('[DEBUG] No <a> post found for this wrapper');
                        return;
                    }
                    const href = linkElement.getAttribute('href');
                    console.log('[DEBUG] href found:', href);
                    const postCode = extractPostCodeFromHref(href);
                    if (!postCode) {
                        console.log('[DEBUG] postCode not extracted, skipping this post');
                        return;
                    }

                    // look directly in the indexed map
                    const node = postIndex[postCode];
                    if (node) {
                        console.log('[DEBUG] Found node from postIndex for code:', postCode, node);
                        const ts = node.taken_at || node.taken_at_timestamp || node.taken_at_ms;
                        if (ts) addDateLabel(postElement, ts);
                        else console.log('[DEBUG] Node has no visible timestamp:', node);

                        if (node.location) {
                            const name = node.location.name || node.location.title || '';
                            const pk = node.location.pk || node.location.id || '';
                            addLocationLabel(postElement, name, pk);
                        } else {
                            console.log('[DEBUG] Node has no location:', node);
                        }
                    } else {
                        // fallback: scan intercepted JSONs
                        console.log('[DEBUG] No node in postIndex for code:', postCode, '→ trying fallback scan');
                        let found = null;
                        for (const j of interceptedJsons) {
                            const candidate = findNodeWithCode(j, postCode);
                            if (candidate) { found = candidate; break; }
                        }
                        if (found) {
                            console.log('[DEBUG] Found node via fallback scan:', found);
                            const ts2 = found.taken_at || found.taken_at_timestamp || found.taken_at_ms;
                            if (ts2) addDateLabel(postElement, ts2);
                            if (found.location) addLocationLabel(postElement, found.location.name || '', found.location.pk || found.location.id || '');
                        } else {
                            console.log('[DEBUG] Node for code not found in any intercepted JSON:', postCode);
                        }
                    }
                } catch (inner) {
                    console.error('[DEBUG] Error processing a single postElement', inner);
                }
            });
        } catch (err) {
            console.error('[DEBUG] Error in processPosts', err);
        }
    }

    // Recursive search for object with property code === target
    function findNodeWithCode(obj, target) {
        if (!obj || typeof obj !== 'object') return null;
        if (Array.isArray(obj)) {
            for (const item of obj) {
                const r = findNodeWithCode(item, target);
                if (r) return r;
            }
            return null;
        }
        if (obj.code === target) return obj;
        for (const k in obj) {
            if (obj.hasOwnProperty && obj.hasOwnProperty(k)) {
                const r = findNodeWithCode(obj[k], target);
                if (r) return r;
            }
        }
        return null;
    }

    // initial run + observer
    console.log('[DEBUG] Script started - running first processPosts');
    processPosts();

    const observer = new MutationObserver((mutations) => {
        console.log('[DEBUG] DOM mutation detected → processPosts');
        processPosts();
    });
    observer.observe(document.body, { childList: true, subtree: true });

})();

QingJ © 2025

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