SWUST 抢课+

自动抢课

目前為 2020-12-20 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    // 没找到就返回刷新
  }
}