SWUST 抢课+

自动抢课

当前为 2020-12-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          SWUST 抢课+
// @author	      lengthmin & Paranoid_AF
// @namespace     dean.swust.Qiangke
// @version  	  1.0
// @description   自动抢课
// @require https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js
// @include https://matrix.dean.swust.edu.cn/acadmicManager/index.cfm?event=chooseCourse:*
// @grant   GM_getValue
// @grant   GM_setValue
// @grant   GM_addValueChangeListener
// ==/UserScript==

function parseSearch() {
  var parms = {};
  //把location.search解析成对象parms
  var query = location.search.substring(1).split("&");
  for (var i = 0; i < query.length; i++) {
    var pos = query[i].split("=");
    if (pos[0]) {
      parms[pos[0]] = pos[1] ? pos[1] : "";
    }
  }
  return parms;
}

var storageKey = "SWUST.Paranoid_AF.CourseList";
var logKey = "SWUST.Paranoid_AF.ShowLog";
var scriptName = "SWUST抢课+";

var subjectInfo = [];

var currentPage = {
  DEFAULT_EVENT: "开始选课",
  fixupTask: "补修",
  programTask: "计划课程",
  commonTask: "全校通选课",
  retakeTask: "重修",
  sportTask: "体育",
  NON_COURSE_PICK: "非选课",
};

var setting = {
  // 给查看每节课详细信息一点缓冲时间
  wait: 100,
  // 抢课刷新间隔
  timeout: 500,
  // 当前抢了多少次
  count: 0,
};

$(document).ready(function () {
  var params = parseSearch();
  var event = params["event"];
  let page_;
  if (event.startsWith("chooseCourse:")) {
    page_ = event.replace("chooseCourse:", "");
    if (page_ === "chargeByTerm" || page_ === "courseTable") {
      page_ = "NON_COURSE_PICK";
    }
  } else {
    page_ = "NON_COURSE_PICK";
  }

  setting.div = $(
    `<div style=" width: 330px; position: fixed; top: 0; right: 0; z-index: 99999; background-color: rgba(255, 255, 255, 0.6); overflow-x: auto;">
        <span style="font-size: medium;"></span>
        <div style="font-size: medium;">${currentPage[page_]}页面</div>
        ${
          page_ === "DEFAULT_EVENT" || page_ === "NON_COURSE_PICK"
            ? ``
            : `
        <button style="margin-right: 10px; font-weight: 800; color: #0078D7; font-size: 16px;" id="startQK">开始选课</button>
            `
        }
        <button style="margin-right: 10px; cursor: pointer; color: #0078D7;" id="newCourse">
          添加课程
        </button>
        <div style="max-height: 300px; overflow-y: auto;">
          抢课列表:
          <table border="1" style="font-size: 12px;">
            <thead>
              <tr>
                <th style="min-width: 100px;">课程</th>
                <th style="min-width: 60px;">教师</th>
                <th style="min-width: 60px;">时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
            </tbody>
          </table>
        </div>
      </div>
      <div id="logWindow" style="bottom: calc(25px - 40vh);transition: bottom 1s;width: 330px; position: fixed; left: 0; z-index: 99999; background-color: #1f4359; overflow-x: auto; height: 40vh;">
        <div style="font-size: medium; color: #fff;">
          <span style="float:left">输出窗口</span>
          <span id="logSwitch" style="float:right;cursor: pointer; user-select: none;"></span>
          <div id="logContent" style="left: calc((100% - 97%) / 2); bottom: 0; width: 97%; position: absolute; background-color: #fff; height: calc(40vh - 25px); color: #000; overflow-y: scroll; font-size: 12px;">
          </div>
        </div>
      </div>
      `
  ).appendTo("body");
  refreshList();
  refreshLogPos();
  $("#newCourse").click((e) => {
    let result = inputCourse();
    if (result !== null) {
      addCourse(result);
      refreshList();
    }
  });
  $("#logSwitch").click((e) => {
    let logEnabled = GM_getValue(logKey, true);
    GM_setValue(logKey, !logEnabled);
    refreshLogPos();
  });
  $("#startQK").click(() => {
    if (subjectInfo.length == 0) {
      pushLog("没有添加课!", 2);
      return;
    }
    if (setting.loop) {
      clearInterval(setting.loop);
      delete setting.loop;
      $("#startQK")[0].innerText = "继续抢课";
    } else {
      $("#startQK")[0].innerText = "暂停抢课";
      setting.loop = setInterval(findClass, setting.timeout);
    }
  });
  GM_addValueChangeListener(
    storageKey,
    (name, old_value, new_value, remote) => {
      if (new_value !== subjectInfo) {
        refreshList();
      }
    }
  );
  pushLog(`${scriptName} 已初始化完毕`, 3);
});

function pushLog(info, type = 0) {
  let text = "信息";
  switch (type) {
    case 0:
      text = "信息";
      break;
    case 1:
      text = "警告";
      break;
    case 2:
      text = "错误";
      break;
    case 3:
      text = "成功";
      break;
    default:
  }
  let date = new Date();
  $("#logContent").append(
    `<p>[${text}] ${date.toLocaleTimeString("zh", {
      hour12: false,
    })} - ${info}</p>`
  );
  $("#logContent").scrollTop($("#logContent")[0].scrollHeight);
}

function refreshLogPos() {
  let logEnabled = GM_getValue(logKey, true);
  if (logEnabled) {
    $("#logSwitch").html("👇收起&nbsp;");
    $("#logWindow").css("bottom", "0");
  } else {
    $("#logSwitch").html("👆展开&nbsp;");
    $("#logWindow").css("bottom", "calc(25px - 40vh)");
  }
}

function refreshList() {
  let subjectInfo = GM_getValue(storageKey, []);
  setting.div.find("tbody").html(`
      <tr>
      <td colspan="2" style="display: none;"></td>
      </tr>
  `);
  subjectInfo.map((value, index) => {
    setting.div.find("tbody").append(
      `
      <tr index="${index}">
        <td>
        ${value.subjectName}
        </td>
        <td>
        ${value.teacherName}
        </td>
        <td>
        ${value.time}
        </td>
        <td>
          <button style="margin-top: -2px; color: #0078D7; font-size: 11.5px; cursor: pointer;" class="editCourse">编辑</button>
          <button style="margin-top: -2px; color: #0078D7; font-size: 11.5px;cursor: pointer;" class="delCourse">移除</button>
        </td>
      </tr>`
    );
  });

  $(".delCourse").on("click", (e) => {
    let targetIndex = Number(
      e.currentTarget.parentElement.parentElement.attributes.index.value
    );
    if (confirm("确认删除吗?")) {
      delCourse(targetIndex);
    }
    refreshList();
  });

  $(".editCourse").on("click", (e) => {
    let targetIndex = Number(
      e.currentTarget.parentElement.parentElement.attributes.index.value
    );
    let result = inputCourse({
      course: subjectInfo[targetIndex].subjectName,
      teacher: subjectInfo[targetIndex].teacherName,
      time: subjectInfo[targetIndex].time,
    });
    if (result !== null) {
      editCourse(targetIndex, result);
      refreshList();
    }
  });
}

function addCourse(info) {
  subjectInfo.push({
    subjectName: info.course, // 要抢的科目名字
    teacherName: info.teacher, // 要抢的科目教师
    time: info.time,
  });
  GM_setValue(storageKey, subjectInfo);
}

function editCourse(index, info) {
  subjectInfo[index] = {
    subjectName: info.course, // 要抢的科目名字
    teacherName: info.teacher, // 要抢的科目教师
    time: info.time,
  };
  GM_setValue(storageKey, subjectInfo);
}

function delCourse(index) {
  subjectInfo.splice(index, 1);
  GM_setValue(storageKey, subjectInfo);
}

function inputCourse(defaultInfo = { course: "", teacher: ".*", time: ".*" }) {
  let result = {};
  let course = prompt(
    "输入课程名称(支持正则表达式)\n可以少写,但不要写错",
    defaultInfo.course
  );
  if (course !== null) {
    if (course === "") {
      alert("课程名称不能为空!");
      return null;
    } else {
      result.course = course;
    }
  } else {
    return null;
  }
  let teacher = prompt(
    "输入教师名称(支持正则表达式)\n.* 为任意。\n可以少写,但不要写错",
    defaultInfo.teacher
  );
  if (teacher !== null) {
    if (teacher === "") {
      alert("教师名称不能为空!");
      return null;
    } else {
      result.teacher = teacher;
    }
  } else {
    return null;
  }
  let time = prompt(
    `输入匹配的时间(支持正则表达式)\n如 ^((?!第一讲).)+$ 就是不匹配带有第一讲的\n可以少写,但不要写错`,
    defaultInfo.time
  );
  if (time !== null) {
    if (time === "") {
      alert("时间不能为空!");
      return null;
    } else {
      result.time = time;
    }
  } else {
    return null;
  }
  return result;
}

function findClass() {
  for (let k = 0; k < subjectInfo.length; k++) {
    // 从选课首页点进教师详情那一页
    $("div.courseShow.clearfix span.name").each(function () {
      let subjectNameRegex = new RegExp(subjectInfo[k].subjectName, "iu");
      let currsubjectName = $(this).text();
      if (currsubjectName.match(subjectNameRegex)) {
        pushLog("已找到" + subjectInfo[k].subjectName, 0);
        // 进入选课详情
        $(this).parent().find("a.trigger[cid]")[0].click();
      }
    });
    var flag = false;
    var empty_flag = true;
    // 遍历详情页
    setTimeout(() => {
      pushLog(
        "对“" +
          subjectInfo[k].subjectName +
          "”开始查询,第" +
          setting.count +
          "次",
        0
      );
      setting.count++;
      // 获取当前课的所有可选课程
      for (var i = 0; i < $("a.stat.off").length; i++) {
        empty_flag = false;
        var $this = $("a.stat.off").eq(i);
        var $parents = $this.parents("tr.editRows");
        // 获取可选课程的老师上课时间地点等信息
        for (var j = 0; j < $parents.length; j++) {
          setting.time = $parents.eq(j).children().eq(8).text();
          pushLog("上课时间:" + setting.time, 0);
          setting.location = $parents.eq(j).children().eq(9).text();
          pushLog("上课地点:" + setting.location, 0);
          setting.teacher = $parents.eq(j).children().eq(2).text();
          pushLog("上课老师:" + setting.teacher, 0);
          let teacherNameRegex = new RegExp(subjectInfo[k].teacherName, "iu");
          let timeRegex = new RegExp(subjectInfo[k].time, "iu");

          if (
            setting.teacher.match(teacherNameRegex) &&
            setting.time.match(timeRegex)
          ) {
            // 选课
            eval($this.attr("href"));
            pushLog("完成对课程“" + subjectInfo[k].subjectName + "”的选课", 3);
            delCourse(k);
            if (subjectInfo.length === 0) {
              clearInterval(setting.loop);
              delete setting.loop;
              $("#startQK")[0].innerText = "开始抢课";
            }
          } else {
            pushLog("目标课暂无满足要求的席位,重试中", 0);
            // 返回课程列表
            $("a.trigger.open")[0].click();
          }
        }
      }
      if (empty_flag) {
        pushLog("课程“" + subjectInfo[k].subjectName + "”暂无空课,重试中", 0);
      }
    }, setting.wait);
    // 没找到就返回刷新
  }
}