Greasy Fork is available in English.

极时学fgj

极时学防掉线

// ==UserScript==
// @name         极时学fgj
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  极时学防掉线
// @author       You
// @license      MIT
// @match        https://gk-elearning.yunxuetang.cn/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=61.156
// @grant        none
// ==/UserScript==

(function () {
  'use strict'
  /*
 * author: wendux
 * email: 824783146@qq.com
 * source code: https://github.com/wendux/Ajax-hook
 */

  // Save original XMLHttpRequest as _rxhr
  const realXhr = '__xhr'

  const events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort']

  function configEvent (event, xhrProxy) {
    const e = {}
    for (const attr in event) e[attr] = event[attr]
    // xhrProxy instead
    e.target = e.currentTarget = xhrProxy
    return e
  }

  function hook (proxy, win) {
    win = win || window
    // Avoid double hookAjax
    win[realXhr] = win[realXhr] || win.XMLHttpRequest

    win.XMLHttpRequest = function () {
      // We shouldn't hookAjax XMLHttpRequest.prototype because we can't
      // guarantee that all attributes are on the prototype。
      // Instead, hooking XMLHttpRequest instance can avoid this problem.

      const xhr = new win[realXhr]()

      // Generate all callbacks(eg. onload) are enumerable (not undefined).
      for (let i = 0; i < events.length; ++i) {
        const key = 'on' + events[i]
        if (xhr[key] === undefined) xhr[key] = null
      }

      for (const attr in xhr) {
        let type = ''
        try {
          type = typeof xhr[attr] // May cause exception on some browser
        } catch (e) {
        }
        if (type === 'function') {
        // hookAjax methods of xhr, such as `open`、`send` ...
          this[attr] = hookFunction(attr)
        } else {
          Object.defineProperty(this, attr, {
            get: getterFactory(attr),
            set: setterFactory(attr),
            enumerable: true
          })
        }
      }
      const that = this
      xhr.getProxy = function () {
        return that
      }
      this.xhr = xhr
    }

    Object.assign(win.XMLHttpRequest, { UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4 })

    // Generate getter for attributes of xhr
    function getterFactory (attr) {
      return function () {
        const v = this.hasOwnProperty(attr + '_') ? this[attr + '_'] : this.xhr[attr]
        const attrGetterHook = (proxy[attr] || {}).getter
        return attrGetterHook && attrGetterHook(v, this) || v
      }
    }

    // Generate setter for attributes of xhr; by this we have an opportunity
    // to hookAjax event callbacks (eg: `onload`) of xhr;
    function setterFactory (attr) {
      return function (v) {
        const xhr = this.xhr
        const that = this
        const hook = proxy[attr]
        // hookAjax  event callbacks such as `onload`、`onreadystatechange`...
        if (attr.substring(0, 2) === 'on') {
          that[attr + '_'] = v
          xhr[attr] = function (e) {
            e = configEvent(e, that)
            const ret = proxy[attr] && proxy[attr].call(that, xhr, e)
            ret || v.call(that, e)
          }
        } else {
        // If the attribute isn't writable, generate proxy attribute
          const attrSetterHook = (hook || {}).setter
          v = attrSetterHook && attrSetterHook(v, that) || v
          this[attr + '_'] = v
          try {
          // Not all attributes of xhr are writable(setter may undefined).
            xhr[attr] = v
          } catch (e) {
          }
        }
      }
    }

    // Hook methods of xhr.
    function hookFunction (fun) {
      return function () {
        const args = [].slice.call(arguments)
        if (proxy[fun]) {
          const ret = proxy[fun].call(this, args, this.xhr)
          // If the proxy return value exists, return it directly,
          // otherwise call the function of xhr.
          if (ret) return ret
        }
        return this.xhr[fun].apply(this.xhr, args)
      }
    }

    // Return the real XMLHttpRequest
    return win[realXhr]
  }

  function unHook (win) {
    win = win || window
    if (win[realXhr]) win.XMLHttpRequest = win[realXhr]
    win[realXhr] = undefined
  }

  const eventLoad = events[0]
  const eventLoadEnd = events[1]
  const eventTimeout = events[2]
  const eventError = events[3]
  const eventReadyStateChange = events[4]
  const eventAbort = events[5]

  const prototype = 'prototype'

  function proxy (pxy, win) {
    win = win || window
    if (win.__xhr) throw new Error('Ajax is already hooked.')
    return proxyAjax(pxy, win)
  }

  function trim (str) {
    return str.replace(/^\s+|\s+$/g, '')
  }

  function getEventTarget (xhr) {
    return xhr.watcher || (xhr.watcher = document.createElement('a'))
  }

  function triggerListener (xhr, name) {
    const xhrProxy = xhr.getProxy()
    const callback = 'on' + name + '_'
    const event = configEvent({ type: name }, xhrProxy)
    xhrProxy[callback] && xhrProxy[callback](event)
    let evt
    if (typeof (Event) === 'function') {
      evt = new Event(name, { bubbles: false })
    } else {
    // https://stackoverflow.com/questions/27176983/dispatchevent-not-working-in-ie11
      evt = document.createEvent('Event')
      evt.initEvent(name, false, true)
    }
    getEventTarget(xhr).dispatchEvent(evt)
  }

  function Handler (xhr) {
    this.xhr = xhr
    this.xhrProxy = xhr.getProxy()
  }

  Handler[prototype] = Object.create({
    resolve: function resolve (response) {
      const xhrProxy = this.xhrProxy
      const xhr = this.xhr
      xhrProxy.readyState = 4
      xhr.resHeader = response.headers
      xhrProxy.response = xhrProxy.responseText = response.response
      xhrProxy.statusText = response.statusText
      xhrProxy.status = response.status
      triggerListener(xhr, eventReadyStateChange)
      triggerListener(xhr, eventLoad)
      triggerListener(xhr, eventLoadEnd)
    },
    reject: function reject (error) {
      this.xhrProxy.status = 0
      triggerListener(this.xhr, error.type)
      triggerListener(this.xhr, eventLoadEnd)
    }
  })

  function makeHandler (next) {
    function sub (xhr) {
      Handler.call(this, xhr)
    }

    sub[prototype] = Object.create(Handler[prototype])
    sub[prototype].next = next
    return sub
  }

  const RequestHandler = makeHandler(function (rq) {
    const xhr = this.xhr
    rq = rq || xhr.config
    xhr.withCredentials = rq.withCredentials
    xhr.open(rq.method, rq.url, rq.async !== false, rq.user, rq.password)
    for (const key in rq.headers) {
      xhr.setRequestHeader(key, rq.headers[key])
    }
    xhr.send(rq.body)
  })

  const ResponseHandler = makeHandler(function (response) {
    this.resolve(response)
  })

  const ErrorHandler = makeHandler(function (error) {
    this.reject(error)
  })

  function proxyAjax (proxy, win) {
    const onRequest = proxy.onRequest
    const onResponse = proxy.onResponse
    const onError = proxy.onError

    function handleResponse (xhr, xhrProxy) {
      const handler = new ResponseHandler(xhr)
      const ret = {
        response: xhrProxy.response || xhrProxy.responseText, // ie9
        status: xhrProxy.status,
        statusText: xhrProxy.statusText,
        config: xhr.config,
        headers: xhr.resHeader || xhr.getAllResponseHeaders().split('\r\n').reduce(function (ob, str) {
          if (str === '') return ob
          const m = str.split(':')
          ob[m.shift()] = trim(m.join(':'))
          return ob
        }, {})
      }
      if (!onResponse) return handler.resolve(ret)
      onResponse(ret, handler)
    }

    function onerror (xhr, xhrProxy, error, errorType) {
      const handler = new ErrorHandler(xhr)
      error = { config: xhr.config, error, type: errorType }
      if (onError) {
        onError(error, handler)
      } else {
        handler.next(error)
      }
    }

    function preventXhrProxyCallback () {
      return true
    }

    function errorCallback (errorType) {
      return function (xhr, e) {
        onerror(xhr, this, e, errorType)
        return true
      }
    }

    function stateChangeCallback (xhr, xhrProxy) {
      if (xhr.readyState === 4 && xhr.status !== 0) {
        handleResponse(xhr, xhrProxy)
      } else if (xhr.readyState !== 4) {
        triggerListener(xhr, eventReadyStateChange)
      }
      return true
    }

    return hook({
      onload: preventXhrProxyCallback,
      onloadend: preventXhrProxyCallback,
      onerror: errorCallback(eventError),
      ontimeout: errorCallback(eventTimeout),
      onabort: errorCallback(eventAbort),
      onreadystatechange: function (xhr) {
        return stateChangeCallback(xhr, this)
      },
      open: function open (args, xhr) {
        const _this = this
        const config = xhr.config = { headers: {} }
        config.method = args[0]
        config.url = args[1]
        config.async = args[2]
        config.user = args[3]
        config.password = args[4]
        config.xhr = xhr
        const evName = 'on' + eventReadyStateChange
        if (!xhr[evName]) {
          xhr[evName] = function () {
            return stateChangeCallback(xhr, _this)
          }
        }

        // 如果有请求拦截器,则在调用onRequest后再打开链接。因为onRequest最佳调用时机是在send前,
        // 所以我们在send拦截函数中再手动调用open,因此返回true阻止xhr.open调用。
        //
        // 如果没有请求拦截器,则不用阻断xhr.open调用
        if (onRequest) return true
      },
      send: function (args, xhr) {
        const config = xhr.config
        config.withCredentials = xhr.withCredentials
        config.body = args[0]
        if (onRequest) {
        // In 'onRequest', we may call XHR's event handler, such as `xhr.onload`.
        // However, XHR's event handler may not be set until xhr.send is called in
        // the user's code, so we use `setTimeout` to avoid this situation
          const req = function () {
            onRequest(config, new RequestHandler(xhr))
          }
          config.async === false ? req() : setTimeout(req)
          return true
        }
      },
      setRequestHeader: function (args, xhr) {
      // Collect request headers
        xhr.config.headers[args[0].toLowerCase()] = args[1]
        if (onRequest) return true
      },
      addEventListener: function (args, xhr) {
        const _this = this
        if (events.indexOf(args[0]) !== -1) {
          const handler = args[1]
          getEventTarget(xhr).addEventListener(args[0], function (e) {
            const event = configEvent(e, _this)
            event.type = args[0]
            event.isTrusted = true
            handler.call(_this, event)
          })
          return true
        }
      },
      getAllResponseHeaders: function (_, xhr) {
        const headers = xhr.resHeader
        if (headers) {
          let header = ''
          for (const key in headers) {
            header += key + ': ' + headers[key] + '\r\n'
          }
          return header
        }
      },
      getResponseHeader: function (args, xhr) {
        const headers = xhr.resHeader
        if (headers) {
          return headers[(args[0] || '').toLowerCase()]
        }
      }
    }, win)
  }

  proxy({
    // 请求发起前进入
    onRequest: (config, handler) => {
      handler.next(config)
    },
    // 请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功
    onError: (err, handler) => {
      handler.next(err)
    },
    // 请求成功后进入
    onResponse: (response, handler) => {
      const msg = {
        enabled: 0,
        maxMin: 100000,
        tips: '已触发防挂机验证,请点击“继续学习”,否则学习将被停止',
        tips1: '已触发防挂机验证,请点击“继续学习”,否则学习将被停止',
        tips2: '学习计时中,请不要走开喔。点击“继续学习”,否则我们认为您已临时走开,在您离开的这段时间我们不会计入您的学习时间及相应学分。',
        countDown: 120,
        selectType: 1,
        progress: 100,
        moreEnabled: 0,
        enableForbidDrag: 0,
        enableAudioBackgroundPlay: 0
      }
      if (response.config.url === 'https://api-kng-phx.yunxuetang.cn/v2/kngConf/cheat') {
        console.log('>>>', response)
        const newResponse = { ...response, response: JSON.stringify(msg) }
        handler.next(newResponse)
      } else {
        handler.next(response)
      }
    }
  })
  // Your code here...
})()