wenku8+ loader

load wenku8+ temporarily

目前為 2021-12-23 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/437461/1001428/wenku8%2B%20loader.js

  1. // 源代码
  2. (function () {
  3. const KEY_LOCALCDN = 'LOCAL-CDN';
  4. const KEY_LOCALCDN_VERSION = 'version';
  5. const VALUE_LOCALCDN_VERSION = '0.1';
  6. const KEY_POLYFILL = 'wenku8-plus-injecter';
  7. GM_PolyFill(KEY_POLYFILL);
  8. DoLog();
  9. DoLog(LogLevel.Success, 'wenku8+ loader loaded successfully on this page');
  10.  
  11. const main_url = 'https://greasyfork.org/scripts/416310/code/script.user.js';
  12. const loader_url = 'https://greasyfork.org/scripts/437461/code/script.js';
  13. dealDocument();
  14.  
  15. function dealDocument(oDom=document) {
  16. // Hook window.open
  17. const open = window.open;
  18. window.open = openWithInjector;
  19. function openWithInjector(strUrl, strWindowName, strWindowFeatures) {
  20. const newTab = open(strUrl, strWindowName, strWindowFeatures);
  21. const oDom = newTab.document;
  22. const oWin = newTab.window;
  23. oWin.addEventListener('load', function() {
  24. /*loadJSPlus(loader_url, function(success) {
  25. if (success) {
  26. DoLog(['wenku8+ loader loaded for', oDom]);
  27. } else {
  28. DoLog(['wenku8+ loader load failed on', oDom]);
  29. }
  30. }, oDom);*/
  31. const script = oDom.createElement('script');
  32. script.src = loader_url;
  33. oWin.document.head.appendChild(script);
  34. });
  35. return newTab;
  36. }
  37.  
  38. // Hook <a>
  39. hooklinks();
  40. setInterval(hooklinks, 1000);
  41.  
  42. // Load wenku8+
  43. loadJSPlus(main_url, function(success) {
  44. if (success) {
  45. DoLog('wenku8+ main loaded');
  46. } else {
  47. DoLog('wenku8+ main load failed');
  48. }
  49. }, oDom);
  50.  
  51. function hooklinks() {
  52. // Hook <a>
  53. for (const a of document.querySelectorAll('a')) {
  54. !a.wenku8_loader_hooked && a.addEventListener('click', onclick);
  55. a.wenku8_loader_hooked = true;
  56. }
  57.  
  58. function onclick(e) {
  59. const a = e.currentTarget;
  60. const href = a.href;;
  61. if (!a.href.match(/^https?:\/\//)) {return;}
  62.  
  63. destroyEvent(e);
  64. window.open(href);
  65. }
  66. }
  67. }
  68.  
  69.  
  70.  
  71. // Just stopPropagation and preventDefault
  72. function destroyEvent(e) {
  73. if (!e) {return false;};
  74. if (!e instanceof Event) {return false;};
  75. e.stopPropagation();
  76. e.preventDefault();
  77. }
  78.  
  79. // Load/Read and Save javascript from given url
  80. // Auto reties when xhr fails.
  81. // If load success then callback(true), else callback(false)
  82. function loadJSPlus(url, callback, oDoc=document, maxRetry=3, retried=0) {
  83. const fn = callback || function () {};
  84. const localCDN = GM_getValue(KEY_LOCALCDN, {});
  85. if (localCDN[url]) {
  86. DoLog(LogLevel.Info, 'Loading js from localCDN: ' + url);
  87. const js = localCDN[url];
  88. appendScript(js);
  89. fn(true);
  90. return;
  91. }
  92.  
  93. DoLog(LogLevel.Info, 'Loading js from web: ' + url);
  94. GM_xmlhttpRequest({
  95. method : 'GET',
  96. url : url,
  97. responseType : 'text',
  98. onload : function(e) {
  99. if (e.status === 200) {
  100. const js = e.responseText;
  101. localCDN[url] = js;
  102. localCDN[KEY_LOCALCDN_VERSION] = VALUE_LOCALCDN_VERSION;
  103. GM_setValue(KEY_LOCALCDN, localCDN);
  104.  
  105. appendScript(js);
  106. fn(true);
  107. } else {
  108. retry();
  109. }
  110. },
  111. onerror : retry
  112. })
  113.  
  114. function appendScript(code) {
  115. const script = oDoc.createElement('script');
  116. script.type = 'text/javascript';
  117. script.innerHTML = code;
  118. oDoc.head.appendChild(script);
  119. }
  120.  
  121. function retry() {
  122. retried++;
  123. if (retried <= maxRetry) {
  124. loadJSPlus(url, callback, oDoc, maxRetry, retried);
  125. } else {
  126. fn(false);
  127. }
  128. }
  129. }
  130.  
  131. // GM_Polyfill By PY-DNG
  132. // 2021.07.18
  133. // Simply provides the following GM_functions using localStorage, XMLHttpRequest and window.open:
  134. // Returns object GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled:
  135. // GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard
  136. function GM_PolyFill(name='default') {
  137. const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';
  138. const GM_POLYFILL_storage = GM_POLYFILL_getStorage();
  139. const GM_POLYFILLED = {
  140. GM_setValue: true,
  141. GM_getValue: true,
  142. GM_deleteValue: true,
  143. GM_listValues: true,
  144. GM_xmlhttpRequest: true,
  145. GM_openInTab: true,
  146. GM_setClipboard: true,
  147. };
  148.  
  149. GM_setValue_polyfill();
  150. GM_getValue_polyfill();
  151. GM_deleteValue_polyfill();
  152. GM_listValues_polyfill();
  153. GM_xmlhttpRequest_polyfill();
  154. GM_openInTab_polyfill();
  155. GM_setClipboard_polyfill();
  156.  
  157. function GM_POLYFILL_getStorage() {
  158. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  159. gstorage = gstorage ? JSON.parse(gstorage) : {};
  160. let storage = gstorage[name] ? gstorage[name] : {};
  161. return storage;
  162. };
  163.  
  164. function GM_POLYFILL_saveStorage() {
  165. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  166. gstorage = gstorage ? JSON.parse(gstorage) : {};
  167. gstorage[name] = GM_POLYFILL_storage;
  168. localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));
  169. };
  170.  
  171. // GM_setValue
  172. function GM_setValue_polyfill() {
  173. typeof (GM_setValue) === 'function' ? GM_POLYFILLED.GM_setValue = false: window.GM_setValue = PF_GM_setValue;;
  174.  
  175. function PF_GM_setValue(name, value) {
  176. name = String(name);
  177. GM_POLYFILL_storage[name] = value;
  178. GM_POLYFILL_saveStorage();
  179. };
  180. };
  181.  
  182. // GM_getValue
  183. function GM_getValue_polyfill() {
  184. typeof (GM_getValue) === 'function' ? GM_POLYFILLED.GM_getValue = false: window.GM_getValue = PF_GM_getValue;
  185.  
  186. function PF_GM_getValue(name, defaultValue) {
  187. name = String(name);
  188. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  189. return GM_POLYFILL_storage[name];
  190. } else {
  191. return defaultValue;
  192. };
  193. };
  194. };
  195.  
  196. // GM_deleteValue
  197. function GM_deleteValue_polyfill() {
  198. typeof (GM_deleteValue) === 'function' ? GM_POLYFILLED.GM_deleteValue = false: window.GM_deleteValue = PF_GM_deleteValue;
  199.  
  200. function PF_GM_deleteValue(name) {
  201. name = String(name);
  202. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  203. delete GM_POLYFILL_storage[name];
  204. GM_POLYFILL_saveStorage();
  205. };
  206. };
  207. };
  208.  
  209. // GM_listValues
  210. function GM_listValues_polyfill() {
  211. typeof (GM_listValues) === 'function' ? GM_POLYFILLED.GM_listValues = false: window.GM_listValues = PF_GM_listValues;
  212.  
  213. function PF_GM_listValues() {
  214. return Object.keys(GM_POLYFILL_storage);
  215. };
  216. };
  217.  
  218. // GM_xmlhttpRequest
  219. // not supported properties of details: synchronous binary nocache revalidate context fetch
  220. // not supported properties of response(onload arguments[0]): finalUrl
  221. // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---
  222. function GM_xmlhttpRequest_polyfill() {
  223. typeof (GM_xmlhttpRequest) === 'function' ? GM_POLYFILLED.GM_xmlhttpRequest = false: window.GM_xmlhttpRequest = PF_GM_xmlhttpRequest;
  224.  
  225. // details.synchronous is not supported as Tempermonkey
  226. function PF_GM_xmlhttpRequest(details) {
  227. const xhr = new XMLHttpRequest();
  228.  
  229. // open request
  230. const openArgs = [details.method, details.url, true];
  231. if (details.user && details.password) {
  232. openArgs.push(details.user);
  233. openArgs.push(details.password);
  234. }
  235. xhr.open.apply(xhr, openArgs);
  236.  
  237. // set headers
  238. if (details.headers) {
  239. for (const key of Object.keys(details.headers)) {
  240. xhr.setRequestHeader(key, details.headers[key]);
  241. };
  242. };
  243. details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};
  244. details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};
  245.  
  246. // properties
  247. xhr.timeout = details.timeout;
  248. xhr.responseType = details.responseType;
  249. details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};
  250.  
  251. // events
  252. xhr.onabort = details.onabort;
  253. xhr.onerror = details.onerror;
  254. xhr.onloadstart = details.onloadstart;
  255. xhr.onprogress = details.onprogress;
  256. xhr.onreadystatechange = details.onreadystatechange;
  257. xhr.ontimeout = details.ontimeout;
  258. xhr.onload = function (e) {
  259. const response = {
  260. readyState: xhr.readyState,
  261. status: xhr.status,
  262. statusText: xhr.statusText,
  263. responseHeaders: xhr.getAllResponseHeaders(),
  264. response: xhr.response
  265. };
  266. (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};
  267. (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};
  268. details.onload(response);
  269. };
  270.  
  271. // send request
  272. details.data ? xhr.send(details.data) : xhr.send();
  273.  
  274. return {
  275. abort: xhr.abort
  276. };
  277. };
  278. };
  279.  
  280. // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.
  281. function GM_openInTab_polyfill() {
  282. typeof (GM_openInTab) === 'function' ? GM_POLYFILLED.GM_openInTab = false: window.GM_openInTab = PF_GM_openInTab;
  283.  
  284. function PF_GM_openInTab(url) {
  285. window.open(url);
  286. };
  287. };
  288.  
  289. // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!
  290. function GM_setClipboard_polyfill() {
  291. typeof (GM_setClipboard) === 'function' ? GM_POLYFILLED.GM_setClipboard = false: window.GM_setClipboard = PF_GM_setClipboard;
  292.  
  293. function PF_GM_setClipboard(text) {
  294. // Create a new textarea for copying
  295. const newInput = document.createElement('textarea');
  296. document.body.appendChild(newInput);
  297. newInput.value = text;
  298. newInput.select();
  299. document.execCommand('copy');
  300. document.body.removeChild(newInput);
  301. };
  302. };
  303.  
  304. return GM_POLYFILLED;
  305. };
  306.  
  307. // Arguments: level=LogLevel.Info, logContent, asObject=false
  308. // Needs one call "DoLog();" to get it initialized before using it!
  309. function DoLog() {
  310. // Global log levels set
  311. window.LogLevel = {
  312. None: 0,
  313. Error: 1,
  314. Success: 2,
  315. Warning: 3,
  316. Info: 4,
  317. }
  318. window.LogLevelMap = {};
  319. window.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
  320. window.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
  321. window.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
  322. window.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
  323. window.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
  324. window.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
  325.  
  326. // Current log level
  327. DoLog.logLevel = window.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  328.  
  329. // Log counter
  330. DoLog.logCount === undefined && (DoLog.logCount = 0);
  331. if (++DoLog.logCount > 512) {
  332. console.clear();
  333. DoLog.logCount = 0;
  334. }
  335.  
  336. // Get args
  337. let level, logContent, asObject;
  338. switch (arguments.length) {
  339. case 1:
  340. level = LogLevel.Info;
  341. logContent = arguments[0];
  342. asObject = false;
  343. break;
  344. case 2:
  345. level = arguments[0];
  346. logContent = arguments[1];
  347. asObject = false;
  348. break;
  349. case 3:
  350. level = arguments[0];
  351. logContent = arguments[1];
  352. asObject = arguments[2];
  353. break;
  354. default:
  355. level = LogLevel.Info;
  356. logContent = 'DoLog initialized.';
  357. asObject = false;
  358. break;
  359. }
  360.  
  361. // Log when log level permits
  362. if (level <= DoLog.logLevel) {
  363. let msg = '%c' + LogLevelMap[level].prefix;
  364. let subst = LogLevelMap[level].color;
  365.  
  366. if (asObject) {
  367. msg += ' %o';
  368. } else {
  369. switch(typeof(logContent)) {
  370. case 'string': msg += ' %s'; break;
  371. case 'number': msg += ' %d'; break;
  372. case 'object': msg += ' %o'; break;
  373. }
  374. }
  375.  
  376. console.log(msg, subst, logContent);
  377. }
  378. }
  379. })();