ChatGPT: Message Records

Remind you how many quota you left

  1. // ==UserScript==
  2. // @name ChatGPT: Message Records
  3. // @namespace UserScripts
  4. // @match https://chatgpt.com/*
  5. // @match https://chat.openai.com/*
  6. // @grant GM.getValue
  7. // @grant GM.setValue
  8. // @grant GM.deleteValue
  9. // @grant GM_addValueChangeListener
  10. // @grant unsafeWindow
  11. // @version 1.2.2
  12. // @author CY Fung
  13. // @license MIT
  14. // @description Remind you how many quota you left
  15. // @run-at document-start
  16. // @inject-into page
  17. // ==/UserScript==
  18.  
  19. const __errorCode21167__ = (() => {
  20.  
  21. try {
  22. Promise.resolve('\u{1F4D9}', ((async () => { })()).constructor);
  23. } catch (e) {
  24. console.log('%cUnsupported Browser', 'background-color: #FAD02E; color: #333; padding: 4px 8px; font-weight: bold; border-radius: 4px;');
  25. return 0x3041;
  26. }
  27.  
  28. if (typeof GM_addValueChangeListener !== 'function' || typeof GM !== 'object' || typeof (GM || 0).setValue !== 'function') {
  29. console.log('%cUnsupported UserScript Manager', 'background-color: #FAD02E; color: #333; padding: 4px 8px; font-weight: bold; border-radius: 4px;');
  30. return 0x3042;
  31. }
  32.  
  33. return 0;
  34. })();
  35.  
  36. __errorCode21167__ || (() => {
  37.  
  38. /** @type {Window} */
  39. const uWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
  40. /** @type {{ globalThis.PromiseConstructor}} */
  41. const Promise = ((async () => { })()).constructor;
  42.  
  43. let __recordId_new = 1;
  44. let abortCounter = 0;
  45.  
  46. let userOpenAction = null;
  47.  
  48. const kPattern = (num) => {
  49. const letters = 'abcdefghijklmnopqrstuvwxyz';
  50. const k = num % 9;
  51. const j = Math.floor(num / 9);
  52. const letter = letters[j];
  53. return `${letter}${k + 1}`;
  54. }
  55.  
  56. const kHash = (n) => {
  57. if (n < 0 || n > 54755) {
  58. throw new Error('Number out of range');
  59. }
  60.  
  61. const nValue = 9 * 26; // precompute this value since it's constant
  62.  
  63. // Simplified equation, combined terms
  64. let hashBase = (n * 9173) % 54756;
  65. // ((54756 - n) * 9173 + (n) * 7919 + (n) * 5119) % 54756;`
  66.  
  67. let hash = '';
  68. for (let i = 0; i < 2; i++) {
  69. const t = hashBase % nValue;
  70. hash = kPattern(t) + hash;
  71. hashBase = Math.floor(hashBase / nValue);
  72. }
  73.  
  74. return hash;
  75. }
  76.  
  77. const cleanContext = async (win, gmWindow) => {
  78. /** @param {Window} fc */
  79. const sanitize = (fc) => {
  80. const { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame } = fc;
  81. const res = { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame };
  82. for (let k in res) res[k] = res[k].bind(win); // necessary
  83. return res;
  84. }
  85. if (gmWindow && typeof gmWindow === 'object' && gmWindow.GM_info && gmWindow.GM) {
  86. let isIsolatedContext = (
  87. (gmWindow.requestAnimationFrame !== win.requestAnimationFrame) &&
  88. (gmWindow.cancelAnimationFrame !== win.cancelAnimationFrame) &&
  89. (gmWindow.setTimeout !== win.setTimeout) &&
  90. (gmWindow.setInterval !== win.setInterval) &&
  91. (gmWindow.clearTimeout !== win.clearTimeout) &&
  92. (gmWindow.clearInterval !== win.clearInterval)
  93. );
  94. if (isIsolatedContext) {
  95. return sanitize(gmWindow);
  96. }
  97. }
  98. const waitFn = requestAnimationFrame; // shall have been binded to window
  99. try {
  100. let mx = 16; // MAX TRIAL
  101. const frameId = 'vanillajs-iframe-v1'
  102. let frame = document.getElementById(frameId);
  103. let removeIframeFn = null;
  104. if (!frame) {
  105. frame = document.createElement('iframe');
  106. frame.id = frameId;
  107. const blobURL = typeof webkitCancelAnimationFrame === 'function' && typeof kagi === 'undefined' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
  108. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  109. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  110. n.appendChild(frame);
  111. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  112. const root = document.documentElement;
  113. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  114. if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));
  115.  
  116. removeIframeFn = (setTimeout) => {
  117. const removeIframeOnDocumentReady = (e) => {
  118. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  119. e = n;
  120. n = win = removeIframeFn = 0;
  121. setTimeout ? setTimeout(() => e.remove(), 200) : e.remove();
  122. }
  123. if (!setTimeout || document.readyState !== 'loading') {
  124. removeIframeOnDocumentReady();
  125. } else {
  126. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  127. }
  128. }
  129. }
  130. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  131. const fc = frame.contentWindow;
  132. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  133. try {
  134. const res = sanitize(fc);
  135. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  136. return res;
  137. } catch (e) {
  138. if (removeIframeFn) removeIframeFn();
  139. return null;
  140. }
  141. } catch (e) {
  142. console.warn(e);
  143. return null;
  144. }
  145. };
  146.  
  147. const observablePromise = (proc, timeoutPromise) => {
  148. let promise = null;
  149. return {
  150. obtain() {
  151. if (!promise) {
  152. promise = new Promise(resolve => {
  153. let mo = null;
  154. const f = () => {
  155. let t = proc();
  156. if (t) {
  157. mo.disconnect();
  158. mo.takeRecords();
  159. mo = null;
  160. resolve(t);
  161. }
  162. }
  163. mo = new MutationObserver(f);
  164. mo.observe(document, { subtree: true, childList: true })
  165. f();
  166. timeoutPromise && timeoutPromise.then(() => {
  167. resolve(null)
  168. });
  169. });
  170. }
  171. return promise
  172. }
  173. }
  174. }
  175.  
  176. cleanContext(uWin, window).then((__CONTEXT__) => {
  177.  
  178. const { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame } = __CONTEXT__;
  179. const console = Object.assign({}, window.console);
  180. /** @type {JSON.parse} */
  181. const jParse = window.JSON.parse.bind(window.JSON);
  182. const jParseCatched = (val) => {
  183. let res = null;
  184. try {
  185. res = jParse(val);
  186. } catch (e) { }
  187. return res;
  188. }
  189. const jStringify = window.JSON.stringify.bind(window.JSON);
  190.  
  191. const GM_RECORD_KEY = 'TOTAL_MESSAGE_RECORDS';
  192.  
  193. let __foregroundActivityMeasure = 0;
  194. let __totalActivityMeasure = 0;
  195. const foregroundActivityMeasureInterval = 500;
  196. const amiUL = foregroundActivityMeasureInterval * 1.1;
  197. const amiLL = foregroundActivityMeasureInterval * 0.9;
  198. const activityMeasure = {
  199. get foreground() {
  200. return __foregroundActivityMeasure;
  201. },
  202.  
  203. get background() {
  204. return activityMeasure.total - activityMeasure.foreground;
  205. },
  206. get total() {
  207. return Math.round(__totalActivityMeasure)
  208. }
  209. }
  210.  
  211.  
  212. let __uid = 0;
  213.  
  214. let message_cap = null;
  215.  
  216. let message_cap_window = null;
  217. let categories = null;
  218. let models = null;
  219. let currentAccount = null;
  220. let currentUser = null;
  221. const getUserId = () => currentAccount && currentUser ? `${currentAccount}.${currentUser}` : '';
  222.  
  223. const dummyObject = {};
  224. for (const [key, value] of Object.entries(console)) {
  225. if (typeof value === 'function' && typeof dummyObject[key] !== 'function') {
  226. console[key] = value.bind(window.console);
  227. }
  228. }
  229.  
  230. const messageRecords = [];
  231. let messageRecordsOnCurrentAccount = null;
  232.  
  233.  
  234.  
  235. let rafPromise = null;
  236. const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
  237. requestAnimationFrame(hRes => {
  238. rafPromise = null;
  239. resolve(hRes);
  240. });
  241. }));
  242.  
  243. const foregroundWrap = (cb) => {
  244.  
  245. let wait = false;
  246.  
  247. return () => {
  248. if (wait) return;
  249. wait = true;
  250. requestAnimationFrame(function () {
  251. wait = false;
  252. cb();
  253. });
  254. }
  255.  
  256. }
  257.  
  258. const findRecordIndexByRId = (rid) => {
  259.  
  260. if (!rid) return null;
  261. for (let i = 0; i < messageRecords.length; i++) {
  262. const record = messageRecords[i];
  263. if (record.$recordId && rid === record.$recordId) {
  264. return i;
  265. }
  266. }
  267. return -1;
  268.  
  269. }
  270.  
  271. const cssStyleText = () => `
  272.  
  273. :root {
  274. --mr-background-color: #2a5c47;
  275. --mr-font-stack: "Ubuntu-Italic", "Lucida Sans", helvetica, sans;
  276.  
  277. --mr-target-width: 30px;
  278. --mr-target-height: 30px;
  279. --mr-target-bottom: 90px;
  280. --mr-target-right: 50px;
  281.  
  282. /* 8-0.5*x+0.25*x*x */
  283. --mr-tb-radius: calc(2.42 * var(--mr-border-width));
  284. /* 20 -> 8px. 14 -> 6px 4px. 10px */
  285. --mr-message-bubble-width: 200px;
  286. --mr-message-bubble-margin: 0;
  287. --mr-border-width: 2px;
  288. --mr-triangle-border-width: var(--mr-tb-radius);
  289.  
  290. --mr-message-bubble-opacity: 0;
  291. --mr-message-bubble-scale: 0.5;
  292. --mr-message-bubble-transform-origin: bottom right;
  293. --mr-message-bubble-transition: opacity 0.3s, transform 0.3s, visibility 0s 0.3s;
  294. --mr-border-color: #666;
  295.  
  296. --mr-tb-btm: calc(var(--mr-tb-radius) * 1.72);
  297. }
  298.  
  299. html {
  300. --mr-message-bubble-bg-color: #ecf3e7;
  301. --mr-message-bubble-text-color: #414351;
  302. --progress-color: #807e1e;
  303. }
  304.  
  305. html.dark{
  306. --mr-message-bubble-bg-color: #40414f;
  307. --mr-message-bubble-text-color: #ececf1;
  308. }
  309.  
  310. html[mr-request-model="gpt-4"]{
  311. --progress-color: #ac68ff;
  312. }
  313.  
  314. html[mr-request-model="gpt-3"] {
  315. --progress-color: #19c37d;
  316. }
  317.  
  318. html[mr-request-state="request"] {
  319. --progress-percent: 25%;
  320. --progress-rr: 9px;
  321. }
  322.  
  323. html[mr-request-state="response"] {
  324. --progress-percent: 75%;
  325. --progress-rr: 9px;
  326. }
  327.  
  328.  
  329. html[mr-request-state=""] {
  330. --progress-percent: 100%;
  331. --progress-rr: 20px;
  332. }
  333.  
  334.  
  335. html[mr-request-state=""] .mr-progress-bar::before {
  336. --mr-animate-background-image: none;
  337. }
  338.  
  339.  
  340.  
  341. .mr-progress-bar.mr-progress-bar-show {
  342.  
  343. visibility:visible;
  344.  
  345. }
  346.  
  347. .mr-progress-bar {
  348. display: inline-block;
  349. width: 200px;
  350. --progress-height: 16px;
  351. --progress-padding: 4px;
  352. /* --progress-percent: 50%; */
  353. /* --progress-color: #2bc253; */
  354. --progress-stripe-color: rgba(255, 255, 255, 0.2);
  355. --progress-shadow1: rgba(255, 255, 255, 0.3);
  356. --progress-shadow2: rgba(0, 0, 0, 0.4);
  357. --progress-rl: 20px;
  358. /* --progress-rr: 9px; */
  359.  
  360. width: 100%;
  361. /* --progress-percent: 100%;
  362. --progress-rr: 20px;*/
  363. visibility: collapse;
  364. }
  365.  
  366.  
  367. @keyframes mr-progress-bar-move {
  368. 0% {
  369. background-position: 0 0;
  370. }
  371.  
  372. 100% {
  373. background-position: 50px 50px;
  374. }
  375. }
  376.  
  377. .mr-progress-bar {
  378. box-sizing: border-box;
  379. height: var(--progress-height);
  380. position: relative;
  381. background: #555;
  382. border-radius: 25px;
  383. box-shadow: inset 0 -1px 1px var(--progress-shadow1);
  384. display: inline-block;
  385. }
  386.  
  387. .mr-progress-bar::before {
  388. box-sizing: border-box;
  389. content: "";
  390. display: block;
  391. margin: var(--progress-padding);
  392. border-top-right-radius: var(--progress-rr);
  393. border-bottom-right-radius: var(--progress-rr);
  394. border-top-left-radius: var(--progress-rl);
  395. border-bottom-left-radius: var(--progress-rl);
  396. background-color: var(--progress-color);
  397. box-shadow: inset 0 2px 9px var(--progress-shadow1), inset 0 -2px 6px var(--progress-shadow2);
  398. position: absolute;
  399. top: 0;
  400. left: 0;
  401. right: calc(100% - var(--progress-percent));
  402. transition: right 300ms, background-color 300ms;
  403. bottom: 0;
  404.  
  405.  
  406.  
  407. --mr-animate-background-image: linear-gradient(-45deg, var(--progress-stripe-color) 25%, transparent 25%, transparent 50%, var(--progress-stripe-color) 50%, var(--progress-stripe-color) 75%, transparent 75%, transparent);
  408.  
  409. background-image: var(--mr-animate-background-image);
  410.  
  411. background-size: 50px 50px;
  412. animation: mr-progress-bar-move 2s linear infinite;
  413.  
  414. }
  415.  
  416. .mr-nostripes::before {
  417. --mr-animate-background-image: none;
  418. }
  419.  
  420.  
  421. #mr-msg-l {
  422.  
  423. text-align: center;
  424. font-size: .875rem;
  425. color: var(--tw-prose-code);
  426. font-size: .875em;
  427. font-weight: 600;
  428. }
  429. #mr-msg-p {
  430. text-align: center;
  431. font-size: 1rem;
  432. }
  433.  
  434. #mr-msg-p1{
  435. display: block;
  436. }
  437. #mr-msg-p2{
  438. display: block;
  439. font-size: .75rem;
  440. }
  441.  
  442.  
  443.  
  444.  
  445. .mr-message-bubble {
  446. margin: 0;
  447. display: inline-block;
  448. position: absolute;
  449. width: var(--mr-message-bubble-width);
  450. height: auto;
  451. background-color: var(--mr-message-bubble-bg-color);
  452. opacity: var(--mr-message-bubble-opacity);
  453. transform: scale(var(--mr-message-bubble-scale));
  454. transform-origin: var(--mr-message-bubble-transform-origin);
  455. transition: var(--mr-message-bubble-transition);
  456. visibility: hidden;
  457. margin-bottom: var(--mr-tb-btm);
  458. bottom:0;
  459. right:0;
  460. color: var(--mr-message-bubble-text-color);
  461. --mr-user-select: auto-user-select;
  462. }
  463.  
  464. .mr-border {
  465. border: var(--mr-border-width) solid var(--mr-border-color);
  466. }
  467.  
  468. .mr-round {
  469. border-radius: var(--mr-tb-radius);
  470. }
  471.  
  472. .mr-tri-right.mr-border.mr-btm-right:before {
  473. content: ' ';
  474. position: absolute;
  475. width: 0;
  476. height: 0;
  477. left: auto;
  478. right: calc(var(--mr-border-width) * -1);
  479. bottom: calc(var(--mr-tb-radius) * -2);
  480. border: calc(var(--mr-border-width) * 4) solid;
  481. border-color: transparent var(--mr-border-color) transparent transparent;
  482. }
  483.  
  484. .mr-tri-right.mr-btm-right:after {
  485. content: ' ';
  486. position: absolute;
  487. width: 0;
  488. height: 0;
  489. left: auto;
  490. right: 0px;
  491. bottom: calc(var(--mr-tb-radius) * -1);
  492. border: var(--mr-triangle-border-width) solid;
  493. border-color: var(--mr-message-bubble-bg-color) var(--mr-message-bubble-bg-color) transparent transparent;
  494. }
  495.  
  496. .mr-msg-text {
  497. padding: 1em;
  498. text-align: left;
  499. line-height: 1.5em;
  500. }
  501.  
  502. .mr-message-bubble.mr-open {
  503. opacity: 1;
  504. transform: scale(1);
  505. visibility: visible;
  506. transition-delay: 0s;
  507. }
  508.  
  509. .mr-msg-text p {
  510. margin: 0;
  511. }
  512.  
  513. .mr-a33 {
  514. position: absolute;
  515. top: auto;
  516. left: auto;
  517. bottom: 0px;
  518. right: 0px;
  519. }
  520.  
  521. .mr-k33 {
  522. position: absolute;
  523. contain: size layout style;
  524. width: 100%;
  525. height: 100%;
  526. transform: translate(-50%, -100%);
  527. }
  528.  
  529.  
  530. .mr-button-container[class] button[class] {
  531. opacity: 0.8;
  532.  
  533. }
  534. .mr-button-container[class] button[class]:hover {
  535. opacity: 1;
  536. }
  537.  
  538. .mr-button-container.mr-clicked[class] button[class],
  539. .mr-button-container.mr-clicked[class] button[class]:hover {
  540. background-color: #616a8a;
  541. opacity:1;
  542. }
  543.  
  544. .mr-button-container[class], .mr-button-container[class] button[class] {
  545.  
  546. --mr-user-select: none;
  547. user-select: var(--mr-user-select);
  548.  
  549. }
  550.  
  551. .mr-offset-book-btn {
  552. margin-right: 28px;
  553. }
  554.  
  555. .flex.w-full.flex-col[class*="rounded-"][class*="bg-"]:hover {
  556. z-index: 999;
  557. }
  558.  
  559.  
  560. `;
  561.  
  562. const addCssText = () => {
  563. if (document.querySelector('#mr-style811')) return;
  564. const style = document.createElement('style');
  565. style.id = 'mr-style811';
  566. style.textContent = cssStyleText();
  567. document.head.appendChild(style);
  568.  
  569. }
  570.  
  571.  
  572. const updateGMRecord = () => {
  573.  
  574. Promise.resolve().then(() => {
  575. GM.setValue(GM_RECORD_KEY, jStringify({
  576. version: 1,
  577. records: messageRecords
  578. }));
  579. });
  580.  
  581. }
  582.  
  583. const updateMessageRecordsOnCurrentAccount = () => {
  584. messageRecordsOnCurrentAccount = messageRecords.filter(entry => entry.$account_uid === `${currentAccount}.${currentUser}`);
  585. }
  586.  
  587. const fixOverRecords = () => {
  588. messageRecords.splice(0, Math.floor(messageRecords.length / 2));
  589. let rid = 1;
  590. for (const record of messageRecords) {
  591. record.$recordId = rid;
  592. messageRecords.push(record);
  593. rid++;
  594. }
  595. __recordId_new = rid;
  596. keep.length = 0;
  597. keep = null;
  598.  
  599. updateMessageRecordsOnCurrentAccount();
  600. updateGMRecord();
  601. }
  602.  
  603. const addRecord = (record) => {
  604. if (!currentAccount || !currentUser || !record || record.$account_uid) {
  605. console.log('addRecord aborted');
  606. return;
  607. }
  608.  
  609. record.$account_uid = getUserId();
  610.  
  611. const recordId = __recordId_new;
  612. record.$recordId = recordId;
  613.  
  614. record.$recorded_at = Date.now(); // Local Time
  615. messageRecords.push(record);
  616. __recordId_new++;
  617. messageRecordsOnCurrentAccount.push(record);
  618.  
  619.  
  620. if (messageRecords.length > 3600 || __recordId_new > 3600) {
  621. // around 4MB
  622. Promise.resolve().then(fixOverRecords);
  623. }
  624.  
  625.  
  626. return recordId;
  627.  
  628. }
  629.  
  630.  
  631.  
  632. Object.assign(uWin, {
  633. $$mr$$getMessageRecords() {
  634. return messageRecords;
  635. },
  636. $$mr$$getMessageRecordsFromGM() {
  637. return GM.getValue(GM_RECORD_KEY);
  638. },
  639. $$mr$$clearMessageRecords() {
  640. return GM.deleteValue(GM_RECORD_KEY);
  641. },
  642. $$mr$$getUserId() {
  643. const r = getUserId();
  644. if (!r) console.log(`!! ${currentAccount}.${currentUser} !!`)
  645. return r;
  646. },
  647. $$mr$$activityMeasure() {
  648. return Object.assign({}, activityMeasure)
  649. }
  650. });
  651.  
  652.  
  653.  
  654. const setRecordsByJSONString = (newValue, initial) => {
  655.  
  656. let tObj = jParseCatched(newValue || '{}');
  657. if (!tObj || !tObj.version || !tObj.records) tObj = { version: 1, records: [] };
  658.  
  659. if (tObj.version !== 1) {
  660. if (initial) {
  661. GM.deleteValue(GM_RECORD_KEY);
  662. // and wait change confirmed by listener
  663. } else {
  664. console.warn('record version is incorrect. please reload the page.');
  665. }
  666. return;
  667. }
  668.  
  669. if (messageRecords.length > 0) messageRecords.length = 0;
  670. __recordId_new = 1;
  671. let rid = 1;
  672. for (const record of tObj.records) {
  673. if (record.$recordId >= rid) rid = record.$recordId + 1;
  674. messageRecords.push(record);
  675. __recordId_new++;
  676. }
  677. __recordId_new = rid;
  678. updateMessageRecordsOnCurrentAccount();
  679.  
  680.  
  681.  
  682. }
  683.  
  684. const onAccountDetectedOrChanged = () => {
  685.  
  686. updateMessageRecordsOnCurrentAccount();
  687.  
  688.  
  689. }
  690.  
  691. let last_GM_RECORD_KEY_newValue = null;
  692.  
  693. const setRecordsByJSONStringX = () => {
  694. setRecordsByJSONString(last_GM_RECORD_KEY_newValue);
  695. }
  696. const setRecordsByJSONStringForegroundWrapped = foregroundWrap(setRecordsByJSONStringX);
  697.  
  698.  
  699. let gmValueListenerId = GM_addValueChangeListener(GM_RECORD_KEY, (key, oldValue, newValue, remote) => {
  700. last_GM_RECORD_KEY_newValue = newValue;
  701. setRecordsByJSONStringForegroundWrapped();
  702. });
  703.  
  704. Promise.resolve().then(() => GM.getValue(GM_RECORD_KEY)).then(result => {
  705. //
  706.  
  707. result = result || '{}';
  708.  
  709. if (typeof result !== 'string') {
  710. console.log('GM.getValue aborted')
  711. return;
  712. }
  713.  
  714. GM.setValue()
  715. setRecordsByJSONString(result, true)
  716.  
  717.  
  718. })
  719.  
  720. const arrayTypeFix = (a) => {
  721. return a === null || a === undefined ? [] : a;
  722. }
  723.  
  724. const getRequestQuotaString = () => {
  725.  
  726. let num = null;
  727.  
  728. if (document.documentElement.getAttribute('mr-request-model') === 'gpt-4') {
  729.  
  730.  
  731. num = messageRecordsOnCurrentAccount.filter(entry => {
  732. return typeof entry.model === 'string' && entry.model.startsWith('gpt-4') && (entry.$recorded_at || entry.$record_time_ms || 0) > (Date.now() - (6 * 1000 * 60) - message_cap_window * 60 * 1000)
  733.  
  734. }).length;
  735.  
  736. + ' out of ' + message_cap;
  737.  
  738. let p1 = document.querySelector('#mr-msg-p1')
  739. let p2 = document.querySelector('#mr-msg-p2')
  740.  
  741. if (p1 && p2) {
  742. p1.textContent = `${num}`
  743. p2.textContent = ' out of ' + message_cap;
  744. }
  745.  
  746.  
  747. } else if (document.documentElement.getAttribute('mr-request-model') === 'gpt-3') {
  748.  
  749.  
  750.  
  751. num = messageRecordsOnCurrentAccount.filter(entry => {
  752. return typeof entry.model === 'string' && entry.model.startsWith('text-davinci-002-render-sha') && (entry.$recorded_at || entry.$record_time_ms || 0) > (Date.now() - (6 * 1000 * 60) - 24 * 60 * 60 * 1000)
  753.  
  754. }).length;
  755.  
  756. let p1 = document.querySelector('#mr-msg-p1')
  757. let p2 = document.querySelector('#mr-msg-p2')
  758.  
  759. if (p1 && p2) {
  760. p1.textContent = `${num}`
  761. p2.textContent = ` in past 24 hours`;
  762. }
  763.  
  764.  
  765.  
  766.  
  767. } else {
  768.  
  769. let p1 = document.querySelector('#mr-msg-p1')
  770. let p2 = document.querySelector('#mr-msg-p2')
  771.  
  772. if (p1 && p2) {
  773. p1.textContent = '';
  774. p2.textContent = '';
  775. }
  776.  
  777. // return '';
  778. }
  779.  
  780.  
  781. }
  782.  
  783. function onRequest(_body) {
  784.  
  785. const body = _body;
  786.  
  787. const bodyObject = jParseCatched(body);
  788. if (!bodyObject) {
  789. console.log('invalid JSON object');
  790. return;
  791. }
  792.  
  793. if (!('messages' in bodyObject)) {
  794. console.log('invalid format of JSON body')
  795. return;
  796. }
  797.  
  798. const model = typeof bodyObject.model === 'string' ? bodyObject.model : null;
  799. const messages = arrayTypeFix(bodyObject.messages);
  800.  
  801. if (!model || !messages || typeof (messages || 0).length !== 'number') {
  802. console.log('invalid format of JSON body')
  803. return;
  804. }
  805.  
  806. if (!currentAccount) {
  807. console.log('No account information is found. Message Record aborted.')
  808. return;
  809. }
  810.  
  811. let conversation_id = bodyObject.conversation_id;
  812. if (!conversation_id) conversation_id = "***"
  813.  
  814. let recordIds = null;
  815.  
  816.  
  817. let request_model = '';
  818. if (typeof model === 'string' && (model === 'text-davinci-002-render-sha' || model.startsWith('text-davinci-002-render-sha'))) {
  819.  
  820.  
  821. request_model = 'gpt-3';
  822. } else if (typeof model === 'string' && (model === 'gpt-4' || model.startsWith('gpt-4'))) {
  823.  
  824. request_model = 'gpt-4';
  825.  
  826. }
  827.  
  828. if (request_model) {
  829.  
  830.  
  831. try {
  832. document.documentElement.setAttribute('mr-request-model', request_model);
  833. getRequestQuotaString();
  834. document.documentElement.setAttribute('mr-request-state', 'request');
  835. document.querySelector('.mr-progress-bar').classList.add('mr-progress-bar-show');
  836.  
  837. } catch (e) { }
  838.  
  839. if (userOpenAction === null && attachedGroup && attachedGroup.isConnected === true && isChatBubbleOpened() === false) {
  840. const myGroup = attachedGroup;
  841. myGroupClicked(myGroup);
  842. }
  843.  
  844.  
  845.  
  846. } else {
  847.  
  848.  
  849. try {
  850. document.documentElement.setAttribute('mr-request-model', '')
  851. getRequestQuotaString();
  852. document.querySelector('.mr-progress-bar').classList.remove('mr-progress-bar-show');
  853.  
  854. } catch (e) { }
  855.  
  856.  
  857.  
  858. }
  859.  
  860.  
  861.  
  862.  
  863. const onAbort = (evt, signal, newChatId) => {
  864.  
  865. if (typeof newChatId === 'string' && newChatId) {
  866.  
  867. const cd002 = !!recordIds && recordIds.length === 1 && recordIds[0] > 0;
  868. console.log('condition 002', cd002);
  869.  
  870. if (cd002) {
  871. const rid = recordIds[0];
  872. const idx = findRecordIndexByRId(rid);
  873.  
  874. if (idx === null || idx < 0 || !messageRecords[idx] || messageRecords[idx].conversation_id !== '***') {
  875. console.warn('error found in onAbort');
  876. } else {
  877. messageRecords[idx].conversation_id = newChatId
  878.  
  879. console.log(`record#${rid} is updated with conversation_id = "${newChatId}"`)
  880. }
  881.  
  882. }
  883.  
  884. }
  885.  
  886. if (recordIds && recordIds.length >= 1) {
  887.  
  888. const completionTime = evt.__aborted_at__ > 0 ? evt.__aborted_at__ : 0;
  889.  
  890. if (completionTime) {
  891. for (const rid of recordIds) {
  892. const idx = findRecordIndexByRId(rid);
  893. if (idx === null || idx < 0 || !messageRecords[idx]) {
  894. console.warn('completionTime found in onAbort');
  895. } else if (messageRecords[idx].conversation_id === '***') {
  896. // TBC
  897. } else {
  898. messageRecords[idx].$completed_at = completionTime;
  899. }
  900. }
  901. }
  902.  
  903. }
  904.  
  905.  
  906.  
  907.  
  908. updateGMRecord();
  909.  
  910.  
  911. if (document.documentElement.getAttribute('mr-request-state') === 'response') document.documentElement.setAttribute('mr-request-state', '')
  912.  
  913.  
  914. console.log('messageHandler: onAbort', evt, signal, newChatId);
  915. };
  916.  
  917. const uid = ++__uid;
  918.  
  919. const onResponse = (response, info) => {
  920.  
  921. const { requestTime, responseTime } = info;
  922.  
  923. // response.lockedBodyStream.then((body) => {
  924.  
  925. // // console.log(13, body)
  926.  
  927. // })
  928.  
  929. if (!currentAccount) {
  930. console.log('No account information is found. Message Record aborted.')
  931. return;
  932. }
  933.  
  934. if (recordIds !== null) {
  935. console.warn('recordIds !== null');
  936. }
  937. recordIds = [];
  938. for (const message of messages) {
  939.  
  940. const rid = addRecord({
  941. model,
  942. conversation_id,
  943. message,
  944. $requested_at: requestTime,
  945. $responsed_at: responseTime
  946. });
  947. recordIds.push(rid);
  948.  
  949. }
  950.  
  951. updateGMRecord();
  952.  
  953. if (document.documentElement.getAttribute('mr-request-state') === 'request') document.documentElement.setAttribute('mr-request-state', 'response')
  954.  
  955. try {
  956.  
  957. getRequestQuotaString();
  958. } catch (e) { }
  959.  
  960. // console.log(bodyObject)
  961. // console.log(response, info)
  962. // console.log({
  963. // message_cap, message_cap_window,
  964. // categories,
  965. // models
  966. // })
  967.  
  968. }
  969.  
  970. return {
  971. uid,
  972. model,
  973. conversation_id,
  974. message_cap, message_cap_window,
  975. categories,
  976. bodyObject,
  977.  
  978. messages,
  979. onAbort,
  980. onResponse,
  981.  
  982. }
  983.  
  984.  
  985. }
  986.  
  987.  
  988. uWin.__fetch247__ = uWin.fetch;
  989.  
  990. let onceRgStr = false;
  991.  
  992. let __newChatIdResolveFn__ = null;
  993.  
  994. const authJsonPn = function () {
  995.  
  996. const target = this['$a039$'] || this;
  997. return new Promise((resolve, reject) => {
  998. // console.log(112)
  999.  
  1000. target.json().then((result) => {
  1001.  
  1002.  
  1003. const __jsonRes__ = result;
  1004.  
  1005.  
  1006. if (typeof (__jsonRes__ || 0).user === 'object' && (__jsonRes__.user || 0).id) {
  1007. currentUser = `${(__jsonRes__.user || 0).id}`;
  1008. // console.log('user??', currentUser)
  1009. // __NEXT_DATA__.props.pageProps.user.id // document.cookie.__puid
  1010. }
  1011.  
  1012.  
  1013. // console.log(566, result)
  1014. resolve(result)
  1015. }).catch(reject)
  1016.  
  1017. })
  1018. }
  1019.  
  1020. const jsonPnForGetRequest = function () {
  1021.  
  1022. const target = this['$a039$'] || this;
  1023. return new Promise((resolve, reject) => {
  1024.  
  1025. target.json().then((result) => {
  1026.  
  1027.  
  1028. const __jsonRes__ = result;
  1029.  
  1030.  
  1031.  
  1032. if (typeof (__jsonRes__ || 0).accounts === 'object') {
  1033.  
  1034. const tmpSet = new Set();
  1035. if (((__jsonRes__ || 0).accounts || 0).length > 0) {
  1036.  
  1037. for (const account of __jsonRes__.accounts) {
  1038. tmpSet.add(`${account.account_id}.${account.account_user_id}`);
  1039. }
  1040.  
  1041. } else {
  1042.  
  1043. for (let [key, account] of Object.entries(__jsonRes__.accounts)) {
  1044. account = account.account || account;
  1045. tmpSet.add(`${account.account_id}.${account.account_user_id}`);
  1046.  
  1047. }
  1048.  
  1049. }
  1050. if (tmpSet.size !== 1) {
  1051. console.log('account detection failed')
  1052. } else {
  1053. let acc = [...tmpSet.keys()][0];
  1054. if (acc !== currentAccount) {
  1055.  
  1056. currentAccount = acc;
  1057. onAccountDetectedOrChanged();
  1058. }
  1059. }
  1060.  
  1061.  
  1062.  
  1063. }
  1064. else if (((__jsonRes__ || 0).categories || 0).length >= 1 && ((__jsonRes__ || 0).models || 0).length >= 1) {
  1065.  
  1066. const jsonRes = __jsonRes__;
  1067.  
  1068.  
  1069. try {
  1070.  
  1071. categories = [...jsonRes.categories];
  1072. } catch (e) { }
  1073. try {
  1074. models = [...jsonRes.models];
  1075.  
  1076. } catch (e) { }
  1077.  
  1078. // console.log(233, categories, models)
  1079.  
  1080. }
  1081.  
  1082.  
  1083.  
  1084.  
  1085. // console.log(544, result)
  1086. resolve(result)
  1087. }).catch(reject)
  1088.  
  1089. })
  1090. };
  1091.  
  1092. const message_limit_jsonPn = function () {
  1093.  
  1094. const target = this['$a039$'] || this;
  1095. return new Promise((resolve, reject) => {
  1096.  
  1097.  
  1098. // console.log(114)
  1099. target.clone().text().then(r => {
  1100.  
  1101. // console.log(r)
  1102. let jr = jParseCatched(r);
  1103. if (jr) {
  1104.  
  1105. if (jr.message_cap > 0 && jr.message_cap_window > 0) {
  1106. message_cap = +jr.message_cap;
  1107. message_cap_window = +jr.message_cap_window;
  1108. }
  1109.  
  1110. }
  1111.  
  1112. })
  1113.  
  1114. target.json().then((result) => {
  1115.  
  1116. // console.log(result)
  1117. resolve(result)
  1118. }).catch(reject)
  1119.  
  1120. })
  1121. };
  1122.  
  1123. uWin.fetch = function (a) {
  1124. const args = arguments;
  1125. return new Promise((resolve, reject) => {
  1126. let doCatch = false;
  1127. let body = null;
  1128.  
  1129. let _onAbort = null;
  1130.  
  1131. if (typeof a === 'string' && a.endsWith('/backend-api/conversation')) {
  1132. const b = args[1] || 0;
  1133. if (b.method === "POST" && typeof b.body === 'string' && ((b.headers || 0)['Content-Type'] || '').includes('application/json')) {
  1134. doCatch = true;
  1135. body = b.body;
  1136.  
  1137. }
  1138. if (b && b.signal) {
  1139.  
  1140. const signal = b.signal;
  1141. const tid = ++abortCounter;
  1142. signal.addEventListener('abort', (evt) => {
  1143. evt.__aborted_at__ = Date.now();
  1144. const aid = abortCounter;
  1145. ++abortCounter;
  1146.  
  1147. console.log('onabort', aid, tid, evt, signal)
  1148.  
  1149. if (aid === tid && _onAbort) {
  1150. _onAbort(evt, signal);
  1151. }
  1152.  
  1153.  
  1154.  
  1155. });
  1156. }
  1157. } else if (typeof a === 'string' && a.startsWith('https://events.statsigapi.net/v1/rgstr')) {
  1158. if (onceRgStr) {
  1159. resolve = null;
  1160. reject = null;
  1161. return; // no resolve or reject for subsequent requests
  1162. }
  1163. onceRgStr = true; // no resolve or reject for next request
  1164. } else if (__newChatIdResolveFn__ && typeof a === 'string' && a.startsWith('https://chat.openai.com/backend-api/conversation/gen_title/')) {
  1165.  
  1166. let m = /gen_title\/([-0-9a-z]+)(\/|$)/.exec(a);
  1167. if (m && m[1]) {
  1168. __newChatIdResolveFn__(m[1]);
  1169. }
  1170. }
  1171.  
  1172. const unprocessedFetch = () => {
  1173.  
  1174. const actualRequest = uWin.__fetch247__.apply(this, args);
  1175.  
  1176. // console.log(269, false, args[0], Object.assign({}, args[1] || {}))
  1177.  
  1178.  
  1179.  
  1180.  
  1181. let rType = 0;
  1182.  
  1183.  
  1184. if (typeof args[0] === 'string' && args[0].includes('/') && args[1] && args[1].method === 'GET') {
  1185. rType = 1;
  1186. } else if (args[0].includes('/api/auth/session')) {
  1187. rType = 2;
  1188. }
  1189.  
  1190.  
  1191. actualRequest.then((result) => {
  1192.  
  1193. if (rType > 0) {
  1194. result = new Proxy(result, {
  1195. get(target, key, receiver) {
  1196. if (key === '$a039$') return target;
  1197. const r = target[key];
  1198. if (key === 'json' && key in target) {
  1199.  
  1200.  
  1201. if (typeof r === 'function') {
  1202.  
  1203. if (rType === 1) {
  1204.  
  1205. if (typeof args[0] === 'string' && args[0].includes('/conversation_limit') && args[1] && args[1].method === 'GET') {
  1206. return message_limit_jsonPn;
  1207. } else {
  1208.  
  1209. return jsonPnForGetRequest;
  1210. }
  1211. }
  1212. else if (rType === 2) {
  1213.  
  1214.  
  1215. return authJsonPn;
  1216.  
  1217.  
  1218. }
  1219.  
  1220.  
  1221. }
  1222. }
  1223. if (typeof r === 'function') {
  1224.  
  1225. return (receiver['$b031$' + key] || (receiver['$b031$' + key] = ((r) => (function () { return r.apply(this['$a039$'] || this, arguments) }))(r)));
  1226.  
  1227. }
  1228. return r;
  1229.  
  1230. }
  1231. });
  1232. }
  1233. // console.log(result)
  1234. resolve(result);
  1235.  
  1236. }).catch((error) => {
  1237. reject(error);
  1238. });
  1239.  
  1240. }
  1241.  
  1242. const messageHandler = doCatch ? onRequest(body) : false;
  1243. if (!messageHandler) {
  1244. unprocessedFetch();
  1245. return;
  1246. }
  1247. const requireNewChatId = (messageHandler.conversation_id === '***');
  1248.  
  1249. _onAbort = (evt, signal) => {
  1250.  
  1251. __newChatIdResolveFn__ = null;
  1252.  
  1253. if (requireNewChatId) {
  1254. let resolveFn = null;
  1255. let promise = new Promise(resolve => {
  1256. resolveFn = resolve;
  1257. })
  1258. __newChatIdResolveFn__ = (x) => {
  1259. resolveFn && resolveFn(x);
  1260. resolveFn = null;
  1261. };
  1262. setTimeout(() => {
  1263. resolveFn && resolveFn();
  1264. resolveFn = null;
  1265. }, 16);
  1266.  
  1267. // 777ms -> 781ms => 16ms shall be sufficient
  1268. promise.then((newChatId) => {
  1269. if (__newChatIdResolveFn__ === null) {
  1270. console.warn('unexpected error');
  1271. return;
  1272. }
  1273. __newChatIdResolveFn__ = null;
  1274.  
  1275. newChatId = newChatId || null;
  1276. console.log(`newChatId: ${newChatId}`);
  1277. messageHandler.onAbort(evt, signal, newChatId);
  1278. })
  1279. } else {
  1280.  
  1281. messageHandler.onAbort(evt, signal, false);
  1282. }
  1283.  
  1284. }
  1285.  
  1286.  
  1287. const requestTime1 = Date.now();
  1288. const actualRequest = uWin.__fetch247__.apply(this, args);
  1289. const requestTime2 = Date.now();
  1290. const requestTime = Math.round((requestTime1 + requestTime2) / 2);
  1291.  
  1292. // console.log(269, true, args[0], Object.assign({}, args[1] || {}))
  1293.  
  1294.  
  1295.  
  1296.  
  1297.  
  1298.  
  1299. actualRequest.then((result) => {
  1300. const responseTime = Date.now();
  1301.  
  1302. let mBodyResolve = null;
  1303. const mBody = new Promise(r => {
  1304. mBodyResolve = r;
  1305. });
  1306.  
  1307. const pRes = new Proxy(result, {
  1308. get(target, property, receiver) {
  1309. if (property === '$a039$') return target;
  1310. const r = target[property];
  1311. /**
  1312. *
  1313. * property's get order
  1314. *
  1315. * then
  1316. * status
  1317. * then
  1318. *
  1319. * ----
  1320. *
  1321. * type
  1322. * status
  1323. * clone
  1324. * headers
  1325. * headers
  1326. * ok
  1327. * body
  1328. *
  1329. *
  1330. */
  1331. if (property === 'body') {
  1332. mBodyResolve && mBodyResolve(r);
  1333. // console.log(667, r);
  1334. } else if (typeof r === 'function') {
  1335.  
  1336.  
  1337. return (receiver['$b031$' + property] || (receiver['$b031$' + property] = ((r) => (function () { return r.apply(this['$a039$'] || this, arguments) }))(r)));
  1338.  
  1339. }
  1340. return r;
  1341. }
  1342. });
  1343.  
  1344. const mResult = {
  1345. headers: result.headers, ok: result.ok, redirected: result.redirected, status: result.status,
  1346. statusText: result.statusText, type: result.type, url: result.url, get lockedBodyStream() { return mBody },
  1347.  
  1348. };
  1349.  
  1350. resolve(pRes);
  1351. Promise.resolve().then(() => {
  1352. messageHandler.onResponse(mResult, { requestTime, responseTime });
  1353. }).catch(console.warn);
  1354.  
  1355. }).catch((error) => {
  1356. reject(error);
  1357. })
  1358.  
  1359. });
  1360. }
  1361.  
  1362. const findButtonByExpression = (()=>{
  1363.  
  1364. const xpathExpressionV1 = '//button[normalize-space(text())="?"][contains(@class, "h-") and contains(@class, "w-")]';
  1365. const xpathExpressionV0 = '//div[normalize-space(text())="?"][contains(@class, "h-") and contains(@class, "w-")]';
  1366.  
  1367. return (contextNode) => {
  1368. let w = document.evaluate(xpathExpressionV1, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  1369. if (!w || !w.singleNodeValue) {
  1370. w = document.evaluate(xpathExpressionV0, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  1371. if (!w || !w.singleNodeValue) {
  1372. w = null;
  1373. }
  1374. }
  1375. return w;
  1376. }
  1377.  
  1378. })();
  1379.  
  1380. let observer = null;
  1381. let mct = 0;
  1382. let wType = 0;
  1383.  
  1384. let attachedGroup = null;
  1385.  
  1386.  
  1387. function openChatBubble() {
  1388. const chatBubble = document.querySelector(".mr-message-bubble");
  1389. if (!chatBubble) return;
  1390. chatBubble.classList.add('mr-open');
  1391. }
  1392.  
  1393. function isChatBubbleOpened() {
  1394. const chatBubble = document.querySelector(".mr-message-bubble");
  1395. if (!chatBubble) return null;
  1396. return chatBubble.classList.contains('mr-open');
  1397. }
  1398.  
  1399. function closeChatBubble() {
  1400. const chatBubble = document.querySelector(".mr-message-bubble");
  1401. if (!chatBubble) return;
  1402. chatBubble.classList.remove('mr-open');
  1403. }
  1404.  
  1405.  
  1406. const myGroupClicked = (myGroup) => {
  1407.  
  1408.  
  1409.  
  1410. const chatBubble = document.querySelector(".mr-message-bubble");
  1411. if (!chatBubble) return null;
  1412.  
  1413. const msgP = document.querySelector("#mr-msg-p1");
  1414. if (!msgP || msgP.firstChild === null) return null;
  1415.  
  1416. if (!chatBubble.classList.contains('mr-open')) {
  1417. myGroup.classList.add('mr-clicked');
  1418. openChatBubble()
  1419.  
  1420. return true;
  1421.  
  1422. } else {
  1423.  
  1424. myGroup.classList.remove('mr-clicked');
  1425. closeChatBubble();
  1426.  
  1427. return false;
  1428. }
  1429. return null;
  1430.  
  1431. }
  1432.  
  1433. /** @param {HTMLElement} myGroup */
  1434. const setupMyGroup = (myGroup) => {
  1435.  
  1436. addCssText();
  1437.  
  1438. const buttonText = findButtonByExpression(myGroup).singleNodeValue;
  1439. buttonText.textContent = '\u{1F4D9}';
  1440.  
  1441.  
  1442.  
  1443. const placeholder = document.createElement('div');
  1444.  
  1445. placeholder.classList.add('mr-k33');
  1446. placeholder.innerHTML = `
  1447. <div class="mr-message-bubble mr-tri-right mr-round mr-border mr-btm-right">
  1448. <div class="mr-msg-text">
  1449. <p id="mr-msg-l">count(messages)</p>
  1450. <p id="mr-msg-p"><span id="mr-msg-p1"></span><span id="mr-msg-p2"></span></p>
  1451. <p class="mr-progress-bar"></p>
  1452. </div>
  1453. </div>
  1454. `;
  1455. myGroup.classList.add('mr-button-container');
  1456.  
  1457. myGroup.insertBefore(placeholder, myGroup.firstChild);
  1458.  
  1459.  
  1460.  
  1461. myGroup.addEventListener('click', function (evt) {
  1462.  
  1463.  
  1464.  
  1465. if (!evt || !evt.target) return;
  1466. if (evt.target.closest('.mr-k33')) return;
  1467.  
  1468. const myGroup = this;
  1469.  
  1470.  
  1471. const chatBubble = document.querySelector(".mr-message-bubble");
  1472. if (!chatBubble) return;
  1473.  
  1474.  
  1475. let clickedResult = myGroupClicked(myGroup);
  1476. if (typeof clickedResult === 'boolean') {
  1477.  
  1478. if (evt.isTrusted === true) {
  1479.  
  1480. if (clickedResult) {
  1481. userOpenAction = true;
  1482.  
  1483. } else {
  1484.  
  1485. userOpenAction = false;
  1486. }
  1487. }
  1488. }
  1489.  
  1490.  
  1491.  
  1492. })
  1493.  
  1494.  
  1495. }
  1496.  
  1497. const onElementFound = (matchedElement) => {
  1498.  
  1499. console.log('onElementFound', matchedElement)
  1500.  
  1501.  
  1502.  
  1503. let group = matchedElement.closest('.group');
  1504. if (!group) {
  1505. console.log('The group parent of Question Mark Button cannot be found.')
  1506. return;
  1507. }
  1508. let groupParent = group;
  1509. let level = 0;
  1510. while (groupParent && groupParent.nextSibling === null && groupParent.previousSibling === null && (groupParent.parentNode instanceof HTMLElement)) {
  1511.  
  1512. groupParent = groupParent.parentNode;
  1513.  
  1514. if (++level === 1) {
  1515. groupParent.style.columnGap = '6px';
  1516. }
  1517.  
  1518.  
  1519. groupParent.classList.remove('flex-col');
  1520. groupParent.classList.add('flex-row');
  1521.  
  1522. groupParent.style.display = 'inline-flex'
  1523.  
  1524.  
  1525. }
  1526.  
  1527.  
  1528. if (!attachedGroup) {
  1529.  
  1530.  
  1531. let myGroup = group.cloneNode(true);
  1532.  
  1533. // if (/\bright-\d+\b/.test(myGroup.className)) {
  1534. myGroup.classList.add('mr-offset-book-btn');
  1535. // }
  1536. group.parentNode.insertBefore(myGroup, group);
  1537. setupMyGroup(myGroup);
  1538.  
  1539. attachedGroup = myGroup;
  1540. } else {
  1541. group.parentNode.insertBefore(attachedGroup, group);
  1542.  
  1543. }
  1544.  
  1545. }
  1546.  
  1547. const setupMRAM = () => {
  1548.  
  1549. const mram = document.createElement('mr-activity-measure');
  1550. mram.setAttribute('m', '');
  1551. document.head.appendChild(document.createElement('style')).textContent = `
  1552. mr-activity-measure[m] {
  1553. visibility: collapse !important;
  1554. width: 1px !important;
  1555. height: 1px !important;
  1556.  
  1557. display: block !important;
  1558. z-index: -1 !important;
  1559. contain: strict !important;
  1560. box-sizing: border-box !important;
  1561.  
  1562. position: fixed !important;
  1563. top: -1000px !important;
  1564. left: -1000px !important;
  1565. animation: ${foregroundActivityMeasureInterval}ms ease-in 500ms infinite alternate forwards running mrActivityMeasure !important;
  1566. }
  1567. @keyframes mrActivityMeasure{
  1568. 0%{
  1569. order: 0;
  1570. }
  1571. 100%{
  1572. order: 1;
  1573. }
  1574. }
  1575. `;
  1576. let lastEt = 0;
  1577. mram.onanimationiteration = function (evt) {
  1578. const et = evt.elapsedTime * 1000;
  1579. if (__totalActivityMeasure > et) {
  1580. this.onanimationiteration = null;
  1581. return;
  1582. }
  1583. const wt = lastEt;
  1584. lastEt = et;
  1585. const dt = et - wt;
  1586. if (dt < amiUL && dt > amiLL) __foregroundActivityMeasure += foregroundActivityMeasureInterval;
  1587.  
  1588. __totalActivityMeasure = et;
  1589. // console.log(evt)
  1590. }
  1591. document.documentElement.appendChild(mram);
  1592. }
  1593.  
  1594. const findAndHandleElement = () => {
  1595. if (!observer) return;
  1596.  
  1597. if (wType !== 0) {
  1598.  
  1599. if (!attachedGroup) return;
  1600. if (attachedGroup.isConnected) return;
  1601. }
  1602.  
  1603.  
  1604. const result = findButtonByExpression( document);
  1605. if(!result) return;
  1606. const matchedElement = result.singleNodeValue;
  1607.  
  1608. if (!matchedElement) return;
  1609. console.log('findAndHandleElement OK');
  1610. const cb = wType === 0 ? setupMRAM : () => { };
  1611. if (wType === 0) {
  1612. wType = 1;
  1613. }
  1614. Promise.resolve(matchedElement).then(onElementFound).then(cb).catch(console.warn);
  1615. }
  1616.  
  1617.  
  1618. const findAndHandleElementForegroundWrapped = foregroundWrap(findAndHandleElement);
  1619.  
  1620. observer = new MutationObserver(function (mutationsList) {
  1621. if (!observer) return;
  1622. findAndHandleElementForegroundWrapped();
  1623. });
  1624.  
  1625. observer.observe(document, { childList: true, subtree: true });
  1626.  
  1627. (async function (){
  1628.  
  1629. if (document.readyState === 'loading') {
  1630. await new Promise(resolve=>window.addEventListener('load', resolve, false));
  1631. }
  1632.  
  1633. if (!observer) return;
  1634. if (wType > 0) return;
  1635.  
  1636. const main = await observablePromise(() => document.querySelector('main')).obtain();
  1637. if (!main) return;
  1638. await getRafPromise();
  1639.  
  1640. setTimeout(function () {
  1641. if (!observer) return;
  1642. if (wType > 0) return;
  1643. console.log('The Question Mark Button cannot be found.')
  1644. observer.disconnect();
  1645. observer.takeRecords();
  1646. observer = null;
  1647. }, 1200);
  1648.  
  1649. })();
  1650.  
  1651.  
  1652.  
  1653.  
  1654.  
  1655.  
  1656. console.log('script loaded')
  1657.  
  1658.  
  1659.  
  1660. })
  1661.  
  1662.  
  1663. })();
  1664.  
  1665.  
  1666. /**
  1667. *
  1668.  
  1669.  
  1670. $record_time_ms: 1692831419486
  1671. $requested_at: 1692831418865
  1672. $responsed_at: 1692831419485
  1673. create_time: 1692831782.061773
  1674.  
  1675.  
  1676. server time is now() + 6 minutes
  1677.  
  1678. *
  1679. *
  1680. */