Repository: ginnnnnncc/GinsMooc
Branch: master
Commit: c2ccb5a03fbe
Files: 70
Total size: 183.7 KB
Directory structure:
gitextract_klorelqm/
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc.json
├── .vscode/
│ └── extensions.json
├── README.md
├── env.d.ts
├── extension/
│ ├── .eslintrc.cjs
│ ├── .prettierrc.json
│ ├── README.md
│ ├── env.d.ts
│ ├── index.html
│ ├── package.json
│ ├── release/
│ │ ├── content-scripts/
│ │ │ └── index-ad710f80.js
│ │ └── manifest.json
│ ├── src/
│ │ ├── main.ts
│ │ ├── newExam.ts
│ │ ├── plugins/
│ │ │ ├── apiAccess.ts
│ │ │ ├── mooc.ts
│ │ │ ├── react.ts
│ │ │ └── tool.ts
│ │ └── type/
│ │ ├── api.ts
│ │ └── mooc.ts
│ ├── tsconfig.config.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── index.html
├── package.json
├── public/
│ ├── background.html
│ ├── css/
│ │ ├── main.css
│ │ └── noscript.css
│ ├── guess.java
│ └── sass/
│ ├── libs/
│ │ ├── _breakpoints.scss
│ │ ├── _functions.scss
│ │ ├── _mixins.scss
│ │ ├── _vars.scss
│ │ └── _vendor.scss
│ ├── main.scss
│ └── noscript.scss
├── src/
│ ├── App.vue
│ ├── components/
│ │ ├── CourseCard.vue
│ │ ├── QuestionCard.vue
│ │ ├── icon/
│ │ │ ├── Extension.vue
│ │ │ ├── Github.vue
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── question/
│ │ ├── Completion.vue
│ │ ├── Homework.vue
│ │ ├── MultipleChoice.vue
│ │ ├── OnlineJudge.vue
│ │ ├── SingleChoice.vue
│ │ └── index.ts
│ ├── main.ts
│ ├── plugins/
│ │ ├── apiAccess.ts
│ │ └── tool.ts
│ ├── router/
│ │ └── index.ts
│ ├── type/
│ │ ├── api.ts
│ │ ├── globleProperties.ts
│ │ └── mooc.ts
│ └── views/
│ ├── BlogView.vue
│ ├── HomeView.vue
│ ├── MoocAside.vue
│ ├── MoocCourseDetail.vue
│ ├── MoocHeader.vue
│ ├── MoocTest.vue
│ ├── MoocView.vue
│ ├── VideoView.vue
│ └── index.ts
├── tsconfig.config.json
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.cjs
================================================
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier"
],
parserOptions: {
ecmaVersion: "latest"
},
rules: {
"no-unused-vars": ["off", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
}
}
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
**/.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
**/*.pem
================================================
FILE: .prettierrc.json
================================================
{
"tabWidth": 4,
"useTabs": false,
"semi": false,
"trailingComma": "none",
"bracketSameLine": false,
"printWidth": 120
}
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
================================================
FILE: README.md
================================================
# 不支持 spoc
# 反馈所需信息
反馈课程更新问题,需要提新 issue,带上课程 id、课程链接和测验截止时间,我会在截止时间前修,上班太忙了
# 插件简介
实现对于中国大学MOOC的
- 非在线测评题的自动答案查询,包括单选题、多选题、判断题、填空题、简答题,支持测验与作业及考试
- 互评阶段的自动评分、自动点评
下载地址:[Github release v2.2.1](https://github.com/ginnnnnn666/GinsMooc/releases/tag/v2.2.1)
在线使用:[GinsMooc](https://ginnnnnn.top/mooc/)
# 功能介绍
在测试的准备页面,将会自动检查是否准备就绪,若为否将自动更新课程

进入测验后,将显示“获取答案”按钮,点击即可




作业的互评阶段支持自动评分、自动点评


# 安装介绍
下载安装包后,将其解压至文件夹内
在浏览器地址栏中输入`edge://extensions`(谷歌浏览器为`chrome://extensions`)
打开开发者模式

点击“加载解压缩的扩展”,选择刚刚解压到的文件夹,即可开始使用

================================================
FILE: env.d.ts
================================================
///
================================================
FILE: extension/.eslintrc.cjs
================================================
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
================================================
FILE: extension/.prettierrc.json
================================================
{
"tabWidth": 4,
"useTabs": false,
"semi": false,
"trailingComma": "none",
"bracketSameLine": false,
"printWidth": 120
}
================================================
FILE: extension/README.md
================================================
# GinsMooc Extention
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```
================================================
FILE: extension/env.d.ts
================================================
///
================================================
FILE: extension/index.html
================================================
Vite App
================================================
FILE: extension/package.json
================================================
{
"name": "ginsmooc-extention",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"axios": "^1.3.2",
"element-plus": "^2.2.29",
"jssha": "^3.3.0",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@types/chrome": "^0.0.212",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}
}
================================================
FILE: extension/release/content-scripts/index-ad710f80.js
================================================
var lt=Object.defineProperty;var ut=(e,t,n)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var k=(e,t,n)=>(ut(e,typeof t!="symbol"?t+"":t,n),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();const Fe=async e=>new Promise(t=>{setTimeout(()=>{t("")},e)}),ge=async e=>new Promise(t=>{const n=setInterval(()=>{console.log("check wait for"),e()&&(clearInterval(n),t(""))},50)}),W=e=>{const t=new RegExp("(^|&)"+e+"=([^&]*)(&|$)"),n=window.location.search.substring(1).match(t)||window.location.hash.substring(window.location.hash.search(/\?/)+1).match(t);return n?decodeURIComponent(n[2]):null},_e=e=>{const t="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";let n="";for(let r=0;rt=>{const n=pt.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),T=e=>(e=e.toLowerCase(),t=>ee(t)===e),te=e=>t=>typeof t===e,{isArray:q}=Array,$=te("undefined");function ht(e){return e!==null&&!$(e)&&e.constructor!==null&&!$(e.constructor)&&S(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const De=T("ArrayBuffer");function mt(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&De(e.buffer),t}const yt=te("string"),S=te("function"),Ue=te("number"),ne=e=>e!==null&&typeof e=="object",wt=e=>e===!0||e===!1,V=e=>{if(ee(e)!=="object")return!1;const t=he(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},Et=T("Date"),gt=T("File"),bt=T("Blob"),St=T("FileList"),xt=e=>ne(e)&&S(e.pipe),Ot=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||S(e.append)&&((t=ee(e))==="formdata"||t==="object"&&S(e.toString)&&e.toString()==="[object FormData]"))},Tt=T("URLSearchParams"),At=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function z(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;let r,s;if(typeof e!="object"&&(e=[e]),q(e))for(r=0,s=e.length;r0;)if(s=n[r],t===s.toLowerCase())return s;return null}const Ie=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),ve=e=>!$(e)&&e!==Ie;function le(){const{caseless:e}=ve(this)&&this||{},t={},n=(r,s)=>{const o=e&&qe(t,s)||s;V(t[o])&&V(r)?t[o]=le(t[o],r):V(r)?t[o]=le({},r):q(r)?t[o]=r.slice():t[o]=r};for(let r=0,s=arguments.length;r(z(t,(s,o)=>{n&&S(s)?e[o]=je(s,n):e[o]=s},{allOwnKeys:r}),e),Nt=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),Ct=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},Lt=(e,t,n,r)=>{let s,o,i;const c={};if(t=t||{},e==null)return t;do{for(s=Object.getOwnPropertyNames(e),o=s.length;o-- >0;)i=s[o],(!r||r(i,e,t))&&!c[i]&&(t[i]=e[i],c[i]=!0);e=n!==!1&&he(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Pt=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;const r=e.indexOf(t,n);return r!==-1&&r===n},kt=e=>{if(!e)return null;if(q(e))return e;let t=e.length;if(!Ue(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},Bt=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&he(Uint8Array)),Ft=(e,t)=>{const r=(e&&e[Symbol.iterator]).call(e);let s;for(;(s=r.next())&&!s.done;){const o=s.value;t.call(e,o[0],o[1])}},_t=(e,t)=>{let n;const r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},jt=T("HTMLFormElement"),Dt=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,s){return r.toUpperCase()+s}),be=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),Ut=T("RegExp"),He=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),r={};z(n,(s,o)=>{let i;(i=t(s,o,e))!==!1&&(r[o]=i||s)}),Object.defineProperties(e,r)},qt=e=>{He(e,(t,n)=>{if(S(e)&&["arguments","caller","callee"].indexOf(n)!==-1)return!1;const r=e[n];if(S(r)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},It=(e,t)=>{const n={},r=s=>{s.forEach(o=>{n[o]=!0})};return q(e)?r(e):r(String(e).split(t)),n},vt=()=>{},Ht=(e,t)=>(e=+e,Number.isFinite(e)?e:t),oe="abcdefghijklmnopqrstuvwxyz",Se="0123456789",Me={DIGIT:Se,ALPHA:oe,ALPHA_DIGIT:oe+oe.toUpperCase()+Se},Mt=(e=16,t=Me.ALPHA_DIGIT)=>{let n="";const{length:r}=t;for(;e--;)n+=t[Math.random()*r|0];return n};function $t(e){return!!(e&&S(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const zt=e=>{const t=new Array(10),n=(r,s)=>{if(ne(r)){if(t.indexOf(r)>=0)return;if(!("toJSON"in r)){t[s]=r;const o=q(r)?[]:{};return z(r,(i,c)=>{const d=n(i,s+1);!$(d)&&(o[c]=d)}),t[s]=void 0,o}}return r};return n(e,0)},Jt=T("AsyncFunction"),Vt=e=>e&&(ne(e)||S(e))&&S(e.then)&&S(e.catch),a={isArray:q,isArrayBuffer:De,isBuffer:ht,isFormData:Ot,isArrayBufferView:mt,isString:yt,isNumber:Ue,isBoolean:wt,isObject:ne,isPlainObject:V,isUndefined:$,isDate:Et,isFile:gt,isBlob:bt,isRegExp:Ut,isFunction:S,isStream:xt,isURLSearchParams:Tt,isTypedArray:Bt,isFileList:St,forEach:z,merge:le,extend:Rt,trim:At,stripBOM:Nt,inherits:Ct,toFlatObject:Lt,kindOf:ee,kindOfTest:T,endsWith:Pt,toArray:kt,forEachEntry:Ft,matchAll:_t,isHTMLForm:jt,hasOwnProperty:be,hasOwnProp:be,reduceDescriptors:He,freezeMethods:qt,toObjectSet:It,toCamelCase:Dt,noop:vt,toFiniteNumber:Ht,findKey:qe,global:Ie,isContextDefined:ve,ALPHABET:Me,generateString:Mt,isSpecCompliantForm:$t,toJSONObject:zt,isAsyncFn:Jt,isThenable:Vt};function m(e,t,n,r,s){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),s&&(this.response=s)}a.inherits(m,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:a.toJSONObject(this.config),code:this.code,status:this.response&&this.response.status?this.response.status:null}}});const $e=m.prototype,ze={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{ze[e]={value:e}});Object.defineProperties(m,ze);Object.defineProperty($e,"isAxiosError",{value:!0});m.from=(e,t,n,r,s,o)=>{const i=Object.create($e);return a.toFlatObject(e,i,function(d){return d!==Error.prototype},c=>c!=="isAxiosError"),m.call(i,e.message,t,n,r,s),i.cause=e,i.name=e.name,o&&Object.assign(i,o),i};const Gt=null;function ue(e){return a.isPlainObject(e)||a.isArray(e)}function Je(e){return a.endsWith(e,"[]")?e.slice(0,-2):e}function xe(e,t,n){return e?e.concat(t).map(function(s,o){return s=Je(s),!n&&o?"["+s+"]":s}).join(n?".":""):t}function Kt(e){return a.isArray(e)&&!e.some(ue)}const Wt=a.toFlatObject(a,{},null,function(t){return/^is[A-Z]/.test(t)});function re(e,t,n){if(!a.isObject(e))throw new TypeError("target must be an object");t=t||new FormData,n=a.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(h,E){return!a.isUndefined(E[h])});const r=n.metaTokens,s=n.visitor||u,o=n.dots,i=n.indexes,d=(n.Blob||typeof Blob<"u"&&Blob)&&a.isSpecCompliantForm(t);if(!a.isFunction(s))throw new TypeError("visitor must be a function");function f(p){if(p===null)return"";if(a.isDate(p))return p.toISOString();if(!d&&a.isBlob(p))throw new m("Blob is not supported. Use a Buffer instead.");return a.isArrayBuffer(p)||a.isTypedArray(p)?d&&typeof Blob=="function"?new Blob([p]):Buffer.from(p):p}function u(p,h,E){let g=p;if(p&&!E&&typeof p=="object"){if(a.endsWith(h,"{}"))h=r?h:h.slice(0,-2),p=JSON.stringify(p);else if(a.isArray(p)&&Kt(p)||(a.isFileList(p)||a.endsWith(h,"[]"))&&(g=a.toArray(p)))return h=Je(h),g.forEach(function(L,ct){!(a.isUndefined(L)||L===null)&&t.append(i===!0?xe([h],ct,o):i===null?h:h+"[]",f(L))}),!1}return ue(p)?!0:(t.append(xe(E,h,o),f(p)),!1)}const l=[],w=Object.assign(Wt,{defaultVisitor:u,convertValue:f,isVisitable:ue});function b(p,h){if(!a.isUndefined(p)){if(l.indexOf(p)!==-1)throw Error("Circular reference detected in "+h.join("."));l.push(p),a.forEach(p,function(g,C){(!(a.isUndefined(g)||g===null)&&s.call(t,g,a.isString(C)?C.trim():C,h,w))===!0&&b(g,h?h.concat(C):[C])}),l.pop()}}if(!a.isObject(e))throw new TypeError("data must be an object");return b(e),t}function Oe(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(r){return t[r]})}function me(e,t){this._pairs=[],e&&re(e,this,t)}const Ve=me.prototype;Ve.append=function(t,n){this._pairs.push([t,n])};Ve.toString=function(t){const n=t?function(r){return t.call(this,r,Oe)}:Oe;return this._pairs.map(function(s){return n(s[0])+"="+n(s[1])},"").join("&")};function Qt(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Ge(e,t,n){if(!t)return e;const r=n&&n.encode||Qt,s=n&&n.serialize;let o;if(s?o=s(t,n):o=a.isURLSearchParams(t)?t.toString():new me(t,n).toString(r),o){const i=e.indexOf("#");i!==-1&&(e=e.slice(0,i)),e+=(e.indexOf("?")===-1?"?":"&")+o}return e}class Xt{constructor(){this.handlers=[]}use(t,n,r){return this.handlers.push({fulfilled:t,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){a.forEach(this.handlers,function(r){r!==null&&t(r)})}}const Te=Xt,Ke={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Yt=typeof URLSearchParams<"u"?URLSearchParams:me,Zt=typeof FormData<"u"?FormData:null,en=typeof Blob<"u"?Blob:null,tn={isBrowser:!0,classes:{URLSearchParams:Yt,FormData:Zt,Blob:en},protocols:["http","https","file","blob","url","data"]},We=typeof window<"u"&&typeof document<"u",nn=(e=>We&&["ReactNative","NativeScript","NS"].indexOf(e)<0)(typeof navigator<"u"&&navigator.product),rn=(()=>typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function")(),sn=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:We,hasStandardBrowserEnv:nn,hasStandardBrowserWebWorkerEnv:rn},Symbol.toStringTag,{value:"Module"})),x={...sn,...tn};function on(e,t){return re(e,new x.classes.URLSearchParams,Object.assign({visitor:function(n,r,s,o){return x.isNode&&a.isBuffer(n)?(this.append(r,n.toString("base64")),!1):o.defaultVisitor.apply(this,arguments)}},t))}function an(e){return a.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function cn(e){const t={},n=Object.keys(e);let r;const s=n.length;let o;for(r=0;r=n.length;return i=!i&&a.isArray(s)?s.length:i,d?(a.hasOwnProp(s,i)?s[i]=[s[i],r]:s[i]=r,!c):((!s[i]||!a.isObject(s[i]))&&(s[i]=[]),t(n,r,s[i],o)&&a.isArray(s[i])&&(s[i]=cn(s[i])),!c)}if(a.isFormData(e)&&a.isFunction(e.entries)){const n={};return a.forEachEntry(e,(r,s)=>{t(an(r),s,n,0)}),n}return null}function ln(e,t,n){if(a.isString(e))try{return(t||JSON.parse)(e),a.trim(e)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(e)}const ye={transitional:Ke,adapter:["xhr","http"],transformRequest:[function(t,n){const r=n.getContentType()||"",s=r.indexOf("application/json")>-1,o=a.isObject(t);if(o&&a.isHTMLForm(t)&&(t=new FormData(t)),a.isFormData(t))return s?JSON.stringify(Qe(t)):t;if(a.isArrayBuffer(t)||a.isBuffer(t)||a.isStream(t)||a.isFile(t)||a.isBlob(t))return t;if(a.isArrayBufferView(t))return t.buffer;if(a.isURLSearchParams(t))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),t.toString();let c;if(o){if(r.indexOf("application/x-www-form-urlencoded")>-1)return on(t,this.formSerializer).toString();if((c=a.isFileList(t))||r.indexOf("multipart/form-data")>-1){const d=this.env&&this.env.FormData;return re(c?{"files[]":t}:t,d&&new d,this.formSerializer)}}return o||s?(n.setContentType("application/json",!1),ln(t)):t}],transformResponse:[function(t){const n=this.transitional||ye.transitional,r=n&&n.forcedJSONParsing,s=this.responseType==="json";if(t&&a.isString(t)&&(r&&!this.responseType||s)){const i=!(n&&n.silentJSONParsing)&&s;try{return JSON.parse(t)}catch(c){if(i)throw c.name==="SyntaxError"?m.from(c,m.ERR_BAD_RESPONSE,this,null,this.response):c}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:x.classes.FormData,Blob:x.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};a.forEach(["delete","get","head","post","put","patch"],e=>{ye.headers[e]={}});const we=ye,un=a.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),dn=e=>{const t={};let n,r,s;return e&&e.split(`
`).forEach(function(i){s=i.indexOf(":"),n=i.substring(0,s).trim().toLowerCase(),r=i.substring(s+1).trim(),!(!n||t[n]&&un[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+", "+r:r)}),t},Ae=Symbol("internals");function H(e){return e&&String(e).trim().toLowerCase()}function G(e){return e===!1||e==null?e:a.isArray(e)?e.map(G):String(e)}function fn(e){const t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const pn=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function ie(e,t,n,r,s){if(a.isFunction(r))return r.call(this,t,n);if(s&&(t=n),!!a.isString(t)){if(a.isString(r))return t.indexOf(r)!==-1;if(a.isRegExp(r))return r.test(t)}}function hn(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,n,r)=>n.toUpperCase()+r)}function mn(e,t){const n=a.toCamelCase(" "+t);["get","set","has"].forEach(r=>{Object.defineProperty(e,r+n,{value:function(s,o,i){return this[r].call(this,t,s,o,i)},configurable:!0})})}let se=class{constructor(t){t&&this.set(t)}set(t,n,r){const s=this;function o(c,d,f){const u=H(d);if(!u)throw new Error("header name must be a non-empty string");const l=a.findKey(s,u);(!l||s[l]===void 0||f===!0||f===void 0&&s[l]!==!1)&&(s[l||d]=G(c))}const i=(c,d)=>a.forEach(c,(f,u)=>o(f,u,d));return a.isPlainObject(t)||t instanceof this.constructor?i(t,n):a.isString(t)&&(t=t.trim())&&!pn(t)?i(dn(t),n):t!=null&&o(n,t,r),this}get(t,n){if(t=H(t),t){const r=a.findKey(this,t);if(r){const s=this[r];if(!n)return s;if(n===!0)return fn(s);if(a.isFunction(n))return n.call(this,s,r);if(a.isRegExp(n))return n.exec(s);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,n){if(t=H(t),t){const r=a.findKey(this,t);return!!(r&&this[r]!==void 0&&(!n||ie(this,this[r],r,n)))}return!1}delete(t,n){const r=this;let s=!1;function o(i){if(i=H(i),i){const c=a.findKey(r,i);c&&(!n||ie(r,r[c],c,n))&&(delete r[c],s=!0)}}return a.isArray(t)?t.forEach(o):o(t),s}clear(t){const n=Object.keys(this);let r=n.length,s=!1;for(;r--;){const o=n[r];(!t||ie(this,this[o],o,t,!0))&&(delete this[o],s=!0)}return s}normalize(t){const n=this,r={};return a.forEach(this,(s,o)=>{const i=a.findKey(r,o);if(i){n[i]=G(s),delete n[o];return}const c=t?hn(o):String(o).trim();c!==o&&delete n[o],n[c]=G(s),r[c]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const n=Object.create(null);return a.forEach(this,(r,s)=>{r!=null&&r!==!1&&(n[s]=t&&a.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+": "+n).join(`
`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const r=new this(t);return n.forEach(s=>r.set(s)),r}static accessor(t){const r=(this[Ae]=this[Ae]={accessors:{}}).accessors,s=this.prototype;function o(i){const c=H(i);r[c]||(mn(s,i),r[c]=!0)}return a.isArray(t)?t.forEach(o):o(t),this}};se.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);a.reduceDescriptors(se.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[n]=r}}});a.freezeMethods(se);const R=se;function ae(e,t){const n=this||we,r=t||n,s=R.from(r.headers);let o=r.data;return a.forEach(e,function(c){o=c.call(n,o,s.normalize(),t?t.status:void 0)}),s.normalize(),o}function Xe(e){return!!(e&&e.__CANCEL__)}function J(e,t,n){m.call(this,e??"canceled",m.ERR_CANCELED,t,n),this.name="CanceledError"}a.inherits(J,m,{__CANCEL__:!0});function yn(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new m("Request failed with status code "+n.status,[m.ERR_BAD_REQUEST,m.ERR_BAD_RESPONSE][Math.floor(n.status/100)-4],n.config,n.request,n))}const wn=x.hasStandardBrowserEnv?{write(e,t,n,r,s,o){const i=[e+"="+encodeURIComponent(t)];a.isNumber(n)&&i.push("expires="+new Date(n).toGMTString()),a.isString(r)&&i.push("path="+r),a.isString(s)&&i.push("domain="+s),o===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function En(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function gn(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function Ye(e,t){return e&&!En(t)?gn(e,t):t}const bn=x.hasStandardBrowserEnv?function(){const t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");let r;function s(o){let i=o;return t&&(n.setAttribute("href",i),i=n.href),n.setAttribute("href",i),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:n.pathname.charAt(0)==="/"?n.pathname:"/"+n.pathname}}return r=s(window.location.href),function(i){const c=a.isString(i)?s(i):i;return c.protocol===r.protocol&&c.host===r.host}}():function(){return function(){return!0}}();function Sn(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function xn(e,t){e=e||10;const n=new Array(e),r=new Array(e);let s=0,o=0,i;return t=t!==void 0?t:1e3,function(d){const f=Date.now(),u=r[o];i||(i=f),n[s]=d,r[s]=f;let l=o,w=0;for(;l!==s;)w+=n[l++],l=l%e;if(s=(s+1)%e,s===o&&(o=(o+1)%e),f-i{const o=s.loaded,i=s.lengthComputable?s.total:void 0,c=o-n,d=r(c),f=o<=i;n=o;const u={loaded:o,total:i,progress:i?o/i:void 0,bytes:c,rate:d||void 0,estimated:d&&i&&f?(i-o)/d:void 0,event:s};u[t?"download":"upload"]=!0,e(u)}}const On=typeof XMLHttpRequest<"u",Tn=On&&function(e){return new Promise(function(n,r){let s=e.data;const o=R.from(e.headers).normalize();let{responseType:i,withXSRFToken:c}=e,d;function f(){e.cancelToken&&e.cancelToken.unsubscribe(d),e.signal&&e.signal.removeEventListener("abort",d)}let u;if(a.isFormData(s)){if(x.hasStandardBrowserEnv||x.hasStandardBrowserWebWorkerEnv)o.setContentType(!1);else if((u=o.getContentType())!==!1){const[h,...E]=u?u.split(";").map(g=>g.trim()).filter(Boolean):[];o.setContentType([h||"multipart/form-data",...E].join("; "))}}let l=new XMLHttpRequest;if(e.auth){const h=e.auth.username||"",E=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";o.set("Authorization","Basic "+btoa(h+":"+E))}const w=Ye(e.baseURL,e.url);l.open(e.method.toUpperCase(),Ge(w,e.params,e.paramsSerializer),!0),l.timeout=e.timeout;function b(){if(!l)return;const h=R.from("getAllResponseHeaders"in l&&l.getAllResponseHeaders()),g={data:!i||i==="text"||i==="json"?l.responseText:l.response,status:l.status,statusText:l.statusText,headers:h,config:e,request:l};yn(function(L){n(L),f()},function(L){r(L),f()},g),l=null}if("onloadend"in l?l.onloadend=b:l.onreadystatechange=function(){!l||l.readyState!==4||l.status===0&&!(l.responseURL&&l.responseURL.indexOf("file:")===0)||setTimeout(b)},l.onabort=function(){l&&(r(new m("Request aborted",m.ECONNABORTED,e,l)),l=null)},l.onerror=function(){r(new m("Network Error",m.ERR_NETWORK,e,l)),l=null},l.ontimeout=function(){let E=e.timeout?"timeout of "+e.timeout+"ms exceeded":"timeout exceeded";const g=e.transitional||Ke;e.timeoutErrorMessage&&(E=e.timeoutErrorMessage),r(new m(E,g.clarifyTimeoutError?m.ETIMEDOUT:m.ECONNABORTED,e,l)),l=null},x.hasStandardBrowserEnv&&(c&&a.isFunction(c)&&(c=c(e)),c||c!==!1&&bn(w))){const h=e.xsrfHeaderName&&e.xsrfCookieName&&wn.read(e.xsrfCookieName);h&&o.set(e.xsrfHeaderName,h)}s===void 0&&o.setContentType(null),"setRequestHeader"in l&&a.forEach(o.toJSON(),function(E,g){l.setRequestHeader(g,E)}),a.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),i&&i!=="json"&&(l.responseType=e.responseType),typeof e.onDownloadProgress=="function"&&l.addEventListener("progress",Re(e.onDownloadProgress,!0)),typeof e.onUploadProgress=="function"&&l.upload&&l.upload.addEventListener("progress",Re(e.onUploadProgress)),(e.cancelToken||e.signal)&&(d=h=>{l&&(r(!h||h.type?new J(null,e,l):h),l.abort(),l=null)},e.cancelToken&&e.cancelToken.subscribe(d),e.signal&&(e.signal.aborted?d():e.signal.addEventListener("abort",d)));const p=Sn(w);if(p&&x.protocols.indexOf(p)===-1){r(new m("Unsupported protocol "+p+":",m.ERR_BAD_REQUEST,e));return}l.send(s||null)})},de={http:Gt,xhr:Tn};a.forEach(de,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{value:t})}catch{}Object.defineProperty(e,"adapterName",{value:t})}});const Ne=e=>`- ${e}`,An=e=>a.isFunction(e)||e===null||e===!1,Ze={getAdapter:e=>{e=a.isArray(e)?e:[e];const{length:t}=e;let n,r;const s={};for(let o=0;o`adapter ${c} `+(d===!1?"is not supported by the environment":"is not available in the build"));let i=t?o.length>1?`since :
`+o.map(Ne).join(`
`):" "+Ne(o[0]):"as no adapter specified";throw new m("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return r},adapters:de};function ce(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new J(null,e)}function Ce(e){return ce(e),e.headers=R.from(e.headers),e.data=ae.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Ze.getAdapter(e.adapter||we.adapter)(e).then(function(r){return ce(e),r.data=ae.call(e,e.transformResponse,r),r.headers=R.from(r.headers),r},function(r){return Xe(r)||(ce(e),r&&r.response&&(r.response.data=ae.call(e,e.transformResponse,r.response),r.response.headers=R.from(r.response.headers))),Promise.reject(r)})}const Le=e=>e instanceof R?e.toJSON():e;function _(e,t){t=t||{};const n={};function r(f,u,l){return a.isPlainObject(f)&&a.isPlainObject(u)?a.merge.call({caseless:l},f,u):a.isPlainObject(u)?a.merge({},u):a.isArray(u)?u.slice():u}function s(f,u,l){if(a.isUndefined(u)){if(!a.isUndefined(f))return r(void 0,f,l)}else return r(f,u,l)}function o(f,u){if(!a.isUndefined(u))return r(void 0,u)}function i(f,u){if(a.isUndefined(u)){if(!a.isUndefined(f))return r(void 0,f)}else return r(void 0,u)}function c(f,u,l){if(l in t)return r(f,u);if(l in e)return r(void 0,f)}const d={url:o,method:o,data:o,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:c,headers:(f,u)=>s(Le(f),Le(u),!0)};return a.forEach(Object.keys(Object.assign({},e,t)),function(u){const l=d[u]||s,w=l(e[u],t[u],u);a.isUndefined(w)&&l!==c||(n[u]=w)}),n}const et="1.6.7",Ee={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{Ee[e]=function(r){return typeof r===e||"a"+(t<1?"n ":" ")+e}});const Pe={};Ee.transitional=function(t,n,r){function s(o,i){return"[Axios v"+et+"] Transitional option '"+o+"'"+i+(r?". "+r:"")}return(o,i,c)=>{if(t===!1)throw new m(s(i," has been removed"+(n?" in "+n:"")),m.ERR_DEPRECATED);return n&&!Pe[i]&&(Pe[i]=!0,console.warn(s(i," has been deprecated since v"+n+" and will be removed in the near future"))),t?t(o,i,c):!0}};function Rn(e,t,n){if(typeof e!="object")throw new m("options must be an object",m.ERR_BAD_OPTION_VALUE);const r=Object.keys(e);let s=r.length;for(;s-- >0;){const o=r[s],i=t[o];if(i){const c=e[o],d=c===void 0||i(c,o,e);if(d!==!0)throw new m("option "+o+" must be "+d,m.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new m("Unknown option "+o,m.ERR_BAD_OPTION)}}const fe={assertOptions:Rn,validators:Ee},P=fe.validators;let Q=class{constructor(t){this.defaults=t,this.interceptors={request:new Te,response:new Te}}async request(t,n){try{return await this._request(t,n)}catch(r){if(r instanceof Error){let s;Error.captureStackTrace?Error.captureStackTrace(s={}):s=new Error;const o=s.stack?s.stack.replace(/^.+\n/,""):"";r.stack?o&&!String(r.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(r.stack+=`
`+o):r.stack=o}throw r}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=_(this.defaults,n);const{transitional:r,paramsSerializer:s,headers:o}=n;r!==void 0&&fe.assertOptions(r,{silentJSONParsing:P.transitional(P.boolean),forcedJSONParsing:P.transitional(P.boolean),clarifyTimeoutError:P.transitional(P.boolean)},!1),s!=null&&(a.isFunction(s)?n.paramsSerializer={serialize:s}:fe.assertOptions(s,{encode:P.function,serialize:P.function},!0)),n.method=(n.method||this.defaults.method||"get").toLowerCase();let i=o&&a.merge(o.common,o[n.method]);o&&a.forEach(["delete","get","head","post","put","patch","common"],p=>{delete o[p]}),n.headers=R.concat(i,o);const c=[];let d=!0;this.interceptors.request.forEach(function(h){typeof h.runWhen=="function"&&h.runWhen(n)===!1||(d=d&&h.synchronous,c.unshift(h.fulfilled,h.rejected))});const f=[];this.interceptors.response.forEach(function(h){f.push(h.fulfilled,h.rejected)});let u,l=0,w;if(!d){const p=[Ce.bind(this),void 0];for(p.unshift.apply(p,c),p.push.apply(p,f),w=p.length,u=Promise.resolve(n);l{if(!r._listeners)return;let o=r._listeners.length;for(;o-- >0;)r._listeners[o](s);r._listeners=null}),this.promise.then=s=>{let o;const i=new Promise(c=>{r.subscribe(c),o=c}).then(s);return i.cancel=function(){r.unsubscribe(o)},i},t(function(o,i,c){r.reason||(r.reason=new J(o,i,c),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}static source(){let t;return{token:new tt(function(s){t=s}),cancel:t}}};const Cn=Nn;function Ln(e){return function(n){return e.apply(null,n)}}function Pn(e){return a.isObject(e)&&e.isAxiosError===!0}const pe={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(pe).forEach(([e,t])=>{pe[t]=e});const kn=pe;function nt(e){const t=new K(e),n=je(K.prototype.request,t);return a.extend(n,K.prototype,t,{allOwnKeys:!0}),a.extend(n,t,null,{allOwnKeys:!0}),n.create=function(s){return nt(_(e,s))},n}const y=nt(we);y.Axios=K;y.CanceledError=J;y.CancelToken=Cn;y.isCancel=Xe;y.VERSION=et;y.toFormData=re;y.AxiosError=m;y.Cancel=y.CanceledError;y.all=function(t){return Promise.all(t)};y.spread=Ln;y.isAxiosError=Pn;y.mergeConfig=_;y.AxiosHeaders=R;y.formToJSON=e=>Qe(a.isHTMLForm(e)?new FormData(e):e);y.getAdapter=Ze.getAdapter;y.HttpStatusCode=kn;y.default=y;const rt=y,{Axios:Vn,AxiosError:Gn,CanceledError:Kn,isCancel:Wn,CancelToken:Qn,VERSION:Xn,all:Yn,Cancel:Zn,isAxiosError:Bn,spread:er,toFormData:tr,AxiosHeaders:nr,HttpStatusCode:rr,formToJSON:sr,getAdapter:or,mergeConfig:ir}=rt,ke={checkTestExist:{url:"/mooc/test/:tid",method:"GET"},selectQustion:{url:"/mooc/test/:tid",method:"POST"},getAnnouncement:{url:"/mooc/announcement",method:"GET"},getNewExamInfo:{url:"https://www.icourse163.org/mm-tiku/web/j/mocExamBean.getPaper.rpc",method:"POST"},getNotice:{url:"/mooc/notice/extension",method:"GET"}},Fn="https://ginnnnnn.top/api";async function _n(e,t,n){try{return await new Promise((r,s)=>{let o=ke[e].url;if(t)for(const[i,c]of Object.entries(t)){const d=RegExp(`(/):${i}(/)?`,"g");d.test(o)&&(o=o.replaceAll(d,`$1${c}$2`),Reflect.deleteProperty(t,i))}if(o.indexOf("http")!=0&&(o=`${Fn}${o}`),n)for(const[i,c]of Object.entries(n))typeof c=="object"&&Reflect.set(n,i,JSON.stringify(c));rt({url:o,method:ke[e].method,params:t||{},data:n||{},headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then(i=>{let c="",d=!1;i.status!==200||!i.data?c="请求出错":i.data.msg&&(c=i.data.msg,i.data.status===200&&(d=!0)),c&&console.log(c),r(i.data)}).catch(i=>{let c=i;Bn(i)&&(c=i.message),console.log(c),s(i)})})}catch{return{}}}const st=()=>_n,jn=()=>{const[e,t]=[new Array,new Array],n=document.querySelectorAll('input[id^="op_"]');for(let s=0;s{if(e)for(const r of e)document.querySelector(`input[id*="_${r}"]`).classList.add("gin-answer");const n=document.getElementsByClassName("m-FillBlank examMode u-questionItem");for(let r=0;r{var n,r;const t=document.getElementsByClassName("f-richEditorText j-richTxt f-fl");for(let s=0;s{var t,n,r,s;const e=document.getElementsByClassName("u-questionItem u-analysisQuestion analysisMode");for(let o=0;o{const n=document.querySelector(".u-homework-evaAction .bottombtnwrap .j-submitbtn"),r=document.querySelector(".u-homework-evaAction .xlinfo"),s=document.querySelector(".u-homework-evaAction .xlinfo .j-gotonext");for(let o=0;or.style.display==="none"),ot(),await Fe(1e3),console.log(new Date),n.click(),await ge(()=>r.style.display!=="none"),t(o+1,e),s.click()},In=async()=>{var s,o;const e=st();let t=(s=document.getElementById("app"))==null?void 0:s.getElementsByTagName("form").item(0);for(;!t;)await Fe(1e3),t=(o=document.getElementById("app"))==null?void 0:o.getElementsByTagName("form").item(0);const n=async()=>{const i=await e("getNewExamInfo",{csrfKey:document.cookie.match(/NTESSTUDYSI=([a-z0-9]+);/)[1]},{answerformId:W("aid"),examId:W("eid")});let c=[];for(let u of i.result.questions)for(let l of u.optionDtos)c.push(l.id);const d=await e("selectQustion",{tid:i.result.tid},{oidList:c}),f=document.querySelectorAll(".ant-checkbox-group>div, .ant-radio-group>div");for(let u of d.data.choiceAns)f[c.indexOf(u)].classList.add("gin-answer-item")},r=document.createElement("button");r.className="ant-btn ant-btn-primary",r.setAttribute("style","margin-bottom: 16px"),r.onclick=n,r.innerText="获取答案",t==null||t.before(r)},X=st(),M=new dt,N=M.add(void 0),A=M.add(-1);let B=null;const[Y,F,Z]=[M.add(!1),M.add(!1),M.add(!1)];location.href.indexOf("newExam")!==-1&&In();const vn=async()=>{if(O.innerText!=="正在获取答案,请稍后..."){if(O.innerText="正在获取答案,请稍后...",N.get()==="quiz"){const e=await X("selectQustion",{tid:B},jn());Dn(e.data.choiceAns,e.data.completionAns)}else if(N.get()==="homework"){const e=await X("selectQustion",{tid:B},{});Un(e.data.homeworkAns)}O.innerText=""}},it=document.createElement("style");it.innerText=`
input.gin-answer:not(:checked) + label, #GinsMooc, .gin-answer-item {
background-color: #d9ecff;
}
.learnPageContentLeft {
background: rgb(240, 242, 245);
}
#GinsMooc {
margin-bottom: 12px !important;
}
.gin-function {
display: flex;
align-items: center;
}
.gin-function .u-btn {
margin-right: 16px;
}
.gin-state-tips {
font-size: 14px;
}
`;document.head.append(it);const I=document.createElement("div");I.id="GinsMooc";I.classList.add("m-learnbox");var Be;(Be=document.querySelector(".learnPageContentLeft"))==null||Be.prepend(I);if(location.href.indexOf("/spoc")!==-1){const e=document.createElement("div");e.innerHTML="当前课程为 SPOC 课程,可能无法获取答案。SPOC 课程可能会有关联的对外课程,你可以尝试搜索并加入,题目大概率是一样的",I.prepend()}const Hn=async()=>{var t;const e=(await X("getNotice",{version:"v2.2.1"},void 0)).data;if(console.log(e),!((t=localStorage.getItem("Gins-ignore-notice"))!=null&&t.split(",").find(n=>Number.parseInt(n)===e.id))){const n=document.createElement("div");n.innerHTML=e.content;const r=document.createElement("a");r.innerText="不再提醒",r.onclick=()=>{const s=localStorage.getItem("Gins-ignore-notice");localStorage.setItem("Gins-ignore-notice",s?`${s},${e.id}`:`${e.id}`),n.remove(),r.remove()},r.style.marginLeft="16px",n.append(r),I.prepend(n)}};Hn();const v=document.createElement("div");v.classList.add("gin-function");I.append(v);const j=document.createElement("button");j.classList.add("u-btn","u-btn-default","f-dn");j.onclick=vn;j.innerText="获取答案";v.append(j);const D=document.createElement("button");D.classList.add("u-btn","u-btn-default","f-dn");D.onclick=()=>{ot(),window.scroll({top:document.documentElement.scrollHeight,behavior:"smooth"})};D.innerText="一键互评";v.append(D);const U=document.createElement("button");U.classList.add("u-btn","u-btn-default","f-dn");U.onclick=()=>{qn(5,(e,t)=>{e>=t?O.innerText=`已完成 ${t} 次互评`:O.innerText=`自动互评中(${e} / ${t})`})};U.innerText="自动互评";v.append(U);const O=document.createElement("div");O.classList.add("gin-state-tips");v.append(O);window.addEventListener("hashchange",()=>{B=W("id"),location.hash.indexOf("quiz")!==-1||location.hash.indexOf("examObject")!==-1?N.set("quiz"):location.hash.indexOf("hw")!==-1||location.hash.indexOf("examSubjective")!==-1?N.set("homework"):N.set(void 0)});window.setInterval(()=>{Z.set(location.hash.indexOf("examlist")!==-1);const e=document.querySelector(".j-prepare.prepare");F.set(e&&!e.classList.contains("f-dn")||document.querySelector(".j-homework-paper")!==null||Z.get()),Y.set(document.querySelector(".u-questionItem.u-analysisQuestion.analysisMode")!==null)},100);const at=async()=>{if(console.log("onTestChange",N.get(),F.get(),Z.get(),B),N.get()==="quiz"&&!F.get()||N.get()==="homework"?j.classList.remove("f-dn"):j.classList.add("f-dn"),A.set(-2),F.get()&&B)if((await X("checkTestExist",{tid:B,type:"isExisting"},void 0)).data.existing){A.set(-1);return}else{const t=new EventSource(`https://ginnnnnn.top/api/mooc/course/refresh/${W("tid")}`);t.onmessage=n=>{console.log(n.data);const r=JSON.parse(n.data);r&&r.total>0&&A.set(Math.round(r.finished/r.total*100)),(A.value===100||r.status===400)&&(t.close(),r.msg&&A.set(-1))}}else if(!Z.get()){A.set(-1);return}},Mn=()=>{console.log("onModeChange",Y.get()),Y.get()?(D.classList.remove("f-dn"),U.classList.remove("f-dn")):(D.classList.add("f-dn"),U.classList.add("f-dn"))};Y.addEventListenr("change",Mn);F.addEventListenr("change",at);N.addEventListenr("change",at);A.addEventListenr("set",()=>{switch(A.get()){case-2:O.innerText="正在检查课程...";break;case-1:O.innerText=F.get()?"已准备就绪":"";break;default:O.innerText=`正在更新课程...${A.get()}%`;break}});
================================================
FILE: extension/release/manifest.json
================================================
{
"manifest_version": 3,
"name": "GinsMooc Extension",
"version": "2.2.1",
"description": "A Chrome extension to get the mooc answers and evaluate auto automatically.",
"icons": {
"16": "icons/favicon16.png",
"32": "icons/favicon32.png",
"48": "icons/favicon48.png",
"128": "icons/favicon128.png"
},
"author": { "email": "ginnnnnn@qq.com" },
"homepage_url": "https://ginnnnnn.top/mooc",
"content_scripts": [
{
"matches": ["https://www.icourse163.org/*learn/*?tid=*", "https://www.icourse163.org/mooc/main/newExam*"],
"js": ["content-scripts/index-ad710f80.js"],
"run_at": "document_end"
}
]
}
================================================
FILE: extension/src/main.ts
================================================
import { CustomRefList } from "./plugins/react"
import { useApiAccess } from "./plugins/apiAccess"
import { sleep, getUrlParam } from "./plugins/tool"
import { getQuizQuestionKeys, setQuizAnswer, setHomeworkAnswer, autoEvaluate, batchEvaluate } from "./plugins/mooc"
import { newExamHandle } from "./newExam"
const apiAccess = useApiAccess()
const refList = new CustomRefList()
const testType = refList.add(<"quiz" | "homework" | undefined>undefined)
const newCourseState = refList.add(-1)
let testId: string | null = null
const [analysis, prepare, examlist] = [refList.add(false), refList.add(false), refList.add(false)]
if (location.href.indexOf("newExam") !== -1) {
newExamHandle()
}
const getAnswer = async () => {
if (stateTips.innerText === "正在获取答案,请稍后...") {
return
}
stateTips.innerText = "正在获取答案,请稍后..."
if (testType.get() === "quiz") {
const answers = await apiAccess("selectQustion", { tid: testId as string }, getQuizQuestionKeys())
setQuizAnswer(answers.data.choiceAns as number[], answers.data.completionAns as Object)
} else if (testType.get() === "homework") {
const answers = await apiAccess("selectQustion", { tid: testId as string }, {})
setHomeworkAnswer(answers.data.homeworkAns as Object)
}
stateTips.innerText = ""
}
const styleNode = document.createElement("style")
styleNode.innerText = `
input.gin-answer:not(:checked) + label, #GinsMooc, .gin-answer-item {
background-color: #d9ecff;
}
.learnPageContentLeft {
background: rgb(240, 242, 245);
}
#GinsMooc {
margin-bottom: 12px !important;
}
.gin-function {
display: flex;
align-items: center;
}
.gin-function .u-btn {
margin-right: 16px;
}
.gin-state-tips {
font-size: 14px;
}
`
document.head.append(styleNode)
const wrapperNode = document.createElement("div")
wrapperNode.id = "GinsMooc"
wrapperNode.classList.add("m-learnbox")
document.querySelector(".learnPageContentLeft")?.prepend(wrapperNode)
if (location.href.indexOf("/spoc") !== -1) {
const spocTipsNode = document.createElement("div")
spocTipsNode.innerHTML = "当前课程为 SPOC 课程,可能无法获取答案。SPOC 课程可能会有关联的对外课程,你可以尝试搜索并加入,题目大概率是一样的"
wrapperNode.prepend()
}
const setNotice = async () => {
const notice = (await apiAccess("getNotice", { version: "v2.2.1" }, undefined)).data
console.log(notice)
if (
!localStorage
.getItem("Gins-ignore-notice")
?.split(",")
.find((item) => Number.parseInt(item) === notice.id)
) {
const noticeNode = document.createElement("div")
noticeNode.innerHTML = notice.content
const closeBtn = document.createElement("a")
closeBtn.innerText = "不再提醒"
closeBtn.onclick = () => {
const origin = localStorage.getItem("Gins-ignore-notice")
localStorage.setItem("Gins-ignore-notice", origin ? `${origin},${notice.id}` : `${notice.id}`)
noticeNode.remove()
closeBtn.remove()
}
closeBtn.style.marginLeft = "16px"
noticeNode.append(closeBtn)
wrapperNode.prepend(noticeNode)
}
}
setNotice()
const functionNode = document.createElement("div")
functionNode.classList.add("gin-function")
wrapperNode.append(functionNode)
const getAnswerBtn = document.createElement("button")
getAnswerBtn.classList.add("u-btn", "u-btn-default", "f-dn")
getAnswerBtn.onclick = getAnswer
getAnswerBtn.innerText = "获取答案"
functionNode.append(getAnswerBtn)
const evaluateBtn = document.createElement("button")
evaluateBtn.classList.add("u-btn", "u-btn-default", "f-dn")
evaluateBtn.onclick = () => {
autoEvaluate()
window.scroll({ top: document.documentElement.scrollHeight, behavior: "smooth" })
}
evaluateBtn.innerText = "一键互评"
functionNode.append(evaluateBtn)
const batchEvaluateBtn = document.createElement("button")
batchEvaluateBtn.classList.add("u-btn", "u-btn-default", "f-dn")
batchEvaluateBtn.onclick = () => {
batchEvaluate(5, (finish: number, total: number) => {
if (finish >= total) {
stateTips.innerText = `已完成 ${total} 次互评`
} else {
stateTips.innerText = `自动互评中(${finish} / ${total})`
}
})
}
batchEvaluateBtn.innerText = "自动互评"
functionNode.append(batchEvaluateBtn)
const stateTips = document.createElement("div")
stateTips.classList.add("gin-state-tips")
functionNode.append(stateTips)
window.addEventListener("hashchange", () => {
testId = getUrlParam("id")
if (location.hash.indexOf("quiz") !== -1 || location.hash.indexOf("examObject") !== -1) {
testType.set("quiz")
} else if (location.hash.indexOf("hw") !== -1 || location.hash.indexOf("examSubjective") !== -1) {
testType.set("homework")
} else {
testType.set(undefined)
}
})
window.setInterval(() => {
examlist.set(location.hash.indexOf("examlist") !== -1)
const prepareNode = document.querySelector(".j-prepare.prepare")
prepare.set(
(prepareNode && !prepareNode.classList.contains("f-dn")) ||
document.querySelector(".j-homework-paper") !== null ||
examlist.get()
)
analysis.set(document.querySelector(".u-questionItem.u-analysisQuestion.analysisMode") !== null)
}, 100);
const onTestChange = async () => {
console.log("onTestChange", testType.get(), prepare.get(), examlist.get(), testId)
if ((testType.get() === "quiz" && !prepare.get()) || testType.get() === "homework") {
getAnswerBtn.classList.remove("f-dn")
} else {
getAnswerBtn.classList.add("f-dn")
}
newCourseState.set(-2)
if (prepare.get() && testId) {
const res = await apiAccess("checkTestExist", { tid: testId, type: "isExisting" }, undefined)
if (res.data.existing) {
newCourseState.set(-1)
return
} else {
const eventSource = new EventSource(`https://ginnnnnn.top/api/mooc/course/refresh/${getUrlParam("tid")}`)
eventSource.onmessage = (event) => {
console.log(event.data)
const state = JSON.parse(event.data)
if (state && state.total > 0) {
newCourseState.set(Math.round((state.finished / state.total) * 100))
}
if (newCourseState.value === 100 || state.status === 400) {
eventSource.close()
if (state.msg) {
newCourseState.set(-1)
}
}
}
}
} else if (!examlist.get()) {
newCourseState.set(-1)
return
}
}
const onModeChange = () => {
console.log("onModeChange", analysis.get())
if (analysis.get()) {
evaluateBtn.classList.remove("f-dn")
batchEvaluateBtn.classList.remove("f-dn")
} else {
evaluateBtn.classList.add("f-dn")
batchEvaluateBtn.classList.add("f-dn")
}
}
analysis.addEventListenr("change", onModeChange)
prepare.addEventListenr("change", onTestChange)
testType.addEventListenr("change", onTestChange)
newCourseState.addEventListenr("set", () => {
switch (newCourseState.get()) {
case -2:
stateTips.innerText = "正在检查课程..."
break
case -1:
stateTips.innerText = prepare.get() ? "已准备就绪" : ""
break
default:
stateTips.innerText = `正在更新课程...${newCourseState.get()}%`
break
}
})
================================================
FILE: extension/src/newExam.ts
================================================
import { useApiAccess } from "./plugins/apiAccess"
import { sleep, getUrlParam } from "./plugins/tool"
export const newExamHandle = async () => {
const apiAccess = useApiAccess()
let form = document.getElementById("app")?.getElementsByTagName("form").item(0)
while (!form) {
await sleep(1000)
form = document.getElementById("app")?.getElementsByTagName("form").item(0)
}
const getAnswer = async () => {
const info = await apiAccess(
"getNewExamInfo",
{ csrfKey: document.cookie.match(/NTESSTUDYSI=([a-z0-9]+);/)![1] },
{ answerformId: getUrlParam("aid")!, examId: getUrlParam("eid")! }
)
let oidList: Array = []
for (let question of info.result.questions) {
for (let option of question.optionDtos) {
oidList.push(option.id)
}
}
const answers = await apiAccess("selectQustion", { tid: info.result.tid }, { oidList: oidList })
const optionElements = document.querySelectorAll(
".ant-checkbox-group>div, .ant-radio-group>div"
) as NodeListOf
// console.log(optionElements)
for (let id of answers.data.choiceAns!) {
optionElements[oidList.indexOf(id)].classList.add("gin-answer-item")
}
}
const getAnswerBtn = document.createElement("button")
getAnswerBtn.className = "ant-btn ant-btn-primary"
getAnswerBtn.setAttribute("style", "margin-bottom: 16px")
getAnswerBtn.onclick = getAnswer
getAnswerBtn.innerText = "获取答案"
form?.before(getAnswerBtn)
}
================================================
FILE: extension/src/plugins/apiAccess.ts
================================================
import type { ApiKeyType, ApiResponseType, ApiRequestType } from "../type/api"
import type { App } from "vue"
import { isAxiosError } from "axios"
import apiInfo from "../type/api"
import axios from "axios"
const baseUrl = "https://ginnnnnn.top/api"
async function apiAccess(api: T): Promise
async function apiAccess(
api: T,
params: ApiRequestType[T]["params"],
data: ApiRequestType[T]["data"]
): Promise
/** 函数重载 */
async function apiAccess(
api: T,
params?: ApiRequestType[T]["params"],
data?: ApiRequestType[T]["data"]
) {
/** 错误处理,主要catch 404,调用者不再需要try-catch */
try {
return await new Promise((resolve, reject) => {
/** 查询参数转动态路由参数 */
let url = apiInfo[api].url
if (params) {
for (const [key, val] of Object.entries(params)) {
const reg = RegExp(`(/):${key}(/)?`, "g")
if (reg.test(url)) {
url = url.replaceAll(reg, `$1${val}$2`)
Reflect.deleteProperty(params, key)
}
}
}
if (url.indexOf("http") != 0) {
url = `${baseUrl}${url}`
}
/** 将对象转为json字符串 */
if (data) {
for (const [key, val] of Object.entries(data)) {
if (typeof val === "object") {
Reflect.set(data, key, JSON.stringify(val))
}
}
}
/** 异步发送请求 */
axios({
url: url,
method: apiInfo[api].method,
params: params || {},
data: data || {},
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
.then((res) => {
let message = "",
success = false
if (res.status !== 200 || !res.data) {
message = "请求出错"
} else if (res.data.msg) {
message = res.data.msg
if (res.data.status === 200) {
success = true
}
}
if (message) {
console.log(message)
}
resolve(res.data)
})
.catch((error) => {
let message = error
if (isAxiosError(error)) {
message = error.message
}
console.log(message)
reject(error)
})
})
} catch {
return {}
}
}
export const useApiAccess = () => apiAccess
export default {
install: (app: App) => {
app.config.globalProperties.$apiAccess = apiAccess
}
}
================================================
FILE: extension/src/plugins/mooc.ts
================================================
import type { quiz, option } from "../type/mooc"
import { sleep, waitFor } from "./tool"
const getQuizQuestionKeys = () => {
const [oidList, titleList] = [new Array(), new Array()]
const choices = document.querySelectorAll('input[id^="op_"]')
for (let i = 0; i < choices.length; i++) {
const choice = choices.item(i) as HTMLInputElement
oidList.push(Number.parseInt(choice.id.split("_")[2].slice(0, -13)))
}
const completions = document.getElementsByClassName("m-FillBlank examMode u-questionItem")
for (let i = 0; i < completions.length; i++) {
const questionNode = completions.item(i) as HTMLDivElement
const titleNode = questionNode.querySelector(".j-richTxt") as HTMLDivElement
titleList.push(titleNode.innerText)
}
return { oidList, titleList }
}
const setQuizAnswer = (choiceAns: number[], completionAns: Object) => {
if (choiceAns) {
for (const id of choiceAns) {
const node = document.querySelector(`input[id*="_${id}"]`) as HTMLInputElement
node.classList.add("gin-answer")
}
}
const completions = document.getElementsByClassName("m-FillBlank examMode u-questionItem")
for (let i = 0; i < completions.length; i++) {
const questionNode = completions.item(i) as HTMLDivElement
const titleNode = questionNode.querySelector(".j-richTxt") as HTMLDivElement
const question = Reflect.get(completionAns, titleNode.innerText) as quiz
const newTitleNode = document.createElement("div")
newTitleNode.innerHTML = question.title
titleNode.appendChild(newTitleNode)
const answerList = (question.stdAnswer).split("##%_YZPRLFH_%##")
const answerListNode = document.createElement("div")
for (let j = 0; j < answerList.length; j++) {
const answerNode = document.createElement("span")
answerNode.classList.add("gin-answer-item")
answerNode.innerHTML = answerList[j]
answerListNode.append(answerNode)
if (j !== answerList.length - 1) {
answerListNode.append(" / ")
}
}
titleNode.append(answerListNode)
}
}
const setHomeworkAnswer = (homeworkAns: Object) => {
const homeworks = document.getElementsByClassName("f-richEditorText j-richTxt f-fl")
for (let i = 0; i < homeworks.length; i++) {
const answerNode = document.createElement("div")
answerNode.classList.add('gin-answer-item')
answerNode.innerHTML = Reflect.get(homeworkAns, `${i}`)?.answer as string
homeworks.item(i)?.append(answerNode)
}
}
const autoEvaluate = () => {
const analysis = document.getElementsByClassName("u-questionItem u-analysisQuestion analysisMode")
for (let i = 0; i < analysis.length; i++) {
const radioGroup = analysis.item(i)?.getElementsByClassName("s") as HTMLCollection
for (let j = 0; j < radioGroup.length; j++) {
const radio = radioGroup.item(j)?.lastElementChild?.querySelector("input") as HTMLInputElement
radio.checked = true
}
const textarea = analysis.item(i)?.querySelector("textarea") as HTMLTextAreaElement
textarea.value = "666"
}
}
const batchEvaluate = async (times: number, updateCaller: (finish: number, total: number) => any) => {
const submitBtn = document.querySelector(".u-homework-evaAction .bottombtnwrap .j-submitbtn") as HTMLDivElement
const xlinfo = document.querySelector(".u-homework-evaAction .xlinfo") as HTMLDivElement
const nextBtn = document.querySelector(".u-homework-evaAction .xlinfo .j-gotonext") as HTMLDivElement
for (let i = 0; i < times; i++) {
await waitFor(() => xlinfo.style.display === 'none')
autoEvaluate()
await sleep(1000)
console.log(new Date())
submitBtn.click()
await waitFor(() => xlinfo.style.display !== 'none')
updateCaller(i + 1, times)
nextBtn.click()
}
}
export { getQuizQuestionKeys, setHomeworkAnswer, setQuizAnswer, autoEvaluate, batchEvaluate }
================================================
FILE: extension/src/plugins/react.ts
================================================
import { randomString } from "./tool"
class CustomRefList extends Array> {
id: string
node: HTMLElement
constructor() {
super()
this.node = document.createElement("gin")
this.id = randomString(8)
this.node.id = `gin-auto-${this.id}`
document.body.appendChild(this.node)
}
add(value: T): CustomRef {
const add = new CustomRef(this, value)
super.push(add)
return add
}
}
class CustomRef {
id: string
node: HTMLElement
parent: CustomRefList
value: T
constructor(parent: CustomRefList, value: T) {
this.parent = parent
this.node = document.createElement("gin")
this.id = randomString(8)
this.node.id = this.id
this.parent.node.appendChild(this.node)
this.value = value
}
get(): T {
return this.value
}
set(value: T): void {
if (this.value !== value) {
const oldValue = this.value
this.value = value
this.node.dispatchEvent(new CustomEvent("change", { detail: { oldValue, newValue: this.value } }))
}
this.node.dispatchEvent(new CustomEvent("set"))
}
addEventListenr(eventName: "change" | "set", callback: (ev: Event) => void): void {
this.node.addEventListener(eventName, callback)
}
}
export { CustomRefList }
================================================
FILE: extension/src/plugins/tool.ts
================================================
const sleep = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('')
}, ms)
})
}
const waitFor = async (checker: any) => {
return new Promise((resolve) => {
const checkDisplay = setInterval(() => {
console.log('check wait for')
if (checker()) {
clearInterval(checkDisplay);
resolve('');
}
}, 50);
});
}
const getUrlParam = (key: string) => {
const reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)')
const result = window.location.search.substring(1).match(reg)
|| window.location.hash.substring((window.location.hash.search(/\?/)) + 1).match(reg)
return result ? decodeURIComponent(result[2]) : null
}
const randomString = (length: number) => {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
let result = ''
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)]
}
return result
}
export { sleep, getUrlParam, randomString, waitFor }
================================================
FILE: extension/src/type/api.ts
================================================
import type { homework, quiz, notice } from "./mooc"
type RequestType = {
params?: Object
data?: Object
}
type Response = {
status: number
data: T
msg: string
}
const apiInfo = {
checkTestExist: {
url: "/mooc/test/:tid",
method: "GET"
},
selectQustion: {
url: "/mooc/test/:tid",
method: "POST"
},
getAnnouncement: {
url: "/mooc/announcement",
method: "GET"
},
getNewExamInfo: {
url: "https://www.icourse163.org/mm-tiku/web/j/mocExamBean.getPaper.rpc",
method: "POST"
},
getNotice: {
url: "/mooc/notice/extension",
method: "GET"
}
}
export type ApiKeyType = keyof typeof apiInfo
export interface ApiResponseType {
checkTestExist: Response<{
existing: boolean
}>
selectQustion: Response<{
choiceAns?: number[]
completionAns?: Object
homeworkAns?: Object
}>
getAnnouncement: Response
getNotice: Response
getNewExamInfo: {
code: number
result: {
tid: number
questions: { optionDtos: { id: number }[] }[]
}
}
}
export interface ApiRequestType {
checkTestExist: RequestType & {
params: { tid: number | string; type: "isExisting" }
}
selectQustion: RequestType & {
params: { tid: number | string }
data: {
oidList?: number[]
titleList?: string[]
}
}
getNotice: RequestType & {
params: { version: string }
}
getNewExamInfo: RequestType & {
params: { csrfKey: string }
data: {
answerformId: number | string
examId: number | string
}
}
}
export default apiInfo
================================================
FILE: extension/src/type/mooc.ts
================================================
export enum QuestionTypeEnumList {
SingleChoice = "SINGLE_CHOICE",
MultipleChoice = "MULTIPLE_CHOICE",
Completion = "COMPLETION",
Judge = "JUDGEMENT",
Homework = "HOMEWORK",
OnlineJudge = "ONLINE_JUDGE"
}
interface course extends Object {
id: number
name: string
school: string
imageUrl: string
}
interface test extends Object {
id: number
name: string
objective: boolean
releaseTime: string
deadline: string
chapterId: number
chapterName: string
}
interface option extends Object {
id: number
content: string
answer: boolean
}
interface quiz extends Object {
id: number
type:
| QuestionTypeEnumList.SingleChoice
| QuestionTypeEnumList.MultipleChoice
| QuestionTypeEnumList.Completion
| QuestionTypeEnumList.Judge
title: string
stdAnswer: string | null
optionList: option[] | null
}
interface homework extends Object {
id: number
type: QuestionTypeEnumList.Homework | QuestionTypeEnumList.OnlineJudge
title: string
answer: string | null
description: string | null
memoryLimit: number | null
timeLimit: number | null
}
interface notice extends Object {
id: number
content: string
}
export type QuestionTypeEnum = typeof QuestionTypeEnumList
export type { course, test, option, quiz, homework, notice }
================================================
FILE: extension/tsconfig.config.json
================================================
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"types": [
"node"
]
}
}
================================================
FILE: extension/tsconfig.json
================================================
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
================================================
FILE: extension/vite.config.ts
================================================
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
"outDir": "./release/content-scripts",
"assetsDir": "./"
}
})
================================================
FILE: index.html
================================================
Gins
================================================
FILE: package.json
================================================
{
"name": "ginsmooc",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14",
"@vueuse/core": "^9.11.1",
"axios": "^1.2.3",
"d3-cloud": "^1.2.5",
"element-plus": "^2.2.28",
"fuzzysort": "^2.0.4",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@types/node": "^18.15.5",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}
}
================================================
FILE: public/background.html
================================================
================================================
FILE: public/css/main.css
================================================
@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900");
@import url("fontawesome-all.min.css");
/*
Aerial by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
html, body, div, span, applet, object,
iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, a, abbr, acronym, address, big, cite,
code, del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var, b,
u, i, center, dl, dt, dd, ol, ul, li, fieldset,
form, label, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside,
canvas, details, embed, figure, figcaption,
footer, header, hgroup, menu, nav, output, ruby,
section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after, q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
-webkit-text-size-adjust: none;
}
mark {
background-color: transparent;
color: inherit;
}
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input, select, textarea {
-moz-appearance: none;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
}
/* Basic */
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
background: #fff;
overflow: hidden;
}
body.is-preload *, body.is-preload *:before, body.is-preload *:after {
-moz-animation: none !important;
-webkit-animation: none !important;
-ms-animation: none !important;
animation: none !important;
-moz-transition: none !important;
-webkit-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}
body, input, select, textarea {
color: #fff;
font-family: 'Source Sans Pro', sans-serif;
font-size: 15pt;
font-weight: 300 !important;
letter-spacing: -0.025em;
line-height: 1.75em;
}
a {
-moz-transition: border-color 0.2s ease-in-out;
-webkit-transition: border-color 0.2s ease-in-out;
-ms-transition: border-color 0.2s ease-in-out;
transition: border-color 0.2s ease-in-out;
border-bottom: dotted 1px;
color: inherit;
outline: 0;
text-decoration: none;
}
a:hover {
border-color: transparent;
}
/* Icon */
.icon {
text-decoration: none;
position: relative;
}
.icon:before {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
font-family: 'Font Awesome 5 Free';
font-weight: 400;
}
.icon > .label {
display: none;
}
.icon.solid:before {
font-weight: 900;
}
.icon.brands:before {
font-family: 'Font Awesome 5 Brands';
}
/* Wrapper */
@-moz-keyframes wrapper {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes wrapper {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-ms-keyframes wrapper {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes wrapper {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#wrapper {
-moz-animation: wrapper 3s forwards;
-webkit-animation: wrapper 3s forwards;
-ms-animation: wrapper 3s forwards;
animation: wrapper 3s forwards;
height: 100%;
left: 0;
opacity: 0;
position: fixed;
top: 0;
width: 100%;
}
/* BG */
#bg {
-moz-animation: bg 60s linear infinite;
-webkit-animation: bg 60s linear infinite;
-ms-animation: bg 60s linear infinite;
animation: bg 60s linear infinite;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
/* Set your background with this */
background: #348cb2 url("https://gins-1255964181.cos.ap-beijing.myqcloud.com/bg.jpg") bottom left;
background-repeat: repeat-x;
height: 100%;
left: 0;
opacity: 1;
position: fixed;
top: 0;
}
@-moz-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-2250px,0,0);
-webkit-transform: translate3d(-2250px,0,0);
-ms-transform: translate3d(-2250px,0,0);
transform: translate3d(-2250px,0,0);
}
}
@-webkit-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-2250px,0,0);
-webkit-transform: translate3d(-2250px,0,0);
-ms-transform: translate3d(-2250px,0,0);
transform: translate3d(-2250px,0,0);
}
}
@-ms-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-2250px,0,0);
-webkit-transform: translate3d(-2250px,0,0);
-ms-transform: translate3d(-2250px,0,0);
transform: translate3d(-2250px,0,0);
}
}
@keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-2250px,0,0);
-webkit-transform: translate3d(-2250px,0,0);
-ms-transform: translate3d(-2250px,0,0);
transform: translate3d(-2250px,0,0);
}
}
#bg {
background-size: auto 100%;
width: 6750px;
}
/* Overlay */
@-moz-keyframes overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-ms-keyframes overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#overlay {
-moz-animation: overlay 1.5s 1.5s forwards;
-webkit-animation: overlay 1.5s 1.5s forwards;
-ms-animation: overlay 1.5s 1.5s forwards;
animation: overlay 1.5s 1.5s forwards;
background-attachment: fixed, fixed;
background-image: url("images/overlay-pattern.png"), url("images/overlay.svg");
background-position: top left, center center;
background-repeat: repeat, no-repeat;
background-size: auto, cover;
height: 100%;
left: 0;
opacity: 0;
position: fixed;
top: 0;
width: 100%;
}
/* Main */
#main {
height: 100%;
left: 0;
position: fixed;
text-align: center;
top: 0;
width: 100%;
}
#main:before {
content: '';
display: inline-block;
height: 100%;
margin-right: 0;
vertical-align: middle;
width: 1px;
}
/* Header */
@-moz-keyframes header {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@-webkit-keyframes header {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@-ms-keyframes header {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@keyframes header {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@-moz-keyframes nav-icons {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@-webkit-keyframes nav-icons {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@-ms-keyframes nav-icons {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
@keyframes nav-icons {
0% {
-moz-transform: translate3d(0,1em,0);
-webkit-transform: translate3d(0,1em,0);
-ms-transform: translate3d(0,1em,0);
transform: translate3d(0,1em,0);
opacity: 0;
}
100% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
opacity: 1;
}
}
#header {
-moz-animation: header 1s 2.25s forwards;
-webkit-animation: header 1s 2.25s forwards;
-ms-animation: header 1s 2.25s forwards;
animation: header 1s 2.25s forwards;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
cursor: default;
display: inline-block;
opacity: 0;
position: relative;
text-align: center;
top: -1em;
vertical-align: middle;
width: 90%;
}
#header h1 {
font-size: 4.35em;
font-weight: 900;
letter-spacing: -0.035em;
line-height: 1em;
}
#header p {
font-size: 1.25em;
margin: 0.75em 0 0.25em 0;
opacity: 0.75;
}
#header nav {
margin: 1.5em 0 0 0;
}
#header nav li {
-moz-animation: nav-icons 0.5s ease-in-out forwards;
-webkit-animation: nav-icons 0.5s ease-in-out forwards;
-ms-animation: nav-icons 0.5s ease-in-out forwards;
animation: nav-icons 0.5s ease-in-out forwards;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
display: inline-block;
height: 5.35em;
line-height: 5.885em;
opacity: 0;
position: relative;
top: 0;
width: 5.35em;
}
#header nav li:nth-child(1) {
-moz-animation-delay: 2.5s;
-webkit-animation-delay: 2.5s;
-ms-animation-delay: 2.5s;
animation-delay: 2.5s;
}
#header nav li:nth-child(2) {
-moz-animation-delay: 2.75s;
-webkit-animation-delay: 2.75s;
-ms-animation-delay: 2.75s;
animation-delay: 2.75s;
}
#header nav li:nth-child(3) {
-moz-animation-delay: 3s;
-webkit-animation-delay: 3s;
-ms-animation-delay: 3s;
animation-delay: 3s;
}
#header nav li:nth-child(4) {
-moz-animation-delay: 3.25s;
-webkit-animation-delay: 3.25s;
-ms-animation-delay: 3.25s;
animation-delay: 3.25s;
}
#header nav li:nth-child(5) {
-moz-animation-delay: 3.5s;
-webkit-animation-delay: 3.5s;
-ms-animation-delay: 3.5s;
animation-delay: 3.5s;
}
#header nav li:nth-child(6) {
-moz-animation-delay: 3.75s;
-webkit-animation-delay: 3.75s;
-ms-animation-delay: 3.75s;
animation-delay: 3.75s;
}
#header nav li:nth-child(7) {
-moz-animation-delay: 4s;
-webkit-animation-delay: 4s;
-ms-animation-delay: 4s;
animation-delay: 4s;
}
#header nav li:nth-child(8) {
-moz-animation-delay: 4.25s;
-webkit-animation-delay: 4.25s;
-ms-animation-delay: 4.25s;
animation-delay: 4.25s;
}
#header nav li:nth-child(9) {
-moz-animation-delay: 4.5s;
-webkit-animation-delay: 4.5s;
-ms-animation-delay: 4.5s;
animation-delay: 4.5s;
}
#header nav li:nth-child(10) {
-moz-animation-delay: 4.75s;
-webkit-animation-delay: 4.75s;
-ms-animation-delay: 4.75s;
animation-delay: 4.75s;
}
#header nav a {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-touch-callout: none;
border: 0;
display: inline-block;
}
#header nav a:before {
-moz-transition: all 0.2s ease-in-out;
-webkit-transition: all 0.2s ease-in-out;
-ms-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
border-radius: 100%;
border: solid 1px #fff;
display: block;
font-size: 1.75em;
height: 2.5em;
line-height: 2.5em;
position: relative;
text-align: center;
top: 0;
width: 2.5em;
}
#header nav a:hover {
font-size: 1.1em;
}
#header nav a:hover:before {
background-color: rgba(255, 255, 255, 0.175);
color: #fff;
}
#header nav a:active {
font-size: 0.95em;
background: none;
}
#header nav a:active:before {
background-color: rgba(255, 255, 255, 0.35);
color: #fff;
}
#header nav a span {
display: none;
}
/* Footer */
#footer {
background-image: -moz-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
background-image: -webkit-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
background-image: -ms-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
background-image: linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
bottom: 0;
cursor: default;
height: 6em;
left: 0;
line-height: 8em;
position: absolute;
text-align: center;
width: 100%;
}
/* Wide */
@media screen and (max-width: 1680px) {
/* Basic */
body, input, select, textarea {
font-size: 13pt;
}
/* BG */
@-moz-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-1500px,0,0);
-webkit-transform: translate3d(-1500px,0,0);
-ms-transform: translate3d(-1500px,0,0);
transform: translate3d(-1500px,0,0);
}
}
@-webkit-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-1500px,0,0);
-webkit-transform: translate3d(-1500px,0,0);
-ms-transform: translate3d(-1500px,0,0);
transform: translate3d(-1500px,0,0);
}
}
@-ms-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-1500px,0,0);
-webkit-transform: translate3d(-1500px,0,0);
-ms-transform: translate3d(-1500px,0,0);
transform: translate3d(-1500px,0,0);
}
}
@keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-1500px,0,0);
-webkit-transform: translate3d(-1500px,0,0);
-ms-transform: translate3d(-1500px,0,0);
transform: translate3d(-1500px,0,0);
}
}
#bg {
background-size: 1500px auto;
width: 4500px;
} }
/* Normal */
@media screen and (max-width: 1280px) {
/* Basic */
body, input, select, textarea {
font-size: 12pt;
}
/* BG */
@-moz-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-750px,0,0);
-webkit-transform: translate3d(-750px,0,0);
-ms-transform: translate3d(-750px,0,0);
transform: translate3d(-750px,0,0);
}
}
@-webkit-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-750px,0,0);
-webkit-transform: translate3d(-750px,0,0);
-ms-transform: translate3d(-750px,0,0);
transform: translate3d(-750px,0,0);
}
}
@-ms-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-750px,0,0);
-webkit-transform: translate3d(-750px,0,0);
-ms-transform: translate3d(-750px,0,0);
transform: translate3d(-750px,0,0);
}
}
@keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-750px,0,0);
-webkit-transform: translate3d(-750px,0,0);
-ms-transform: translate3d(-750px,0,0);
transform: translate3d(-750px,0,0);
}
}
#bg {
background-size: 750px auto;
width: 2250px;
} }
/* Mobile */
@media screen and (max-width: 736px) {
/* Basic */
body {
min-width: 320px;
}
body, input, select, textarea {
font-size: 11pt;
}
/* BG */
@-moz-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-300px,0,0);
-webkit-transform: translate3d(-300px,0,0);
-ms-transform: translate3d(-300px,0,0);
transform: translate3d(-300px,0,0);
}
}
@-webkit-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-300px,0,0);
-webkit-transform: translate3d(-300px,0,0);
-ms-transform: translate3d(-300px,0,0);
transform: translate3d(-300px,0,0);
}
}
@-ms-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-300px,0,0);
-webkit-transform: translate3d(-300px,0,0);
-ms-transform: translate3d(-300px,0,0);
transform: translate3d(-300px,0,0);
}
}
@keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-300px,0,0);
-webkit-transform: translate3d(-300px,0,0);
-ms-transform: translate3d(-300px,0,0);
transform: translate3d(-300px,0,0);
}
}
#bg {
background-size: 300px auto;
width: 900px;
}
/* Header */
#header h1 {
font-size: 2.5em;
}
#header p {
font-size: 1em;
}
#header nav {
font-size: 1em;
}
#header nav a:hover {
font-size: 1em;
}
#header nav a:active {
font-size: 1em;
} }
/* Mobile (Portrait) */
@media screen and (max-width: 480px) {
/* BG */
@-moz-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-412.5px,0,0);
-webkit-transform: translate3d(-412.5px,0,0);
-ms-transform: translate3d(-412.5px,0,0);
transform: translate3d(-412.5px,0,0);
}
}
@-webkit-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-412.5px,0,0);
-webkit-transform: translate3d(-412.5px,0,0);
-ms-transform: translate3d(-412.5px,0,0);
transform: translate3d(-412.5px,0,0);
}
}
@-ms-keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-412.5px,0,0);
-webkit-transform: translate3d(-412.5px,0,0);
-ms-transform: translate3d(-412.5px,0,0);
transform: translate3d(-412.5px,0,0);
}
}
@keyframes bg {
0% {
-moz-transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
100% {
-moz-transform: translate3d(-412.5px,0,0);
-webkit-transform: translate3d(-412.5px,0,0);
-ms-transform: translate3d(-412.5px,0,0);
transform: translate3d(-412.5px,0,0);
}
}
#bg {
background-size: 412.5px auto;
width: 1237.5px;
}
/* Header */
#header nav {
padding: 0 1em;
} }
================================================
FILE: public/css/noscript.css
================================================
/*
Aerial by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
/* Wrapper */
#wrapper {
opacity: 1 !important;
}
/* Overlay */
#overlay {
opacity: 1 !important;
}
/* Header */
#header {
opacity: 1 !important;
}
#header nav li {
opacity: 1 !important;
}
================================================
FILE: public/guess.java
================================================
package top.ginnnnnn.mooc.service.implement;
import java.security.SecureRandom;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import top.ginnnnnn.mooc.service.GameService;
@Service
public class GameServiceImpl implements GameService {
private static final SecureRandom RNG = new SecureRandom();
private static final String GUESS_GAME_PREFIX = "GinsGame-guess-";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public HashMap guess(String token, Double guess) {
final String key = GUESS_GAME_PREFIX + token;
init(key);
HashOperations hashOperations = stringRedisTemplate.opsForHash();
Double number = Double.valueOf((String)hashOperations.get(key, "number"));
boolean isLess = number > guess + 1e-6 / 2;
boolean isMore = number < guess - 1e-6 / 2;
boolean isPassed = !isLess && !isMore;
boolean isTalented = isPassed && !hashOperations.hasKey(key, "previous");
HashMap state = new HashMap<>(5);
state.put("less", isLess);
state.put("more", isMore);
hashOperations.putIfAbsent(key, "previous", "0");
hashOperations.increment(key, "previous", 1);
if (isPassed) {
hashOperations.put(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
hashOperations.delete(key, "previous");
hashOperations.increment(key, "passed", 1);
}
if (isTalented) {
hashOperations.increment(key, "talented", 1);
state.put("reward", Boolean.TRUE);
} else {
state.put("reward", Boolean.FALSE);
}
HashMap ret = new HashMap<>(3);
ret.put("info", infoBuild(key));
ret.put("state", state);
return ret;
}
public HashMap getState(String token) {
final String key = GUESS_GAME_PREFIX + token;
init(key);
return infoBuild(key);
}
public boolean refresh(String token) {
final String key = GUESS_GAME_PREFIX + token;
init(key);
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put(key, "passed", "0");
hashOperations.put(key, "talented", "0");
hashOperations.put(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
hashOperations.delete(key, "previous");
return true;
}
private void init(String key) {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.putIfAbsent(key, "passed", "0");
hashOperations.putIfAbsent(key, "talented", "0");
hashOperations.putIfAbsent(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
}
private HashMap infoBuild(String key) {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
HashMap info = new HashMap<>(3);
info.put("passed", hashOperations.get(key, "passed"));
info.put("talented", hashOperations.get(key, "talented"));
return info;
}
}
================================================
FILE: public/sass/libs/_breakpoints.scss
================================================
// breakpoints.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Breakpoints.
/// @var {list}
$breakpoints: () !global;
// Mixins.
/// Sets breakpoints.
/// @param {map} $x Breakpoints.
@mixin breakpoints($x: ()) {
$breakpoints: $x !global;
}
/// Wraps @content in a @media block targeting a specific orientation.
/// @param {string} $orientation Orientation.
@mixin orientation($orientation) {
@media screen and (orientation: #{$orientation}) {
@content;
}
}
/// Wraps @content in a @media block using a given query.
/// @param {string} $query Query.
@mixin breakpoint($query: null) {
$breakpoint: null;
$op: null;
$media: null;
// Determine operator, breakpoint.
// Greater than or equal.
@if (str-slice($query, 0, 2) == '>=') {
$op: 'gte';
$breakpoint: str-slice($query, 3);
}
// Less than or equal.
@elseif (str-slice($query, 0, 2) == '<=') {
$op: 'lte';
$breakpoint: str-slice($query, 3);
}
// Greater than.
@elseif (str-slice($query, 0, 1) == '>') {
$op: 'gt';
$breakpoint: str-slice($query, 2);
}
// Less than.
@elseif (str-slice($query, 0, 1) == '<') {
$op: 'lt';
$breakpoint: str-slice($query, 2);
}
// Not.
@elseif (str-slice($query, 0, 1) == '!') {
$op: 'not';
$breakpoint: str-slice($query, 2);
}
// Equal.
@else {
$op: 'eq';
$breakpoint: $query;
}
// Build media.
@if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
$a: map-get($breakpoints, $breakpoint);
// Range.
@if (type-of($a) == 'list') {
$x: nth($a, 1);
$y: nth($a, 2);
// Max only.
@if ($x == null) {
// Greater than or equal (>= 0 / anything)
@if ($op == 'gte') {
$media: 'screen';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< 0 / invalid)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: -1px)';
}
// Not (> y)
@elseif ($op == 'not') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (<= y)
@else {
$media: 'screen and (max-width: ' + $y + ')';
}
}
// Min only.
@else if ($y == null) {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= inf / anything)
@elseif ($op == 'lte') {
$media: 'screen';
}
// Greater than (> inf / invalid)
@elseif ($op == 'gt') {
$media: 'screen and (max-width: -1px)';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Equal (>= x)
@else {
$media: 'screen and (min-width: ' + $x + ')';
}
}
// Min and max.
@else {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x and > y)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (>= x and <= y)
@else {
$media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
}
}
}
// String.
@else {
// Missing a media type? Prefix with "screen".
@if (str-slice($a, 0, 1) == '(') {
$media: 'screen and ' + $a;
}
// Otherwise, use as-is.
@else {
$media: $a;
}
}
}
// Output.
@media #{$media} {
@content;
}
}
================================================
FILE: public/sass/libs/_functions.scss
================================================
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Gets a value from a map.
/// @author Hugo Giraudel
/// @param {map} $map Map.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function val($map, $keys...) {
@if nth($keys, 1) == null {
$keys: remove-nth($keys, 1);
}
@each $key in $keys {
$map: map-get($map, $key);
}
@return $map;
}
/// Gets a duration value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _duration($keys...) {
@return val($duration, $keys...);
}
/// Gets a font value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _font($keys...) {
@return val($font, $keys...);
}
/// Gets a misc value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _misc($keys...) {
@return val($misc, $keys...);
}
/// Gets a palette value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _palette($keys...) {
@return val($palette, $keys...);
}
/// Gets a size value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _size($keys...) {
@return val($size, $keys...);
}
================================================
FILE: public/sass/libs/_mixins.scss
================================================
/// Makes an element's :before pseudoelement a FontAwesome icon.
/// @param {string} $content Optional content value to use.
/// @param {string} $category Optional category to use.
/// @param {string} $where Optional pseudoelement to target (before or after).
@mixin icon($content: false, $category: regular, $where: before) {
text-decoration: none;
&:#{$where} {
@if $content {
content: $content;
}
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
@if ($category == brands) {
font-family: 'Font Awesome 5 Brands';
}
@elseif ($category == solid) {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
@else {
font-family: 'Font Awesome 5 Free';
font-weight: 400;
}
}
}
/// Applies padding to an element, taking the current element-margin value into account.
/// @param {mixed} $tb Top/bottom padding.
/// @param {mixed} $lr Left/right padding.
/// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left)
/// @param {bool} $important If true, adds !important.
@mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) {
@if $important {
$important: '!important';
}
$x: 0.1em;
@if unit(_size(element-margin)) == 'rem' {
$x: 0.1rem;
}
padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important};
}
/// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp).
/// @param {string} $svg SVG data URL.
/// @return {string} Encoded SVG data URL.
@function svg-url($svg) {
$svg: str-replace($svg, '"', '\'');
$svg: str-replace($svg, '%', '%25');
$svg: str-replace($svg, '<', '%3C');
$svg: str-replace($svg, '>', '%3E');
$svg: str-replace($svg, '&', '%26');
$svg: str-replace($svg, '#', '%23');
$svg: str-replace($svg, '{', '%7B');
$svg: str-replace($svg, '}', '%7D');
$svg: str-replace($svg, ';', '%3B');
@return url("data:image/svg+xml;charset=utf8,#{$svg}");
}
================================================
FILE: public/sass/libs/_vars.scss
================================================
// Misc.
$misc: (
bg: #348cb2 url("images/bg.jpg") bottom left,
bg-width: 1500px
);
// Duration.
$duration: (
bg: 60s,
wrapper: 3s,
overlay: 1.5s,
header: 1s,
nav-icons: 0.5s
);
// Size.
$size: (
nav-icon-wrapper: 5.35em,
nav-icon: 1.75em
);
// Font.
$font: (
);
// Palette.
$palette: (
bg: #fff,
fg: #fff,
nav-icon: (
hover-bg: rgba(255,255,255,0.175),
hover-fg: #fff,
active-bg: rgba(255,255,255,0.35),
active-fg: #fff
)
);
================================================
FILE: public/sass/libs/_vendor.scss
================================================
// vendor.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Vendor prefixes.
/// @var {list}
$vendor-prefixes: (
'-moz-',
'-webkit-',
'-ms-',
''
);
/// Properties that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-properties: (
// Animation.
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-fill-mode',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
// Appearance.
'appearance',
// Backdrop filter.
'backdrop-filter',
// Background image options.
'background-clip',
'background-origin',
'background-size',
// Box sizing.
'box-sizing',
// Clip path.
'clip-path',
// Filter effects.
'filter',
// Flexbox.
'align-content',
'align-items',
'align-self',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'justify-content',
'order',
// Font feature.
'font-feature-settings',
'font-language-override',
'font-variant-ligatures',
// Font kerning.
'font-kerning',
// Fragmented borders and backgrounds.
'box-decoration-break',
// Grid layout.
'grid-column',
'grid-column-align',
'grid-column-end',
'grid-column-start',
'grid-row',
'grid-row-align',
'grid-row-end',
'grid-row-start',
'grid-template-columns',
'grid-template-rows',
// Hyphens.
'hyphens',
'word-break',
// Masks.
'mask',
'mask-border',
'mask-border-outset',
'mask-border-repeat',
'mask-border-slice',
'mask-border-source',
'mask-border-width',
'mask-clip',
'mask-composite',
'mask-image',
'mask-origin',
'mask-position',
'mask-repeat',
'mask-size',
// Multicolumn.
'break-after',
'break-before',
'break-inside',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
// Object fit.
'object-fit',
'object-position',
// Regions.
'flow-from',
'flow-into',
'region-fragment',
// Scroll snap points.
'scroll-snap-coordinate',
'scroll-snap-destination',
'scroll-snap-points-x',
'scroll-snap-points-y',
'scroll-snap-type',
// Shapes.
'shape-image-threshold',
'shape-margin',
'shape-outside',
// Tab size.
'tab-size',
// Text align last.
'text-align-last',
// Text decoration.
'text-decoration-color',
'text-decoration-line',
'text-decoration-skip',
'text-decoration-style',
// Text emphasis.
'text-emphasis',
'text-emphasis-color',
'text-emphasis-position',
'text-emphasis-style',
// Text size adjust.
'text-size-adjust',
// Text spacing.
'text-spacing',
// Transform.
'transform',
'transform-origin',
// Transform 3D.
'backface-visibility',
'perspective',
'perspective-origin',
'transform-style',
// Transition.
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
// Unicode bidi.
'unicode-bidi',
// User select.
'user-select',
// Writing mode.
'writing-mode',
);
/// Values that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-values: (
// Cross fade.
'cross-fade',
// Element function.
'element',
// Filter function.
'filter',
// Flexbox.
'flex',
'inline-flex',
// Grab cursors.
'grab',
'grabbing',
// Gradients.
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient',
// Grid layout.
'grid',
'inline-grid',
// Image set.
'image-set',
// Intrinsic width.
'max-content',
'min-content',
'fit-content',
'fill',
'fill-available',
'stretch',
// Sticky position.
'sticky',
// Transform.
'transform',
// Zoom cursors.
'zoom-in',
'zoom-out',
);
// Functions.
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Replaces a substring within another string.
/// @author Hugo Giraudel
/// @param {string} $string String.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {string} Updated string.
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/// Replaces a substring within each string in a list.
/// @param {list} $strings List of strings.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {list} Updated list of strings.
@function str-replace-all($strings, $search, $replace: '') {
@each $string in $strings {
$strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
}
@return $strings;
}
// Mixins.
/// Wraps @content in vendorized keyframe blocks.
/// @param {string} $name Name.
@mixin keyframes($name) {
@-moz-keyframes #{$name} { @content; }
@-webkit-keyframes #{$name} { @content; }
@-ms-keyframes #{$name} { @content; }
@keyframes #{$name} { @content; }
}
/// Vendorizes a declaration's property and/or value(s).
/// @param {string} $property Property.
/// @param {mixed} $value String/list of value(s).
@mixin vendor($property, $value) {
// Determine if property should expand.
$expandProperty: index($vendor-properties, $property);
// Determine if value should expand (and if so, add '-prefix-' placeholder).
$expandValue: false;
@each $x in $value {
@each $y in $vendor-values {
@if $y == str-slice($x, 1, str-length($y)) {
$value: set-nth($value, index($value, $x), '-prefix-' + $x);
$expandValue: true;
}
}
}
// Expand property?
@if $expandProperty {
@each $vendor in $vendor-prefixes {
#{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Expand just the value?
@elseif $expandValue {
@each $vendor in $vendor-prefixes {
#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Neither? Treat them as a normal declaration.
@else {
#{$property}: #{$value};
}
}
================================================
FILE: public/sass/main.scss
================================================
@import 'libs/vars';
@import 'libs/functions';
@import 'libs/mixins';
@import 'libs/vendor';
@import 'libs/breakpoints';
@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900");
@import url("fontawesome-all.min.css");
/*
Aerial by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
// Breakpoints.
@include breakpoints((
wide: ( 1281px, 1680px ),
normal: ( 737px, 1280px ),
mobile: ( 481px, 736px ),
mobilep: ( null, 480px )
));
// Mixins.
@mixin bg($width) {
@include keyframes('bg') {
0% { @include vendor('transform', 'translate3d(0,0,0)'); }
100% { @include vendor('transform', 'translate3d(#{$width * -1},0,0)'); }
}
#bg {
background-size: $width auto;
width: ($width * 3);
}
}
$delay-wrapper: _duration(wrapper) - 1s;
$delay-overlay: $delay-wrapper - 0.5s;
$delay-header: $delay-overlay + _duration(overlay) - 0.75s;
$delay-nav-icons: $delay-header + _duration(header) - 1s;
$delay-nav-icon: 0.25s;
// Reset.
// Based on meyerweb.com/eric/tools/css/reset (v2.0 | 20110126 | License: public domain)
html, body, div, span, applet, object,
iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, a, abbr, acronym, address, big, cite,
code, del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var, b,
u, i, center, dl, dt, dd, ol, ul, li, fieldset,
form, label, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside,
canvas, details, embed, figure, figcaption,
footer, header, hgroup, menu, nav, output, ruby,
section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style:none;
}
blockquote, q {
quotes: none;
&:before,
&:after {
content: '';
content: none;
}
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
-webkit-text-size-adjust: none;
}
mark {
background-color: transparent;
color: inherit;
}
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input, select, textarea {
-moz-appearance: none;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
}
/* Basic */
// Set box model to border-box.
// Based on css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
background: _palette(bg);
overflow: hidden;
// Stops initial animations until page loads.
&.is-preload {
*, *:before, *:after {
@include vendor('animation', 'none !important');
@include vendor('transition', 'none !important');
}
}
}
body, input, select, textarea {
color: _palette(fg);
font-family: 'Source Sans Pro', sans-serif;
font-size: 15pt;
font-weight: 300 !important;
letter-spacing: -0.025em;
line-height: 1.75em;
}
a {
@include vendor('transition', 'border-color 0.2s ease-in-out');
border-bottom: dotted 1px;
color: inherit;
outline: 0;
text-decoration: none;
&:hover {
border-color: transparent;
}
}
/* Icon */
.icon {
@include icon;
position: relative;
> .label {
display: none;
}
&.solid {
&:before {
font-weight: 900;
}
}
&.brands {
&:before {
font-family: 'Font Awesome 5 Brands';
}
}
}
/* Wrapper */
@include keyframes('wrapper') {
0% { opacity: 0; }
100% { opacity: 1; }
}
#wrapper {
@include vendor('animation', 'wrapper #{_duration(wrapper)} forwards');
height: 100%;
left: 0;
opacity: 0;
position: fixed;
top: 0;
width: 100%;
}
/* BG */
#bg {
@include vendor('animation', 'bg #{_duration(bg)} linear infinite');
@include vendor('backface-visibility', 'hidden');
@include vendor('transform', 'translate3d(0,0,0)');
/* Set your background with this */
background: _misc(bg);
background-repeat: repeat-x;
height: 100%;
left: 0;
opacity: 1;
position: fixed;
top: 0;
}
@include bg(_misc(bg-width) * 1.5);
/* Overlay */
@include keyframes('overlay') {
0% { opacity: 0; }
100% { opacity: 1; }
}
#overlay {
@include vendor('animation', 'overlay #{_duration(overlay)} #{$delay-overlay} forwards');
background-attachment: fixed, fixed;
background-image: url('images/overlay-pattern.png'), url('images/overlay.svg');
background-position: top left, center center;
background-repeat: repeat, no-repeat;
background-size: auto, cover;
height: 100%;
left: 0;
opacity: 0;
position: fixed;
top: 0;
width: 100%;
}
/* Main */
#main {
height: 100%;
left: 0;
position: fixed;
text-align: center;
top: 0;
width: 100%;
&:before {
content: '';
display: inline-block;
height: 100%;
margin-right: 0;
vertical-align: middle;
width: 1px;
}
}
/* Header */
@include keyframes('header') {
0% { @include vendor('transform', 'translate3d(0,1em,0)'); opacity: 0; }
100% { @include vendor('transform', 'translate3d(0,0,0)'); opacity: 1; }
}
@include keyframes('nav-icons') {
0% { @include vendor('transform', 'translate3d(0,1em,0)'); opacity: 0; }
100% { @include vendor('transform', 'translate3d(0,0,0)'); opacity: 1; }
}
#header {
@include vendor('animation', 'header #{_duration(header)} #{$delay-header} forwards');
@include vendor('backface-visibility', 'hidden');
@include vendor('transform', 'translate3d(0,0,0)');
cursor: default;
display: inline-block;
opacity: 0;
position: relative;
text-align: center;
top: -1em;
vertical-align: middle;
width: 90%;
h1 {
font-size: 4.35em;
font-weight: 900;
letter-spacing: -0.035em;
line-height: 1em;
}
p {
font-size: 1.25em;
margin: 0.75em 0 0.25em 0;
opacity: 0.75;
}
nav {
margin: 1.5em 0 0 0;
li {
@include vendor('animation', 'nav-icons #{_duration(nav-icons)} ease-in-out forwards');
@include vendor('backface-visibility', 'hidden');
@include vendor('transform', 'translate3d(0,0,0)');
display: inline-block;
height: _size(nav-icon-wrapper);
line-height: _size(nav-icon-wrapper) * 1.1;
opacity: 0;
position: relative;
top: 0;
width: _size(nav-icon-wrapper);
@for $x from 1 through 10 {
&:nth-child(#{$x}) {
@include vendor('animation-delay', ($delay-nav-icons + ($x * $delay-nav-icon)) + '');
}
}
}
a {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
border: 0;
display: inline-block;
&:before {
@include vendor('transition', 'all 0.2s ease-in-out');
border-radius: 100%;
border: solid 1px _palette(fg);
display: block;
font-size: _size(nav-icon);
height: 2.5em;
line-height: 2.5em;
position: relative;
text-align: center;
top: 0;
width: 2.5em;
}
&:hover {
font-size: 1.1em;
&:before {
background-color: _palette(nav-icon, hover-bg);
color: _palette(nav-icon, hover-fg);
}
}
&:active {
font-size: 0.95em;
background: none;
&:before {
background-color: _palette(nav-icon, active-bg);
color: _palette(nav-icon, active-fg);
}
}
span {
display: none;
}
}
}
}
/* Footer */
#footer {
@include vendor('background-image', 'linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%)');
bottom: 0;
cursor: default;
height: 6em;
left: 0;
line-height: 8em;
position: absolute;
text-align: center;
width: 100%;
}
/* Wide */
@include breakpoint('<=wide') {
/* Basic */
body, input, select, textarea {
font-size: 13pt;
}
/* BG */
@include bg(_misc(bg-width));
}
/* Normal */
@include breakpoint('<=normal') {
/* Basic */
body, input, select, textarea {
font-size: 12pt;
}
/* BG */
@include bg(_misc(bg-width) * 0.5);
}
/* Mobile */
@include breakpoint('<=mobile') {
/* Basic */
body {
min-width: 320px;
}
body, input, select, textarea {
font-size: 11pt;
}
/* BG */
@include bg(_misc(bg-width) * 0.2);
/* Header */
#header {
h1 {
font-size: 2.5em;
}
p {
font-size: 1em;
}
nav {
font-size: 1em;
a {
&:hover {
font-size: 1em;
}
&:active {
font-size: 1em;
}
}
}
}
}
/* Mobile (Portrait) */
@include breakpoint('<=mobilep') {
/* BG */
@include bg(_misc(bg-width) * 0.275);
/* Header */
#header {
nav {
padding: 0 1em;
}
}
}
================================================
FILE: public/sass/noscript.scss
================================================
@import 'libs/vars';
@import 'libs/functions';
@import 'libs/mixins';
@import 'libs/vendor';
@import 'libs/breakpoints';
/*
Aerial by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
/* Wrapper */
#wrapper {
opacity: 1 !important;
}
/* Overlay */
#overlay {
opacity: 1 !important;
}
/* Header */
#header {
opacity: 1 !important;
nav {
li {
opacity: 1 !important;
}
}
}
================================================
FILE: src/App.vue
================================================
================================================
FILE: src/components/CourseCard.vue
================================================
{{ course.name }}
{{ course.school }}
================================================
FILE: src/components/QuestionCard.vue
================================================
================================================
FILE: src/components/icon/Extension.vue
================================================
================================================
FILE: src/components/icon/Github.vue
================================================
================================================
FILE: src/components/icon/index.ts
================================================
import Github from "./Github.vue"
import Extension from "./Extension.vue"
export { Github, Extension }
================================================
FILE: src/components/index.ts
================================================
import CourseCard from "./CourseCard.vue"
import QuestionCard from "./QuestionCard.vue"
export {CourseCard, QuestionCard}
================================================
FILE: src/components/question/Completion.vue
================================================
================================================
FILE: src/components/question/Homework.vue
================================================
================================================
FILE: src/components/question/MultipleChoice.vue
================================================
================================================
FILE: src/components/question/OnlineJudge.vue
================================================
描述
时间限制{{ timeLimit }}
内存限制{{ memoryLimit }}
================================================
FILE: src/components/question/SingleChoice.vue
================================================
================================================
FILE: src/components/question/index.ts
================================================
import SingleChoice from "./SingleChoice.vue"
import MultipleChoice from "./MultipleChoice.vue"
import Completion from "./Completion.vue"
import Homework from "./Homework.vue"
import OnlineJudge from "./OnlineJudge.vue"
export { SingleChoice, MultipleChoice, Completion, Homework, OnlineJudge }
================================================
FILE: src/main.ts
================================================
import { createApp } from "vue"
import ElementPlus from 'element-plus'
import router from "./router"
import App from "./App.vue"
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'element-plus/theme-chalk/display.css'
const app = createApp(App)
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(router)
app.mount("body")
================================================
FILE: src/plugins/apiAccess.ts
================================================
import type { ApiKeyType, ApiResponseType, ApiRequestType } from "../type/api"
import type { App } from "vue"
import { isAxiosError } from "axios"
import { ElMessage } from "element-plus"
import apiInfo from "../type/api"
import axios from "axios"
async function apiAccess(api: T): Promise
async function apiAccess(
api: T,
params: ApiRequestType[T]["params"],
data: ApiRequestType[T]["data"]
): Promise
/** 函数重载 */
async function apiAccess(
api: T,
params?: ApiRequestType[T]["params"],
data?: ApiRequestType[T]["data"]
) {
/** 错误处理,主要catch 404,调用者不再需要try-catch */
try {
return await new Promise((resolve, reject) => {
/** 查询参数转动态路由参数 */
let url = apiInfo[api].url
if (params) {
for (const [key, val] of Object.entries(params)) {
const reg = RegExp(`(/):${key}(/)?`, "g")
if (reg.test(url)) {
url = url.replaceAll(reg, `$1${val}$2`)
Reflect.deleteProperty(params, key)
}
}
}
/** 将对象转为json字符串 */
if (data) {
for (const [key, val] of Object.entries(data)) {
if (typeof val === "object") {
Reflect.set(data, key, JSON.stringify(val))
}
}
}
/** 异步发送请求 */
let urlPrefix = "https://ginnnnnn.top/api"
axios({
url: urlPrefix + url,
method: apiInfo[api].method,
params: params || {},
data: data || {},
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
.then((res) => {
let message = "",
success = false
if (res.status !== 200 || !res.data) {
message = "请求出错"
} else if (res.data.msg) {
message = res.data.msg
if (res.data.status === 200) {
success = true
}
}
if (message) {
ElMessage({ message, type: success ? "success" : "error" })
}
resolve(res.data)
})
.catch((error) => {
let message = error
if (isAxiosError(error)) {
message = error.message
}
ElMessage({ message, type: "error" })
reject(error)
})
})
} catch {
return {}
}
}
export const useApiAccess = () => apiAccess
export default {
install: (app: App) => {
app.config.globalProperties.$apiAccess = apiAccess
}
}
================================================
FILE: src/plugins/tool.ts
================================================
const sleep = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('');
}, ms)
})
}
export { sleep }
================================================
FILE: src/router/index.ts
================================================
import { createRouter, createWebHistory } from "vue-router"
import { HomeView, MoocView, MoocHeader, MoocCourseDetail, MoocTest, VideoView } from "@/views"
import { ElMessage } from "element-plus"
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Home",
meta: { title: "Gins" },
component: HomeView
},
{
path: "/ginnnnnn/video",
name: "Video",
meta: { title: "GinsVideo" },
component: VideoView
},
{
path: "/mooc",
name: "Mooc",
components: {
default: MoocView,
header: MoocHeader
},
meta: { title: "GinsMooc" },
children: [
{ path: "course/:cid", name: "MoocCourse", component: MoocCourseDetail, props: true },
{ path: "test/:tid", name: "MoocTest", component: MoocTest, props: true }
]
}
]
})
router.beforeEach((to, from) => {
if (!to.name) {
ElMessage.error("路由错误")
return { name: "Home" }
}
if (to.meta.title) {
document.title = to.meta.title
}
return true
})
export default router
const checkMoocItemId = (id: string) => /[0-9]/.test(id)
================================================
FILE: src/type/api.ts
================================================
import type { course, homework, notice, quiz, test } from "./mooc"
type RequestType = {
params?: Object
data?: Object
}
type Response = {
status: number
data: T
msg: string
}
export interface courseList extends Object {
courseList: course[]
totalPages: number
currentPage: number
}
export interface courseDetail extends Object {
course: course
testList: test[]
}
export interface testDetail extends Object {
course: course
test: test
questionList: quiz[] | homework[]
totalPages: number
}
const apiInfo = {
getCourseList: {
url: "/mooc/course",
method: "GET"
},
getCourseDetail: {
url: "/mooc/course/:cid",
method: "GET"
},
getTestDetail: {
url: "/mooc/test/:tid",
method: "GET"
},
getNotice: {
url: "/mooc/notice/website",
method: "GET"
}
}
export type ApiKeyType = keyof typeof apiInfo
export interface ApiResponseType {
getCourseList: Response
getCourseDetail: Response
getTestDetail: Response
getNotice: Response
}
export interface ApiRequestType {
getCourseList: RequestType & {
params: {
page?: number | string
search?: string
cid?: number | string
}
}
getCourseDetail: RequestType & {
params: { cid: number | string }
}
getTestDetail: RequestType & {
params: {
tid: number | string
page: number | string
search?: string
}
}
getNotice: RequestType & {
params: { version: string }
}
}
export default apiInfo
================================================
FILE: src/type/globleProperties.ts
================================================
import { useApiAccess } from "@/plugins/apiAccess"
const apiAccess = useApiAccess()
declare module 'vue' {
interface ComponentCustomProperties {
$apiAccess: typeof apiAccess
}
}
================================================
FILE: src/type/mooc.ts
================================================
export enum QuestionTypeEnumList {
SingleChoice = "SINGLE_CHOICE",
MultipleChoice = "MULTIPLE_CHOICE",
Completion = "COMPLETION",
Judge = "JUDGEMENT",
Homework = "HOMEWORK",
OnlineJudge = "ONLINE_JUDGE"
}
interface course extends Object {
id: number
name: string
school: string
imageUrl: string
}
interface test extends Object {
id: number
name: string
objective: boolean
releaseTime: string
deadline: string
chapterId: number
chapterName: string
}
interface option extends Object {
id: number
content: string
answer: boolean
}
interface quiz extends Object {
id: number
type:
| QuestionTypeEnumList.SingleChoice
| QuestionTypeEnumList.MultipleChoice
| QuestionTypeEnumList.Completion
| QuestionTypeEnumList.Judge
title: string
stdAnswer: string | null
optionList: option[] | null
}
interface homework extends Object {
id: number
type: QuestionTypeEnumList.Homework | QuestionTypeEnumList.OnlineJudge
title: string
answer: string | null
description: string | null
memoryLimit: number | null
timeLimit: number | null
}
interface notice extends Object {
id: number
content: string
}
export type QuestionTypeEnum = typeof QuestionTypeEnumList
export type { course, test, option, quiz, homework, notice }
================================================
FILE: src/views/BlogView.vue
================================================
================================================
FILE: src/views/HomeView.vue
================================================
================================================
FILE: src/views/MoocAside.vue
================================================
添加新课程
确认
================================================
FILE: src/views/MoocCourseDetail.vue
================================================
{{ chapter[0].chapterName }}
{{ test.name }}
{{ test.releaseTime }}
{{ test.deadline }}
================================================
FILE: src/views/MoocHeader.vue
================================================
GinsMooc Extension Helper
实现对于中国大学MOOC的
- 非在线测评题的自动答案查询,包括单选题、多选题、判断题、填空题、简答题,支持测验与作业及考试
- 互评阶段的自动评分、自动点评
下载地址:
https://ginnnnnn.top/download/GinsMoocExtension_v2.2.1.zip
在测试的准备页面,将会自动检查是否准备就绪,若为否将自动更新课程
进入测验后,将显示“获取答案”按钮,点击即可
作业的互评阶段支持自动评分、自动点评
下载安装包后,将其解压至文件夹内
在浏览器地址栏中输入edge://extensions(谷歌浏览器为chrome://extensions)
打开开发者模式
点击“加载解压缩的扩展”,选择刚刚解压到的文件夹,即可开始使用
================================================
FILE: src/views/MoocTest.vue
================================================
================================================
FILE: src/views/MoocView.vue
================================================
显示答案
显示答案
================================================
FILE: src/views/VideoView.vue
================================================
{{ api.name }}
本系统只为内部交流学习,不以盈利为目的
所有资源均来源第三方资源,并不提供影片资源存储,录制、上传相关视频等,视频版权归属其合法持有人所有,本站不对使用者的行为负担任何法律责任
如果有因为本站而导致您的权益受到损害,请与我们联系,我们将理性对待,协助你解决相关问题
================================================
FILE: src/views/index.ts
================================================
import HomeView from "./HomeView.vue"
import MoocView from "./MoocView.vue"
import MoocHeader from "./MoocHeader.vue"
import MoocAside from "./MoocAside.vue"
import MoocCourseDetail from "./MoocCourseDetail.vue"
import MoocTest from "./MoocTest.vue"
import VideoView from "./VideoView.vue"
export { HomeView, MoocView, MoocHeader, MoocAside, MoocCourseDetail, MoocTest, VideoView }
================================================
FILE: tsconfig.config.json
================================================
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"types": [
"node"
]
}
}
================================================
FILE: tsconfig.json
================================================
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
"types": ["element-plus/global"]
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
================================================
FILE: vite.config.ts
================================================
import { fileURLToPath, URL } from "node:url"
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [ vue() ],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
}
})