GPT Auto task

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

当前为 2023-07-27 提交的版本,查看 最新版本

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