ac-favorite-manager

AtCoderのお気に入りの管理を行います。

目前为 2019-08-03 提交的版本。查看 最新版本

  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/
  5. /******/ // The require function
  6. /******/ function __webpack_require__(moduleId) {
  7. /******/
  8. /******/ // Check if module is in cache
  9. /******/ if(installedModules[moduleId]) {
  10. /******/ return installedModules[moduleId].exports;
  11. /******/ }
  12. /******/ // Create a new module (and put it into the cache)
  13. /******/ var module = installedModules[moduleId] = {
  14. /******/ i: moduleId,
  15. /******/ l: false,
  16. /******/ exports: {}
  17. /******/ };
  18. /******/
  19. /******/ // Execute the module function
  20. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21. /******/
  22. /******/ // Flag the module as loaded
  23. /******/ module.l = true;
  24. /******/
  25. /******/ // Return the exports of the module
  26. /******/ return module.exports;
  27. /******/ }
  28. /******/
  29. /******/
  30. /******/ // expose the modules object (__webpack_modules__)
  31. /******/ __webpack_require__.m = modules;
  32. /******/
  33. /******/ // expose the module cache
  34. /******/ __webpack_require__.c = installedModules;
  35. /******/
  36. /******/ // define getter function for harmony exports
  37. /******/ __webpack_require__.d = function(exports, name, getter) {
  38. /******/ if(!__webpack_require__.o(exports, name)) {
  39. /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
  40. /******/ }
  41. /******/ };
  42. /******/
  43. /******/ // define __esModule on exports
  44. /******/ __webpack_require__.r = function(exports) {
  45. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  46. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  47. /******/ }
  48. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  49. /******/ };
  50. /******/
  51. /******/ // create a fake namespace object
  52. /******/ // mode & 1: value is a module id, require it
  53. /******/ // mode & 2: merge all properties of value into the ns
  54. /******/ // mode & 4: return value when already ns object
  55. /******/ // mode & 8|1: behave like require
  56. /******/ __webpack_require__.t = function(value, mode) {
  57. /******/ if(mode & 1) value = __webpack_require__(value);
  58. /******/ if(mode & 8) return value;
  59. /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  60. /******/ var ns = Object.create(null);
  61. /******/ __webpack_require__.r(ns);
  62. /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  63. /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  64. /******/ return ns;
  65. /******/ };
  66. /******/
  67. /******/ // getDefaultExport function for compatibility with non-harmony modules
  68. /******/ __webpack_require__.n = function(module) {
  69. /******/ var getter = module && module.__esModule ?
  70. /******/ function getDefault() { return module['default']; } :
  71. /******/ function getModuleExports() { return module; };
  72. /******/ __webpack_require__.d(getter, 'a', getter);
  73. /******/ return getter;
  74. /******/ };
  75. /******/
  76. /******/ // Object.prototype.hasOwnProperty.call
  77. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78. /******/
  79. /******/ // __webpack_public_path__
  80. /******/ __webpack_require__.p = "";
  81. /******/
  82. /******/
  83. /******/ // Load entry module and return exports
  84. /******/ return __webpack_require__(__webpack_require__.s = 3);
  85. /******/ })
  86. /************************************************************************/
  87. /******/ ([
  88. /* 0 */
  89. /***/ (function(module, exports) {
  90.  
  91. module.exports = jQuery;
  92.  
  93. /***/ }),
  94. /* 1 */
  95. /***/ (function(module, exports) {
  96.  
  97. module.exports = "<li><a id=\"fav-manager-dropdown-button\" data-toggle=\"modal\" data-target=\"#modal-fav-manager\" style=\"cursor : pointer;\"><span class=\"glyphicon glyphicon-star\" aria-hidden=\"true\"></span> お気に入り管理</a></li>";
  98.  
  99. /***/ }),
  100. /* 2 */
  101. /***/ (function(module, exports) {
  102.  
  103. module.exports = "<div id=\"modal-fav-manager\" class=\"modal fade\" tabindex=\"-1\" role=\"dialog\">\r\n\t<div class=\"modal-dialog\" role=\"document\">\r\n\t\t<div class=\"modal-content\">\r\n\t\t\t<div class=\"modal-header\">\r\n\t\t\t\t<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">×</span></button>\r\n\t\t\t\t<h4 class=\"modal-title\">お気に入り管理</h4>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"modal-body\">\r\n <div class=\"container-fluid\">\r\n <div class=\"row\">\r\n <div class=\"form-inline\">\r\n <div class=\"col-sm-12 form-group\">\r\n <label for=\"fav-manager-set-select\">セット: </label>\r\n <select id=\"fav-manager-set-select\" class=\"form-control\" data-placeholder=\"default\" data-allow-clear=\"false\" style=\"width: 30%;\">\r\n <option value=\"default\">default</option>\r\n <option value=\"blacklist\">blacklist</option>\r\n </select>\r\n <button id=\"fav-manager-toggle-set-activeness-button\" type=\"button\" class=\"btn btn-default\"></button>\r\n <a id=\"fav-manager-set-export-button\" type=\"button\" class=\"btn btn-default\" target=\"_blank\">エクスポート</a>\r\n <button id=\"fav-manager-set-delete-button\" type=\"button\" class=\"btn btn-danger pull-right\">削除</button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"row\" style=\"max-height: 500px; overflow-y: auto; overflow-x: hidden\">\r\n <table class=\"table\">\r\n <tbody id=\"fav-manager-users-table\">\r\n </tbody>\r\n </table>\r\n </div>\r\n <p></p>\r\n <div class=\"row\">\r\n <div class=\"col-sm-6\">\r\n <div class=\"input-group\">\r\n <span class=\"input-group-addon\">セット名</span>\r\n <input id=\"fav-manager-add-set-input\" type=\"text\" class=\"form-control\" placeholder=\"サークル\">\r\n <span class=\"input-group-btn\">\r\n <button id=\"fav-manager-add-set-button\" type=\"button\" class=\"btn btn-default\">作成</button>\r\n </span>\r\n </div>\r\n </div>\r\n <div class=\"col-sm-6\">\r\n <div class=\"input-group\">\r\n <span class=\"input-group-addon\">ユーザ名</span>\r\n <input id=\"fav-manager-add-user-input\" type=\"text\" class=\"form-control\" placeholder=\"tourist\">\r\n <span class=\"input-group-btn\">\r\n <button id=\"fav-manager-add-user-button\" type=\"button\" class=\"btn btn-default\">追加</button>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n <p></p>\r\n <div class=\"row\">\r\n <label class=\"col-sm-6\">\r\n <span class=\"btn btn-primary col-sm-12\">ファイルからインポート<input id=\"fav-manager-select-import-file-button\" type=\"file\" style=\"display:none\" accept=\".json\" multiple=\"\"></span>\r\n </label>\r\n <label class=\"col-sm-6\">\r\n <a id=\"fav-manager-export-all-button\" type=\"button\" target=\"_blank\" class=\"btn btn-default col-sm-12\">全てエクスポート</a>\r\n </label>\r\n </div>\r\n </div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"modal-footer\">\r\n\t\t\t\t<button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">閉じる</button>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n</div>";
  104.  
  105. /***/ }),
  106. /* 3 */
  107. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  108.  
  109. "use strict";
  110. __webpack_require__.r(__webpack_exports__);
  111.  
  112. // CONCATENATED MODULE: ./src/favs.js
  113.  
  114. class favSets{
  115. constructor(){
  116. this.sets = {};
  117. this.isActive = {};
  118. }
  119.  
  120. initialize(){
  121. this.sets = {default:new Set(),blacklist:new Set()};
  122. this.isActive = {default:true,blacklist:true};
  123. }
  124.  
  125. createNewSet(key){
  126. if (typeof(key) !== "string") throw new Error(`set ${JSON.stringify(key)} は文字列型ではありません`);
  127. if (this.sets[key]) throw new Error(`set ${key} は既に存在しています`);
  128. this.sets[key] = new Set();
  129. this.isActive[key] = true;
  130. }
  131.  
  132. setActive(key, activeness){
  133. if (typeof(key) !== "string") throw new Error(`set ${JSON.stringify(key)} は文字列型ではありません`);
  134. if (key === "blacklist") throw Error(`set ${key} の有効値は変更できません`);
  135. if (!this.isActive.hasOwnProperty(key)) throw Error(`set ${key} は存在していません`);
  136. this.isActive[key] = !!(activeness);
  137. }
  138.  
  139. mergeWith(newSets){
  140. for (const key in newSets.sets){
  141. if (this.sets.hasOwnProperty(key)) {
  142. newSets.sets[key].forEach((user) => {
  143. this.sets[key].add(user);
  144. });
  145. this.isActive[key] |= !!newSets.isActive[key];
  146. }
  147. else{
  148. this.sets[key] = newSets.sets[key];
  149. this.isActive[key] = !!newSets.isActive[key];
  150. }
  151. }
  152. }
  153.  
  154. //defaultは常に有効、blacklistは常に無効
  155. get favSet() {
  156. let a = [];
  157. for (const key in this.isActive){
  158. if (!this.isActive[key]) continue;
  159. a.push(...this.sets[key]);
  160. }
  161. let set = new Set(a);
  162. this.sets["blacklist"].forEach((user) => {
  163. set.delete(user);
  164. });
  165. return set;
  166. }
  167.  
  168. /**
  169. * favSetsをJSON化する
  170. * @param {favSets} favSets
  171. * @param {boolean} containActivenessData
  172. * @return {string}
  173. */
  174. static stringify(favSets, containActivenessData = true){
  175. let res = [];
  176. for (const key in favSets.isActive){
  177. let data = {name: key, users: [...favSets.sets[key]]};
  178. if (containActivenessData) data.isActive = favSets.isActive[key];
  179. res.push(data);
  180. }
  181. return JSON.stringify(res);
  182. }
  183.  
  184. /**
  185. * JSONからfavSetsを復元する
  186. * @param {string} json
  187. * @return {favSets}
  188. */
  189. static parse(json){
  190. let sets = new favSets();
  191. JSON.parse(json).forEach((elem) => {
  192. if (!elem.hasOwnProperty("name")) throw new Error(`key "name" がオブジェクト ${JSON.stringify(elem)} に存在しません`);
  193. if (!elem.hasOwnProperty("users")) throw new Error(`key "users" がオブジェクト ${JSON.stringify(elem)} に存在しません`);
  194. if (typeof(elem.name) !== "string") throw new Error(`key "name" の値 (${JSON.stringify(elem.name)}) は文字列型でありません`);
  195. if (!Array.isArray(elem.users)) throw new Error(`key "users" の値 (${JSON.stringify(elem.users)}) は配列ではありません`);
  196.  
  197. sets.sets[elem.name] = arrayToSet(elem.users);
  198. sets.isActive[elem.name] = elem.hasOwnProperty("isActive") ? !!(elem.isActive) : true;
  199. });
  200. return sets;
  201. }
  202.  
  203. static isSpecialSet(key){
  204. return key === "default" || key === "blacklist";
  205. }
  206. }
  207.  
  208. // CONCATENATED MODULE: ./src/atcoder/globalFavSets.js
  209.  
  210.  
  211. let globalFavSets = new favSets();
  212. globalFavSets.initialize();
  213.  
  214. /* harmony default export */ var atcoder_globalFavSets = (globalFavSets);
  215. // CONCATENATED MODULE: ./src/atcoder/injectFavHandler.js
  216.  
  217.  
  218.  
  219.  
  220. /* harmony default export */ var injectFavHandler = (function () {
  221. storeFavs = () => {
  222. setLS('favmanager-favSets', favSets.stringify(atcoder_globalFavSets));
  223. setLS('fav', setToArray(favSet = atcoder_globalFavSets.favSet));
  224. };
  225.  
  226. reloadFavs = () => {
  227. atcoder_globalFavSets.initialize();
  228. atcoder_globalFavSets.mergeWith(favSets.parse(getLS('favmanager-favSets') || "[]"));
  229. favSet = atcoder_globalFavSets.favSet;
  230. };
  231.  
  232. toggleFav = (val) => {
  233. reloadFavs();
  234. let res;
  235. if (favSet.has(val)) {
  236. atcoder_globalFavSets.sets.default.delete(val);
  237. atcoder_globalFavSets.sets.blacklist.add(val);
  238. res = false;
  239. } else {
  240. atcoder_globalFavSets.sets.default.add(val);
  241. atcoder_globalFavSets.sets.blacklist.delete(val);
  242. res = true;
  243. }
  244. favSet = atcoder_globalFavSets.favSet;
  245. storeFavs();
  246. return res; // has val now
  247. };
  248.  
  249.  
  250. //migration
  251. if (!getLS('favmanager-favSets')) {
  252. (getLS('fav') || []).forEach((user) => {
  253. atcoder_globalFavSets.sets.default.add(user);
  254. });
  255. storeFavs();
  256. }
  257. else{
  258. reloadFavs();
  259. }
  260. });
  261. // EXTERNAL MODULE: ./src/atcoder/html/dropdownElement.html
  262. var dropdownElement = __webpack_require__(1);
  263. var dropdownElement_default = /*#__PURE__*/__webpack_require__.n(dropdownElement);
  264.  
  265. // EXTERNAL MODULE: ./src/atcoder/html/modal.html
  266. var modal = __webpack_require__(2);
  267. var modal_default = /*#__PURE__*/__webpack_require__.n(modal);
  268.  
  269. // CONCATENATED MODULE: ./src/files.js
  270. /**
  271. * ファイルに保存するためのURLを取得
  272. * @param {string} content
  273. */
  274. function getObjectURL(content) {
  275. const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
  276. const blob = new Blob([bom, content], { type: "text/plain" });
  277. return window.URL.createObjectURL(blob);
  278. }
  279.  
  280. // EXTERNAL MODULE: external "jQuery"
  281. var external_jQuery_ = __webpack_require__(0);
  282.  
  283. // CONCATENATED MODULE: ./src/getTimeStamp.js
  284. function padNumber(n) {
  285. const padZero = "00"+n;
  286. return padZero.substring(padZero.length - 2);
  287. }
  288.  
  289. /* harmony default export */ var getTimeStamp = (function(){
  290. const now = new Date();
  291. return `${now.getFullYear()}${padNumber(now.getMonth())}${padNumber(now.getDate())}-${padNumber(now.getHours())}${padNumber(now.getMinutes())}${padNumber(now.getSeconds())}`;
  292. });
  293. // CONCATENATED MODULE: ./src/atcoder/generateElement.js
  294.  
  295.  
  296.  
  297.  
  298.  
  299.  
  300.  
  301.  
  302. const modalNode = external_jQuery_(modal_default.a);
  303. const dropdownNode = external_jQuery_(dropdownElement_default.a);
  304.  
  305. const setSelectSelector = external_jQuery_("#fav-manager-set-select", modalNode);
  306. const usersTableSelector = external_jQuery_("#fav-manager-users-table", modalNode);
  307. const setNameInputSelector = external_jQuery_("#fav-manager-add-set-input", modalNode);
  308. const addSetButtonSelector = external_jQuery_("#fav-manager-add-set-button", modalNode);
  309. const userNameInputSelector = external_jQuery_("#fav-manager-add-user-input", modalNode);
  310. const addUserButtonSelector = external_jQuery_("#fav-manager-add-user-button", modalNode)
  311. const setDeleteButtonSelector = external_jQuery_("#fav-manager-set-delete-button", modalNode);
  312. const selectImportFileButtonSelector = external_jQuery_("#fav-manager-select-import-file-button", modalNode);
  313. const toggleSetActivenessButtonSelector = external_jQuery_("#fav-manager-toggle-set-activeness-button", modalNode);
  314.  
  315. function getSelectedSet() {
  316. return setSelectSelector.val();
  317. }
  318.  
  319. function setSelectedSet(value) {
  320. setSelectSelector.val(value);
  321. }
  322.  
  323. function updateSelector() {
  324. const selected = getSelectedSet();
  325. setSelectSelector.empty();
  326. for (const key in atcoder_globalFavSets.isActive){
  327. setSelectSelector.append(`<option value="${E(key)}">${E(key)}${atcoder_globalFavSets.isActive[key] ? "" : "(無効)"}</option>`);
  328. }
  329. setSelectedSet(selected);
  330. }
  331.  
  332. function updateTable() {
  333. usersTableSelector.empty();
  334. appendRow();
  335. atcoder_globalFavSets.sets[getSelectedSet()].forEach((user) => {
  336. if (usersTableSelector[0].lastElementChild.children.length === 3)
  337. appendRow();
  338. external_jQuery_(usersTableSelector[0].lastElementChild).append(
  339. `<td class="col-sm-4"><a href="/users/${encodeURIComponent(user)}">${E(user)}</a><a class="fav-manager-user-delete-button pull-right" name="${E(user)}" style="cursor : pointer; user-select: none;">×</a></td>`
  340. );
  341. });
  342. while (usersTableSelector[0].lastElementChild.children.length < 3){
  343. external_jQuery_(usersTableSelector[0].lastElementChild).append('<td class="col-sm-4"></td>');
  344. }
  345. function appendRow() {
  346. usersTableSelector.append("<tr></tr>");
  347. }
  348. }
  349.  
  350. function updateDownloadLink() {
  351. const setExportElem = external_jQuery_("#fav-manager-set-export-button", modalNode)[0];
  352. const key = getSelectedSet();
  353. let exportSets = new favSets();
  354. exportSets.sets[key] = atcoder_globalFavSets.sets[key];
  355. exportSets.isActive[key] = true;
  356. setExportElem.download = `favset-${key}-${getTimeStamp()}.json`;
  357. setExportElem.href = getObjectURL(favSets.stringify(exportSets, false));
  358.  
  359. const allExportElem = external_jQuery_("#fav-manager-export-all-button", modalNode)[0];
  360. allExportElem.download = `all-favsets-${getTimeStamp()}.json`;
  361. allExportElem.href = getObjectURL(favSets.stringify(atcoder_globalFavSets));
  362. }
  363.  
  364. function updateView() {
  365. const selectedSet = getSelectedSet();
  366. toggleSetActivenessButtonSelector.text(atcoder_globalFavSets.isActive[selectedSet] ? "無効にする" : "有効にする");
  367. setDeleteButtonSelector.text(favSets.isSpecialSet(selectedSet) ? "クリア" : "セット削除");
  368. toggleSetActivenessButtonSelector.prop("disabled", selectedSet === "blacklist");
  369. updateDownloadLink();
  370. updateSelector();
  371. updateTable();
  372. }
  373.  
  374. window.addEventListener("storage", event => {
  375. if (event.key !== 'favmanager-favSets') return;
  376. reloadFavs();
  377. updateView();
  378. });
  379.  
  380. /* harmony default export */ var generateElement = (function(){
  381. external_jQuery_("body").prepend(modalNode);
  382. external_jQuery_(".navbar-right .dropdown-menu .divider:nth-last-child(2)").before(dropdownNode);
  383. setSelectSelector.change(updateView);
  384. setDeleteButtonSelector.click(() => {
  385. const key = getSelectedSet();
  386. if (favSets.isSpecialSet(key)) {
  387. atcoder_globalFavSets.sets[key] = new Set();
  388. }
  389. else{
  390. delete atcoder_globalFavSets.sets[key];
  391. delete atcoder_globalFavSets.isActive[key];
  392. updateSelector();
  393. setSelectedSet("default");
  394. }
  395. storeFavs();
  396. updateView();
  397. });
  398. usersTableSelector.on("click", ".fav-manager-user-delete-button", (event) => {
  399. const key = getSelectedSet();
  400. const userName = event.target.getAttribute("name");
  401. atcoder_globalFavSets.sets[key].delete(userName);
  402. storeFavs();
  403. updateView();
  404. });
  405. setNameInputSelector.keypress(e => {
  406. if (e.which === 13) addSetButtonSelector.click();
  407. });
  408. addSetButtonSelector.click(() => {
  409. const newSetName = setNameInputSelector.val().trim();
  410. if (newSetName) {
  411. atcoder_globalFavSets.createNewSet(newSetName);
  412. updateSelector();
  413. setSelectedSet(newSetName);
  414. }
  415. setNameInputSelector.val("");
  416. storeFavs();
  417. updateView();
  418. });
  419. userNameInputSelector.keypress(e => {
  420. if (e.which === 13) addUserButtonSelector.click();
  421. });
  422. addUserButtonSelector.click(() => {
  423. atcoder_globalFavSets.sets[getSelectedSet()].add(userNameInputSelector.val().trim());
  424. userNameInputSelector.val("");
  425. storeFavs();
  426. updateView();
  427. });
  428. toggleSetActivenessButtonSelector.click(() => {
  429. const selectedSet = getSelectedSet();
  430. atcoder_globalFavSets.setActive(selectedSet, !atcoder_globalFavSets.isActive[selectedSet]);
  431. storeFavs();
  432. updateView();
  433. });
  434.  
  435. let reader = new FileReader();
  436. reader.onload = (readerEvent) => {
  437. try{
  438. let parsedSets = favSets.parse(readerEvent.target.result);
  439. atcoder_globalFavSets.mergeWith(parsedSets);
  440. updateSelector();
  441. setSelectedSet(Object.keys(parsedSets.sets)[0]);
  442. storeFavs();
  443. updateView();
  444. }
  445. catch (e){
  446. alert("failed to load");
  447. }
  448. };
  449. selectImportFileButtonSelector.change((event) => {
  450. let files = event.target.files;
  451. for (let i = 0; i < files.length; i++){
  452. reader.readAsText(files[i]);
  453. }
  454. selectImportFileButtonSelector.val("");
  455. });
  456. external_jQuery_("#fav-manager-dropdown-button", dropdownNode).click(updateView);
  457. modalNode.ready(updateView);
  458. });
  459.  
  460. // CONCATENATED MODULE: ./src/atcoder/main.js
  461.  
  462.  
  463.  
  464. /* harmony default export */ var main = (function(){
  465. injectFavHandler();
  466. generateElement();
  467. });
  468.  
  469. // CONCATENATED MODULE: ./src/circles/main.js
  470.  
  471.  
  472.  
  473.  
  474.  
  475. /* harmony default export */ var circles_main = (function () {
  476. const circleName = location.pathname.split('/')[2];
  477. $.get(`/circles/${circleName}/api`).then(members => {
  478. let exportSets = new favSets();
  479. exportSets.sets[circleName] = new Set();
  480. exportSets.isActive[circleName] = true;
  481. members.forEach((member) => {
  482. exportSets.sets[circleName].add(member);
  483. });
  484. let elem = $(`<div style="text-align: center;">
  485. <a style="color: gray;" download="${`circles-${circleName}-${getTimeStamp()}.json`}" href="${getObjectURL(favSets.stringify(exportSets, false))}" target="_blank">
  486. お気に入り登録用ファイルをダウンロード
  487. </a>
  488. </div>`);
  489. $("table").before(elem);
  490. });
  491. });
  492. // CONCATENATED MODULE: ./src/main.js
  493. // ==UserScript==
  494. // @name ac-favorite-manager
  495. // @namespace https://atcoder.jp/
  496. // @version 1.1.1
  497. // @description AtCoderのお気に入りの管理を行います。
  498. // @author keymoon
  499. // @license MIT
  500. // @match https://atcoder.jp/*
  501. // @exclude https://atcoder.jp/*/json
  502. // @match http://atcoder-circles.com/circles/*
  503. // @exclude http://atcoder-circles.com/circles/
  504. // ==/UserScript==
  505.  
  506.  
  507.  
  508.  
  509. if (location.hostname === "atcoder.jp"){
  510. main();
  511. }
  512. else if (location.hostname === "atcoder-circles.com"){
  513. circles_main();
  514. }
  515.  
  516. /***/ })
  517. /******/ ]);