您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
コンテスト中にAtCoderのパフォーマンスを予測します
当前为
// ==UserScript== // @name ac-predictor // @namespace http://ac-predictor.azurewebsites.net/ // @version 1.1.0 // @description コンテスト中にAtCoderのパフォーマンスを予測します // @author keymoon // @license MIT // @supportURL https://github.com/key-moon/ac-predictor.user.js/issues // @match https://atcoder.jp/* // @exclude https://atcoder.jp/*/json // ==/UserScript== /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 10); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { module.exports = jQuery; /***/ }), /* 1 */ /***/ (function(module, exports) { module.exports = moment; /***/ }), /* 2 */ /***/ (function(module, exports) { module.exports = "<div id=\"menu_wrap\">\r\n <div id=\"sidemenu\" class=\"container\"></div>\r\n <div id=\"sidemenu-key\" class=\"glyphicon glyphicon-menu-left\"></div>\r\n</div>"; /***/ }), /* 3 */ /***/ (function(module, exports) { module.exports = "<div id=\"predictor-alert\" class=\"row\"><h5 class='sidemenu-txt'>順位表読み込み中…</h5></div>\r\n<div id=\"predictor-data\" class=\"row\">\r\n <div class=\"input-group col-xs-offset-1 col-xs-10\">\r\n <span class=\"input-group-addon\">順位<span class=\"glyphicon glyphicon-question-sign\" aria-hidden=\"true\" data-html=\"true\" data-toggle=\"tooltip\" data-placement=\"right\" title=\"\" data-original-title=\"Rated内の順位です。複数人同順位の際は人数を加味します(5位が4人居たら6.5位として計算)\"></span></span>\r\n <input class=\"form-control\" id=\"predictor-input-rank\">\r\n <span class=\"input-group-addon\">位</span>\r\n </div>\r\n \r\n <div class=\"input-group col-xs-offset-1 col-xs-10\">\r\n <span class=\"input-group-addon\">パフォーマンス</span>\r\n <input class=\"form-control\" id=\"predictor-input-perf\">\r\n </div>\r\n\r\n <div class=\"input-group col-xs-offset-1 col-xs-10\">\r\n <span class=\"input-group-addon\">レーティング</span>\r\n <input class=\"form-control\" id=\"predictor-input-rate\">\r\n </div>\r\n</div>\r\n<div class=\"row\">\r\n <div class=\"btn-group col-xs-offset-1\">\r\n <button class=\"btn btn-default\" id=\"predictor-current\">現在の順位</button>\r\n <button type=\"button\" class=\"btn btn-primary\" id=\"predictor-reload\" data-loading-text=\"更新中…\">更新</button>\r\n <a class=\"btn btn-default\" rel=\"nofollow\" onClick=\"window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;\" id='predictor-tweet'>ツイート</a>\r\n <!--<button class=\"btn btn-default\" id=\"predictor-solved\" disabled>現問題AC後</button>-->\r\n </div>\r\n</div>"; /***/ }), /* 4 */ /***/ (function(module, exports) { module.exports = "<div id=\"estimator-alert\"></div>\r\n<div class=\"row\">\r\n\t<div class=\"input-group\">\r\n\t\t<span class=\"input-group-addon\" id=\"estimator-input-desc\"></span>\r\n\t\t<input type=\"number\" class=\"form-control\" id=\"estimator-input\">\r\n\t</div>\r\n</div>\r\n<div class=\"row\">\r\n\t<div class=\"input-group\">\r\n\t\t<span class=\"input-group-addon\" id=\"estimator-res-desc\"></span>\r\n\t\t<input class=\"form-control\" id=\"estimator-res\" disabled=\"disabled\">\r\n\t\t<span class=\"input-group-btn\">\r\n\t\t\t<button class=\"btn btn-default\" id=\"estimator-toggle\">入替</button>\r\n\t\t</span>\r\n\t</div>\r\n</div>\r\n<div class=\"row\" style=\"margin: 10px 0px;\">\r\n\t<a class=\"btn btn-default col-xs-offset-8 col-xs-4\" rel=\"nofollow\" onClick=\"window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;\" id='estimator-tweet'>ツイート</a>\r\n</div>"; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { var content = __webpack_require__(6); if(typeof content === 'string') content = [[module.i, content, '']]; var transform; var insertInto; var options = {"hmr":true} options.transform = transform options.insertInto = undefined; var update = __webpack_require__(8)(content, options); if(content.locals) module.exports = content.locals; if(false) {} /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { exports = module.exports = __webpack_require__(7)(false); // Module exports.push([module.i, "@charset \"UTF-8\";\n#menu_wrap {\n display: block;\n position: fixed;\n top: 0;\n z-index: 20;\n width: 400px;\n right: -350px;\n transition: all 150ms 0ms ease;\n margin-top: 50px; }\n\n#sidemenu {\n background: #000;\n opacity: 0.85; }\n\n#sidemenu-key {\n border-radius: 5px 0px 0px 5px;\n background: #000;\n opacity: 0.85;\n color: #FFF;\n padding: 30px 0;\n cursor: pointer;\n margin-top: 100px;\n text-align: center; }\n\n#sidemenu {\n display: inline-block;\n width: 350px;\n float: right; }\n\n#sidemenu-key {\n display: inline-block;\n width: 50px;\n float: right; }\n\n.sidemenu-active {\n transform: translateX(-350px); }\n\n.sidemenu-txt {\n color: #DDD; }\n\n/*アコーディオンメニュー*/\n.menu-wrapper {\n border-bottom: 1px solid #FFF; }\n\n.menu-header {\n margin: 10px 20px 10px 20px;\n user-select: none; }\n\n.menu-box {\n overflow: hidden;\n transition: all 300ms 0s ease; }\n\n.menu-box-collapse {\n height: 0px !important; }\n .menu-box-collapse .menu-content {\n transform: translateY(-100%); }\n\n.menu-content {\n padding: 10px 20px 10px 20px;\n transition: all 300ms 0s ease; }\n", ""]); /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ // css base code, injected by the css-loader module.exports = function (useSourceMap) { var list = []; // return the list of modules as css string list.toString = function toString() { return this.map(function (item) { var content = cssWithMappingToString(item, useSourceMap); if (item[2]) { return '@media ' + item[2] + '{' + content + '}'; } else { return content; } }).join(''); }; // import a list of modules into the list list.i = function (modules, mediaQuery) { if (typeof modules === 'string') { modules = [[null, modules, '']]; } var alreadyImportedModules = {}; for (var i = 0; i < this.length; i++) { var id = this[i][0]; if (id != null) { alreadyImportedModules[id] = true; } } for (i = 0; i < modules.length; i++) { var item = modules[i]; // skip already imported module // this implementation is not 100% perfect for weird media query combinations // when a module is imported multiple times with different media queries. // I hope this will never occur (Hey this way we have smaller bundles) if (item[0] == null || !alreadyImportedModules[item[0]]) { if (mediaQuery && !item[2]) { item[2] = mediaQuery; } else if (mediaQuery) { item[2] = '(' + item[2] + ') and (' + mediaQuery + ')'; } list.push(item); } } }; return list; }; function cssWithMappingToString(item, useSourceMap) { var content = item[1] || ''; var cssMapping = item[3]; if (!cssMapping) { return content; } if (useSourceMap && typeof btoa === 'function') { var sourceMapping = toComment(cssMapping); var sourceURLs = cssMapping.sources.map(function (source) { return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'; }); return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); } return [content].join('\n'); } // Adapted from convert-source-map (MIT) function toComment(sourceMap) { // eslint-disable-next-line no-undef var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))); var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; return '/*# ' + data + ' */'; } /***/ }), /* 8 */ /***/ (function(module, exports, __webpack_require__) { /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ var stylesInDom = {}; var memoize = function (fn) { var memo; return function () { if (typeof memo === "undefined") memo = fn.apply(this, arguments); return memo; }; }; var isOldIE = memoize(function () { // Test for IE <= 9 as proposed by Browserhacks // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 // Tests for existence of standard globals is to allow style-loader // to operate correctly into non-standard environments // @see https://github.com/webpack-contrib/style-loader/issues/177 return window && document && document.all && !window.atob; }); var getTarget = function (target, parent) { if (parent){ return parent.querySelector(target); } return document.querySelector(target); }; var getElement = (function (fn) { var memo = {}; return function(target, parent) { // If passing function in options, then use it for resolve "head" element. // Useful for Shadow Root style i.e // { // insertInto: function () { return document.querySelector("#foo").shadowRoot } // } if (typeof target === 'function') { return target(); } if (typeof memo[target] === "undefined") { var styleTarget = getTarget.call(this, target, parent); // Special case to return head of iframe instead of iframe itself if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { try { // This will throw an exception if access to iframe is blocked // due to cross-origin restrictions styleTarget = styleTarget.contentDocument.head; } catch(e) { styleTarget = null; } } memo[target] = styleTarget; } return memo[target] }; })(); var singleton = null; var singletonCounter = 0; var stylesInsertedAtTop = []; var fixUrls = __webpack_require__(9); module.exports = function(list, options) { if (typeof DEBUG !== "undefined" && DEBUG) { if (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); } options = options || {}; options.attrs = typeof options.attrs === "object" ? options.attrs : {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style> // tags it will allow on a page if (!options.singleton && typeof options.singleton !== "boolean") options.singleton = isOldIE(); // By default, add <style> tags to the <head> element if (!options.insertInto) options.insertInto = "head"; // By default, add <style> tags to the bottom of the target if (!options.insertAt) options.insertAt = "bottom"; var styles = listToStyles(list, options); addStylesToDom(styles, options); return function update (newList) { var mayRemove = []; for (var i = 0; i < styles.length; i++) { var item = styles[i]; var domStyle = stylesInDom[item.id]; domStyle.refs--; mayRemove.push(domStyle); } if(newList) { var newStyles = listToStyles(newList, options); addStylesToDom(newStyles, options); } for (var i = 0; i < mayRemove.length; i++) { var domStyle = mayRemove[i]; if(domStyle.refs === 0) { for (var j = 0; j < domStyle.parts.length; j++) domStyle.parts[j](); delete stylesInDom[domStyle.id]; } } }; }; function addStylesToDom (styles, options) { for (var i = 0; i < styles.length; i++) { var item = styles[i]; var domStyle = stylesInDom[item.id]; if(domStyle) { domStyle.refs++; for(var j = 0; j < domStyle.parts.length; j++) { domStyle.parts[j](item.parts[j]); } for(; j < item.parts.length; j++) { domStyle.parts.push(addStyle(item.parts[j], options)); } } else { var parts = []; for(var j = 0; j < item.parts.length; j++) { parts.push(addStyle(item.parts[j], options)); } stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts}; } } } function listToStyles (list, options) { var styles = []; var newStyles = {}; for (var i = 0; i < list.length; i++) { var item = list[i]; var id = options.base ? item[0] + options.base : item[0]; var css = item[1]; var media = item[2]; var sourceMap = item[3]; var part = {css: css, media: media, sourceMap: sourceMap}; if(!newStyles[id]) styles.push(newStyles[id] = {id: id, parts: [part]}); else newStyles[id].parts.push(part); } return styles; } function insertStyleElement (options, style) { var target = getElement(options.insertInto) if (!target) { throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid."); } var lastStyleElementInsertedAtTop = stylesInsertedAtTop[stylesInsertedAtTop.length - 1]; if (options.insertAt === "top") { if (!lastStyleElementInsertedAtTop) { target.insertBefore(style, target.firstChild); } else if (lastStyleElementInsertedAtTop.nextSibling) { target.insertBefore(style, lastStyleElementInsertedAtTop.nextSibling); } else { target.appendChild(style); } stylesInsertedAtTop.push(style); } else if (options.insertAt === "bottom") { target.appendChild(style); } else if (typeof options.insertAt === "object" && options.insertAt.before) { var nextSibling = getElement(options.insertAt.before, target); target.insertBefore(style, nextSibling); } else { throw new Error("[Style Loader]\n\n Invalid value for parameter 'insertAt' ('options.insertAt') found.\n Must be 'top', 'bottom', or Object.\n (https://github.com/webpack-contrib/style-loader#insertat)\n"); } } function removeStyleElement (style) { if (style.parentNode === null) return false; style.parentNode.removeChild(style); var idx = stylesInsertedAtTop.indexOf(style); if(idx >= 0) { stylesInsertedAtTop.splice(idx, 1); } } function createStyleElement (options) { var style = document.createElement("style"); if(options.attrs.type === undefined) { options.attrs.type = "text/css"; } if(options.attrs.nonce === undefined) { var nonce = getNonce(); if (nonce) { options.attrs.nonce = nonce; } } addAttrs(style, options.attrs); insertStyleElement(options, style); return style; } function createLinkElement (options) { var link = document.createElement("link"); if(options.attrs.type === undefined) { options.attrs.type = "text/css"; } options.attrs.rel = "stylesheet"; addAttrs(link, options.attrs); insertStyleElement(options, link); return link; } function addAttrs (el, attrs) { Object.keys(attrs).forEach(function (key) { el.setAttribute(key, attrs[key]); }); } function getNonce() { if (false) {} return __webpack_require__.nc; } function addStyle (obj, options) { var style, update, remove, result; // If a transform function was defined, run it on the css if (options.transform && obj.css) { result = typeof options.transform === 'function' ? options.transform(obj.css) : options.transform.default(obj.css); if (result) { // If transform returns a value, use that instead of the original css. // This allows running runtime transformations on the css. obj.css = result; } else { // If the transform function returns a falsy value, don't add this css. // This allows conditional loading of css return function() { // noop }; } } if (options.singleton) { var styleIndex = singletonCounter++; style = singleton || (singleton = createStyleElement(options)); update = applyToSingletonTag.bind(null, style, styleIndex, false); remove = applyToSingletonTag.bind(null, style, styleIndex, true); } else if ( obj.sourceMap && typeof URL === "function" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function" && typeof Blob === "function" && typeof btoa === "function" ) { style = createLinkElement(options); update = updateLink.bind(null, style, options); remove = function () { removeStyleElement(style); if(style.href) URL.revokeObjectURL(style.href); }; } else { style = createStyleElement(options); update = applyToTag.bind(null, style); remove = function () { removeStyleElement(style); }; } update(obj); return function updateStyle (newObj) { if (newObj) { if ( newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap ) { return; } update(obj = newObj); } else { remove(); } }; } var replaceText = (function () { var textStore = []; return function (index, replacement) { textStore[index] = replacement; return textStore.filter(Boolean).join('\n'); }; })(); function applyToSingletonTag (style, index, remove, obj) { var css = remove ? "" : obj.css; if (style.styleSheet) { style.styleSheet.cssText = replaceText(index, css); } else { var cssNode = document.createTextNode(css); var childNodes = style.childNodes; if (childNodes[index]) style.removeChild(childNodes[index]); if (childNodes.length) { style.insertBefore(cssNode, childNodes[index]); } else { style.appendChild(cssNode); } } } function applyToTag (style, obj) { var css = obj.css; var media = obj.media; if(media) { style.setAttribute("media", media) } if(style.styleSheet) { style.styleSheet.cssText = css; } else { while(style.firstChild) { style.removeChild(style.firstChild); } style.appendChild(document.createTextNode(css)); } } function updateLink (link, options, obj) { var css = obj.css; var sourceMap = obj.sourceMap; /* If convertToAbsoluteUrls isn't defined, but sourcemaps are enabled and there is no publicPath defined then lets turn convertToAbsoluteUrls on by default. Otherwise default to the convertToAbsoluteUrls option directly */ var autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap; if (options.convertToAbsoluteUrls || autoFixUrls) { css = fixUrls(css); } if (sourceMap) { // http://stackoverflow.com/a/26603875 css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */"; } var blob = new Blob([css], { type: "text/css" }); var oldSrc = link.href; link.href = URL.createObjectURL(blob); if(oldSrc) URL.revokeObjectURL(oldSrc); } /***/ }), /* 9 */ /***/ (function(module, exports) { /** * When source maps are enabled, `style-loader` uses a link element with a data-uri to * embed the css on the page. This breaks all relative urls because now they are relative to a * bundle instead of the current page. * * One solution is to only use full urls, but that may be impossible. * * Instead, this function "fixes" the relative urls to be absolute according to the current page location. * * A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command. * */ module.exports = function (css) { // get current location var location = typeof window !== "undefined" && window.location; if (!location) { throw new Error("fixUrls requires window.location"); } // blank or null? if (!css || typeof css !== "string") { return css; } var baseUrl = location.protocol + "//" + location.host; var currentDir = baseUrl + location.pathname.replace(/\/[^\/]*$/, "/"); // convert each url(...) /* This regular expression is just a way to recursively match brackets within a string. /url\s*\( = Match on the word "url" with any whitespace after it and then a parens ( = Start a capturing group (?: = Start a non-capturing group [^)(] = Match anything that isn't a parentheses | = OR \( = Match a start parentheses (?: = Start another non-capturing groups [^)(]+ = Match anything that isn't a parentheses | = OR \( = Match a start parentheses [^)(]* = Match anything that isn't a parentheses \) = Match a end parentheses ) = End Group *\) = Match anything and then a close parens ) = Close non-capturing group * = Match anything ) = Close capturing group \) = Match a close parens /gi = Get all matches, not the first. Be case insensitive. */ var fixedCss = css.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi, function(fullMatch, origUrl) { // strip quotes (if they exist) var unquotedOrigUrl = origUrl .trim() .replace(/^"(.*)"$/, function(o, $1){ return $1; }) .replace(/^'(.*)'$/, function(o, $1){ return $1; }); // already a full url? no change if (/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(unquotedOrigUrl)) { return fullMatch; } // convert the url to a full url var newUrl; if (unquotedOrigUrl.indexOf("//") === 0) { //TODO: should we add protocol? newUrl = unquotedOrigUrl; } else if (unquotedOrigUrl.indexOf("/") === 0) { // path should be relative to the base url newUrl = baseUrl + unquotedOrigUrl; // already starts with '/' } else { // path should be relative to current directory newUrl = currentDir + unquotedOrigUrl.replace(/^\.\//, ""); // Strip leading './' } // send back the fixed url(...) return "url(" + JSON.stringify(newUrl) + ")"; }); // send back the fixed css return fixedCss; }; /***/ }), /* 10 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); // CONCATENATED MODULE: ./src/libs/tabID.js let created; let id; function initializeID() { created = Date.now(); id = Date.now() * 1024 + Math.floor(Math.random() * 1024); } // EXTERNAL MODULE: ./src/libs/sidemenu/sidemenu.scss var sidemenu = __webpack_require__(5); // EXTERNAL MODULE: external "jQuery" var external_jQuery_ = __webpack_require__(0); // EXTERNAL MODULE: ./src/libs/sidemenu/sidemenu.html var sidemenu_sidemenu = __webpack_require__(2); var sidemenu_sidemenu_default = /*#__PURE__*/__webpack_require__.n(sidemenu_sidemenu); // CONCATENATED MODULE: ./src/libs/sidemenu/element.js /** * サイドメニューに追加される要素のクラスです。 * @property {RegExp} [match] */ class SideMenuElement { /** * オブジェクト生成用のコンストラクタです * @param {string} [id] 要素のコンテナに付加されるIDです。 * @param {string} [title] 要素に表示されるタイトルです。 * @param {RegExp} [match] 表示されるページを指定するための正規表現です。 * @param {string} [document] 要素のHTMLです。 * @param {Function} [afterAppend] 要素が追加された後に実行される処理用の関数です。 */ constructor(id, title, match, document, afterAppend) { this.id = id; this.title = title; this.match = match; this.document = document; this.afterAppend = afterAppend; } ElementShouldDisplayed(url) { return this.match.test(url); } GetHTML() { return `<div class="menu-wrapper"> <div class="menu-header"> <h4 class="sidemenu-txt">${this.title}<span class="glyphicon glyphicon-menu-up" style="float: right"></span></h4> </div> <div class="menu-box"><div class="menu-content" id="${this.id}">${this.document}</div></div> </div>`; } } // CONCATENATED MODULE: ./src/libs/sidemenu/sidemenu.js class sidemenu_SideMenu { constructor() { this.Generate(); } Generate() { external_jQuery_('#main-div').append(sidemenu_sidemenu_default.a); resizeSidemenuHeight(); external_jQuery_('#sidemenu-key').click(function () { external_jQuery_('#sidemenu-key').toggleClass('glyphicon-menu-left glyphicon-menu-right'); external_jQuery_('#menu_wrap').toggleClass('sidemenu-active'); }); external_jQuery_(window).resize(resizeSidemenuHeight); external_jQuery_('#sidemenu').on('click', '.menu-header', (event) => { external_jQuery_(event.target).parents('.menu-wrapper').find('.menu-box').toggleClass('menu-box-collapse'); external_jQuery_(event.target).find('.glyphicon').toggleClass('glyphicon-menu-down glyphicon-menu-up'); }); function resizeSidemenuHeight() { external_jQuery_('#sidemenu').height(external_jQuery_(window).height()); } } /** * サイドメニューに要素を追加します * @param {SideMenuElement} [element] 追加する要素 */ AddElement(element) { const elementHtml = external_jQuery_(element.GetHTML()); external_jQuery_('#sidemenu').append(elementHtml); elementHtml.ready(() => { const content = external_jQuery_('.menu-content', elementHtml); content.parents('.menu-box').css('height', content.outerHeight(true)); element.afterAppend(); }) } } // EXTERNAL MODULE: ./src/elements/predictor/dom.html var dom = __webpack_require__(3); var dom_default = /*#__PURE__*/__webpack_require__.n(dom); // EXTERNAL MODULE: external "moment" var external_moment_ = __webpack_require__(1); var external_moment_default = /*#__PURE__*/__webpack_require__.n(external_moment_); // CONCATENATED MODULE: ./src/libs/datas/data.js /** * データを保存/更新するためのクラスです。 */ class Data { /** * オブジェクト生成用のコンストラクタです * @param {Function} [getNewData] 更新の際に新たなデータオブジェクトを返す関数です。 */ constructor(getNewData) { this.getNewData = getNewData; } /** * データのアップデートをする関数です。 */ async update() { this.data = await this.getNewData(); return this.data; } } /** * GETでデータを取得します。 */ class WebData extends Data { /** * オブジェクト生成用のコンストラクタです * @param {string} [dataURL] データ取得先のURLです。 */ constructor(dataURL) { super(async () => { return await $.ajax(dataURL); }); } } // CONCATENATED MODULE: ./src/libs/datas/history.js /** * ユーザのパフォーマンス履歴を取得し、他のタブと同期的にデータを扱います。 */ class history_HistoryData extends WebData { constructor(userScreenName) { super( `https://atcoder.jp/users/${userScreenName}/history/json` ); } } // CONCATENATED MODULE: ./src/libs/datas/standings.js /** * コンテストの順位表データを取得し、他のタブと同期的にデータを扱います。 */ class standings_StandingsData extends WebData { constructor(contestScreenName) { super( `https://atcoder.jp/contests/${contestScreenName}/standings/json` ); } } // CONCATENATED MODULE: ./src/libs/datas/aperfs.js class aperfs_APerfsData extends WebData { constructor(contestScreenName) { super( `https://ac-predictor.azurewebsites.net/api/aperfs/${contestScreenName}` ); } } // CONCATENATED MODULE: ./src/libs/utils/ratingColor.js const colorBounds = { "gray": 0, "brown": 400, "green": 800, "cyan": 1200, "blue": 1600, "yellow": 2000, "orange": 2400, "red": 2800 }; const colorNames = ["unrated", "gray", "brown", "green", "cyan", "blue", "yellow", "orange", "red"]; function getColor(rating) { let colorIndex = rating > 0 ? Math.min(Math.floor(rating / 400) + 1, 8) : 0; return colorNames[colorIndex]; } // CONCATENATED MODULE: ./src/libs/utils/atcoderRating.js //Copyright © 2017 koba-e964. //from : https://github.com/koba-e964/atcoder-rating-estimator const finf = bigf(400); function bigf(n) { let numerator = 1.0; let denominator = 1.0; for (let i = 0; i < n; ++i) { numerator *= 0.81; denominator *= 0.9; } numerator = (1 - numerator) * 0.81 / 0.19; denominator = (1 - denominator) * 0.9 / 0.1; return Math.sqrt(numerator) / denominator; } function f(n) { return (bigf(n) - finf) / (bigf(1) - finf) * 1200.0; } /** * calculate unpositivized rating from performance history * @param {Number[]} [history] performance history * @returns {Number} unpositivized rating */ function calcRatingFromHistory(history) { let n = history.length; let numerator = 0.0; let denominator = 0.0; for (let i = n - 1; i >= 0; --i) { numerator *= 0.9; numerator += 0.9 * Math.pow(2, history[i] / 800.0); denominator *= 0.9; denominator += 0.9; } return Math.log2(numerator / denominator) * 800.0 - f(n); } /** * calculate unpositivized rating from last state * @param {Number} [last] last unpositivized rating * @param {Number} [perf] performance * @param {Number} [ratedMatches] count of participated rated contest * @returns {number} estimated unpositivized rating */ function calcRatingFromLast(last, perf, ratedMatches) { if (ratedMatches === 0) return perf - 1200; last += f(ratedMatches); const weight = 9 - 9 * 0.9 ** ratedMatches; const numerator = weight * (2 ** (last / 800.0)) + 2 ** (perf / 800.0); const denominator = 1 + weight; return Math.log2(numerator / denominator) * 800.0 - f(ratedMatches + 1); } /** * (-inf, inf) -> (0, inf) * @param {Number} [rating] unpositivized rating * @returns {number} positivized rating */ function positivizeRating(rating) { if (rating >= 400.0) { return rating; } return 400.0 * Math.exp((rating - 400.0) / 400.0); } /** * (0, inf) -> (-inf, inf) * @param {Number} [rating] positivized rating * @returns {number} unpositivized rating */ function unpositivizeRating(rating) { if (rating >= 400.0) { return rating; } return 400.0 + 400.0 * Math.log(rating / 400.0); } /** * calculate the performance required to reach a target rate * @param {Number} [targetRating] targeted unpositivized rating * @param {Number[]} [history] performance history * @returns {number} performance */ function calcRequiredPerformance(targetRating, history) { let upper = 10000.0; let lower = -10000.0; for (let i = 0; i < 100; ++i) { const mid = (lower + upper) / 2; const rating = calcRatingFromHistory([mid].concat(history)); if (targetRating <= rating) upper = mid; else lower = mid; } return lower; } // CONCATENATED MODULE: ./src/libs/contest/fetchContestInformation.js class ContestInformation{ /** * @param {number[]} [participatableRange] * @param {number[]} [ratedRange] * @param {number} [penalty] */ constructor(participatableRange, ratedRange, penalty){ this.ParticipatableRange = participatableRange; this.RatedRange = ratedRange; this.Penalty = penalty; } /** * @param {object} object * @return {ContestInformation} */ static GenerateFromObject(object){ return new ContestInformation(object.ParticipatableRange, object.RatedRange, object.Penalty); } } /** * @param contestScreenName * @return {Promise<ContestInformation>} */ async function fetchContestInformation(contestScreenName) { return new Promise(async (resolve) => { const topPageDom = await $.ajax(`https://atcoder.jp/contests/${contestScreenName}`).then(x => new DOMParser().parseFromString(x, "text/html")); const dataParagraph = topPageDom.getElementsByClassName("small")[0]; const data = Array.from(dataParagraph.children).map(x => x.innerText.split(':')[1].trim()); resolve(new ContestInformation(parseRangeString(data[0]), parseRangeString(data[1]), parseDurationString(data[2]))); }); /** * @param {string} [s] * @return {number[]} */ function parseRangeString(s){ if (s === 'All') return [0, Infinity]; if (s.indexOf('~') === -1) return [0, -1]; let res = s.split('~').map(x => parseInt(x.trim())); if (isNaN(res[0])) res[0] = 0; if (isNaN(res[1])) res[1] = Infinity; return res; } /** * parse duration string and return a millisecond * @param {string} [s] * @return {number} */ function parseDurationString(s) { const dic = {ヶ月: "month", 日: "day", 時間: "hour", 分: "minute", 秒: "second"}; let res = {}; s.match(/(\d+[^\d]+)/g).forEach(x => { const trimmed = x.trim(' ','s'); const num = trimmed.match(/\d+/)[0]; const unit = trimmed.match(/[^\d]+/)[0]; const convertedUnit = dic[unit]||unit; res[convertedUnit] = num; }); return external_moment_["duration"](res).asMilliseconds(); } } // CONCATENATED MODULE: ./src/libs/contest/results/results.js class Results{ constructor(){} /** * @param {string} userScreenName * @return {Result} */ getUserResult(userScreenName){} } // CONCATENATED MODULE: ./src/libs/utils/twitter.js /** * * @param {string} [content] * @param {string} [url] * @return {string} */ function GetEmbedTweetLink(content, url){ return `https://twitter.com/share?text=${encodeURI(content)}&url=${encodeURI(url)}` } // CONCATENATED MODULE: ./src/libs/database/database.js /** * オブジェクト生成用のコンストラクタです * @param {Function} [getNewData] 更新の際に新たなデータオブジェクトを返す関数です。 * @param {string} [lsKey] 保存に用いるローカルストレージのkeyです。 * @param {Function} [onUpdate] 更新の際に呼ばれる関数です。 */ class DataBase { /** * オブジェクト生成用のコンストラクタです * @param {string} [name] indexedDBにアクセスする際に用いる名前です。 * @param {Number} [version] indexedDBにアクセスする際に用いるバージョンです。 */ constructor(name, version, update) { this.name = name; this.version = version; indexedDB.open(name, version).onupgradeneeded = update; } /** * データをデータベースに追加/更新します。 * @param {string} [storeName] indexedDBからストアを取得する際の名前です。 * @param {string} [key] ストアにセットする際に用いるkeyです。 * @param {Object} [value] ストアにセットする値です。 * @returns {Promise} 非同期のpromiseです。 */ async setData(storeName, key, value) { return new Promise((resolve, reject) => { try { indexedDB.open(this.name).onsuccess = (e) => { const db = e.target.result; const trans = db.transaction(storeName, 'readwrite'); const objStore = trans.objectStore(storeName); const data = {id: key, data: value}; const putReq = objStore.put(data); putReq.onsuccess = () => { db.close(); resolve(); }; }; } catch (e) { reject(e); } }); } /** * データをデータベースから取得します。存在しなかった場合はrejectされます。 * @param {string} [storeName] indexedDBからストアを取得する際の名前です。 * @param {string} [key] ストアにセットする際に用いるkeyです。 * @returns {Promise} 非同期のpromiseです。 */ async getData(storeName, key) { return new Promise((resolve, reject) => { try { indexedDB.open(this.name).onsuccess = (openEvent) => { const db = openEvent.target.result; const trans = db.transaction(storeName, 'readwrite'); const objStore = trans.objectStore(storeName); objStore.get(key).onsuccess = (getEvent) => { const result = getEvent.target.result; db.close(); if (!result) reject(`key '${key}' not found in store '${storeName}'`); else resolve(result); }; }; } catch (e) { reject(e); } }); } } // CONCATENATED MODULE: ./src/libs/database/predictorDB.js const StoreKeys = { aperfs: "APerfs", standings: "Standings" }; class predictorDB_PredictorDB extends DataBase { constructor() { super("PredictorDB", 1, (event) => { const db = event.target.result; const storeNames = ["APerfs", "Standings"]; storeNames.forEach(store => { db.createObjectStore(store, { keyPath: "id" }); }); }); } } // CONCATENATED MODULE: ./src/libs/contest/results/result.js class Result{ /*** * @param {boolean} isRated * @param {boolean} isSubmitted * @param {string} userScreenName * @param {number} performance * @param {number} place * @param {number} ratedRank * @param {number} competitions * @param {number} innerPerformance * @param {number} oldRating * @param {number} newRating */ constructor(isRated, isSubmitted, userScreenName, place, ratedRank, oldRating, newRating, competitions, performance, innerPerformance){ this.IsRated = isRated; this.IsSubmitted = isSubmitted; this.UserScreenName = userScreenName; this.Place = place; this.RatedRank = ratedRank; this.OldRating = oldRating; this.NewRating = newRating; this.Competitions = competitions; this.Performance = performance; this.InnerPerformance = innerPerformance; } } // CONCATENATED MODULE: ./src/libs/contest/results/standingsResults.js class standingsResults_OnDemandResults extends Results{ /** * @param {Contest} contest */ constructor(contest, templateResults){ super(); this.Contest = contest; this.TemplateResults = templateResults; } /** * @param {string} userScreenName * @return {Result} */ getUserResult(userScreenName){ const baseResults = this.TemplateResults[userScreenName]; if (!baseResults) return null; if (!baseResults.Performance) { baseResults.InnerPerformance = this.Contest.getInnerPerf(baseResults.RatedRank); baseResults.Performance = Math.min(baseResults.InnerPerformance, this.Contest.perfLimit); baseResults.NewRating = Math.round(positivizeRating(calcRatingFromLast(unpositivizeRating(baseResults.OldRating), baseResults.Performance, baseResults.Competitions))); } return baseResults; } } // CONCATENATED MODULE: ./src/libs/contest/contest.js class Tied{} class contest_Contest{ constructor(contestScreenName, contestInformation, standings, aPerfs){ this.ratedLimit = contestInformation.RatedRange[1] + 1; this.perfLimit = this.ratedLimit + 400; this.standings = standings; this.rankMemo = {}; const hoge = getHoge(standings.StandingsData, aPerfs, {2000: 800, 2800: 1000, Infinity: 1200}[this.ratedLimit] || 1200, this.ratedLimit); this.contestantAPerf = hoge.contestantAPerf; this.templateResults = hoge.templateResults; /** @return {{contestantAPerf: number[], templateResults: Object<string, Result>}} */ function getHoge(standingsData, aPerfs, defaultAPerf, ratedLimit) { let data = getData((data) => data.IsRated && data.TotalResult.Count !== 0); console.log(data); if (data.contestantAPerf.length === 0) data = getData((data) => data.OldRating < ratedLimit && data.TotalResult.Count !== 0); console.log(data); return data; /** @return {{contestantAPerf: number[], templateResults: Object.<string, Result>}}*/ function getData(isUserRated) { let contestantAPerf = []; let templateResults = {}; let currentRatedRank = 1; let lastRank = 0; let tiedUsers = []; let ratedInTiedUsers = 0; function applyTiedUsers(){ tiedUsers.forEach((data) => { if (isUserRated(data)){ contestantAPerf.push(aPerfs[data.UserScreenName] || defaultAPerf); ratedInTiedUsers++; } }); let ratedRank = currentRatedRank + Math.max(0, ratedInTiedUsers - 1) / 2; tiedUsers.forEach((data) => { templateResults[data.UserScreenName] = new Result( isUserRated(data), data.TotalResult.Count !== 0, data.UserScreenName, data.Rank, ratedRank, data.OldRating, null, data.Competitions, null, null ); }); currentRatedRank += ratedInTiedUsers; tiedUsers.length = 0; ratedInTiedUsers = 0; } standingsData.forEach((data) => { if (lastRank !== data.Rank) applyTiedUsers(); tiedUsers.push(data); }); applyTiedUsers(); return {contestantAPerf: contestantAPerf, templateResults: templateResults}; } } } getRatedRank(X) { if (this.rankMemo[X]) return this.rankMemo[X]; return this.rankMemo[X] = this.contestantAPerf.reduce((val, APerf) => val + (1.0 / (1.0 + Math.pow(6.0, ((X - APerf) / 400.0)))), 0); } getPerf(ratedRank){ return Math.min(this.getInnerPerf(ratedRank), this.perfLimit); } getInnerPerf(ratedRank){ let upper = 6144; let lower = -2048; while (upper - lower > 0.5) { const mid = (upper + lower) / 2; if (ratedRank - 0.5 > this.getRatedRank(mid)) upper = mid; else lower = mid; } return Math.round((upper + lower) / 2); } } // CONCATENATED MODULE: ./src/elements/predictor/script.js let predictor = new SideMenuElement('predictor','Predictor',/atcoder.jp\/contests\/.+/, dom_default.a, afterAppend); const firstContestDate = external_moment_("2016-07-16 21:00"); const predictorElements = ['predictor-input-rank', 'predictor-input-perf', 'predictor-input-rate', 'predictor-current', 'predictor-reload', 'predictor-tweet']; //クソの上にクソを塗り固めたようなゴミ実装 やめたら? async function afterAppend() { const isStandingsPage = /standings([^\/]*)?$/.test(document.location.href); const predictorDB = new predictorDB_PredictorDB(); const historyData = new history_HistoryData(userScreenName); const standingsData = new standings_StandingsData(contestScreenName); const aperfsData = new aperfs_APerfsData(contestScreenName); await historyData.update(); const contestInformation = await fetchContestInformation(contestScreenName); if (isStandingsPage && contestInformation.RatedRange[0] <= contestInformation.RatedRange[1]){ initStandings(); } /** @type Results */ let results; /** @type Contest */ let contest; $('[data-toggle="tooltip"]').tooltip(); $('#predictor-reload').click(function () { UpdatePredictorsData(); }); $('#predictor-current').click(function () { //自分の順位を確認 let myRank = 0; let ratedCount = 0; let lastRank = 0; let rank = 1; let isContainedMe = false; //全員回して自分が出てきたら順位更新フラグを立てる standingsData.data.StandingsData.forEach(function (element) { if (lastRank !== element.Rank) { if (isContainedMe) { myRank = rank + Math.max(0, ratedCount - 1) / 2; isContainedMe = false; } rank += ratedCount; ratedCount = 0; } if (userScreenName === element.UserScreenName) isContainedMe = true; if (element.IsRated && element.TotalResult.Count !== 0) ratedCount++; lastRank = element.Rank; }) if (isContainedMe) { myRank = rank + ratedCount / 2; } if (myRank === 0) return; $('#predictor-input-rank').val(myRank); lastUpdated = 0; drawPredictor(); }); $('#predictor-input-rank').keyup(function (event) { lastUpdated = 0; drawPredictor(); }); $('#predictor-input-perf').keyup(function (event) { lastUpdated = 1; drawPredictor(); }); $('#predictor-input-rate').keyup(function (event) { lastUpdated = 2; drawPredictor(); }); let lastUpdated = 0; Promise.all( [predictorDB.getData("APerfs", contestScreenName), predictorDB.getData("Standings", contestScreenName)] ).then((result) => { aperfsData.data = result[0].data; standingsData.data = result[1].data; CalcActivePerf(); drawPredictor(); enabled(); AddAlert('ローカルストレージから取得されました。'); if (isStandingsPage) { updateResultsData(); addPerfToStandings(); } }).catch(() => { UpdatePredictorsData(); }); //データを更新して描画する function UpdatePredictorsData() { if (!startTime.isBefore()) { disabled(); AddAlert('コンテストは始まっていません'); return; } if (external_moment_(startTime) < firstContestDate) { disabled(); AddAlert('現行レートシステム以前のコンテストです'); return; } if (contestInformation.RatedRange[0] > contestInformation.RatedRange[1]) { disabled(); AddAlert('ratedなコンテストではありません'); return; } $('#predictor-reload').button('loading'); AddAlert('順位表読み込み中…'); Promise.all( [aperfsData.update(), standingsData.update()] ).then(() => { if (Object.keys(aperfsData.data).length === 0) { disabled(); AddAlert('APerfのデータが提供されていません'); return; } if (standingsData.data.Fixed) { predictorDB.setData('APerfs', contestScreenName, aperfsData.data); predictorDB.setData('Standings', contestScreenName, standingsData.data); } CalcActivePerf(); if (isStandingsPage) { updateResultsData(); addPerfToStandings(); } drawPredictor(); enabled(); AddAlert(`最終更新 : ${external_moment_().format('HH:mm:ss')}`); }).catch(() => { disabled(); AddAlert('データの読み込みに失敗しました。'); }); } //ActivePerfの再計算 function CalcActivePerf() { contest = new contest_Contest(contestScreenName, contestInformation, standingsData.data, aperfsData.data); } //フォームを更新 function drawPredictor() { switch (lastUpdated) { case 0: UpdatePredictorFromRank(); break; case 1: UpdatePredictorFromPerf(); break; case 2: UpdatePredictorFromRate(); break; } function UpdatePredictorFromRank() { let rank = $("#predictor-input-rank").val(); lastUpdated = 0; let perf = contest.getPerf(rank); let rate = getRate(perf); UpdatePredictor(rank, perf, rate); } function UpdatePredictorFromPerf() { let perf = $("#predictor-input-perf").val(); lastUpdated = 1; let rank = contest.getRatedRank(perf); let rate = getRate(perf); UpdatePredictor(rank, perf, rate) } function UpdatePredictorFromRate() { let rate = $("#predictor-input-rate").val(); lastUpdated = 2; let upper = 10000; let lower = -10000; while (upper - lower > 0.125) { const mid = (upper + lower) / 2; if (rate < getRate(mid)) upper = mid; else lower = mid; } let perf = (upper + lower) / 2; let rank = contest.getRatedRank(perf); UpdatePredictor(rank, perf, rate); } function UpdatePredictor(rank, perf, rate) { $("#predictor-input-rank").val(round(rank)); $("#predictor-input-perf").val(round(perf)); $("#predictor-input-rate").val(round(rate)); updatePredictorTweetBtn(); function round(val) { return Math.round(val * 100) / 100; } } function getRate(perf) { return positivizeRating(calcRatingFromHistory(historyData.data.filter(x => x.IsRated).map(x => x.Performance).concat(perf).reverse())); } //ツイートボタンを更新する function updatePredictorTweetBtn() { let tweetStr = `Rated内順位: ${$("#predictor-input-rank").val()}位%\nパフォーマンス: ${$("#predictor-input-perf").val()}\nレート: ${$("#predictor-input-rate").val()}\n`; $('#predictor-tweet').attr("href", GetEmbedTweetLink(tweetStr, "https://gf.qytechs.cn/ja/scripts/369954-ac-predictor")); } } //最終更新などの要素を追加する function AddAlert(content) { $("#predictor-alert").html(`<h5 class='sidemenu-txt'>${content}</h5>`); } //要素のDisabledを外す function enabled() { $('#predictor-reload').button('reset'); predictorElements.forEach(element => { $(`#${element}`).removeAttr("disabled"); }); } //要素にDisabledをつける function disabled() { $('#predictor-reload').button('reset'); predictorElements.forEach(element => { $(`#${element}`).attr("disabled", true); }); } //全員の結果データを更新する function updateResultsData() { results = new standingsResults_OnDemandResults(contest, contest.templateResults); } function initStandings() { (new MutationObserver(() => { addPerfToStandings(); })).observe(document.getElementById('standings-tbody'), { childList: true }); $('thead > tr').append('<th class="standings-result-th" style="width:84px;min-width:84px;">perf</th><th class="standings-result-th" style="width:168px;min-width:168px;">レート変化</th>'); } //結果データを順位表に追加する function addPerfToStandings() { $('.standings-perf , .standings-rate').remove(); $('#standings-tbody > tr').each((index, elem) => { if (elem.childNodes.length <= 3) { let unparticipatedResultCell = elem.getElementsByClassName("standings-result")[0]; unparticipatedResultCell.setAttribute("colspan", parseInt(unparticipatedResultCell.getAttribute("colspan")) + 2); return; } const userName = $('.standings-username .username', elem).text(); const result = results.getUserResult(userName); const perfElem = !result || !result.IsSubmitted ? '-' : getRatingSpan(result.Performance); const rateElem = !result ? '-' : result.IsRated ? getRatingChangeElem(result.OldRating, result.NewRating) : getUnratedElem(result.OldRating); $(elem).append(`<td class="standings-result standings-perf">${perfElem}</td>`); $(elem).append(`<td class="standings-result standings-rate">${rateElem}</td>`); function getRatingChangeElem(oldRate, newRate) { return `<span class="bold">${getRatingSpan(oldRate)}</span> → <span class="bold">${getRatingSpan(newRate)}</span> <span class="grey">(${(newRate >= oldRate ? '+' : '')}${newRate - oldRate})</span>`; } function getUnratedElem(rate) { return `<span class="bold">${getRatingSpan(rate)}</span> <span class="grey">(unrated)</span>`; } function getRatingSpan(rate) { return `<span class="user-${getColor(rate)}">${rate}</span>`; } }); } } // EXTERNAL MODULE: ./src/elements/estimator/dom.html var estimator_dom = __webpack_require__(4); var estimator_dom_default = /*#__PURE__*/__webpack_require__.n(estimator_dom); // CONCATENATED MODULE: ./src/elements/estimator/state/EstimatorModel.js class EstimatorModel{ constructor(inputValue, perfHistory){ this.inputDesc = ""; this.resultDesc = ""; this.perfHistory = perfHistory; this.updateInput(inputValue); } updateInput(value){ this.inputValue = value; this.resultValue = this.calcResult(value); } toggle(){} /** * @param {Number} [input] * @return {Number} */ calcResult(input){ return input } } // CONCATENATED MODULE: ./src/elements/estimator/state/CalcRatingModel.js class CalcRatingModel_CalcRatingModel extends EstimatorModel{ constructor(inputValue, perfHistory){ super(inputValue, perfHistory); this.inputDesc = "パフォーマンス"; this.resultDesc = "到達レーティング"; } toggle(){ return new CalcPerfModel_CalcPerfModel(this.resultValue, this.perfHistory); } calcResult(input){ return positivizeRating(calcRatingFromHistory([input].concat(this.perfHistory))); } } // CONCATENATED MODULE: ./src/elements/estimator/state/CalcPerfModel.js class CalcPerfModel_CalcPerfModel extends EstimatorModel{ constructor(inputValue, perfHistory){ super(inputValue, perfHistory); this.inputDesc = "目標レーティング"; this.resultDesc = "必要パフォーマンス"; } toggle(){ return new CalcRatingModel_CalcRatingModel(this.resultValue, this.perfHistory); } calcResult(input){ return calcRequiredPerformance(unpositivizeRating(input), this.perfHistory); } } // CONCATENATED MODULE: ./src/atcoder-lib/utils.js // format if (typeof String.prototype.format === 'undefined') { String.prototype.format = function(arg) { var rep_fn = undefined; if (typeof arg == "object") { rep_fn = function(m, k) { return arg[k];}; } else { var args = arguments; rep_fn = function(m, k) { return args[parseInt(k)];}; } return this.replace(/\{(\w+)\}/g, rep_fn); } } // array search function has(a, val) { return a.indexOf(val) != -1; } // other util function arrayToSet(a) { var s = new Set(); for (var i in a) s.add(a[i]); return s; } function setToArray(s) { var a = []; s.forEach(function(val) { a.push(val);}); return a; } // cookie var COOKIE_EXPIRES = 10000; function setCookie(key, val, expires) { Cookies.set(key, val, { expires: expires || COOKIE_EXPIRES}); } function getCookie(key) { return Cookies.getJSON(key); } function getCookieBool(key) { return (Cookies.get(key) === 'true'); } function delCookie(key) { Cookies.remove(key); } // local storage function setLS(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch(error) { console.log(error); } } function getLS(key) { var val = localStorage.getItem(key); return val?JSON.parse(val):val; } function delLS(key) { localStorage.removeItem(key); } // migrate Cookie to LocalStorage { var val; if (val = getCookie('fav')) { for (var i in val) favSet.add(val[i]); storeFavs(); delCookie('fav'); } var keys = ['plain_editor','auto_height','defaultLang', 'show_affiliation','show_fav_btn','show_fav_only','show_rated_only']; for (var i = 0; i < keys.length; i++) { if (val = Cookies.get(keys[i])) { setLS(keys[i], val); delCookie(keys[i]); } } } // server time var timeDelta = getCookie('timeDelta'); if (typeof timeDelta === 'undefined') { timeDelta = 0; setCookie('timeDelta', 0, 1/24.0); $.ajax('/servertime?ts={}'.format(moment().unix())).done(function(serverTime) { serverTime = moment(serverTime); if (!serverTime.isValid()) return; timeDelta = serverTime.diff(moment()); setCookie('timeDelta', timeDelta, 1/24.0); }); } function getServerTime() { return moment().add(timeDelta);} // escape function E(str) { return str .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // toRegExp function toRegExp(pattern) { pattern = pattern .replace(/[-\/\\^+.()|[\]{}]/g, '\\$&') .replace(/\?/g, '.') .replace(/\*/g, '.*'); return new RegExp('^'+pattern); } // randint function rand(range) { return Math.floor(Math.random()*(range[1]-range[0]))+range[0]; } // clipboard function copy(textVal){ var copyFrom = document.createElement("textarea"); copyFrom.textContent = textVal; var bodyElm = document.getElementsByTagName("body")[0]; bodyElm.appendChild(copyFrom); copyFrom.select(); var retVal = document.execCommand('copy'); bodyElm.removeChild(copyFrom); return retVal; } // fit font size (function($) { $.fn.fitFontSize = function(width, len, max) { $(this).css('font-size', Math.min(width/len, max)+'px'); }; })(jQuery); // user favs var favSet; function storeFavs() { setLS('fav', setToArray(favSet)); } function reloadFavs() { favSet = arrayToSet(getLS('fav') || []); } function toggleFav(val) { reloadFavs(); var res; if (favSet.has(val)) { favSet.delete(val); res = false; } else { favSet.add(val); res = true; } storeFavs(); return res; // has val now } // CONCATENATED MODULE: ./src/elements/estimator/script.js let estimator = new SideMenuElement('estimator','Estimator',/atcoder.jp/, estimator_dom_default.a, script_afterAppend); async function script_afterAppend() { const estimatorInputSelector = $("#estimator-input"); const estimatorResultSelector = $("#estimator-res"); let model = GetModelFromStateCode(getLS("sidemenu_estimator_state"), getLS("sidemenu_estimator_value"), await GetHistory()); updateView(); $("#estimator-toggle").click(function () { model = model.toggle(); updateLocalStorage(); updateView(); }); estimatorInputSelector.keyup(() => { updateModel(); updateLocalStorage(); updateView(); }); /** modelをinputの値に応じて更新 */ function updateModel() { const inputString = estimatorInputSelector.val(); if (!isFinite(inputString)) return; const inputNumber = parseInt(inputString); model.updateInput(inputNumber); } /** modelの状態をLSに保存 */ function updateLocalStorage() { setLS("sidemenu_estimator_value", model.inputValue); setLS("sidemenu_estimator_state", model.constructor.name); } /** modelを元にviewを更新 */ function updateView() { const roundedInput = roundVal(model.inputValue, 2); const roundedResult = roundVal(model.resultValue, 2); $("#estimator-input-desc").text(model.inputDesc); $("#estimator-res-desc").text(model.resultDesc); estimatorInputSelector.val(roundedInput); estimatorResultSelector.val(roundedResult); const tweetStr = `AtCoderのハンドルネーム: ${userScreenName}\n${model.inputDesc}: ${roundedInput}\n${model.resultDesc}: ${roundedResult}\n`; $('#estimator-tweet').attr("href", GetEmbedTweetLink(tweetStr, "https://gf.qytechs.cn/ja/scripts/369954-ac-predictor")); function roundVal(value, digit) { return Math.round(value * (10 ** digit)) / (10 ** digit); } } } /** * パフォーマンス履歴を取得し、いい感じで整形して返します * @return {Promise<Number[]>} パフォーマンス履歴を返すpromise */ async function GetHistory(){ return new Promise((resolve) => { new history_HistoryData(userScreenName).update().then((data) => { resolve(data.filter(x => x.IsRated) .sort((a, b) => external_moment_default()(b.EndTime) - external_moment_default()(a.EndTime)) .map(x => x.Performance)); }) }); } const models = [CalcPerfModel_CalcPerfModel, CalcRatingModel_CalcRatingModel]; /** * LocalStorageに保存されたステートコードから状態を復元します * @param {string} [state] ステートを示す文字列(型名) * @param {number} [value] 最初に入る値 * @param {number[]} [history] パフォーマンス履歴(時間降順) * @return {EstimatorModel} 構築されたモデル */ function GetModelFromStateCode(state, value, history) { let model = models.find(model => model.name === state); if (!model) model = CalcPerfModel_CalcPerfModel; return new model(value, history); } // CONCATENATED MODULE: ./src/main.js initializeID(); let main_sidemenu = new sidemenu_SideMenu(); if (predictor.ElementShouldDisplayed(document.location.href)) main_sidemenu.AddElement(predictor); if (estimator.ElementShouldDisplayed(document.location.href)) main_sidemenu.AddElement(estimator); /***/ }) /******/ ]);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址