SIT上应口语伙伴工具箱

可查询自己读了多少秒,读了什么课文

  1. // ==UserScript==
  2. // @name SIT上应口语伙伴工具箱
  3. // @namespace snomiao@gmail.com
  4. // @version 20190427.121142
  5. // @description 可查询自己读了多少秒,读了什么课文
  6. // @author snomiao
  7. // @include *://210.35.98.12:8844/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. var httpGetAsync = (theUrl) => {
  12. return new Promise((resolve, reject) => {
  13. var httpRequest = new XMLHttpRequest();
  14. httpRequest.onreadystatechange = function () {
  15. if (httpRequest.readyState == 4 && httpRequest.status == 200) {
  16. //if you fetch a file you can JSON.parse(httpRequest.responseText)
  17. var data = httpRequest.responseText;
  18. resolve(data);
  19. }
  20. };
  21. httpRequest.open('GET', theUrl, true);
  22. httpRequest.send(null);
  23. });
  24. };
  25.  
  26. var getLearningReports = async (e) => {
  27. e.target.disabled = true; //不可用
  28.  
  29. var timeZone = 28800000; // === 8 * 60 * 60 * 1000
  30. // isoDateTimeExample = "1970-01-01T00:00:00.000Z"
  31. var isoDateTime = (timestamp) =>
  32. new Date(timestamp * 1000 + timeZone).toISOString();
  33. var isoDate = (timestamp) => isoDateTime(timestamp).substring(0, 10); // "1970-01-01" .split(/T|\./)[0]
  34. var isoTime = (timestamp) => isoDateTime(timestamp).substring(11, 19); // "00:00:00" .split(/T|\./)[1]
  35. var localeDate = (anIsoDate) => {
  36. // "1970-01-01" => "1970年1月1日 星期四"
  37. anIsoDate = new Date(anIsoDate);
  38. return (
  39. anIsoDate.toLocaleDateString('zh-CN', {
  40. year: 'numeric',
  41. month: 'long',
  42. day: 'numeric',
  43. }) +
  44. ' ' +
  45. anIsoDate.toLocaleDateString('zh-CN', { weekday: 'long' })
  46. );
  47. };
  48.  
  49. var getUrlPrefix = '/report_new.php?do=ajax&op=';
  50. var getList = async (starttime) => {
  51. console.log('getList(starttime)', { starttime });
  52. return JSON.parse(
  53. await httpGetAsync(
  54. getUrlPrefix +
  55. 'list' +
  56. ((starttime && '&starttime=' + starttime) || '')
  57. )
  58. );
  59. };
  60. var getOne = async (cid) => {
  61. console.log('getOne(cid)', { cid });
  62. return JSON.parse(await httpGetAsync(getUrlPrefix + 'one&cid=' + cid));
  63. };
  64. var getDetail = async (cid, date) => {
  65. console.log('getDetail(cid, date)', { cid, date });
  66. return JSON.parse(
  67. await httpGetAsync(
  68. getUrlPrefix + 'detail&cid=' + cid + '&date=' + date
  69. )
  70. );
  71. };
  72.  
  73. var starttime = +new Date('2019-02-24') / 1000;
  74. var reList = await getList(starttime);
  75. //var reList = await getList();
  76. if (reList.success) {
  77. var cids = Object.keys(reList.data);
  78. var sum_time = 0;
  79. var sum_scores = [];
  80.  
  81. var student_id = document.cookie.match(/uchome_loginuser=(\d+)/)[1];
  82. var report = (
  83. await Promise.all(
  84. cids.map(async (cid) => {
  85. // WARP -3
  86.  
  87. var reOne = await getOne(cid);
  88.  
  89. if (reOne.success) {
  90. var days = Math.round(
  91. Object.values(reList.data[cid].days)[0].day_count
  92. ); // 这些四舍五入必要吗?
  93. var lessons = Math.round(
  94. Object.values(reList.data[cid].lessons)[0]
  95. .lesson_count
  96. );
  97.  
  98. var avgscore =
  99. Math.round(
  100. Object.values(reList.data[cid].avgscore)[0]
  101. .avgscore * 100
  102. ) / 100;
  103. var maxscore =
  104. Math.round(
  105. Object.values(reList.data[cid].maxscore)[0]
  106. .maxscore * 100
  107. ) / 100;
  108. var spendtime = Math.round(
  109. Object.values(reList.data[cid].spendtime)[0]
  110. .spendtime
  111. );
  112. var timeCalc = (s, n = true) => {
  113. // 秒 => [小时, 分钟, 秒]
  114. let m = Math.trunc(s / 60);
  115. s -= m * 60;
  116. return n ? [...timeCalc(m, false), s] : [m, s];
  117. };
  118. var spendtimehms = timeCalc(spendtime);
  119. spendtimehms = `${spendtimehms[0]} 小时 ${spendtimehms[1]} 分钟 ${spendtimehms[2]} 秒`;
  120. var dates_summary = `<h3 class="StudyReport-lesson">课程 ${cid}</h3>
  121. <p><strong>学习了 ${days} 天,共 ${lessons} 篇。</strong></p>
  122. <table class="StudyReport-summary">
  123. <tr><th>平均分</th><th>最高分</th><th>累计秒数</th><th>累计时间</th></tr>
  124. <tr><td>${avgscore} 分</td><td>${maxscore} 分</td><td>${spendtime} 秒</td><td>${spendtimehms}</td></tr>
  125. </table>
  126. <p>详情如下:</p>`;
  127.  
  128. var dates = Object.keys(reOne.data).filter(
  129. (x) => parseInt(x) > starttime
  130. );
  131. var dates_report = (
  132. await Promise.all(
  133. dates.map(async (date) => {
  134. console.log(date);
  135. var str_date = localeDate(isoDate(date));
  136. var lesson_count =
  137. reOne.data[date].lesson_count;
  138. var studied_count =
  139. reOne.data[date].studied_count;
  140. var details_summary = `<h4 class="StudyReport-date"><time datetime="${str_date}">${str_date}</time></h4>
  141. <p>读了 ${lesson_count} 篇课文, 共读 ${studied_count} 次,分别为:</p>`;
  142.  
  143. var details_thead =
  144. '<tr><th>时间</th><th>课文</th><th>分数</th></tr>\n';
  145.  
  146. var reDetail = await getDetail(cid, date);
  147. var details_tbody = Object.values(
  148. reDetail.data
  149. )
  150. .map(function (obj) {
  151. var lessontitle = obj.lessontitle;
  152. // var lessonid = obj.lessonid;
  153. var details_tr = obj.data
  154. .map(function (data_score) {
  155. var time = isoTime(
  156. data_score.dateline
  157. );
  158. var score = Number(
  159. data_score.score
  160. );
  161.  
  162. sum_scores.push(score);
  163.  
  164. return `<tr><td><time datetime="${time}">${time}</time></td><td>${lessontitle}</td><td>${score} 分</td></tr>`;
  165. })
  166. .join('\n');
  167. return details_tr;
  168. })
  169. .join('\n');
  170.  
  171. details_table = `<table class="StudyReport-details">${
  172. details_thead + details_tbody
  173. }</table>`;
  174.  
  175. return details_summary + details_table;
  176. })
  177. )
  178. ).join('\n');
  179.  
  180. sum_time += spendtime;
  181.  
  182. return `<li>${dates_summary + dates_report}</li>`;
  183. } // if end
  184.  
  185. // WARP +3
  186. })
  187. )
  188. ).join('\n');
  189.  
  190. var report_title = `<h2>${student_id} 口语伙伴学习情况汇总</h2>`;
  191.  
  192. var avg = (array) => array.reduce((a, b, i) => (a * i + b) / (i + 1));
  193.  
  194. var avg_score = Math.round(avg(sum_scores) * 100) / 100;
  195. var sum_time_str =
  196. Math.round((sum_time / 60 / 60) * 100) / 100 + ' 小时';
  197. var report_summary = `总地来看,累计时间为 ${sum_time_str}, 平均分为 ${avg_score} 分。`;
  198. } // if end
  199.  
  200. // alert(report_title + report + report_summary)
  201.  
  202. var report_style = `
  203. #StudyReport {
  204. box-sizing: border-box;
  205. width: 1000px;
  206. margin: 0 auto 1rem;
  207. border-bottom: 1px dashed #08c;
  208. font-size: .8rem;
  209. }
  210.  
  211. #StudyReport details {
  212. background-color: #f5f5f5;
  213. border: 1px solid #cbcbcb;
  214. }
  215.  
  216. #StudyReport details > summary,
  217. #StudyReport details > ul,
  218. #StudyReport details ~ p { margin: 1rem; }
  219.  
  220. #StudyReport summary { cursor: pointer; }
  221. #StudyReport summary h2 {
  222. font-size: 1rem;
  223. display: inline-block;
  224. }
  225. #StudyReport details > ul {
  226. display: grid;
  227. grid-template-columns: 1fr 1fr;
  228. grid-gap: 1rem;
  229. }
  230. #StudyReport details > ul > li {
  231. padding: 1rem;
  232. -webkit-column-break-inside: avoid;
  233. break-inside: avoid;
  234. background-color: rgba(255, 255, 255, .5);
  235. }
  236.  
  237. #StudyReport strong { font-size: larger; }
  238. #StudyReport button {
  239. padding: .125em 1ch;
  240. border: 1px solid #cbcbcb;
  241. }
  242. h3.StudyReport-lesson,
  243. h4.StudyReport-date {
  244. margin-bottom: 1ex;
  245. margin-block-end: 1ex;
  246. }
  247. h4.StudyReport-date { font-size: 1rem; }
  248. h3.StudyReport-lesson::before {
  249. content: "";
  250. display: inline-block;
  251. background-image: url(../images/icon.png);
  252. background-repeat: no-repeat;
  253. width: 1.6em;
  254. height: 1.6em;
  255. background-position: center -27px;
  256. vertical-align: middle;
  257. }
  258. h4.StudyReport-date {
  259. font-size: .9rem;
  260. text-align: center;
  261. }
  262. /* HTML table style below uses a color scheme partly from:
  263. https://purecss.io/tables/
  264. By Yahoo! Inc.
  265. License: https://github.com/pure-css/pure-site/blob/master/LICENSE.md */
  266. #StudyReport table {
  267. width: 100%;
  268. margin-top: 1rem;
  269. margin-bottom: 1rem;
  270. margin-block: 1rem;
  271. }
  272. #StudyReport table:last-child {
  273. margin-bottom: 0;
  274. margin-block-end: 0;
  275. }
  276. #StudyReport th,
  277. #StudyReport td {
  278. padding: .125em .5ch; /* = 1/8em 1/2ch */
  279. border: 1px solid #cbcbcb;
  280. font-size: inherit;
  281. }
  282. #StudyReport th { background-color: #e0e0e0; }
  283. #StudyReport td,
  284. #StudyReport tr:nth-of-type(even) td { background-color: #fff; }
  285. #StudyReport tr:nth-of-type(odd) td { background-color: #f0f0f0; }
  286.  
  287. /* 将除 课文 以外的列全部居中 */
  288. /*
  289. table.StudyReport-summary th,
  290. table.StudyReport-summary td, */
  291. table.StudyReport-summary,
  292. table.StudyReport-details th:nth-of-type(odd),
  293. table.StudyReport-details td:nth-of-type(odd) { text-align: center; }
  294. /* 为可能有大量数据的表格添加数据行悬停样式 */
  295. table.StudyReport-details tr:hover td { background-image: linear-gradient(to left, #3bf2, #3bf2) }
  296. /* 给口语伙伴修一行 CSS… */
  297. .my_message { background-position: 15px -135px; }
  298. `;
  299.  
  300. var ReportSection = `<section id="StudyReport">
  301. <style>${report_style}</style>
  302. <details open>
  303. <summary>${report_title}</summary>
  304. <ul>${report}</ul>
  305. </details>
  306. <p>${report_summary}</p>
  307. <p><button>复制</button></p>
  308. </section>`;
  309.  
  310. document.body.insertAdjacentHTML('afterbegin', ReportSection);
  311. ReportSection = document.getElementById('StudyReport'); // #text => Node/Element
  312.  
  313. // 不知为什么,即使复制操作是点击获取成绩按钮触发的,还是得再给个 button 让人手动复制,才不会被浏览器禁止
  314. var CopyReport = () => {
  315. var selection = window.getSelection();
  316. selection.selectAllChildren(ReportSection);
  317. var msgbox = (msg) => {
  318. var themsgbox = document.createElement('div');
  319. themsgbox.style =
  320. 'position: fixed; font-size: 2rem; top: 1rem; left: 1rem; background-color: #fff8; z-index: 999;';
  321. themsgbox.textContent = msg;
  322. document.body.append(themsgbox);
  323. setTimeout(() => themsgbox.parentNode.removeChild(themsgbox), 3000);
  324. };
  325. document.execCommand('copy')
  326. ? msgbox('内容已复制')
  327. : msgbox('内容复制失败');
  328. selection.removeAllRanges();
  329. };
  330.  
  331. ReportSection.getElementsByTagName('button')[0].addEventListener(
  332. 'click',
  333. CopyReport
  334. );
  335.  
  336. e.target.disabled = false; //可用
  337. };
  338.  
  339. var LearningReport = async () => {
  340. var btn = document.createElement('button');
  341. btn.innerHTML = `了解我的学习情况<br>(查成绩查时间)`;
  342. btn.addEventListener('click', getLearningReports);
  343. document.querySelector('.u_name').append(btn);
  344. };
  345.  
  346. var ForceSubmitScore = async (lid, uid) => {
  347. let httpGetAsync = (theUrl) => {
  348. return new Promise((resolve, reject) => {
  349. var httpRequest = new XMLHttpRequest();
  350. httpRequest.onreadystatechange = function () {
  351. if (httpRequest.readyState == 4 && httpRequest.status == 200) {
  352. //if you fetch a file you can JSON.parse(httpRequest.responseText)
  353. var data = httpRequest.responseText;
  354. resolve(data);
  355. }
  356. };
  357. httpRequest.open('GET', theUrl, true);
  358. httpRequest.send(null);
  359. });
  360. };
  361.  
  362. if (!uid) {
  363. let profileHTML = await httpGetAsync(
  364. 'http://210.35.98.12:8844/cp.php?ac=profile'
  365. );
  366. let match = profileHTML.match(/uid=(\d+)/);
  367. if (!match) return false;
  368. let uid = match[1];
  369. return ForceSubmitScore(lid, uid);
  370. }
  371.  
  372. let pron = round(Math.random() * 10 + 84, 2);
  373. let tone = round(Math.random() * 10 + 84, 2);
  374. let rhythm = round(Math.random() * 10 + 84, 2);
  375. let scope = round(Math.random() * 10 + 84, 2);
  376. let total = round(Math.random() * 10 + 84, 2);
  377. let spendtime = round(Math.random() * 150 + 400, 0);
  378. let token = 11200000 + parseInt(Math.random() * 10000);
  379. let url = `http://210.35.98.12:8844//playserver.php?target=&lid=${lid}&testtype=0&targetid=&uid=${uid}&do=submitscore&total=${total}&pron=${pron}&tone=${tone}&rhythm=${rhythm}&scope=${scope}&spendtime=${spendtime}&token=${token}`;
  380.  
  381. return true;
  382. };
  383.  
  384. var ForceSubmitScoreButtons = async (uid) => {
  385. if (!uid) {
  386. let profileHTML = await httpGetAsync(
  387. 'http://210.35.98.12:8844/cp.php?ac=profile'
  388. );
  389. let match = profileHTML.match(/uid=(\d+)/);
  390. if (!match) return false;
  391. let uid = match[1];
  392. return ForceSubmitScoreButtons(uid);
  393. }
  394. // 历史成绩页面 */s.php?do=lesson&lid=*
  395. [...document.querySelectorAll('a')]
  396. .filter((e) => e.href.match(/\.\/s\.php\?do=lesson&lid=(\d+)/))
  397. .map((e) => {
  398. var round = (number, precision) =>
  399. Math.round(+number + 'e' + precision) / Math.pow(10, precision);
  400. let lid = e.href.match(/.\/s\.php\?do=lesson&lid=(\d+)/)[1];
  401. let pron = round(Math.random() * 10 + 84, 2);
  402. let tone = round(Math.random() * 10 + 84, 2);
  403. let rhythm = round(Math.random() * 10 + 84, 2);
  404. let scope = round(Math.random() * 10 + 84, 2);
  405. let total = round(Math.random() * 10 + 84, 2);
  406. let spendtime = round(Math.random() * 150 + 400, 0);
  407. let token = 11200000 + parseInt(Math.random() * 10000);
  408. let url = `http://210.35.98.12:8844//playserver.php?target=&lid=${lid}&testtype=0&targetid=&uid=${uid}&do=submitscore&total=${total}&pron=${pron}&tone=${tone}&rhythm=${rhythm}&scope=${scope}&spendtime=${spendtime}&token=${token}`;
  409.  
  410. let btn = document.createElement('button');
  411. btn.innerHTML = `我要 ${total} 分!`;
  412. let a = document.createElement('a');
  413. a.append(btn);
  414. a.href = url;
  415. a.target = '_BLANK';
  416. let div = document.createElement('div');
  417. div.append(a);
  418. e.parentNode.append(div);
  419. });
  420.  
  421. // 课文列表页面 */s.php?do=lesson&iden=*
  422. [...document.querySelectorAll('a')]
  423. .filter((e) => e.href.match(/.*\/s\.php\?do=lesson&iden=.*/))
  424. .map(async (e) => {
  425. e.addEventListener('mouseover', async () => {
  426. if (e.disabled) return;
  427. e.disabled = true;
  428. let lessonHTML = await httpGetAsync(e.href);
  429. let match1 = lessonHTML.match(/uid=(\d+)/);
  430. if (!match1) return false;
  431. let uid = match1[1];
  432. let match2 = lessonHTML.match(/lid=(\d+)/);
  433. if (!match2) return false;
  434. let lid = match2[1];
  435.  
  436. var round = (number, precision) =>
  437. Math.round(+number + 'e' + precision) /
  438. Math.pow(10, precision);
  439. let pron = round(Math.random() * 10 + 84, 2);
  440. let tone = round(Math.random() * 10 + 84, 2);
  441. let rhythm = round(Math.random() * 10 + 84, 2);
  442. let scope = round(Math.random() * 10 + 84, 2);
  443. let total = round(Math.random() * 10 + 84, 2);
  444. let spendtime = round(Math.random() * 150 + 400, 0);
  445. let token = 11200000 + parseInt(Math.random() * 10000);
  446. let url = `http://210.35.98.12:8844//playserver.php?target=&lid=${lid}&testtype=0&targetid=&uid=${uid}&do=submitscore&total=${total}&pron=${pron}&tone=${tone}&rhythm=${rhythm}&scope=${scope}&spendtime=${spendtime}&token=${token}`;
  447. let btn = document.createElement('button');
  448. btn.innerHTML = `我要 ${total} 分!`;
  449. btn.addEventListener('click', async () => {
  450. await httpGetAsync(url);
  451. window.location = window.location;
  452. });
  453. let a = document.createElement('a');
  454. a.append(btn); // a.href=url; a.target = "_BLANK";
  455. let div = document.createElement('div');
  456. div.append(a);
  457. e.parentNode.append(a);
  458. });
  459. });
  460.  
  461. return true;
  462. };
  463.  
  464. LearningReport();
  465. ForceSubmitScoreButtons();