Twitter userscript affects pages other than the in

2020-04-18 07:31发布

问题:

The below UserScript is meant to work on my own twitter profile page (not the timeline).

// ==UserScript==
// @name            CoolScript
// @include         https://twitter.com/IJNanayakkara
// @include         https://twitter.com/IJNanayakkara/status/*
// @require         http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @require         https://gist.github.com/raw/2625891/waitForKeyElements.js
// @version         1.0
// @license         GPL v3 or any later version (http://www.gnu.org/copyleft/gpl.html)
// @grant           GM_addStyle
// ==/UserScript==

function prependToTweet(jNode) {
    jNode.prepend("Hello World ");
}

waitForKeyElements("p.js-tweet-text", prependToTweet);

What it does is it appends a given text string to the beginning of every tweet in the page.

Although the script does what I want to, I'm facing two issues.

1. Script affects pages other than the ones defined in @include.

I install the script and go to my twitter profile. And all the tweets have the Hello World string added to the beginning. This is expected.

But then I go to my timeline page and all the tweets over there also have the Hello World string appended to them which is not an intended behavior.

I need to know why this is happening.


2. Hello World string string duplicates itself.

Say I click on a tweet to view it in a single page. Works fine.

Then I go back to my profile page.

If I go some other page and return again, the string has repeated once more.

These are the two issues I'm facing. I assume these occur due to AJAX but I can't figure out a way to tackle it.

回答1:

A few things seem to be going on:

  1. When twitter changes to a "new" page, it sometimes keeps the modified text but trashes the jQuery data that waitForKeyElements uses to track nodes.

  2. When navigating to "new", "unwanted" pages, since there is no page load, the script does not refire and the old waitForKeyElements is still active.

  3. If you start out on a page were you don't want the adjustments and then navigate to a page were you do, then the script doesn't fire because twitter just displayed a new URL, it didn't actually load much of anything.

  4. Twitter is loading iframes and the script is firing on those too.


To work around the problems we:

  1. Also track changed nodes by a hard attribute -- which twitter does not trash.
  2. Change the @include to fire on https://twitter.com/* and then use location.pathname to refine that further.
  3. Deactivate waitForKeyElements when AJAXing to unwanted pages.
  4. Monitor <title> changes as a reliable way to detect "new" pages.
  5. Use @noframes.

So, this script ought to work better for you (only tested on Greasemonkey, but should work on Tampermonkey):

// ==UserScript==
// @name            Twitter: Modify tweets on select pages.
// @include         https://twitter.com/*
// @require         http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @require         https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant           GM_addStyle
// @noframes
// ==/UserScript==
var tweetSelector = "p.js-tweet-text";

function fireMainCode () {
    /*--- Only fire on desired pages. This fires on:
        twitter.com/IJNanayakkara*
        twitter.com/IJNanayakkara/status/*
    */
    if (/^\/IJNanayakkara(\/status\/)?/.test (location.pathname) ) {
        if ( ! this.WFKE_fired) {
            waitForKeyElements (tweetSelector, prependToTweet);
            this.WFKE_fired = true;
        }
    }
    else {
        //--- Undesired page, shut off waitForKeyElements
        if (this.WFKE_fired) {
            this.WFKE_fired = null;
            var controlObj      = waitForKeyElements.controlObj  ||  {};
            var controlKey      = tweetSelector.replace (/[^\w]/g, "_");
            var timeControl     = controlObj [controlKey];
            if (timeControl) {
                clearInterval (timeControl);
                delete controlObj [controlKey]
            }
        }
    }
}
fireMainCode ();

function prependToTweet (jNode) {
    var altDoneFlag = jNode.attr ("data-altdoneflag");
    if ( ! altDoneFlag) {
        jNode.prepend ("XXXXX => ")
        .attr ("data-altdoneflag", "yes");
    }
}

/*--- Twitter uses some pretty screwy AJAX to "change" pages, but it at least
    changes the title. So listen for that to (re) trigger waitForKeyElements.
*/
var myObserver  = new MutationObserver (titleChangeDetector);
var obsConfig   = {
    //-- Subtree needed.
    childList: true, characterData: true, subtree: true
};

myObserver.observe (document, obsConfig);

function titleChangeDetector (mutationRecords) {
    mutationRecords.forEach ( function (mutation) {
        //-- Sensible, Firefox
        if (    mutation.type                       == "childList"
            &&  mutation.target.nodeName            == "TITLE"
        ) {
            fireMainCode ();
        }
        //-- WTF, Chrome
        else if (mutation.type                      == "characterData"
            &&  mutation.target.parentNode.nodeName == "TITLE"
        ) {
            fireMainCode ();
        }
    } );
}