GPT Auto task

根据缓存中的数据自动在网页上与chat gpt对话

当前为 2023-08-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @namespace https://greasyfork.org/zh-CN/users/1106595-ikkem-lin
  3. // @name GPT Auto task
  4. // @author Mark
  5. // @description 根据缓存中的数据自动在网页上与chat gpt对话
  6. // @description "snippetSourceData", "mock_prompt1", "mock_prompt2", "model_number" 四个localStorage变量用于存储数据
  7. // @homepageURL https://github.com/IKKEM-Lin/gpt-auto-task
  8. // @version 0.1.6
  9. // @match *chat.openai.com/*
  10. // @run-at document-idle
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js
  12. // @require https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/umd.js
  13. // ==/UserScript==
  14. (function () {
  15. "use strict";
  16.  
  17. const tableName = "data";
  18.  
  19. const dbTable = {
  20. // tasks: idbKeyval.createStore("tasks", tableName),
  21. skipSnippet: idbKeyval.createStore("skipSnippet", tableName),
  22. response1: idbKeyval.createStore("response1", tableName),
  23. response2: idbKeyval.createStore("response2", tableName),
  24. responseProcessed: idbKeyval.createStore("responseProcessed", tableName),
  25. };
  26.  
  27. const locationReload = () => {
  28. location.href = "https://chat.openai.com/?model=gpt-4";
  29. }
  30.  
  31. const downloadFile = (data, fileName) => {
  32. const a = document.createElement("a");
  33. document.body.appendChild(a);
  34. a.style = "display: none";
  35. const blob = new Blob([data], {
  36. type: "application/octet-stream",
  37. });
  38. const url = window.URL.createObjectURL(blob);
  39. a.href = url;
  40. a.download = fileName;
  41. a.click();
  42. window.URL.revokeObjectURL(url);
  43. };
  44.  
  45. const yaml2object = (yamlStr) => {
  46. try {
  47. return jsyaml.load(yamlStr);
  48. } catch (error) {
  49. return null;
  50. }
  51. };
  52.  
  53. function hashFnv32a(str, asString = true, seed = undefined) {
  54. /*jshint bitwise:false */
  55. var i,
  56. l,
  57. hval = seed === undefined ? 0x811c9dc5 : seed;
  58.  
  59. for (i = 0, l = str.length; i < l; i++) {
  60. hval ^= str.charCodeAt(i);
  61. hval +=
  62. (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
  63. }
  64. if (asString) {
  65. // Convert to 8 digit hex string
  66. return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
  67. }
  68. return hval >>> 0;
  69. }
  70.  
  71. function reactionObjHandler(input) {
  72. let result = [];
  73. const validateKeys = ["reactants", "products", "condition", "catalysts"];
  74. function test(reaction) {
  75. // if (!reaction) {
  76. // return
  77. // }
  78. if (reaction instanceof Array) {
  79. return reaction.map((item) => {
  80. return test(item);
  81. });
  82. } else {
  83. try {
  84. var keys = Object.keys(reaction);
  85. } catch (error) {
  86. // debugger;
  87. console.error(error);
  88. throw new Error();
  89. }
  90. if (validateKeys.some((key) => keys.includes(key))) {
  91. result.push(reaction);
  92. return;
  93. }
  94. keys.forEach((key) => {
  95. if (reaction[key] && typeof reaction[key] === "object") {
  96. test(reaction[key]);
  97. }
  98. });
  99. }
  100. }
  101. test(input);
  102. return result;
  103. }
  104.  
  105. class GPT_ASK_LOOP {
  106. queue = [];
  107. abstract = [];
  108. responds = [];
  109. checkInterval = 20000;
  110. account = "";
  111. downloadBtn = null;
  112. retrying = false;
  113. defaultMode = 2;
  114. lastSaveTime = 0;
  115.  
  116. INPUT_SELECTOR = "#prompt-textarea";
  117. SUBMIT_BTN_SELECTOR = "#prompt-textarea + button";
  118. RESPOND_SELECTOR = "main .group.text-token-text-primary";
  119. NEW_CHART_BTN_SELECTOR = "nav>div.mb-1>a:first-child";
  120. NORMAL_RESPOND_BTN_SELECTOR = "form div button.btn-neutral";
  121. ERROR_RESPOND_BTN_SELECTOR = "form div button.btn-primary";
  122.  
  123. sleep(duration) {
  124. return new Promise((resolve, reject) => {
  125. setTimeout(() => {
  126. resolve(true);
  127. }, duration);
  128. });
  129. }
  130.  
  131. constructor(account) {
  132. this.initData().then(() => {
  133. this.account = account || Math.ceil(Math.random() * 1e10).toString(32);
  134. const btnWrap = document.createElement("div");
  135. btnWrap.innerHTML = `<button style="padding: 4px 8px;position: fixed;bottom: 20%;right: 8px;border-radius: 4px;background-color: #224466;color: #fff;">下载已生成结果(queue: ${this.queue.length}, res: ${this.responds.length})</button>`;
  136. this.downloadBtn = btnWrap.querySelector("button");
  137. this.downloadBtn.onclick = this.handleDownload.bind(this);
  138. document.body.appendChild(btnWrap);
  139. const btnWrapBackup = document.createElement("div");
  140. btnWrapBackup.innerHTML = `<button style="padding: 4px 8px;position: fixed;bottom: 30%;right: 8px;border-radius: 4px;background-color: #224466;color: #fff;">备份</button>`;
  141. const backupBtn = btnWrapBackup.querySelector("button");
  142. backupBtn.onclick = this.backUp.bind(this);
  143. document.body.appendChild(btnWrapBackup);
  144. this.main();
  145. });
  146. }
  147.  
  148. async initData() {
  149. const skipSnippetKeys = await idbKeyval.keys(dbTable.skipSnippet);
  150. const responseKeys = await idbKeyval.keys(dbTable.responseProcessed);
  151. const responseValues = await idbKeyval.values(dbTable.responseProcessed);
  152. this.responds = responseValues.map((item) => ({
  153. articleId: item.articleId,
  154. snippetId: item.snippetId,
  155. createdTime: item.createdTime,
  156. }));
  157. const snippetSourceData = JSON.parse(
  158. localStorage.getItem("snippetSourceData") || "[]"
  159. );
  160. this.abstract = snippetSourceData.filter(
  161. (item) => item.type == "abstract"
  162. );
  163. const paragraphs = snippetSourceData.filter(
  164. (item) => item.type != "abstract"
  165. );
  166. this.queue = paragraphs.filter(
  167. (item) =>
  168. !(responseKeys || []).includes(`${item.article_id}-${item.id}`) &&
  169. !(skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
  170. );
  171. if (this.queue.length !== 0) {
  172. return;
  173. }
  174. this.queue = paragraphs.filter((item) =>
  175. (skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
  176. );
  177. }
  178.  
  179. async backUp() {
  180. const response1 = await idbKeyval.entries(dbTable.response1);
  181. const response2 = await idbKeyval.entries(dbTable.response2);
  182. const responseProcessed = await idbKeyval.entries(dbTable.responseProcessed);
  183.  
  184. const now = new Date();
  185. const current = `${now.getFullYear()}-${
  186. now.getMonth() + 1
  187. }-${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}`;
  188. downloadFile(
  189. JSON.stringify(response1), `backup-${current}-response1-${response1.length}.json`
  190. );
  191. downloadFile(
  192. JSON.stringify(response2), `backup-${current}-response2-${response2.length}.json`
  193. );
  194. downloadFile(
  195. JSON.stringify(responseProcessed), `backup-${current}-responseProcessed-${responseProcessed.length}.json`
  196. );
  197. }
  198.  
  199. async handleDownload() {
  200. const reactionGroups = await idbKeyval.values(dbTable.responseProcessed);
  201. if (!reactionGroups.length) {
  202. return;
  203. }
  204. const reactions = [];
  205. reactionGroups.forEach((item) => {
  206. const { articleId, snippetId, reaction } = item;
  207. const uniqReaction = Array.from(
  208. new Set(reaction.map((v) => JSON.stringify(v)))
  209. ).map((v) => JSON.parse(v));
  210. uniqReaction.forEach((data) => {
  211. const name = hashFnv32a(JSON.stringify(data));
  212. reactions.push({ articleId, snippetId, data, name });
  213. });
  214. });
  215.  
  216. const now = new Date();
  217. downloadFile(
  218. JSON.stringify(reactions),
  219. `${now.getFullYear()}-${
  220. now.getMonth() + 1
  221. }-${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}-${
  222. reactions.length
  223. }.json`
  224. );
  225. }
  226.  
  227. async report(tip = "") {
  228. await fetch("https://gpt-hit.deno.dev/api/update", {
  229. method: "POST",
  230. body: JSON.stringify({
  231. account: this.account,
  232. reaction_count: this.responds.length,
  233. queue_count: this.queue.length,
  234. tip: tip,
  235. }),
  236. }).catch((err) => {
  237. console.error({ err });
  238. });
  239. }
  240.  
  241. genPrompt(content, step = 1) {
  242. return step === 1
  243. ? `${localStorage.getItem("mock_prompt" + step)}
  244. ''' ${content} ''' `
  245. : localStorage.getItem("mock_prompt" + step);
  246. }
  247.  
  248. _updateDownloadBtnText() {
  249. if (this.downloadBtn) {
  250. const snippetSourceData = JSON.parse(
  251. localStorage.getItem("snippetSourceData") || "[]"
  252. );
  253. const paragraphs = snippetSourceData.filter(
  254. (item) => item.type != "abstract"
  255. );
  256. this.downloadBtn.innerText = `下载已生成结果(queue: ${
  257. this.queue.length
  258. }, res: ${this.responds.length}, skip: ${
  259. paragraphs.length - this.queue.length - this.responds.length
  260. })`;
  261. }
  262. }
  263.  
  264. _getLastRespondTime() {
  265. return Math.max.apply(
  266. null,
  267. this.responds
  268. .map((item) => item.createdTime)
  269. .filter((item) => item)
  270. .concat([0])
  271. );
  272. }
  273.  
  274. getTask() {
  275. const task = this.queue[0];
  276. const maxTime = this._getLastRespondTime();
  277. this.report(
  278. (task &&
  279. `Working on articleId: ${task.article_id}, snippetId: ${
  280. task.id
  281. }, last-update-time: ${new Date(maxTime).toLocaleString()}`) ||
  282. ""
  283. );
  284. if (!task) {
  285. console.log("任务队列为空");
  286. return async () => null;
  287. }
  288. return async () => {
  289. const { article_id, id, content } = task;
  290. const relatedAbstract =
  291. this.abstract.find((item) => item.article_id === article_id)
  292. ?.content || "";
  293. console.log(
  294. `开始触发 ${article_id}-${id}, ${new Date().toTimeString()}`
  295. );
  296. const promptContent = `
  297. ${relatedAbstract}
  298. ${content}
  299. `;
  300. const prompt1 = this.genPrompt(promptContent, 1);
  301. const prompt2 = this.genPrompt(promptContent, 2);
  302. const result1 = await this.trigger(prompt1).catch((err) => {
  303. return null;
  304. });
  305. if (!result1) {
  306. return null;
  307. }
  308. await idbKeyval.set(
  309. `${article_id}-${id}`,
  310. {
  311. articleId: article_id,
  312. snippetId: id,
  313. reaction: result1,
  314. createdTime: new Date().valueOf(),
  315. },
  316. dbTable.response1
  317. );
  318. await this.sleep(3 * 1000);
  319. const result2 = await this.trigger(prompt2).catch((err) => {
  320. return null;
  321. });
  322. if (!result2) {
  323. return { articleId: article_id, snippetId: id, reaction: result1 };
  324. }
  325. await idbKeyval.set(
  326. `${article_id}-${id}`,
  327. {
  328. articleId: article_id,
  329. snippetId: id,
  330. reaction: result2,
  331. createdTime: new Date().valueOf(),
  332. },
  333. dbTable.response2
  334. );
  335. return { articleId: article_id, snippetId: id, reaction: result2 };
  336. };
  337. }
  338.  
  339. async rawReactionProcess(rawReactionHTML) {
  340. const ele = document.createElement("div");
  341. ele.innerHTML = rawReactionHTML;
  342. const res = Array.from(ele.querySelectorAll("code"))
  343. .map((el) => el.innerText)
  344. .map((yml) => yaml2object(yml));
  345.  
  346. if (res && res.length > 0 && res.every((s) => s !== null)) {
  347. const result = reactionObjHandler(res);
  348. return result.length > 0 ? result : null;
  349. }
  350. return null;
  351. }
  352.  
  353. async skipSnippetHandler(articleId, snippetId) {
  354. const oldVal = await idbKeyval.get(
  355. `${articleId}-${snippetId}`,
  356. dbTable.skipSnippet
  357. );
  358. await idbKeyval.set(
  359. `${articleId}-${snippetId}`,
  360. (oldVal || 0) + 1,
  361. dbTable.skipSnippet
  362. );
  363. this.queue = this.queue.filter((item) => item.id !== snippetId);
  364. }
  365.  
  366. async saveRespond(respond) {
  367. const { articleId, snippetId } = respond;
  368. const currentTimeStamp = new Date().valueOf();
  369. const reactionProcessed = await this.rawReactionProcess(respond.reaction);
  370. if (!reactionProcessed) {
  371. console.warn(`${articleId}-${snippetId} 无法解析出 reaction, 即将跳过`);
  372. await this.skipSnippetHandler(articleId, snippetId);
  373. return;
  374. }
  375. this.responds.push({
  376. articleId,
  377. snippetId,
  378. createdTime: currentTimeStamp,
  379. });
  380. this.queue = this.queue.filter((item) => item.id !== snippetId);
  381.  
  382. await idbKeyval.set(
  383. `${articleId}-${snippetId}`,
  384. {
  385. ...respond,
  386. reaction: reactionProcessed,
  387. createdTime: new Date().valueOf(),
  388. },
  389. dbTable.responseProcessed
  390. );
  391. try {
  392. await idbKeyval.del(`${articleId}-${snippetId}`, dbTable.skipSnippet);
  393. } catch (err) {}
  394. if (this.responds.length && this.responds.length % 50 === 0) {
  395. this.handleDownload.bind(this)();
  396. }
  397. }
  398.  
  399. trigger(prompt, checkInterval = this.checkInterval) {
  400. return new Promise((resolve, reject) => {
  401. const textEl = document.querySelector(this.INPUT_SELECTOR);
  402. const submitEl = document.querySelector(this.SUBMIT_BTN_SELECTOR);
  403. textEl.value = prompt;
  404. textEl.dispatchEvent(new Event("input", { bubbles: true }));
  405. setTimeout(() => {
  406. submitEl.click();
  407.  
  408. let resCache = null;
  409. let checkOutputCount = 0;
  410. (async () => {
  411. while (true) {
  412. await this.sleep(checkInterval);
  413. const result = Array.from(
  414. document.querySelectorAll(this.RESPOND_SELECTOR)
  415. );
  416. const temp = result[result.length - 1];
  417. if (!temp) {
  418. if (checkOutputCount > 0) {
  419. console.log("检查结果超时");
  420. reject(null);
  421. break;
  422. }
  423. checkOutputCount++;
  424. continue;
  425. }
  426. if (resCache === temp.innerHTML) {
  427. // console.log("匹配,resCache:", resCache);
  428. const validateResult = await this.validate(resCache).catch(
  429. (err) => {
  430. reject(null);
  431. return;
  432. }
  433. );
  434. if (validateResult === true) {
  435. resolve(resCache);
  436. break;
  437. } else if (validateResult === false) {
  438. continue;
  439. }
  440. reject(null);
  441. break;
  442. }
  443. resCache = temp.innerHTML;
  444. console.log(`${checkInterval / 1000}s后再次检查结果`);
  445. }
  446. })();
  447. }, 4000);
  448. });
  449. }
  450.  
  451. async validate(innerHTML) {
  452. const buttons = document.querySelectorAll(
  453. this.NORMAL_RESPOND_BTN_SELECTOR
  454. );
  455. const errorBtn = document.querySelectorAll(
  456. this.ERROR_RESPOND_BTN_SELECTOR
  457. );
  458. // 如果触发gpt-4 3小时25次限制
  459. if (!buttons[0] && !errorBtn[0] && innerHTML.includes("usage cap")) {
  460. console.error("触发gpt-4 3小时25次限制,等待10min后重试");
  461. await this.sleep(10 * 60 * 1000);
  462. throw new Error("触发gpt-4 3小时25次限制");
  463. }
  464. // 如果openAI服务器报错未返回结果
  465. if (errorBtn[0]) {
  466. // && innerHTML.includes("wrong")) {
  467. if (this.retrying) {
  468. this.retrying = false;
  469. return true;
  470. }
  471. errorBtn[0].click();
  472. this.retrying = true;
  473. return false;
  474. }
  475. // 如果输出结果未包含code标签
  476. if (!innerHTML.includes("</code>")) {
  477. if (this.retrying) {
  478. this.retrying = false;
  479. console.error("第二次还是未输出yaml结构");
  480. throw new Error("未返回yaml结构");
  481. }
  482. console.error("未输出yaml结构,重试一次");
  483. buttons[0].click();
  484. this.retrying = true;
  485. return false;
  486. }
  487. this.retrying = false;
  488. // 如果还未完全输出
  489. if (buttons.length > 1) {
  490. buttons[buttons.length - 1].click();
  491. return false;
  492. }
  493. return true;
  494. }
  495.  
  496. async main(sleepTime = 5000) {
  497. let emptyCount = 0;
  498. while (true) {
  499. // {0: gpt-3.5, 1: gpt-4, 2: gpt-4 mobile}
  500. const modelNum =
  501. +localStorage.getItem("model_number") || this.defaultMode;
  502. const gpt4btn = document.querySelectorAll(
  503. "ul > li > button.cursor-pointer"
  504. )[modelNum];
  505.  
  506. if (gpt4btn) {
  507. console.log(`当前模型为:${gpt4btn.innerText}`);
  508. gpt4btn.firstChild.click();
  509. } else {
  510. console.warn(`无法选择模型,2分钟后刷新`);
  511. await this.sleep(2 * 60 * 1000);
  512. locationReload();
  513. }
  514. await this.sleep(sleepTime / 2);
  515. if (modelNum === 1 && !(location.href.endsWith("gpt-4") || gpt4btn.firstChild.className?.includes("shadow"))) {
  516. console.log("未切换到gpt-4模式, 5分钟后重试");
  517. const maxTime = this._getLastRespondTime();
  518. const diff = new Date().valueOf() - maxTime;
  519. if (maxTime && diff > 1.5 * 60 * 60 * 1000) {
  520. console.warn("超时未刷新, 5分钟后刷新页面");
  521. await this.sleep(5 * 60 * 1000);
  522. locationReload();
  523. break;
  524. }
  525. this.report(
  526. `触发gpt-4 3小时25次限制,上次运行时间:${new Date(
  527. maxTime
  528. ).toLocaleString()}`
  529. );
  530. await this.sleep(5 * 60 * 1000);
  531. const newChatBtn = document.querySelector(
  532. this.NEW_CHART_BTN_SELECTOR
  533. );
  534. newChatBtn.click();
  535. continue;
  536. }
  537. const task = this.getTask();
  538. if (!task) {
  539. if (emptyCount > 0) {
  540. console.warn("连续两次未获取到任务,2分钟后刷新");
  541. await this.sleep(2 * 60 * 1000);
  542. locationReload();
  543. break;
  544. }
  545. emptyCount++;
  546. await this.sleep(5 * 60 * 1000);
  547. continue;
  548. }
  549.  
  550. const result = await task();
  551. if (result) {
  552. this.saveRespond(result);
  553. emptyCount = 0;
  554. } else {
  555. if (emptyCount > 0) {
  556. const task = this.queue[0];
  557. const { article_id, id } = task;
  558. console.warn(
  559. `${article_id}-${id}连续两次未获取到任务值,2分钟后刷新`
  560. );
  561. await this.skipSnippetHandler(article_id, id);
  562. await this.sleep(2 * 60 * 1000);
  563. locationReload();
  564. break;
  565. }
  566. emptyCount += 1;
  567. }
  568. console.log(`${sleepTime / 1000}s后将再次触发`);
  569. const newChatBtn = document.querySelector(this.NEW_CHART_BTN_SELECTOR);
  570. newChatBtn.click();
  571. await this.sleep(sleepTime / 2);
  572. this._updateDownloadBtnText();
  573. }
  574. }
  575. }
  576.  
  577. function secondInterval() {
  578. console.log("start secondInterval...");
  579. const sleep = (duration) => {
  580. return new Promise((resolve, reject) => {
  581. setTimeout(() => {
  582. resolve(true);
  583. }, duration);
  584. });
  585. };
  586. setInterval(async () => {
  587. const responds = await idbKeyval.values(dbTable.responseProcessed);
  588. const maxTime = Math.max.apply(
  589. null,
  590. responds
  591. .map((item) => item.createdTime)
  592. .filter((item) => item)
  593. .concat([0])
  594. );
  595. const diff = new Date().valueOf() - maxTime;
  596.  
  597. console.log(`last updated at: ${maxTime}, diff is ${diff}`);
  598. if (maxTime && diff > 30 * 60 * 1000) {
  599. console.warn("超时未刷新, 2分钟后刷新页面");
  600. await sleep(2 * 60 * 1000);
  601. locationReload();
  602. }
  603. }, 10 * 60 * 1000);
  604. }
  605.  
  606. function start() {
  607. const ACCOUNT_NAME_SELECTOR = "nav > div:last-child > div:last-child";
  608. const nameEl = document.querySelector(ACCOUNT_NAME_SELECTOR);
  609. const name = nameEl && nameEl.innerText;
  610. if (name) {
  611. new GPT_ASK_LOOP(name);
  612. secondInterval();
  613. } else {
  614. setTimeout(() => {
  615. start();
  616. }, 5000);
  617. }
  618. }
  619. start();
  620. })();