Fuck Chaoxing

解除超星自动暂停播放的限制并添加自动播放下一集的功能

  1. // ==UserScript==
  2. // @name Fuck Chaoxing
  3. // @namespace xuyiming.open@outlook.com
  4. // @description 解除超星自动暂停播放的限制并添加自动播放下一集的功能
  5. // @author 依然独特
  6. // @version 1.2.4
  7. // @grant none
  8. // @run-at document-start
  9. // @require https://gf.qytechs.cn/scripts/18715-hooks/code/Hooks.js?version=661566
  10. // @require https://gf.qytechs.cn/scripts/29782-docsready/code/docsReady.js?version=603417
  11. // @include *://*.chaoxing.com/mycourse/studentstudy*
  12. // @include *://*.chaoxing.com/ananas/modules/video/index.html*
  13. // @match *://*.chaoxing.com/mycourse/studentstudy*
  14. // @match *://*.chaoxing.com/ananas/modules/video/index.html*
  15. // @include *://*.chaoxing.com/ananas/modules/work/index.html
  16. // @include *://*.chaoxing.com/ananas/modules/work/index.html*
  17. // @match *://*.chaoxing.com/ananas/modules/work/index.html
  18. // @match *://*.chaoxing.com/ananas/modules/work/index.html*
  19. // @license BSD 2-Clause
  20. // @homepageURL https://gist.github.com/xymopen/eb65046644ff5cb7c0668e5d4f9607d1
  21. // ==/UserScript==
  22.  
  23. // TODO: CX update with an HTML5 Player. Need some time digging with it
  24. // NOTE: I no longer have any lesson on CX. development may delayed.
  25.  
  26. ( function () {
  27. "use strict";
  28.  
  29. // So, let's first clarify the structure of Chaoxing Student Study Page
  30.  
  31. // A course is made up of mulitple chapters
  32. // A chapter is made up of mulitple cards, saying a multi-media card and a unit test card
  33. // A card is made up of multiple jobs, saying two video jobs and a ppt job
  34.  
  35. // When a video job is finished, finishJob() would be called on MoocPlayer,
  36. // which calls proxy_completed(),
  37. // whick calls ed_complete(),
  38. // which calls JC.completed on card iframe,
  39. // which emits an completed event,
  40. // which trigger unlock(),
  41. // which calls onReadComplete() on the top window
  42.  
  43. // When a ppt job is loaded, uParse() would be called
  44. // which calls unlock()
  45.  
  46. // onReadComplete() then calls onReadComplete1() to pull updated chapter list from the server
  47. // and calls jobflag() to figure out how many jobs remaining to finish
  48.  
  49. // jobflag() looks in card iframe for .ans-job-icon as total jobs and .ans-job-finished as unfinished ones.
  50. // ppt jobs doesn't count for they don't have .ans-job-icon or .ans-job-finished
  51.  
  52. // Generally speaking we only need to handle video jobs
  53. // However Chrome blocks Flash. :facepalm:
  54.  
  55. /**
  56. * @param {(config: any, createCXPlayer: Function) => any} onPlayerInit
  57. * @param {Window} [contextWindow]
  58. */
  59. function hookCXPlayer ( onPlayerInit, contextWindow ) {
  60. if ( undefined === contextWindow ) {
  61. contextWindow = window;
  62. }
  63.  
  64. // CXPlayer and pauseMovie() loaded as jQuery plug-ins
  65. // so hook jQuery to access it.
  66. Hooks.set( contextWindow, "jQuery", function ( target, propertyName, ignored, jQuery ) {
  67. Hooks.method( jQuery.fn, "cxplayer", function ( target, methodName, method, thisArg, args ) {
  68. var replyArgs = arguments, $globalPlayer, $player,
  69. globalConfig = args[ 0 ];
  70.  
  71. function createCXPlayer ( config ) {
  72. if ( undefined !== config ) {
  73. globalConfig = config;
  74. args[ 0 ] = config;
  75. }
  76.  
  77. $globalPlayer = Hooks.Reply.method( replyArgs );
  78.  
  79. return $globalPlayer;
  80. }
  81.  
  82. $player = onPlayerInit( globalConfig, createCXPlayer );
  83.  
  84. if ( undefined !== $player ) {
  85. $globalPlayer = $player;
  86. }
  87.  
  88. return $globalPlayer;
  89. } );
  90.  
  91. return Hooks.Reply.set( arguments );
  92. } );
  93. };
  94.  
  95. var globalVideoJs;
  96.  
  97. /**
  98. * @param {Window} [contextWindow]
  99. * @see {@link [videojs-ext.min.js](https://mooc1-2.chaoxing.com/ananas/videojs-ext/videojs-ext.min.js)}
  100. */
  101. function videoJsStudyUncontrolAndTimelineNull ( contextWindow ) {
  102. if ( undefined === contextWindow ) {
  103. contextWindow = window;
  104. }
  105.  
  106. Hooks.set( contextWindow, "videojs", function ( target, propertyName, ignored, videojs ) {
  107. globalVideoJs = videojs;
  108.  
  109. Hooks.method( videojs, "registerPlugin", function ( target, methodName, method, thisArg, args ) {
  110. if ( "studyControl" === args[ 0 ] ) {
  111. method.call( thisArg, "studyControl", function () { } );
  112. return args[ 1 ]
  113. } else if ( "timelineObjects" === args[ 0 ] ) {
  114. method.call( thisArg, "timelineObjects", function () { } );
  115. return args[ 1 ]
  116. } else {
  117. return Hooks.Reply.method( arguments );
  118. }
  119. } );
  120.  
  121. return Hooks.Reply.set( arguments );
  122. } );
  123. }
  124.  
  125. /**
  126. * @param {(config: any, createPlayer: Function) => any} onPlayerInit
  127. * @param {Window} [contextWindow]
  128. */
  129. function hookVideojs ( onPlayerInit, contextWindow ) {
  130. if ( undefined === contextWindow ) {
  131. contextWindow = window;
  132. }
  133.  
  134. Hooks.set( contextWindow, "ans", function ( target, propertyName, ignored, ans ) {
  135. Hooks.method( ans, "VideoJs", function ( target, methodName, method, thisArg, args ) {
  136. var replyArgs = arguments, $globalPlayer, $player,
  137. globalConfig = args[ 0 ].params;
  138.  
  139. function createPlayer ( config ) {
  140. var player;
  141.  
  142. if ( undefined !== config ) {
  143. globalConfig = config;
  144. args[ 0 ].params = config;
  145. }
  146.  
  147. // CX didn't return player instance to us
  148. // nail it
  149. Hooks.Reply.method( replyArgs );
  150.  
  151. return globalVideoJs( args[ 0 ].videojs );
  152. }
  153.  
  154. $player = onPlayerInit( globalConfig, createPlayer );
  155.  
  156. if ( undefined !== $player ) {
  157. $globalPlayer = $player;
  158. }
  159.  
  160. return $globalPlayer;
  161. } );
  162.  
  163. return Hooks.Reply.set( arguments );
  164. } );
  165. };
  166.  
  167. /**
  168. * @param {NodeList} list
  169. * @returns {number}
  170. */
  171. function findCurIdx ( list ) {
  172. return Array.prototype.findIndex.call( list, function ( chapter ) {
  173. return chapter.classList.contains( "currents" );
  174. } );
  175. };
  176.  
  177. function canNextCard () {
  178. var contextDocument = window.top.document.querySelector( "iframe" ).contentDocument;
  179.  
  180. return Array.prototype.filter.call( contextDocument.querySelectorAll( ".ans-job-icon" ), function ( jobContainer ) {
  181. return !jobContainer.parentNode.classList.contains( "ans-job-finished" );
  182. } ).length === 0;
  183. }
  184.  
  185. function nextCard () {
  186. var cards, nextSectionIndex;
  187.  
  188. cards = document.querySelectorAll( "#mainid .tabtags span" );
  189. nextSectionIndex = findCurIdx( cards ) + 1;
  190.  
  191. if ( nextSectionIndex < cards.length ) {
  192. cards[ nextSectionIndex ].click();
  193.  
  194. return true;
  195. } else {
  196. return false;
  197. }
  198. }
  199.  
  200. function nextChapter () {
  201. var document = window.top.document,
  202. chapters = document.querySelectorAll(
  203. "#coursetree .ncells h1," +
  204. "#coursetree .ncells h2," +
  205. "#coursetree .ncells h3," +
  206. "#coursetree .ncells h4," +
  207. "#coursetree .ncells h5," +
  208. "#coursetree .ncells h6"
  209. ),
  210. curChapterIdx = findCurIdx( chapters ),
  211. nextChapter = Array.prototype.slice.call( chapters, curChapterIdx + 1 ).find( function ( chapter ) {
  212. // finished chapters are classified as blue
  213. // and locked chapters are classified as lock
  214. return !chapter.querySelector( ".blue" ) && !chapter.querySelector( ".lock" );
  215. } );
  216.  
  217.  
  218. // Go to the first unfinished and unlocked chapter
  219. if ( nextChapter ) {
  220. nextChapter.click();
  221.  
  222. return true;
  223. } else {
  224. // or wait for next call when one locked chapter may be unlocked
  225. return false;
  226. }
  227. }
  228.  
  229. if ( "/ananas/modules/video/index.html" === window.location.pathname ) {
  230. // Video Job iframe
  231. hookCXPlayer( function ( config, createCXPlayer ) {
  232. var $player;
  233.  
  234. // https://mooc1-1.chaoxing.com/ananas/modules/video/cxplayer/moocplayer_4.0.11.js
  235. config.datas.enableFastForward = true;
  236. config.datas.enableSwitchWindow = 1;
  237. config.datas.errorBackTime = false;
  238. config.datas.isAutoPlayNext = true;
  239. config.datas.isDefaultPlay = true;
  240. config.datas.pauseAdvertList = [];
  241. config.datas.preAdvertList = [];
  242.  
  243. // if ( config.events &&
  244. // config.events.onAnswerRight &&
  245. // !config.events.onAnswerRight.toString()
  246. // .replace( /(function .*?\(.*?\))/g, "" ).trim() // remove function signifigure
  247. // .replace( /^\{|\}$/g, "" )
  248. // .replace( /\/\/.*(\r|\n|(\r\n))/g, "" ) // remove single line comment
  249. // .replace( /\/\*.*\*\//mg, "" ) // remove multiple line comment
  250. // .match( /^\s*$/ )
  251. // ) {
  252. // window.alert( "onAnswerRight() is not empty. It's unsafe to block the resource URL." );
  253. // }
  254.  
  255. $player = createCXPlayer();
  256.  
  257. // Remove native `onPause` listener
  258. // prevent pause the movie from JS side
  259. $player.unbind( "onPause" );
  260.  
  261. // Unpausable playback
  262. // TODO: find better way handling multiple players playing at the same time
  263. $player.pauseMovie = function () { };
  264. $player.bind( "onPause", function () {
  265. $player.playMovie();
  266. } );
  267.  
  268. $player.bind( "onError", function () {
  269. if ( 4 === $player.getPlayState() ) {
  270. window.location.reload();
  271. }
  272. } );
  273.  
  274. window.MoocPlayer.prototype.switchWindow = function () { return this; };
  275. window.jQuery.fn.pauseMovie = function () { };
  276.  
  277. // Object.keys( config.events ).forEach( e => $player.bind( e, () => {
  278. // const id = $player.find( 'object[type="application/x-shockwave-flash"]' ).attr( 'id' );
  279. // const state = [ "error", "playing", "paused", "hanging", "stop" ][ $player.getPlayState() ];
  280.  
  281. // console.log( `[Fuck Chaoxing]${ e } is triggered. Player#${ id } is ${ state }.` );
  282. // } ) );
  283. } );
  284. videoJsStudyUncontrolAndTimelineNull();
  285. hookVideojs( function ( config, createPlayer ) {
  286. var $player;
  287.  
  288. config.enableFastForward = 1;
  289. config.enableSwitchWindow = 1;
  290.  
  291. $player = createPlayer();
  292.  
  293. $player.on( "ready", function () {
  294. // immediate play video may cause DOMException
  295. setTimeout( function () {
  296. $player.play();
  297. }, 5000 );
  298. } );
  299. } );
  300. } else if ( "/mycourse/studentstudy" === window.location.pathname ) {
  301. // Card iframe
  302. domReady().then( function () {
  303.  
  304. var hasNextCard = true,
  305. jobflagApplied = false,
  306. ajaxesPending = 0;
  307.  
  308. function onReadComplete () {
  309. if ( jobflagApplied && ajaxesPending && !hasNextCard ) {
  310. nextChapter();
  311. jobflagApplied = false;
  312. }
  313. };
  314.  
  315. window.jQuery( document ).ajaxComplete( function () {
  316. ajaxesPending -= 1;
  317.  
  318. if ( ajaxesPending === 0 ) {
  319. onReadComplete();
  320. }
  321. } );
  322.  
  323. Hooks.method( window.jQuery, "ajax", function ( target, methodName, method, thisArg, args ) {
  324. ajaxesPending += 1;
  325.  
  326. return Hooks.Reply.method( arguments );
  327. } );
  328.  
  329. Hooks.method( window, "onReadComplete1", function ( target, methodName, method, thisArg, args ) {
  330. var returns = Hooks.Reply.method( arguments );
  331.  
  332. onReadComplete();
  333.  
  334. return returns;
  335. } );
  336.  
  337. Hooks.method( window, "jobflag", function ( target, methodName, method, thisArg, args ) {
  338. if ( canNextCard() ) {
  339. hasNextCard = nextCard();
  340. }
  341.  
  342. jobflagApplied = true;
  343. onReadComplete();
  344.  
  345. return Hooks.Reply.method( arguments );
  346. } );
  347. } );
  348. }
  349. } )();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址