Greasy Fork is available in English.

tame QTKJ

at the end of with it.

נכון ליום 10-02-2020. ראה הגרסה האחרונה.

// ==UserScript==
// @name          tame QTKJ
// @namespace     Vionlentmonkey
// @version       3.6.0
// @description   at the end of with it.
// @author        someone
// @icon          https://www.skynj.com/theme/theme_443/images/sinosoft.ico

// @match         http://218-94-1-181.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*
// @match         http://218-94-1-179.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*
// @match         http://218-94-1-175.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*

// @match         http://218.94.1.181:8087/sfxzwsxy/*
// @match         http://218.94.1.179:8087/sfxzwsxy/*
// @match         http://218.94.1.175:8087/sfxzwsxy/*

// @match         http://218.94.1.181:5088/unzipapp/project/ware/attach/*

// @require       https://openuserjs.org/src/libs/sizzle/GM_config.js

// @require       https://greasyfork.org/scripts/381401-addstyle/code/addStyle.js
// @require       https://greasyfork.org/scripts/395959-binary2text/code/binary2Text.js
// @require       https://greasyfork.org/scripts/381403-fakenavigators/code/fakeNavigators.js
// @require       https://greasyfork.org/scripts/395958-htmltoelements/code/htmlToElements.js

// @require       https://greasyfork.org/scripts/395952-wsxy-autologin/code/wsxy_autoLogin.js
// @require       https://greasyfork.org/scripts/395997-wsxy-windowtotab/code/wsxy_windowToTab.js
// @require       https://greasyfork.org/scripts/396054-wsxy-storagedata/code/wsxy_storageData.js
// @require       https://greasyfork.org/scripts/396002-wsxy-autolearn/code/wsxy_autoLearn.js
// @require       https://greasyfork.org/scripts/395994-wsxy-handleplayer/code/wsxy_handlePlayer.js
// @require       https://greasyfork.org/scripts/395966-wsxy-autoExamineTest/code/wsxy_autoExamineTest.js

// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_openInTab
// @grant         GM_notification

// @run-at        document-start

// ==/UserScript==

const windowCSS = `
#Cfg {
  height: auto;
  background-color: lightblue;
}
#Cfg .reset_holder {
  float: left;
  position: relative;
  bottom: -1em;
}
#Cfg .saveclose_buttons {
  margin: 1em;
}
`;

const openCfg = () => {
  // 避免在包含框架的页面打开造成多个设置界面重叠
  if (window.top === window.self) {
    GM_config.open();
  }
};

// https://github.com/sizzlemctwizzle/GM_config/wiki#using-an-element-instead-of-an-iframe
const frame = document.createElement('div');
window.addEventListener('load', () => {
  document.body.appendChild(frame);
});

GM_config.init({
  id: 'Cfg',
  title: '⚙个性化设置⚙',
  frame,
  fields: {
    loginName: {
      section: ['登录', '完整填写尝试自动登录'],
      label: '账号',
      labelPos: 'right',
      type: 'text',
      default: ''
    },
    pwd: {
      label: '密码',
      labelPos: 'right',
      type: 'password',
      default: ''
    },
    unlimited: {
      label: '无限尝试',
      labelPos: 'right',
      type: 'checkbox',
      default: false
    },
    batch: {
      section: ['操作', '如不计时请自行调低参数'],
      label: '后台批量打开新标签页个数。疑似大于 6 无效。',
      labelPos: 'right',
      type: 'int',
      default: 3
    },
    muted: {
      label: '静音播放',
      labelPos: 'right',
      type: 'checkbox',
      default: true
    }
  },
  css: windowCSS,
  events: {
    save: () => {
      GM_config.close();
      // 服务器选择页面或登录页面自动刷新
      if (
        location.pathname.match(/\/sfxzwsxy\/?(serverSelect.jsp)?#?$/i) ||
        location.pathname.includes('index.jsp')
      ) {
        location.reload(true);
      }
    }
  }
});
/**
 * 创建设置按钮
 * */
const addSettingButton = parent_node => {
  setting_button = document.createElement('button');
  setting_button.id = 'setting';
  setting_button.textContent = '⚙个性化设置⚙';
  setting_button.onclick = openCfg;
  parent_node.appendChild(setting_button);
};

/**
 * 创建批量打开按钮
 * */
const addOpenButton = parent_node => {
  open_button = document.createElement('button');
  open_button.id = 'openTabs';
  open_button.textContent = `批量打开${GM_config.get('batch')}个课程📖`;
  open_button.onclick = openTrains;
  parent_node.appendChild(open_button);
};

// 彩蛋:通关密语
const passPhrase = btoa(GM_config.get('pwd')).startsWith('TzUxbzE5');

const body = document.body || document.documentElement;

// 统一清理网址
uniformURLs();

// http://218.94.1.175:8087/sfxzwsxy/#
if (location.pathname.match(/\/sfxzwsxy\/?(serverSelect.jsp)?#?$/i)) {
  // 自动选择最空闲的服务器
  if (GM_config.get('loginName') && GM_config.get('pwd')) {
    setInterval(autoServerSelect, 1000);
  } else {
    // 若更早载入可能导致设置界面偏在右下角
    window.addEventListener('load', () => {
      addSettingButton(document.getElementsByClassName('title')[0]);
      // 因必要信息不全,自动打开设置界面
      openCfg();
    });
  }
}

// 登录页面
if (location.pathname.includes('index.jsp')) {
  document.addEventListener('DOMContentLoaded', () => {
    // 尝试自动登录
    autoLogin();
  });

  // 进不去就刷新重来。GM_notification 的消息驻留事件为 15 秒。
  window.addEventListener('load', () => {
    setTimeout(() => {
      location.assign(`${location.origin}/sfxzwsxy/serverSelect.jsp`);
    }, 15500);
  });
}

// 暴力覆盖登录时触发的 alert 函数
if (location.pathname.includes('login.jsp') || location.pathname.includes('index.jsp')) {
  unsafeWindow.alert = message => {
    //console.log(message);
    if (message === '密码错误,请重新输入!') {
      GM_notification(
        `${message}\n15 秒后重设。`,
        '❌',
        'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
      );
      GM_config.set('pwd', '');
      GM_config.save();
    } else if (message === '该用户名不存在或已被删除,请重新输入!') {
      // 可能是用户名输入错误;可能是尚未输入用户名就触发登录事件;可能是服务器过于拥堵。
      GM_notification(
        `${message}\n该提示有多种可能:\n若服务器空闲则大概率为用户名设置错误;\n若服务器拥堵则很可能为系统问题。\n点击🖱本消息清除已设置的用户名。\n否则将 15 秒一次循环♻重试。`,
        'Alert',
        'https://www.skynj.com/theme/theme_443/images/sinosoft.ico',
        () => {
          if (GM_config.get('loginName')) {
            GM_config.set('loginName', '');
            GM_config.save();
          }
        }
      );
    } else {
      GM_notification(
        message,
        'Alert',
        'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
      );
    }
  };
}

// 主页
if (location.pathname.includes('index.html')) {
  // 修复页面过长无法完整显示的 Bug; 隐藏 密码修改提示 和 每日一题
  const css = `
  html[style="overflow: hidden;"] {
    overflow: visible !important;
  }
  #layui-layer1, #layui-layer-shade1, #layui-layer2, #layui-layer-shade2 {
    display: none !important;
  }
  `;
  addStyle(css);

  // 判断是否已经完成并处理
  const isFinish = () => {
    // 主页直接获取页面值会快不少,避免与 iframe 同时抓取存储远程数据造成双重阻塞
    // 规定需达到的总学时
    const total_hour = Number(document.getElementById('totalHour').textContent);
    // 规定需达到的必修学时
    const required_hour = Number(document.getElementById('requiredHour').textContent);
    // 规定需达到的总学分
    const required_credit = Number(document.getElementById('requiredCredit').textContent);
    // 用户已获得的总学时
    const user_total_hour = Number(document.getElementById('userTotalHour').textContent);
    // 用户已获得的必修学时
    const user_required_hour = Number(document.getElementById('userRequiredHour').textContent);
    // 用户已获得的总学分
    const user_required_credit = Number(document.getElementById('userRequiredCredit').textContent);
    if (
      user_total_hour < total_hour ||
      user_required_hour < required_hour ||
      user_required_credit < required_credit
    ) {
      console.log(`本学年任务尚未完成:`);
      if (passPhrase) {
        // 打开 iframe
        GM_openInTab(document.getElementById('mainFrame').src, true);
      } else {
        // 长时间不动会被弹出,故 30 分钟刷新一次
        setInterval(() => {
          location.reload(true);
        }, 1800000);
      }
      if (user_total_hour < total_hour) {
        console.log(`总学时差:${(total_hour - user_total_hour).toFixed(1)}`);
      }
      if (user_required_hour < required_hour) {
        console.log(`必修学时差:${(required_hour - user_required_hour).toFixed(1)}`);
      }
      if (user_required_credit < required_credit) {
        console.log(`总学分差:${required_credit - user_required_credit}`);
      }
    } else {
      console.log(`本学年任务已经完成`);
    }
  };

  window.addEventListener('DOMContentLoaded', () => {
    // 添加在顶部⚙齿轮下用户名后。取代 GM_registerMenuCommand 需要在登录前也有设置区域。
    addSettingButton(document.getElementsByClassName('selexit-li1')[0]);
    isFinish();
  });

  // 自动聚焦以便快捷键默认生效
  const document_observer = new MutationObserver(() => {
    if (document.getElementById('mainFrame')) {
      document.getElementById('mainFrame').focus();
      console.log('focus mainFrame');
    }
  });
  document_observer.observe(body, {
    childList: true,
    subtree: true
  });
}

// 首页培训课程 iframe
if (location.pathname.includes('homepage.jsp')) {
  window.addEventListener('load', async () => {
    // 第一页 #courseExam1,第二页 #courseExam2,依此类推。
    const exams = document.querySelectorAll('div[id^="courseExam"] > a[title]');
    // 先将本地执行的非阻塞函数并发启动
    Promise.all([
      // 插入批量打开按钮
      addOpenButton(document.getElementsByClassName('calschous')[0]),
      // 新标签页打开考试
      recoverExamList(exams),
      // 新标签页打开题库
      openKnowledge()
    ]);
    // 每次载入均阻塞获取数据。
    await Promise.all([
      await storageUserData(),
      await storageCourseData(),
      await storageCourseInfo()
    ]);
    // 获取待考试数据后才能添加答案链接。
    const exam_courses = JSON.parse(localStorage.getItem('exam_courses'));
    const waitCourseInfo = JSON.parse(localStorage.getItem('waitCourseInfo'));
    addAnswer4ExamList(exams, exam_courses);
    GM_notification(
      '准备就绪✅',
      'Fetch & Storage',
      'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
    );
    passPhrase ? autoLearn(exams, waitCourseInfo) : autoSignupMaxCredit(waitCourseInfo);
  });
}

// 培训课程查询 iframe
if (location.pathname.includes('course_query.jsp')) {
  // 保证翻页生效
  const document_observer = new MutationObserver(() => {
    inquireList();
  });
  document_observer.observe(body, {
    childList: true,
    subtree: true
  });
  // 插入批量打开课程按钮
  window.addEventListener('load', addOpenButton(document.getElementsByClassName('px-tits')[0]));
}

// 培训课程查询 - 查看 - 题干 iframe
if (
  // 点击 “下一页” 或 “上一页” 后 iframe 实际地址会去除 .jsp 之后的尾巴
  location.pathname.includes('subject_list.jsp')
) {
  // 清理“题干”链接
  document.addEventListener('DOMContentLoaded', () => {
    viewSubject();
  });
}

// 课程视频播放 跨域 iframe
if (location.href.startsWith('http://218.94.1.181:5088/unzipapp/project/ware/attach/')) {
  // 旧播放器在 Windows 下要求 Flash,新播放器不兼容苹果系列。
  fakeUA('Linux');
  setInterval(killFlash, 1000);
  const document_observer = new MutationObserver(() => {
    autoBegin();
    autoQue();
  });
  document_observer.observe(body, {
    attributes: true,
    subtree: true
  });
}

// 在线考试 - 课程考试 iframe
if (location.pathname.includes('course_exam_list.jsp')) {
  // 清理“参加考试”链接,新标签页打开考试及答案。
  document.addEventListener('DOMContentLoaded', () => {
    const exams = document.querySelectorAll('a[href="#"][onclick^=openWindowFullScreen]');
    const exam_courses = JSON.parse(localStorage.getItem('exam_courses'));
    recoverExamList(exams);
    addAnswer4ExamList(exams, exam_courses);
  });
}

// 考试
if (location.pathname.includes('course_examine_test.jsp')) {
  if (passPhrase) {
    document.addEventListener('DOMContentLoaded', () => {
      autoExamineTest();
    });
  }
}