如何使我的用户脚本在使用历史记录 API 的网站的特定页面上工作

How to make my userscript work on specific pages of a site that uses the history api?

本文关键字:网站 工作 API 记录 用户 我的 何使 脚本 历史      更新时间:2023-09-26

(这是本讨论的延续)

我一直在尝试制作一个脚本(用于 instagram),以显示查看个人资料页面(示例个人资料)时当前可见的总共有多少张图片。它在屏幕右上角显示计数器,并支持无限滚动。

在这里:

// ==UserScript==
// @name        Instagram - visible images counter
// @name        Instagram - visible images counter
// @include     https://instagram.com/*
// @grant       none
// ==/UserScript==
var p = document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root');
var m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count');
var ww = m[0].innerHTML.replace(/('d+),(?='d{3}('D|$))/g, "$1");                       // Regex to strip the thousand comma seperator from the posts number
var z = (p.length/ww)*100;
var counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%';
var div = document.createElement("div");
div.style.top = "1px";
div.style.right = "1px";
div.style.position = "fixed";
document.body.appendChild(div);
div.id="mycounter";
mycounter = document.getElementById('mycounter');
mycounter.innerHTML = counter;
mycounter.style.top = "1px";
mycounter.style.right = "1px";
mycounter.style.position = "fixed";
/// ---------------------------------
/// mutation observer -monitors the Posts grid for infinite scrolling event- 
/// ---------------------------------
var target1 = document.querySelector('.-cx-PRIVATE-PostsGrid__root');
var observer1 = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    p=document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root');
    m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count');
    ww = m[0].innerHTML.replace(/('d+),(?='d{3}('D|$))/g, "$1");
    z = (p.length/ww)*100;
    counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%';    
    mycounter.innerHTML = counter;
  });
})
var config = { attributes: true, childList: true, characterData: true }
observer1.observe(target1, config);

我的脚本工作正常,但我有这个问题:

Instagram,在最近重新设计之后 - 我认为 - 似乎使用单页应用程序工作流程
即获取点击的链接内容并用它替换当前页面,然后更改浏览器的当前 URL,所有这些都无需实际重新加载页面

因此,我的脚本仅在新选项卡中打开配置文件 URL 时才有效。
在我的时间线(https://instagram.com)中在同一选项卡中打开配置文件URL时,它不起作用
换句话说,它不起作用(在我打开 https://instagram.com 并登录后)
如果我单击以查看我关注的用户的个人资料 URL,例如。https://instagram.com/instagram/

我该如何解决这个问题?

有人善意地建议了这 3 种方法:

  1. 尝试为Instagram网站触发的某些事件event handler,例如 例如,GitHub上的pjax:end。
  2. 使用等待特定于配置文件的 html 元素的mutation observer
  3. 或者使用 setInterval 定期检查 location.href


所以,我一直在尝试各种方法(包括建议#2和#3),但没有成功。
(关于建议#1:我找不到任何类似于pjax:end的元素)。
所以,到目前为止我尝试过:


  1. (基于建议#2)添加另一个mutation observer来检查显示"帖子计数"元素的元素是否存在,如果是,则运行我的代码。

    var target0 = document.querySelector('.-cx-PRIVATE-PostsStatistic__count');
    var observer0 = new MutationObserver(function (mutations) {
      mutations.forEach(function (mutation) {    
            myCode();
      });
    })
    var config0 = { attributes: true, childList: true, characterData: true }
    observer0.observe(target0, config0);
    

  1. (基于建议#3)每1秒location.href检查一次当前位置是否是配置文件(即不是时间线(https://instagram.com/)。如果为 true,则清除周期函数并运行我以前的代码。

    var interval = setInterval(testHref() , 1000);
    function testHref(){
        if (location.href != "https://instagram.com/")
            clearInterval(interval);
            myCode();   
    }
    

  1. 只需在我的代码上添加 1 秒的延迟(并将@require规则更改为仅适用于配置文件 URL // @include https://instagram.com/*/),但没有成功:

    setTimeout(function() { }, 1000);
    

  1. 我甚至尝试使用waitForKeyElements实用程序函数,它可以检测和处理AJAXed内容。
    我认为以这种方式实现要容易得多,就像一个简单的"等到元素存在"一样工作(我只是使用主配置文件图片作为选择器来等待,因为我找不到任何相关的 AJAX 选择器。此外,我没有在我的代码中使用jNode)。
    所以我只是将我的整个代码包含在一个函数visibleCounter中,并添加了一个 waitForKeyElements 行(见下文),但不幸的是它也不起作用:

    waitForKeyElements (".-cx-PRIVATE-ProfilePage__avatar", visibleCounter);         
    function visibleCounter(jNode){
        myCode()
    }
    

我使用 arrive.js 库解决了这个问题。它提供了监视 DOM 元素创建和删除的事件。它在内部使用突变观察器。该库不依赖于jQuery,您可以将以下示例中的jQuery元素替换为纯javascript元素,它可以正常工作。

我引用其作者的这条评论:

我一直觉得 MutationObserver api 有点复杂,所以我构建了一个 库,到达.js,以提供更简单的 API 来侦听元素 创建/删除。

以及它的两种用途:

注意元素创建

使用 到达事件监视元素创建:

// watch for creation of an element which satisfies the selector ".test-elem"
$(document).arrive(".test-elem", function() {
    // 'this' refers to the newly created element
    var $newElem = $(this);
});

注意元素去除

使用 leave 事件监视元素删除。第一个 离开不得是后代或子选择器,即您不能通过 .page .test-elem,而是通过.test-elem。这是因为 MutationObserver 的 API 中的限制。

// watch for removal of an element which satisfies the selector ".test-elem"
$(".container-1").leave(".test-elem", function() {
    var $removedElem = $(this);
});  [1]: https://github.com/uzairfarooq/arrive

而且,这是完整的脚本:

// ==UserScript==
// @name        Instagram - visible images counter
// @include     https://www.instagram.com/*
// @grant       none
// @require     https://code.jquery.com/jquery-3.0.0.min.js
// @require     https://greasyfork.org/scripts/21927-arrive-js/code/arrivejs.js?version=139586
// ==/UserScript==


function showCounter() {
    var visibleCount = $( "a[href*='taken-by']" ).length;   // Count of visible images
    var totalString = $("span:contains('posts')").eq(1).children().eq(0).html();    // The 'total' value (it's a string)
    var total = totalString.replace(/('d+),(?='d{3}('D|$))/g, '$1');    // Apply regex to 'total' string to strip the thousand comma seperator
    if (visibleCount > total){
        visibleCount = total;
    }
    var visiblePercent = ((visibleCount / total) * 100).toFixed(1); // Visible images as percentage
    var counter = visibleCount + ' / ' + totalString + ' that is ' + visiblePercent + '%';
    return counter;
}


function createDiv(){
    // Creation of the counter element
    document.body.appendChild(div);
    div.innerHTML = showCounter();      // Initial display of the counter
    div.style.top = '1px';
    div.style.right = '1px';
    div.style.position = 'fixed';
}

function observer(){
    /// ---------------------------------
    /// mutation observer -monitors the Posts grid for infinite scrolling event-.
    /// ---------------------------------
    observer1 = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            div.innerHTML = showCounter();                      // In each infinite scrolling event, re-calculate counter
        });
    }).observe($('article').children().eq(1).children()[0],     // target of the observer
        {
            // attributes: true,
            childList: true,
            // characterData: true,
        }); // config of the observer
}



var div = document.createElement('div');    // global variable
var observer1;                              // global variable
if (document.URL !== 'https://www.instagram.com/' &&
    document.URL.indexOf('https://www.instagram.com/p/') === -1 ){
    createDiv();
    observer();
}

$(document).arrive('article ._5axto', function() {      // 'article .5axto'
    createDiv();
    observer();
});

$(document).leave('article ._5axto', function() {
    div.remove();
    observer1.disconnect();
});

从我的回答中复制:

对于类似的用例,我使用MutationObserver并检查了每个mutation.addedNodes中每个节点的node.baseURI

(async function() {
    'use strict';
    const selector = '.quiz--answered';
    const observer = new MutationObserver(mutations => {
        if (!mutations.some(mutation => Array.from(mutation.addedNodes).some(node => node.baseURI.includes('/quiz/')))) {
            return;
        }
        const elm = document.querySelector(selector);
        if (elm) {
            // work with elm
        }
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();