// ==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 // @downloadURL 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... })()