yesterdayxing 发表于 2024-6-14 14:58:52

jquery和ajaxhooker在抖音网页的爱恨交加

需求:用ajaxhooker拦截抖音网页视频的xhr请求并且改写responseText实现视频不需要加载,但要不影响 关注、私信、评论、点赞等社交功能的正常使用

代码:有引入 jquery ajaxHooker ElementGetter

环境:最新版油猴、最新版谷歌浏览器

目前解决方案:
在搜索页 https://www.douyin.com/search/%E4%BA%94%E9%87%91?type=user ,脚本run-at document-start 并且引入jquery就会报一个createElement报错,无论是低还是高版本的jquery都会报错,估计是与抖音这个搜索页有不兼容的地方,但为了拦截其他页面的视频不加载就必须要document-start + ajaxHooker 才可以实现,目前做了两个一样的脚本,只不过搜索页就是普通运行,其他页面就是document-start,已经实现批量打开视频页都不加载视频

!(data/attachment/forum/202406/14/145155shzhed9dgs11qqqp.png)
!(data/attachment/forum/202406/14/145327ui4iibz4gi9bs5ku.png)

`
// ==UserScript==
// @name         demo
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @author       You
// @descriptiondemo
// @match      https://www.douyin.com/user/*
// @match      https://www.douyin.com/search/*?*type=user*
// @include      https://www.douyin.com*
// @icon         https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico
// @require      https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.7.1.min.js
// @grant      unsafeWindow
// @run-at       document-start
// ==/UserScript==
`

李恒道 发表于 2024-6-14 15:44:20

这个属于jquery的问题
根据调试确定了
在document.createElement( "fieldset" );找不到document
```
function assert( fn ) {
        var el = document.createElement( "fieldset" );

        try {
                return !!fn( el );
        } catch ( e ) {
                return false;
        } finally {

                // Remove from its parent by def
```
相关github可以找到https://github.com/jquery/jquery/issues/4308
索引到了https://github.com/jquery/jquery/issues/3333
但是官方并不认为这个属于一个问题

根据我个人的调试
这里的document读取的是window.document
document的处理逻辑在
```
    function setDocument( node ) {
      var subWindow,
            doc = node ? node.ownerDocument || node : preferredDoc;
   
      // Return early if doc is invalid or already selected
      // Support: IE 11+, Edge 17 - 18+
      // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
      // two documents; shallow comparisons work.
      // eslint-disable-next-line eqeqeq
      if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
            return document;
      }
   
      // Update global variables
      document = doc;
      documentElement = document.documentElement;
      documentIsHTML = !jQuery.isXMLDoc( document );
   
      // Support: iOS 7 only, IE 9 - 11+
      // Older browsers didn't support unprefixed `matches`.
      matches = documentElement.matches ||
            documentElement.webkitMatchesSelector ||
            documentElement.msMatchesSelector;
   
      // Support: IE 9 - 11+, Edge 12 - 18+
      // Accessing iframe documents after unload throws "permission denied" errors
      // (see trac-13936).
      // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`,
      // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.
      if ( documentElement.msMatchesSelector &&
   
            // Support: IE 11+, Edge 17 - 18+
            // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
            // two documents; shallow comparisons work.
            // eslint-disable-next-line eqeqeq
            preferredDoc != document &&
            ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
   
            // Support: IE 9 - 11+, Edge 12 - 18+
            subWindow.addEventListener( "unload", unloadHandler );
      }
   
      // Support: IE <10
      // Check if getElementById returns elements by name
      // The broken getElementById methods don't pick up programmatically-set names,
      // so use a roundabout getElementsByName test
      support.getById = assert( function( el ) {
            documentElement.appendChild( el ).id = jQuery.expando;
            return !document.getElementsByName ||
                !document.getElementsByName( jQuery.expando ).length;
      } );
   
      // Support: IE 9 only
      // Check to see if it's possible to do matchesSelector
      // on a disconnected node.
      support.disconnectedMatch = assert( function( el ) {
            return matches.call( el, "*" );
      } );
   
      // Support: IE 9 - 11+, Edge 12 - 18+
      // IE/Edge don't support the :scope pseudo-class.
      support.scope = assert( function() {
            return document.querySelectorAll( ":scope" );
      } );
   
      // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only
      // Make sure the `:has()` argument is parsed unforgivingly.
      // We include `*` in the test to detect buggy implementations that are
      // _selectively_ forgiving (specifically when the list includes at least
      // one valid selector).
      // Note that we treat complete lack of support for `:has()` as if it were
      // spec-compliant support, which is fine because use of `:has()` in such
      // environments will fail in the qSA path and fall back to jQuery traversal
      // anyway.
      support.cssHas = assert( function() {
            try {
                document.querySelector( ":has(*,:jqfake)" );
                return false;
            } catch ( e ) {
                return true;
            }
      } );
   
      // ID filter and find
      if ( support.getById ) {
            Expr.filter.ID = function( id ) {
                var attrId = id.replace( runescape, funescape );
                return function( elem ) {
                  return elem.getAttribute( "id" ) === attrId;
                };
            };
            Expr.find.ID = function( id, context ) {
                if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
                  var elem = context.getElementById( id );
                  return elem ? [ elem ] : [];
                }
            };
      } else {
            Expr.filter.ID =function( id ) {
                var attrId = id.replace( runescape, funescape );
                return function( elem ) {
                  var node = typeof elem.getAttributeNode !== "undefined" &&
                        elem.getAttributeNode( "id" );
                  return node && node.value === attrId;
                };
            };
   
            // Support: IE 6 - 7 only
            // getElementById is not reliable as a find shortcut
            Expr.find.ID = function( id, context ) {
                if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
                  var node, i, elems,
                        elem = context.getElementById( id );
   
                  if ( elem ) {
   
                        // Verify the id attribute
                        node = elem.getAttributeNode( "id" );
                        if ( node && node.value === id ) {
                            return [ elem ];
                        }
   
                        // Fall back on getElementsByName
                        elems = context.getElementsByName( id );
                        i = 0;
                        while ( ( elem = elems[ i++ ] ) ) {
                            node = elem.getAttributeNode( "id" );
                            if ( node && node.value === id ) {
                              return [ elem ];
                            }
                        }
                  }
   
                  return [];
                }
            };
      }
   
      // Tag
      Expr.find.TAG = function( tag, context ) {
            if ( typeof context.getElementsByTagName !== "undefined" ) {
                return context.getElementsByTagName( tag );
   
            // DocumentFragment nodes don't have gEBTN
            } else {
                return context.querySelectorAll( tag );
            }
      };
   
      // Class
      Expr.find.CLASS = function( className, context ) {
            if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
                return context.getElementsByClassName( className );
            }
      };
   
      /* QSA/matchesSelector
      ---------------------------------------------------------------------- */
   
      // QSA and matchesSelector support
   
      rbuggyQSA = [];
   
      // Build QSA regex
      // Regex strategy adopted from Diego Perini
      assert( function( el ) {
   
            var input;
   
            documentElement.appendChild( el ).innerHTML =
                "<a id='" + expando + "' href='' disabled='disabled'></a>" +
                "<select id='" + expando + "-\r\\' disabled='disabled'>" +
                "<option selected=''></option></select>";
   
            // Support: iOS <=7 - 8 only
            // Boolean attributes and "value" are not treated correctly in some XML documents
            if ( !el.querySelectorAll( "" ).length ) {
                rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
            }
   
            // Support: iOS <=7 - 8 only
            if ( !el.querySelectorAll( "" ).length ) {
                rbuggyQSA.push( "~=" );
            }
   
            // Support: iOS 8 only
            // https://bugs.webkit.org/show_bug.cgi?id=136851
            // In-page `selector#id sibling-combinator selector` fails
            if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
                rbuggyQSA.push( ".#.+[+~]" );
            }
   
            // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
            // In some of the document kinds, these selectors wouldn't work natively.
            // This is probably OK but for backwards compatibility we want to maintain
            // handling them through jQuery traversal in jQuery 3.x.
            if ( !el.querySelectorAll( ":checked" ).length ) {
                rbuggyQSA.push( ":checked" );
            }
   
            // Support: Windows 8 Native Apps
            // The type and name attributes are restricted during .innerHTML assignment
            input = document.createElement( "input" );
            input.setAttribute( "type", "hidden" );
            el.appendChild( input ).setAttribute( "name", "D" );
   
            // Support: IE 9 - 11+
            // IE's :disabled selector does not pick up the children of disabled fieldsets
            // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
            // In some of the document kinds, these selectors wouldn't work natively.
            // This is probably OK but for backwards compatibility we want to maintain
            // handling them through jQuery traversal in jQuery 3.x.
            documentElement.appendChild( el ).disabled = true;
            if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
                rbuggyQSA.push( ":enabled", ":disabled" );
            }
   
            // Support: IE 11+, Edge 15 - 18+
            // IE 11/Edge don't find elements on a `` query in some cases.
            // Adding a temporary attribute to the document before the selection works
            // around the issue.
            // Interestingly, IE 10 & older don't seem to have the issue.
            input = document.createElement( "input" );
            input.setAttribute( "name", "" );
            el.appendChild( input );
            if ( !el.querySelectorAll( "" ).length ) {
                rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
                  whitespace + "*(?:''|\"\")" );
            }
      } );
   
      if ( !support.cssHas ) {
   
            // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
            // Our regular `try-catch` mechanism fails to detect natively-unsupported
            // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
            // in browsers that parse the `:has()` argument as a forgiving selector list.
            // https://drafts.csswg.org/selectors/#relational now requires the argument
            // to be parsed unforgivingly, but browsers have not yet fully adjusted.
            rbuggyQSA.push( ":has" );
      }
   
      rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
   
      /* Sorting
      ---------------------------------------------------------------------- */
   
      // Document order sorting
      sortOrder = function( a, b ) {
   
            // Flag for duplicate removal
            if ( a === b ) {
                hasDuplicate = true;
                return 0;
            }
   
            // Sort on method existence if only one input has compareDocumentPosition
            var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
            if ( compare ) {
                return compare;
            }
   
            // Calculate position if both inputs belong to the same document
            // Support: IE 11+, Edge 17 - 18+
            // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
            // two documents; shallow comparisons work.
            // eslint-disable-next-line eqeqeq
            compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
                a.compareDocumentPosition( b ) :
   
                // Otherwise we know they are disconnected
                1;
   
            // Disconnected nodes
            if ( compare & 1 ||
                ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
   
                // Choose the first element that is related to our preferred document
                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                // eslint-disable-next-line eqeqeq
                if ( a === document || a.ownerDocument == preferredDoc &&
                  find.contains( preferredDoc, a ) ) {
                  return -1;
                }
   
                // Support: IE 11+, Edge 17 - 18+
                // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
                // two documents; shallow comparisons work.
                // eslint-disable-next-line eqeqeq
                if ( b === document || b.ownerDocument == preferredDoc &&
                  find.contains( preferredDoc, b ) ) {
                  return 1;
                }
   
                // Maintain original order
                return sortInput ?
                  ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
                  0;
            }
   
            return compare & 4 ? -1 : 1;
      };
   
      return document;
    }
```
根据我的测试可以使
```
// @require      data:application/javascript,with%20(%7Bwindow%3A%7Bdocument%3A%7BdocumentElement%3Atrue%2CnodeType%3A9%7D%7D%7D)%20%7B
// @require      https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.7.1.js
// @require      data:application/javascript,%7D
```
控制document的修改读数

可以在此基础上通过proxy代理document并劫持nodeType以及documentElement属性应该即可解决这个问题

因为编写的代码太多就跳过了

还是更简易直接原生

yhzc2023 发表于 2024-6-14 21:42:39

自己写个hook xhr不就解决了?
我给我常用的你参考一下。
unsafeWindow.XMLHttpRequest = class extends unsafeWindow.XMLHttpRequest {
    get xhrResponseValue() {
      const xhr = this;
      if (xhr.readyState === unsafeWindow.XMLHttpRequest.DONE && xhr.status === 200) {
            const url = xhr.responseURL;
            // 匹配url
            if (url.includes('xxxx.com')) {
                try {
                  //获取返回的值
                  let response_text = super.response;
                  //处理 response_text 返回处理后结果
                  //return 处理后的字符串
                  return response_text;
                } catch (error) {
                  console.log('处理失败!', error);
                }
      
            }
      }
      return super.response;
    }
    get responseText() {
      return this.xhrResponseValue;
    }
    get response() {
      return this.xhrResponseValue;
    }
};

cxxjackie 发表于 2024-6-14 23:24:33

嗯。。。ajaxHooker也不是非得在document-start引入,之前发生过网页劫持与库劫持相互争夺主权的问题,所以我刻意弱化了库对执行时机的依赖,以你这种情况来说,在document-body阶段引入应该也没有问题,只要目标请求没那么快发生就行。
页: [1]
查看完整版本: jquery和ajaxhooker在抖音网页的爱恨交加