GPT Auto task

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

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

  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.3
  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. this.main();
  136. });
  137. }
  138.  
  139. async initData() {
  140. const skipSnippetKeys = await idbKeyval.keys(dbTable.skipSnippet);
  141. const responseKeys = await idbKeyval.keys(dbTable.responseProcessed);
  142. const responseValues = await idbKeyval.values(dbTable.responseProcessed);
  143. this.responds = responseValues.map(item => ({articleId: item.articleId, snippetId: item.snippetId, createdTime: item.createdTime}));
  144. const snippetSourceData = JSON.parse(
  145. localStorage.getItem("snippetSourceData") || "[]"
  146. );
  147. this.abstract = snippetSourceData.filter(
  148. (item) => item.type == "abstract"
  149. );
  150. const paragraphs = snippetSourceData.filter(
  151. (item) => item.type != "abstract"
  152. );
  153. this.queue = paragraphs.filter(
  154. (item) =>
  155. !(responseKeys || []).includes(`${item.article_id}-${item.id}`) && !(skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
  156. );
  157. if (this.queue.length !== 0) {
  158. return;
  159. }
  160. this.queue = paragraphs.filter(
  161. (item) =>
  162. (skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
  163. );
  164. }
  165.  
  166. async handleDownload() {
  167. const reactionGroups = await idbKeyval.values(dbTable.responseProcessed);
  168. if (!reactionGroups.length) {
  169. return;
  170. }
  171. const reactions = [];
  172. reactionGroups.forEach((item) => {
  173. const { articleId, snippetId, reaction } = item;
  174. const uniqReaction = Array.from(
  175. new Set(reaction.map((v) => JSON.stringify(v)))
  176. ).map((v) => JSON.parse(v));
  177. uniqReaction.forEach((data) => {
  178. const name = hashFnv32a(JSON.stringify(data));
  179. reactions.push({ articleId, snippetId, data, name });
  180. });
  181. });
  182.  
  183. const now = new Date();
  184. downloadFile(
  185. JSON.stringify(reactions),
  186. `${now.getFullYear()}-${
  187. now.getMonth() + 1
  188. }-${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}-${
  189. reactions.length
  190. }.json`
  191. );
  192. }
  193.  
  194. async report(tip = "") {
  195. await fetch("https://gpt-hit.deno.dev/api/update", {
  196. method: "POST",
  197. body: JSON.stringify({
  198. account: this.account,
  199. reaction_count: this.responds.length,
  200. queue_count: this.queue.length,
  201. tip: tip,
  202. }),
  203. }).catch((err) => {
  204. console.error({ err });
  205. });
  206. }
  207.  
  208. genPrompt(content, step = 1) {
  209. return step === 1
  210. ? `${localStorage.getItem("mock_prompt" + step)}
  211. ''' ${content} ''' `
  212. : localStorage.getItem("mock_prompt" + step);
  213. }
  214.  
  215. _updateDownloadBtnText() {
  216. if (this.downloadBtn) {
  217. const snippetSourceData = JSON.parse(
  218. localStorage.getItem("snippetSourceData") || "[]"
  219. );
  220. const paragraphs = snippetSourceData.filter(
  221. (item) => item.type != "abstract"
  222. );
  223. this.downloadBtn.innerText = `下载已生成结果(queue: ${
  224. this.queue.length
  225. }, res: ${this.responds.length}, skip: ${
  226. paragraphs.length - this.queue.length - this.responds.length
  227. })`;
  228. }
  229. }
  230.  
  231. _getLastRespondTime() {
  232. return Math.max.apply(
  233. null,
  234. this.responds
  235. .map((item) => item.createdTime)
  236. .filter((item) => item)
  237. .concat([0])
  238. );
  239. }
  240.  
  241. getTask() {
  242. const task = this.queue[0];
  243. const maxTime = this._getLastRespondTime();
  244. this.report(
  245. (task &&
  246. `Working on articleId: ${task.article_id}, snippetId: ${
  247. task.id
  248. }, last-update-time: ${new Date(maxTime).toLocaleString()}`) ||
  249. ""
  250. );
  251. if (!task) {
  252. console.log("任务队列为空");
  253. return async () => null;
  254. }
  255. return async () => {
  256. const { article_id, id, content } = task;
  257. const relatedAbstract =
  258. this.abstract.find((item) => item.article_id === article_id)
  259. ?.content || "";
  260. console.log(
  261. `开始触发 ${article_id}-${id}, ${new Date().toTimeString()}`
  262. );
  263. const promptContent = `
  264. ${relatedAbstract}
  265. ${content}
  266. `;
  267. const prompt1 = this.genPrompt(promptContent, 1);
  268. const prompt2 = this.genPrompt(promptContent, 2);
  269. const result1 = await this.trigger(prompt1).catch((err) => {
  270. return null;
  271. });
  272. if (!result1) {
  273. return null;
  274. }
  275. await idbKeyval.set(
  276. `${article_id}-${id}`,
  277. {
  278. articleId: article_id,
  279. snippetId: id,
  280. reaction: result1,
  281. createdTime: new Date().valueOf(),
  282. },
  283. dbTable.response1
  284. );
  285. await this.sleep(3 * 1000);
  286. const result2 = await this.trigger(prompt2).catch((err) => {
  287. return null;
  288. });
  289. if (!result2) {
  290. return { articleId: article_id, snippetId: id, reaction: result1 };
  291. }
  292. await idbKeyval.set(
  293. `${article_id}-${id}`,
  294. {
  295. articleId: article_id,
  296. snippetId: id,
  297. reaction: result2,
  298. createdTime: new Date().valueOf(),
  299. },
  300. dbTable.response2
  301. );
  302. return { articleId: article_id, snippetId: id, reaction: result2 };
  303. };
  304. }
  305.  
  306. async rawReactionProcess(rawReactionHTML) {
  307. const ele = document.createElement("div");
  308. ele.innerHTML = rawReactionHTML;
  309. const res = Array.from(ele.querySelectorAll("code"))
  310. .map((el) => el.innerText)
  311. .map((yml) => yaml2object(yml));
  312.  
  313. if (res && res.length > 0 && res.every((s) => s !== null)) {
  314. const result = reactionObjHandler(res);
  315. return result.length > 0 ? result : null;
  316. }
  317. return null;
  318. }
  319.  
  320. async skipSnippetHandler(articleId, snippetId) {
  321. const oldVal = await idbKeyval.get( `${articleId}-${snippetId}`, dbTable.skipSnippet);
  322. await idbKeyval.set(
  323. `${articleId}-${snippetId}`,
  324. (oldVal || 0) + 1,
  325. dbTable.skipSnippet
  326. );
  327. this.queue = this.queue.filter((item) => item.id !== snippetId);
  328. }
  329.  
  330. async saveRespond(respond) {
  331. const { articleId, snippetId } = respond;
  332. const currentTimeStamp = new Date().valueOf();
  333. const reactionProcessed = await this.rawReactionProcess(respond.reaction);
  334. if (!reactionProcessed) {
  335. console.warn(`${articleId}-${snippetId} 无法解析出 reaction, 即将跳过`);
  336. await this.skipSnippetHandler(articleId, snippetId);
  337. return;
  338. }
  339. this.responds.push({
  340. articleId,
  341. snippetId,
  342. createdTime: currentTimeStamp,
  343. });
  344. this.queue = this.queue.filter((item) => item.id !== snippetId);
  345.  
  346. await idbKeyval.set(
  347. `${articleId}-${snippetId}`,
  348. {
  349. ...respond,
  350. reaction: reactionProcessed,
  351. createdTime: new Date().valueOf(),
  352. },
  353. dbTable.responseProcessed
  354. );
  355. try {
  356. await idbKeyval.del(
  357. `${articleId}-${snippetId}`,
  358. dbTable.skipSnippet
  359. );
  360. } catch(err) {
  361.  
  362. }
  363. if (this.responds.length && this.responds.length % 50 === 0) {
  364. this.handleDownload.bind(this)();
  365. }
  366. }
  367.  
  368. trigger(prompt, checkInterval = this.checkInterval) {
  369. return new Promise((resolve, reject) => {
  370. const textEl = document.querySelector(this.INPUT_SELECTOR);
  371. const submitEl = document.querySelector(this.SUBMIT_BTN_SELECTOR);
  372. textEl.value = prompt;
  373. textEl.dispatchEvent(new Event("input", { bubbles: true }));
  374. setTimeout(() => {
  375. submitEl.click();
  376.  
  377. let resCache = null;
  378. let checkOutputCount = 0;
  379. (async () => {
  380. while (true) {
  381. await this.sleep(checkInterval);
  382. const result = Array.from(
  383. document.querySelectorAll(this.RESPOND_SELECTOR)
  384. );
  385. const temp = result[result.length - 1];
  386. if (!temp) {
  387. if (checkOutputCount > 0) {
  388. console.log("检查结果超时");
  389. reject(null);
  390. break;
  391. }
  392. checkOutputCount++;
  393. continue;
  394. }
  395. if (resCache === temp.innerHTML) {
  396. // console.log("匹配,resCache:", resCache);
  397. const validateResult = await this.validate(resCache).catch(
  398. (err) => {
  399. reject(null);
  400. return;
  401. }
  402. );
  403. if (validateResult === true) {
  404. resolve(resCache);
  405. break;
  406. } else if (validateResult === false) {
  407. continue;
  408. }
  409. reject(null);
  410. break;
  411. }
  412. resCache = temp.innerHTML;
  413. console.log(`${checkInterval / 1000}s后再次检查结果`);
  414. }
  415. })();
  416. }, 4000);
  417. });
  418. }
  419.  
  420. async validate(innerHTML) {
  421. const buttons = document.querySelectorAll(
  422. this.NORMAL_RESPOND_BTN_SELECTOR
  423. );
  424. const errorBtn = document.querySelectorAll(
  425. this.ERROR_RESPOND_BTN_SELECTOR
  426. );
  427. // 如果触发gpt-4 3小时25次限制
  428. if (!buttons[0] && !errorBtn[0] && innerHTML.includes("usage cap")) {
  429. console.error("触发gpt-4 3小时25次限制,等待10min后重试");
  430. await this.sleep(10 * 60 * 1000);
  431. throw new Error("触发gpt-4 3小时25次限制");
  432. }
  433. // 如果openAI服务器报错未返回结果
  434. if (errorBtn[0]) {
  435. // && innerHTML.includes("wrong")) {
  436. if (this.retrying) {
  437. this.retrying = false;
  438. return true;
  439. }
  440. errorBtn[0].click();
  441. this.retrying = true;
  442. return false;
  443. }
  444. // 如果输出结果未包含code标签
  445. if (!innerHTML.includes("</code>")) {
  446. if (this.retrying) {
  447. this.retrying = false;
  448. console.error("第二次还是未输出yaml结构");
  449. throw new Error("未返回yaml结构");
  450. }
  451. console.error("未输出yaml结构,重试一次");
  452. buttons[0].click();
  453. this.retrying = true;
  454. return false;
  455. }
  456. this.retrying = false;
  457. // 如果还未完全输出
  458. if (buttons.length > 1) {
  459. buttons[buttons.length - 1].click();
  460. return false;
  461. }
  462. return true;
  463. }
  464.  
  465. async main(sleepTime = 5000) {
  466. let emptyCount = 0;
  467. while (true) {
  468. // {0: gpt-3.5, 1: gpt-4, 2: gpt-4 mobile}
  469. const modelNum =
  470. +localStorage.getItem("model_number") || this.defaultMode;
  471. const gpt4btn = document.querySelectorAll(
  472. "ul > li > button.cursor-pointer"
  473. )[modelNum];
  474.  
  475. if (gpt4btn) {
  476. console.log(`当前模型为:${gpt4btn.innerText}`);
  477. gpt4btn.firstChild.click();
  478. } else {
  479. console.warn(`无法选择模型,2分钟后刷新`);
  480. await this.sleep(2 * 60 * 1000);
  481. location.reload();
  482. }
  483. await this.sleep(sleepTime / 2);
  484. if (modelNum === 1 && !location.href.endsWith("gpt-4")) {
  485. console.log("未切换到gpt-4模式, 5分钟后重试");
  486. const maxTime = this._getLastRespondTime();
  487. const diff = new Date().valueOf() - maxTime;
  488. if (maxTime && diff > 1.5 * 60 * 60 * 1000) {
  489. console.warn("超时未刷新, 5分钟后刷新页面");
  490. await this.sleep(5 * 60 * 1000);
  491. location.reload();
  492. break;
  493. }
  494. this.report(
  495. `触发gpt-4 3小时25次限制,上次运行时间:${new Date(
  496. maxTime
  497. ).toLocaleString()}`
  498. );
  499. await this.sleep(5 * 60 * 1000);
  500. const newChatBtn = document.querySelector(
  501. this.NEW_CHART_BTN_SELECTOR
  502. );
  503. newChatBtn.click();
  504. continue;
  505. }
  506. const task = this.getTask();
  507. if (!task) {
  508. if (emptyCount > 0) {
  509. console.warn("连续两次未获取到任务,2分钟后刷新");
  510. await this.sleep(2 * 60 * 1000);
  511. location.reload();
  512. break;
  513. }
  514. emptyCount++;
  515. await this.sleep(5 * 60 * 1000);
  516. continue;
  517. }
  518.  
  519. const result = await task();
  520. if (result) {
  521. this.saveRespond(result);
  522. emptyCount = 0;
  523. } else {
  524. if (emptyCount > 0) {
  525. const task = this.queue[0];
  526. const { article_id, id } = task;
  527. console.warn(`${article_id}-${id}连续两次未获取到任务值,2分钟后刷新`);
  528. await this.skipSnippetHandler(article_id, id);
  529. await this.sleep(2 * 60 * 1000);
  530. location.reload();
  531. break;
  532. }
  533. emptyCount += 1;
  534. }
  535. console.log(`${sleepTime / 1000}s后将再次触发`);
  536. const newChatBtn = document.querySelector(this.NEW_CHART_BTN_SELECTOR);
  537. newChatBtn.click();
  538. await this.sleep(sleepTime / 2);
  539. this._updateDownloadBtnText();
  540. }
  541. }
  542. }
  543.  
  544. function secondInterval() {
  545. console.log("start secondInterval...");
  546. const sleep = (duration) => {
  547. return new Promise((resolve, reject) => {
  548. setTimeout(() => {
  549. resolve(true);
  550. }, duration);
  551. });
  552. }
  553. setInterval(async () => {
  554. const responds = await idbKeyval.values(dbTable.responseProcessed);
  555. const maxTime = Math.max.apply(
  556. null,
  557. responds
  558. .map((item) => item.createdTime)
  559. .filter((item) => item)
  560. .concat([0])
  561. );
  562. const diff = new Date().valueOf() - maxTime;
  563.  
  564. console.log(`last updated at: ${maxTime}, diff is ${diff}`);
  565. if (maxTime && diff > 30 * 60 * 1000) {
  566. console.warn("超时未刷新, 2分钟后刷新页面");
  567. await sleep(2 * 60 * 1000);
  568. location.reload();
  569. }
  570. }, 10 * 60 * 1000);
  571. }
  572.  
  573. function start() {
  574. const ACCOUNT_NAME_SELECTOR = "nav > div:last-child > div:last-child";
  575. const nameEl = document.querySelector(ACCOUNT_NAME_SELECTOR);
  576. const name = nameEl && nameEl.innerText;
  577. if (name) {
  578. new GPT_ASK_LOOP(name);
  579. secondInterval();
  580. } else {
  581. setTimeout(() => {
  582. start();
  583. }, 5000);
  584. }
  585. }
  586. start();
  587. })();