// ==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: [email protected]
* 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...
})()