apani
apani

Reputation: 31

Greasemonkey: take from ajaxrespond for later use

The site http://project-gc.com/Statistics/TopFavWilson views data depending on a set filter via ajax.

I can see this data as bargraphs.(I'm logged in and authenticated)

But I would like to save this data for import into e.g. a spreadsheet program. I need the Fav%Wilson points (also in the ajaxresponde), I can't get them in a different way.

I thought about using a greasemonkey script to fetch the responddata from the after the ajaxcall. Maybe with "waitForKeyElements"?

Can anybody suggest a sollution or give me a hint how to solve the problem?

Upvotes: 1

Views: 449

Answers (1)

Pinke Helga
Pinke Helga

Reputation: 6682

If you only want to monitor the responses without any interaction, you can replace the XHR constructor with a wrapper function and add an event listener to each newly created instance.

// ==UserScript==
// @name        ajaxobserver
// @namespace   http://example.com
// @description observes ajax responses
// @include     http://project-gc.com/Statistics/TopFavWilson
// @include     http://project-gc.com/Statistics/TopFavWilson/*
// @version     1
// @grant       none
// @run-at      document-start
// ==/UserScript==



// scope encapsulation in newer GM versions not necessary, nevertheless...
(function()
{
  // save constructor
  let XMLHttpRequest = window.XMLHttpRequest;

  // replace constructor function
  window.XMLHttpRequest = function()
  {
    // new instance
    let obj = new XMLHttpRequest();

    // register a listener
    obj.addEventListener('load', function (event)
    {
      console.log('EVENT LISTENER: ajax load: responseText =', event.target.responseText);
    });

    //return the created instance instead of `this`
    return obj;
  };
})();

If you also want to manipulate results, you will need a proxy or you have rebuild the entire XHR Object by hand as a wrapper. This is a proxy example:

// ==UserScript==
// @name        ajaxmanipulator
// @namespace   http://example.com
// @description observes & manipulate ajax responses
// @include     http://project-gc.com/Statistics/TopFavWilson
// @include     http://project-gc.com/Statistics/TopFavWilson/*
// @version     1
// @grant       none
// @run-at      document-start
// ==/UserScript==

(function()
{
  let
    // a function call handler for onevent functions
    applyEventHandler =
    {
      apply: function(targetFunc, thisArg, [event])
      {
        if
        (
          'readystatechange' === event.type && 4 === event.target.readyState && 200 === event.target.status
          ||
          'load' === event.type
        )
          console.log('EVENT', event.type + ':', event.target.responseText);

        return targetFunc.call(thisArg, event);
      }
    },

    // a function call handler for onevent functions
    applyOpenHandler =
    {                                     // destructuring arguments array into individual named arguments
      apply: function(targetFunc, thisArg, [method, url, async, user, password])
      {
        console.log('open handler:', 'target =', targetFunc, ', thisArg =', thisArg );

        console.log
        ( 'XMLHttpRequest.open\n',
          'method:'  , method,
          'url:'     , url,
          'async:'   , async,
          'user:'    , user,
          'password:', password
        );

        // let's manipulate some argument
        url += '?get-parameter-added-by-proxy';

        // finally call the trapped function in context of thisArg passing our manipulated arguments
        return targetFunc.call(thisArg, method, url, async, user, password);
      }
    },

    // a property handler for XMLHttpRequest instances
    xmlHttpReq_PropertiesHandler =
    {
      // target  : the proxied object (native XMLHttpRequest instance)
      // property: name of the property
      // value   : the new value to assign to the property (only in setter trap)
      // receiver: the Proxy instance

      get:
        function (target, property /*, receiver*/)
        {
          console.log('%cget handler: ', 'color:green', property);

          switch (property)
          {
            case 'responseText':
              // let's return a manipulated string when the property `responseText` is read
              return '<div style="color:red;border:1px solid red">response manipulated by proxy'
                + target.responseText + '</div>';

            case 'open':
              // All we can detect here is a get access to the *property*, which
              // usually returns a function object. The apply trap does not work at this
              // point. Only a *proxied function* can be trapped by the apply trap.
              // Thus we return a proxy of the open function using the apply trap.
              // (A simple wrapper function would do the trick as well, but could be easier
              // detected by the site's script.)
              // We use bind to set the this-context to the native `XMLHttpRequest` instance.
              // It will be passed as `thisArg` to the trap handler.
              return new Proxy(target.open, applyOpenHandler).bind(target);

            default:
              return 'function' === typeof target[property]
                  // function returned by proxy must be executed in slave context (target)
                ? target[property].bind(target)
                  // non-function properties are just returned
                : target[property]
              ;
          }
        },

      set:
        function (target, property, value, receiver)
        {
          try
          {
            console.log('%cset handler: ', 'color:orange', property, '=', value);

            switch (property)
            {
              // Event handlers assigned to the proxy must be stored into the proxied object (slave),
              // so that its prototype can access them (in slave context). Such an access is not trapped by
              // the proxy's get trap. We need to store proxied functions to get ready to observe invokations.

              // Old ajax style was checking the readystate,
              // newer scripts use the onload event handler. Both can still be found.
              case 'onreadystatechange':
              case 'onload':
                // only create proxy if `value` is a function
                target[property] = 'function' === typeof value
                  ? new Proxy(value, applyEventHandler).bind(receiver)
                  : value
                ;
                break;

              default:
                target[property] = value;

            }
            return true; // success
          }

          catch (e)
          {
            console.error(e);
            return false; // indicate setter error
          }
        }
    },

    oldXMLHttpRequest = window.XMLHttpRequest
  ; // end of local variable declaration

  window.XMLHttpRequest = function(...argv)
  {
    return new Proxy(new oldXMLHttpRequest(...argv), xmlHttpReq_PropertiesHandler);
  }
})();

Be aware that this direct acces only works as long as you do not grant any privileged GM_functions. When you get enclosed into sandbox, you will have to inject the function as sting into the site's scope, e.g. via setTimeout.

Upvotes: 0

Related Questions