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/) # 功能介绍 在测试的准备页面,将会自动检查是否准备就绪,若为否将自动更新课程 ![](/public/extension-updating.png) 进入测验后,将显示“获取答案”按钮,点击即可 ![](/public/extension-single-choice.png) ![](/public/extension-multiple-choice.png) ![](/public/extension-completion.png) ![](/public/extension-homework.png) 作业的互评阶段支持自动评分、自动点评 ![](/public/extension-auto-evaluate-1.png) ![](/public/extension-auto-evaluate-2.png) # 安装介绍 下载安装包后,将其解压至文件夹内 在浏览器地址栏中输入`edge://extensions`(谷歌浏览器为`chrome://extensions`) 打开开发者模式 ![](/public/extension-developer-mode.png) 点击“加载解压缩的扩展”,选择刚刚解压到的文件夹,即可开始使用 ![](/public/extension-load-decompression.png) ================================================ 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 ================================================ ================================================ 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 ================================================ ================================================ 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 ================================================ ================================================ FILE: src/views/MoocHeader.vue ================================================ ================================================ FILE: src/views/MoocTest.vue ================================================ ================================================ FILE: src/views/MoocView.vue ================================================ ================================================ FILE: src/views/VideoView.vue ================================================ ================================================ 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)) } } })