Nowcoder Better!

牛客竞赛题目题解markdown一键复制

目前为 2023-09-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Nowcoder Better!
  3. // @namespace https://greasyfork.org/users/747162
  4. // @version 1.11
  5. // @description 牛客竞赛题目题解markdown一键复制
  6. // @author 北极小狐
  7. // @match https://ac.nowcoder.com/*
  8. // @connect www2.deepl.com
  9. // @connect m.youdao.com
  10. // @connect translate.google.com
  11. // @connect openai.api2d.net
  12. // @connect api.openai.com
  13. // @connect www.luogu.com.cn
  14. // @connect greasyfork.org
  15. // @connect *
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_info
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_deleteValue
  21. // @grant GM_addStyle
  22. // @grant GM_setClipboard
  23. // @icon https://aowuucdn.oss-cn-beijing.aliyuncs.com/nowcoder.png
  24. // @require https://cdn.staticfile.org/turndown/7.1.2/turndown.min.js
  25. // @require https://cdn.staticfile.org/markdown-it/13.0.1/markdown-it.min.js
  26. // @license MIT
  27. // @compatible Chrome
  28. // @compatible Firefox
  29. // @compatible Edge
  30. // ==/UserScript==
  31.  
  32. // 状态与初始化
  33. const getGMValue = (key, defaultValue) => {
  34. const value = GM_getValue(key);
  35. if (value === undefined || value === "") {
  36. GM_setValue(key, defaultValue);
  37. return defaultValue;
  38. }
  39. return value;
  40. };
  41. const hoverTargetAreaDisplay = getGMValue("hoverTargetAreaDisplay", false);
  42. var enableSegmentedTranslation = getGMValue("enableSegmentedTranslation", false);
  43. var translation = getGMValue("translation", "deepl");
  44. //openai
  45. var openai_model, openai_key, openai_proxy, openai_header, openai_data;
  46. var opneaiConfig = getGMValue("chatgpt-config", {
  47. "choice": -1,
  48. "configurations": []
  49. });
  50. if (opneaiConfig.choice !== -1 && opneaiConfig.configurations.length !== 0) {
  51. const configAtIndex = opneaiConfig.configurations[opneaiConfig.choice];
  52.  
  53. if (configAtIndex == undefined) {
  54. let existingConfig = GM_getValue('chatgpt-config');
  55. existingConfig.choice = 0;
  56. GM_setValue('chatgpt-config', existingConfig);
  57. location.reload();
  58. }
  59. openai_model = configAtIndex.model;
  60. openai_key = configAtIndex.key;
  61. openai_proxy = configAtIndex.proxy;
  62. openai_header = configAtIndex._header ?
  63. configAtIndex._header.split("\n").map(header => {
  64. const [key, value] = header.split(":");
  65. return { [key.trim()]: value.trim() };
  66. }) : [];
  67. openai_data = configAtIndex._data ?
  68. configAtIndex._data.split("\n").map(header => {
  69. const [key, value] = header.split(":");
  70. return { [key.trim()]: value.trim() };
  71. }) : [];
  72. }
  73.  
  74. // 常量
  75. const helpCircleHTML = '<div class="help-icon"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm23.744 191.488c-52.096 0-92.928 14.784-123.2 44.352-30.976 29.568-45.76 70.4-45.76 122.496h80.256c0-29.568 5.632-52.8 17.6-68.992 13.376-19.712 35.2-28.864 66.176-28.864 23.936 0 42.944 6.336 56.32 19.712 12.672 13.376 19.712 31.68 19.712 54.912 0 17.6-6.336 34.496-19.008 49.984l-8.448 9.856c-45.76 40.832-73.216 70.4-82.368 89.408-9.856 19.008-14.08 42.24-14.08 68.992v9.856h80.96v-9.856c0-16.896 3.52-31.68 10.56-45.76 6.336-12.672 15.488-24.64 28.16-35.2 33.792-29.568 54.208-48.576 60.544-55.616 16.896-22.528 26.048-51.392 26.048-86.592 0-42.944-14.08-76.736-42.24-101.376-28.16-25.344-65.472-37.312-111.232-37.312zm-12.672 406.208a54.272 54.272 0 0 0-38.72 14.784 49.408 49.408 0 0 0-15.488 38.016c0 15.488 4.928 28.16 15.488 38.016A54.848 54.848 0 0 0 523.072 768c15.488 0 28.16-4.928 38.72-14.784a51.52 51.52 0 0 0 16.192-38.72 51.968 51.968 0 0 0-15.488-38.016 55.936 55.936 0 0 0-39.424-14.784z"></path></svg></div>';
  76. const darkenPageStyle = `body::before { content: ""; display: block; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.4); z-index: 9999; }`;
  77. const darkenPageStyle2 = `body::before { content: ""; display: block; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.4); z-index: 10000; }`;
  78.  
  79. // 语言判断
  80. const isEnglishLanguage = (function () {
  81. var metaElement = $('meta[http-equiv="Content-Language"]');
  82. var contentValue = metaElement.attr('content');
  83. return (contentValue === 'en');
  84. })();
  85.  
  86. // 样式
  87. GM_addStyle(`
  88. :root {
  89. --vp-font-family-base: "Chinese Quotes", "Inter var", "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  90. }
  91. span.mdViewContent {
  92. white-space: pre-wrap;
  93. }
  94. /*翻译区域提示*/
  95. .overlay {
  96. pointer-events: none;
  97. position: absolute;
  98. top: 0;
  99. left: 0;
  100. width: 100%;
  101. height: 100%;
  102. background: repeating-linear-gradient(135deg, #97e7cacc, #97e7cacc 30px, #e9fbf1cc 0px, #e9fbf1cc 55px);
  103. border-radius: 5px;
  104. display: flex;
  105. align-items: center;
  106. justify-content: center;
  107. color: #00695C;
  108. font-size: 16px;
  109. font-weight: bold;
  110. text-shadow: 0px 0px 2px #edfcf4;
  111. }
  112. /*翻译div*/
  113. .translate-problem-statement {
  114. justify-items: start;
  115. letter-spacing: 1.8px;
  116. color: #059669;
  117. background-color: #f9f9fa;
  118. border: 1px solid #10b981;
  119. border-radius: 0.3rem;
  120. padding: 5px;
  121. margin: 10px 0px;
  122. width: 100%;
  123. box-sizing: border-box;
  124. font-size: 13px;
  125. }
  126. .translate-problem-statement.error_translate {
  127. color: red;
  128. border-color: red;
  129. }
  130.  
  131. .translate-problem-statement h2, .translate-problem-statement h3 {
  132. font-size: 16px;
  133. }
  134.  
  135. .translate-problem-statement ul {
  136. line-height: 100%;
  137. }
  138.  
  139. .translate-problem-statement a {
  140. color: #10b981;
  141. font-weight: 600;
  142. background: 0 0;
  143. text-decoration: none;
  144. }
  145. .translate-problem-statement p {
  146. font-size: 14px !important;
  147. }
  148. .translate-problem-statement img {
  149. max-width: 100.0%;
  150. max-height: 100.0%;
  151. }
  152.  
  153. .translate-problem-statement .katex {
  154. font-size: 14px;
  155. }
  156. .translate-problem-statement a:hover {
  157. text-decoration: revert;
  158. }
  159. .html2md-panel {
  160. display: flex;
  161. justify-content: flex-end;
  162. }
  163. .html2md-panel a {
  164. text-decoration: none;
  165. }
  166. button.html2mdButton {
  167. display: flex;
  168. align-items: center;
  169. cursor: pointer;
  170. background-color: #ffffff;
  171. color: #606266;
  172. height: 22px;
  173. width: auto;
  174. font-size: 13px;
  175. border-radius: 0.3rem;
  176. padding: 1px 5px;
  177. margin: 5px;
  178. border: 1px solid #dcdfe6;
  179. }
  180. button.html2mdButton:hover {
  181. color: #409eff;
  182. border-color: #409eff;
  183. }
  184. button.html2mdButton.copied {
  185. background-color: #f0f9eb;
  186. color: #67c23e;
  187. border: 1px solid #b3e19d;
  188. }
  189. button.html2mdButton.mdViewed {
  190. background-color: #fdf6ec;
  191. color: #e6a23c;
  192. border: 1px solid #f3d19e;
  193. }
  194. button.html2mdButton.error {
  195. background-color: #fef0f0;
  196. color: #f56c6c;
  197. border: 1px solid #fab6b6;
  198. }
  199. button.translated {
  200. cursor: not-allowed;
  201. background-color: #f0f9eb;
  202. color: #67c23e;
  203. border: 1px solid #b3e19d;
  204. }
  205. button.html2mdButton.reTranslation {
  206. background-color: #f4f4f5;
  207. color: #909399;
  208. border: 1px solid #c8c9cc;
  209. }
  210. .translate-problem-statement table {
  211. border: 1px #ccc solid !important;
  212. margin: 1.5em 0 !important;
  213. color: #059669 !important;
  214. }
  215. .translate-problem-statement table thead th {
  216. border: 1px #ccc solid !important;
  217. color: #059669 !important;
  218. }
  219. .translate-problem-statement table td {
  220. border-right: 1px solid #ccc;
  221. border-top: 1px solid #ccc;
  222. padding: 0.7143em 0.5em;
  223. }
  224. .translate-problem-statement table th {
  225. padding: 0.7143em 0.5em;
  226. }
  227. .translate-problem-statement p:not(:first-child) {
  228. margin: 1.5em 0 0;
  229. }
  230. /*设置面板*/
  231. header .enter-or-register-box, header .languages {
  232. position: absolute;
  233. right: 170px;
  234. }
  235. button.html2mdButton.NowcoderBetter_setting {
  236. float: right;
  237. height: 30px;
  238. background: #25bb9b;
  239. color: white;
  240. margin: 10px;
  241. border: 0px;
  242. }
  243.  
  244. button.html2mdButton.NowcoderBetter_setting.open {
  245. background-color: #e6e6e61f;
  246. color: #727378;
  247. cursor: not-allowed;
  248. }
  249. .NowcoderBetter_setting_menu {
  250. z-index: 9999;
  251. box-shadow: 0px 0px 0px 4px #ffffff;
  252. display: grid;
  253. position: fixed;
  254. top: 50%;
  255. left: 50%;
  256. width: 480px;
  257. max-height: 90vh;
  258. overflow-y: auto;
  259. transform: translate(-50%, -50%);
  260. border-radius: 6px;
  261. background-color: #edf1ff;
  262. border-collapse: collapse;
  263. border: 1px solid #ffffff;
  264. color: #697e91;
  265. font-family: var(--vp-font-family-base);
  266. padding: 10px 20px 20px 20px;
  267. box-sizing: content-box;
  268. }
  269. .NowcoderBetter_setting_menu h4,.NowcoderBetter_setting_menu h5 {
  270. font-weight: 600;
  271. margin: 5px 0px;
  272. }
  273. .NowcoderBetter_setting_menu h4 {
  274. font-size: 17px;
  275. }
  276. .NowcoderBetter_setting_menu h5 {
  277. font-size: 16px;
  278. }
  279. .NowcoderBetter_setting_menu h3 {
  280. margin-top: 10px;
  281. font-weight: 600;
  282. font-size: 18px;
  283. }
  284. .NowcoderBetter_setting_menu hr {
  285. border: none;
  286. height: 1px;
  287. background-color: #ccc;
  288. margin: 10px 0;
  289. }
  290. /*设置面板-关闭按钮*/
  291. .NowcoderBetter_setting_menu .tool-box {
  292. position: absolute;
  293. align-items: center;
  294. justify-content: center;
  295. width: 20px;
  296. height: 20px;
  297. overflow: hidden;
  298. border-radius: 10px;
  299. top: 3px;
  300. right: 3px;
  301. }
  302.  
  303. .NowcoderBetter_setting_menu .btn-close {
  304. display: flex;
  305. text-align: center;
  306. width: 20px;
  307. height: 20px;
  308. color: transparent;
  309. font-size: 0;
  310. cursor: pointer;
  311. background-color: #ff000080;
  312. border: none;
  313. margin: 0px;
  314. padding: 0px;
  315. overflow: hidden;
  316. transition: .15s ease all;
  317. align-items: center;
  318. justify-content: center;
  319. box-sizing: border-box;
  320. }
  321.  
  322. .NowcoderBetter_setting_menu .btn-close:hover {
  323. width: 20px;
  324. height: 20px !important;
  325. font-size: 17px;
  326. color: #ffffff;
  327. background-color: #ff0000cc;
  328. box-shadow: 0 5px 5px 0 #00000026;
  329. }
  330.  
  331. .NowcoderBetter_setting_menu .btn-close:active {
  332. width: 20px;
  333. height: 20px;
  334. font-size: 1px;
  335. color: #ffffffde;
  336. --shadow-btn-close: 0 3px 3px 0 #00000026;
  337. box-shadow: var(--shadow-btn-close);
  338. }
  339.  
  340. /*设置面板-checkbox*/
  341. .NowcoderBetter_setting_menu input[type=checkbox]:focus {
  342. outline: 0px;
  343. }
  344.  
  345. .NowcoderBetter_setting_menu input[type="checkbox"] {
  346. margin: 0px;
  347. appearance: none;
  348. -webkit-appearance: none;
  349. width: 40px;
  350. height: 20px !important;
  351. border: 1.5px solid #D7CCC8;
  352. padding: 0px !important;
  353. border-radius: 20px;
  354. background: #efebe978;
  355. position: relative;
  356. box-sizing: border-box;
  357. }
  358.  
  359. .NowcoderBetter_setting_menu input[type="checkbox"]::before {
  360. content: "";
  361. width: 14px;
  362. height: 14px;
  363. background: #D7CCC8;
  364. border: 1.5px solid #BCAAA4;
  365. border-radius: 50%;
  366. position: absolute;
  367. top: 0;
  368. left: 0;
  369. transform: translate(2%, 2%);
  370. transition: all 0.3s ease-in-out;
  371. -webkit-box-sizing: content-box;
  372. -moz-box-sizing: content-box;
  373. box-sizing: content-box;
  374. }
  375.  
  376. .NowcoderBetter_setting_menu input[type="checkbox"]::after {
  377. content: url("data:image/svg+xml,%3Csvg xmlns='://www.w3.org/2000/svg' width='23' height='23' viewBox='0 0 23 23' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.55021 5.84315L17.1568 16.4498L16.4497 17.1569L5.84311 6.55026L6.55021 5.84315Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.1567 6.55021L6.55012 17.1568L5.84302 16.4497L16.4496 5.84311L17.1567 6.55021Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3C/svg%3E");
  378. position: absolute;
  379. top: 0;
  380. left: 24px;
  381. }
  382.  
  383. .NowcoderBetter_setting_menu input[type="checkbox"]:checked {
  384. border: 1.5px solid #C5CAE9;
  385. background: #E8EAF6;
  386. }
  387.  
  388. .NowcoderBetter_setting_menu input[type="checkbox"]:checked::before {
  389. background: #C5CAE9;
  390. border: 1.5px solid #7986CB;
  391. transform: translate(122%, 2%);
  392. transition: all 0.3s ease-in-out;
  393. }
  394.  
  395. .NowcoderBetter_setting_menu input[type="checkbox"]:checked::after {
  396. content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 15 13' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.8185 0.114533C15.0314 0.290403 15.0614 0.605559 14.8855 0.818454L5.00187 12.5L0.113036 6.81663C-0.0618274 6.60291 -0.0303263 6.2879 0.183396 6.11304C0.397119 5.93817 0.71213 5.96967 0.886994 6.18339L5.00187 11L14.1145 0.181573C14.2904 -0.0313222 14.6056 -0.0613371 14.8185 0.114533Z' fill='%2303A9F4' fill-opacity='0.9'/%3E%3C/svg%3E");
  397. position: absolute;
  398. top: 1.5px;
  399. left: 4.5px;
  400. }
  401.  
  402. .NowcoderBetter_setting_menu label {
  403. font-size: 16px;
  404. font-weight: initial;
  405. margin-bottom: 0px;
  406. }
  407.  
  408. .NowcoderBetter_setting_list {
  409. display: flex;
  410. align-items: center;
  411. padding: 10px;
  412. margin: 5px 0px;
  413. background-color: #ffffff;
  414. border-bottom: 1px solid #c9c6c696;
  415. border-radius: 8px;
  416. justify-content: space-between;
  417. }
  418.  
  419. /*设置面板-radio*/
  420. .NowcoderBetter_setting_menu>label {
  421. display: flex;
  422. list-style-type: none;
  423. padding-inline-start: 0px;
  424. overflow-x: auto;
  425. max-width: 100%;
  426. margin: 0px;
  427. align-items: center;
  428. margin: 3px 0px;
  429. }
  430.  
  431. .NowcoderBetter_setting_menu_label_text {
  432. display: flex;
  433. border: 1px dashed #00aeeccc;
  434. height: 35px;
  435. width: 100%;
  436. color: gray;
  437. font-weight: 300;
  438. font-size: 14px;
  439. letter-spacing: 2px;
  440. padding: 7px;
  441. align-items: center;
  442. -webkit-box-sizing: border-box;
  443. -moz-box-sizing: border-box;
  444. box-sizing: border-box;
  445. }
  446.  
  447. input[type="radio"]:checked+.NowcoderBetter_setting_menu_label_text {
  448. background: #41e49930;
  449. border: 1px solid green;
  450. color: green;
  451. font-weight: 500;
  452. }
  453.  
  454. .NowcoderBetter_setting_menu>label input[type="radio"] {
  455. -webkit-appearance: none;
  456. appearance: none;
  457. list-style: none;
  458. padding: 0px !important;
  459. margin: 0px;
  460. }
  461.  
  462. .NowcoderBetter_setting_menu input[type="text"] {
  463. display: block;
  464. height: 25px !important;
  465. width: 100%;
  466. background-color: #ffffff;
  467. color: #727378;
  468. font-size: 12px;
  469. border-radius: 0.3rem;
  470. padding: 1px 5px !important;
  471. box-sizing: border-box;
  472. margin: 5px 0px 5px 0px;
  473. border: 1px solid #00aeeccc;
  474. box-shadow: 0 0 1px #0000004d;
  475. }
  476.  
  477. .NowcoderBetter_setting_menu input[type="text"]:focus-visible{
  478. border-style: solid;
  479. border-color: #3f51b5;
  480. outline: none;
  481. }
  482.  
  483. .NowcoderBetter_setting_menu_input {
  484. width: 100%;
  485. display: grid;
  486. margin-top: 5px;
  487. -webkit-box-sizing: border-box;
  488. -moz-box-sizing: border-box;
  489. box-sizing: border-box;
  490. }
  491. .NowcoderBetter_setting_menu input::placeholder {
  492. color: #727378;
  493. }
  494. .NowcoderBetter_setting_menu input.no_default::placeholder{
  495. color: #BDBDBD;
  496. }
  497. .NowcoderBetter_setting_menu input.is_null::placeholder{
  498. color: red;
  499. border-width: 1.5px;
  500. }
  501. .NowcoderBetter_setting_menu input.is_null{
  502. border-color: red;
  503. }
  504. .NowcoderBetter_setting_menu textarea {
  505. display: block;
  506. width: 100%;
  507. height: 60px;
  508. background-color: #ffffff;
  509. color: #727378;
  510. font-size: 12px;
  511. padding: 1px 5px !important;
  512. box-sizing: border-box;
  513. margin: 5px 0px 5px 0px;
  514. border: 1px solid #00aeeccc;
  515. box-shadow: 0 0 1px #0000004d;
  516. }
  517. .NowcoderBetter_setting_menu textarea:focus-visible{
  518. border-style: solid;
  519. border-color: #3f51b5;
  520. outline: none;
  521. }
  522. .NowcoderBetter_setting_menu textarea::placeholder{
  523. color: #BDBDBD;
  524. font-size: 14px;
  525. }
  526.  
  527. .NowcoderBetter_setting_menu #save {
  528. cursor: pointer;
  529. display: inline-flex;
  530. padding: 5px;
  531. background-color: #1aa06d;
  532. color: #ffffff;
  533. font-size: 14px;
  534. line-height: 1.5rem;
  535. font-weight: 500;
  536. justify-content: center;
  537. width: 100%;
  538. border-radius: 0.375rem;
  539. border: none;
  540. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  541. margin-top: 20px
  542. }
  543. .NowcoderBetter_setting_menu button#debug_button.debug_button {
  544. width: 18%;
  545. }
  546.  
  547. .NowcoderBetter_setting_menu span.tip {
  548. color: #999;
  549. font-size: 12px;
  550. font-weight: 500;
  551. padding: 5px 0px;
  552. }
  553. /*设置面板-tip*/
  554. .help_tip {
  555. margin-right: auto;
  556. }
  557. .help_tip .tip_text {
  558. display: none;
  559. position: absolute;
  560. color: #697e91;
  561. font-weight: 400;
  562. letter-spacing: 0px;
  563. background-color: #ffffff;
  564. padding: 10px;
  565. margin: 5px 0px;
  566. border-radius: 4px;
  567. border: 1px solid #e4e7ed;
  568. box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
  569. z-index: 999;
  570. }
  571. .help_tip .tip_text p {
  572. margin-bottom: 5px;
  573. }
  574. .help_tip .tip_text:before {
  575. content: "";
  576. position: absolute;
  577. top: -20px;
  578. right: -10px;
  579. bottom: -10px;
  580. left: -10px;
  581. z-index: -1;
  582. }
  583. .help-icon {
  584. cursor: help;
  585. width: 15px;
  586. color: rgb(255, 153, 0);
  587. margin-left: 5px;
  588. margin-top: 3px;
  589. }
  590. #CFBetter_setting_menu .CFBetter_setting_menu_label_text .help_tip .help-icon {
  591. color: #7fbeb2;
  592. }
  593. .help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
  594. display: block;
  595. cursor: help;
  596. width: 250px;
  597. }
  598. /*确认弹窗*/
  599. .wordsExceeded {
  600. z-index: 99999;
  601. box-shadow: 0px 0px 5px 1px rgb(0 0 0 / 10%), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  602. display: grid;
  603. position: fixed;
  604. top: 50%;
  605. left: 50%;
  606. transform: translate(-50%, -50%);
  607. border-radius: 4px;
  608. background-color: #ffffff;
  609. border: 1px solid #e4e7ed;
  610. color: #697e91;
  611. font-family: var(--vp-font-family-base);
  612. padding: 10px 20px 20px 20px;
  613. }
  614. .wordsExceeded button {
  615. display: inline-flex;
  616. justify-content: center;
  617. align-items: center;
  618. line-height: 1;
  619. white-space: nowrap;
  620. cursor: pointer;
  621. text-align: center;
  622. box-sizing: border-box;
  623. outline: none;
  624. transition: .1s;
  625. user-select: none;
  626. vertical-align: middle;
  627. -webkit-appearance: none;
  628. height: 24px;
  629. padding: 5px 11px;
  630. font-size: 12px;
  631. border-radius: 4px;
  632. color: #ffffff;
  633. background: #409eff;
  634. border-color: #409eff;
  635. border: none;
  636. margin-right: 12px;
  637. }
  638. .wordsExceeded button:hover{
  639. background-color:#79bbff;
  640. }
  641. .wordsExceeded .help-icon {
  642. margin: 0px 8px 0px 0px;
  643. height: 1em;
  644. width: 1em;
  645. line-height: 1em;
  646. display: inline-flex;
  647. justify-content: center;
  648. align-items: center;
  649. position: relative;
  650. fill: currentColor;
  651. font-size: inherit;
  652. }
  653. .wordsExceeded p {
  654. margin: 5px 0px;
  655. }
  656. /*更新检查*/
  657. div#update_panel {
  658. z-index: 9999;
  659. position: fixed;
  660. top: 50%;
  661. left: 50%;
  662. width: 240px;
  663. transform: translate(-50%, -50%);
  664. box-shadow: 0px 0px 4px 0px #0000004d;
  665. padding: 10px 20px 20px 20px;
  666. color: #444242;
  667. background-color: #f5f5f5;
  668. border: 1px solid #848484;
  669. border-radius: 8px;
  670. }
  671. div#update_panel #updating {
  672. cursor: pointer;
  673. display: inline-flex;
  674. padding: 3px;
  675. background-color: #1aa06d;
  676. color: #ffffff;
  677. font-size: 14px;
  678. line-height: 1.5rem;
  679. font-weight: 500;
  680. justify-content: center;
  681. width: 100%;
  682. border-radius: 0.375rem;
  683. border: none;
  684. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  685. }
  686. div#update_panel #updating a {
  687. text-decoration: none;
  688. color: white;
  689. display: flex;
  690. position: inherit;
  691. top: 0;
  692. left: 0;
  693. width: 100%;
  694. height: 22px;
  695. font-size: 14px;
  696. justify-content: center;
  697. align-items: center;
  698. }
  699. #skip_menu {
  700. display: flex;
  701. margin-top: 10px;
  702. justify-content: flex-end;
  703. align-items: center;
  704. }
  705. #skip_menu .help_tip {
  706. margin-right: 5px;
  707. margin-left: -5px;
  708. }
  709. #skip_menu .help-icon {
  710. color: #f44336;
  711. }
  712. /* 配置管理 */
  713. .embed-responsive {
  714. height: max-content;
  715. padding-bottom: 0px;
  716. }
  717. .config_bar {
  718. height: 70px;
  719. width: 100%;
  720. display: flex;
  721. justify-content: space-between;
  722. }
  723. li#add_button {
  724. cursor: pointer;
  725. height: 40px;
  726. border: 1px dashed #BDBDBD;
  727. border-radius: 8px;
  728. background-color: #fcfbfb36;
  729. color: #bdbdbd;
  730. font-size: 14px;
  731. align-items: center;
  732. justify-content: center;
  733. }
  734. li#add_button:hover {
  735. border: 1px dashed #03A9F4;
  736. background-color: #d7f0fb8c;
  737. color: #03A9F4;
  738. }
  739. div#config_bar_list {
  740. display: flex;
  741. width: 480px;
  742. border: 1px solid #c5cae9;
  743. border-radius: 8px;
  744. background-color: #f0f8ff;
  745. box-sizing: border-box;
  746. }
  747. div#config_bar_list input[type="radio"] {
  748. appearance: none;
  749. width: 0;
  750. height: 0;
  751. overflow: hidden;
  752. }
  753. div#config_bar_list input[type="radio"] {
  754. margin: 0px;
  755. }
  756. div#config_bar_list input[type=radio]:focus {
  757. outline: 0px;
  758. }
  759. label.config_bar_ul_li_text {
  760. display: flex;
  761. align-items: center;
  762. justify-content: center;
  763. max-width: 100%;
  764. height: 40px;
  765. overflow-x: auto;
  766. font-size: 14px;
  767. font-weight: 400;
  768. margin: 0px 4px;
  769. padding: 3px;
  770. border: 1px solid #dedede;
  771. border-radius: 10px;
  772. box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
  773. box-sizing: border-box;
  774. }
  775. ul#config_bar_ul li button {
  776. background-color: #e6e6e6;
  777. color: #727378;
  778. height: 23px;
  779. font-size: 14px;
  780. border-radius: 0.3rem;
  781. padding: 1px 5px;
  782. margin: 5px;
  783. border: none;
  784. box-shadow: 0 0 1px #0000004d;
  785. }
  786. ul#config_bar_ul {
  787. display: flex;
  788. align-items: center;
  789. list-style-type: none;
  790. padding-inline-start: 0px;
  791. overflow-x: auto;
  792. max-width: 100%;
  793. margin: 0px;
  794. }
  795. ul#config_bar_ul li {
  796. width: 80px;
  797. display: grid;
  798. margin: 4px 4px;
  799. min-width: 100px;
  800. box-sizing: border-box;
  801. }
  802. label.config_bar_ul_li_text:hover {
  803. background-color: #eae4dc24;
  804. }
  805. input[type="radio"]:checked + .config_bar_ul_li_text {
  806. background: #41b3e430;
  807. border: 1px solid #5e7ce0;
  808. color: #5e7ce0;
  809. }
  810. ul#config_bar_ul::-webkit-scrollbar {
  811. width: 5px;
  812. height: 5px;
  813. }
  814. ul#config_bar_ul::-webkit-scrollbar-thumb {
  815. background-clip: padding-box;
  816. background-color: #d7d9e4;
  817. border-radius: 8px;
  818. }
  819. ul#config_bar_ul::-webkit-scrollbar-button:start:decrement {
  820. width: 4px;
  821. background-color: transparent;
  822. }
  823. ul#config_bar_ul::-webkit-scrollbar-button:end:increment {
  824. width: 4px;
  825. background-color: transparent;
  826. }
  827. ul#config_bar_ul::-webkit-scrollbar-track {
  828. background-color: #f1f1f1;
  829. border-radius: 5px;
  830. }
  831. label.config_bar_ul_li_text::-webkit-scrollbar {
  832. width: 5px;
  833. height: 7px;
  834. background-color: #aaa;
  835. }
  836. label.config_bar_ul_li_text::-webkit-scrollbar-thumb {
  837. background-clip: padding-box;
  838. background-color: #d7d9e4;
  839. }
  840. label.config_bar_ul_li_text::-webkit-scrollbar-track {
  841. background-color: #f1f1f1;
  842. }
  843. .config_bar_list_add_div {
  844. display: flex;
  845. height: 40px;
  846. margin: 4px 2px;
  847. }
  848. /* 修改菜单 */
  849. div#config_bar_menu {
  850. z-index: 99999;
  851. position: absolute;
  852. width: 60px;
  853. background: #ffffff;
  854. box-shadow: 1px 1px 4px 0px #0000004d;
  855. border: 0px solid rgba(0,0,0,0.04);
  856. border-radius: 4px;
  857. padding: 8px 0;
  858. }
  859. div.config_bar_menu_item {
  860. cursor: pointer;
  861. padding: 2px 6px;
  862. display: flex;
  863. justify-content: center;
  864. align-items: center;
  865. height: 32px;
  866. color: rgba(0,0,0,0.75);
  867. font-size: 14px;
  868. font-weight: 500;
  869. box-shadow: inset 0px 0px 0px 0px #8bb2d9;
  870. }
  871. div#config_bar_menu_edit:hover {
  872. background-color: #00aeec;
  873. color: white;
  874. }
  875. div#config_bar_menu_delete:hover {
  876. background-color: #FF5722;
  877. color: white;
  878. }
  879. /* 配置页面 */
  880. #config_edit_menu {
  881. z-index: 11000;
  882. width: 450px;
  883. }
  884. `);
  885.  
  886. // 获取cookie
  887. function getCookie(name) {
  888. const cookies = document.cookie.split(";");
  889. for (let i = 0; i < cookies.length; i++) {
  890. const cookie = cookies[i].trim();
  891. const [cookieName, cookieValue] = cookie.split("=");
  892.  
  893. if (cookieName === name) {
  894. return decodeURIComponent(cookieValue);
  895. }
  896. }
  897. return "";
  898. }
  899.  
  900. // 防抖函数
  901. function debounce(callback) {
  902. let timer;
  903. let immediateExecuted = false;
  904. const delay = 500;
  905. return function () {
  906. clearTimeout(timer);
  907. if (!immediateExecuted) { callback.call(this); immediateExecuted = true; }
  908. timer = setTimeout(() => { immediateExecuted = false; }, delay);
  909. };
  910. }
  911.  
  912. // 为元素添加鼠标拖动
  913. function addDraggable(element) {
  914. let isDragging = false;
  915. let initialX, initialY; // 元素的初始位置
  916. let startX, startY, offsetX, offsetY; // 鼠标起始位置,移动偏移量
  917. let isSpecialMouseDown = false; // 选取某些元素时不拖动
  918.  
  919. element.on('mousedown', function (e) {
  920. var elem = $(this);
  921. var elemOffset = elem.offset();
  922. var centerX = elemOffset.left + elem.outerWidth() / 2;
  923. var centerY = elemOffset.top + elem.outerHeight() / 2;
  924. initialX = centerX - window.pageXOffset;
  925. initialY = centerY - window.pageYOffset;
  926.  
  927. isDragging = true;
  928. startX = e.clientX;
  929. startY = e.clientY;
  930.  
  931. isSpecialMouseDown = $(e.target).is('label, p, input, textarea, span');
  932.  
  933. $('body').css('cursor', 'all-scroll');
  934. });
  935.  
  936.  
  937. $(document).on('mousemove', function (e) {
  938. if (!isDragging) return;
  939. // 不执行拖动操作
  940. if ($(e.target).is('label, p, input, textarea, span') || isSpecialMouseDown && !$(e.target).is('input, textarea')) return;
  941. e.preventDefault();
  942. offsetX = e.clientX - startX;
  943. offsetY = e.clientY - startY;
  944. element.css({ top: initialY + offsetY + 'px', left: initialX + offsetX + 'px' });
  945. });
  946.  
  947. $(document).on('mouseup', function () {
  948. isDragging = false;
  949. isSpecialMouseDown = false;
  950. $('body').css('cursor', 'default');
  951. });
  952. }
  953.  
  954. // 更新检查
  955. (function checkScriptVersion() {
  956. function compareVersions(version1 = "0", version2 = "0") {
  957. const v1Array = String(version1).split(".");
  958. const v2Array = String(version2).split(".");
  959. const minLength = Math.min(v1Array.length, v2Array.length);
  960. let result = 0;
  961. for (let i = 0; i < minLength; i++) {
  962. const curV1 = Number(v1Array[i]);
  963. const curV2 = Number(v2Array[i]);
  964. if (curV1 > curV2) {
  965. result = 1;
  966. break;
  967. } else if (curV1 < curV2) {
  968. result = -1;
  969. break;
  970. }
  971. }
  972. if (result === 0 && v1Array.length !== v2Array.length) {
  973. const v1IsBigger = v1Array.length > v2Array.length;
  974. const maxLenArray = v1IsBigger ? v1Array : v2Array;
  975. for (let i = minLength; i < maxLenArray.length; i++) {
  976. const curVersion = Number(maxLenArray[i]);
  977. if (curVersion > 0) {
  978. v1IsBigger ? result = 1 : result = -1;
  979. break;
  980. }
  981. }
  982. }
  983. return result;
  984. }
  985.  
  986. GM_xmlhttpRequest({
  987. method: "GET",
  988. url: "https://greasyfork.org/zh-CN/scripts/473210.json",
  989. timeout: 10 * 1e3,
  990. onload: function (response) {
  991. const scriptData = JSON.parse(response.responseText);
  992. const skipUpdate = getCookie("skipUpdate");
  993.  
  994. if (
  995. scriptData.name === GM_info.script.name &&
  996. compareVersions(scriptData.version, GM_info.script.version) === 1 &&
  997. skipUpdate !== "true"
  998. ) {
  999. const styleElement = GM_addStyle(darkenPageStyle);
  1000. $("body").append(`
  1001. <div id='update_panel'>
  1002. <h3>${GM_info.script.name}有新版本!</h3>
  1003. <hr>
  1004. <div class='update_panel_menu'>
  1005. <span class ='tip'>版本信息:${GM_info.script.version} ${scriptData.version}</span>
  1006. </div>
  1007. <br>
  1008. <div id="skip_menu">
  1009. <div class="help_tip">
  1010. `+ helpCircleHTML + `
  1011. <div class="tip_text">
  1012. <p><b>更新遇到了问题?</b></p>
  1013. <p>由于 Greasyfork 平台的原因,当新版本刚发布时,点击 Greasyfork 上的更新按钮<u>可能</u>会出现<u>实际更新/安装的却是上一个版本</u>的情况</p>
  1014. <p>通常你只需要稍等几分钟,然后再次前往更新/安装即可</p>
  1015. <p>你也可以<u>点击下方按钮,在本次浏览器会话期间将不再提示更新</u></p>
  1016. <button id='skip_update' class='html2mdButton'>暂不更新</button>
  1017. </div>
  1018. </div>
  1019. <button id='updating'><a target="_blank" href="${scriptData.url}">更新</a></button>
  1020. </div>
  1021. </div>
  1022. `);
  1023.  
  1024. $("#skip_update").click(function () {
  1025. document.cookie = "skipUpdate=true; expires=session; path=/";
  1026. styleElement.remove();
  1027. $("#update_panel").remove();
  1028. });
  1029. }
  1030. }
  1031. });
  1032.  
  1033. })();
  1034.  
  1035. // 设置面板
  1036. $(document).ready(function () {
  1037. var htmlContent = "<button class='html2mdButton NowcoderBetter_setting'>Nowcoder Better设置</button>";
  1038. if ($('.acm-nav-info').length > 0) $('.acm-nav-info > li:last-child').after("<li class='dropdown'>" + htmlContent + "</li>");
  1039. else $('.header-bar .header-right > :last-child').after(htmlContent);
  1040. });
  1041.  
  1042. // 配置管理函数
  1043. function setupConfigManagement(element, tempConfig, structure, configHTML, checkable) {
  1044. let counter = 0;
  1045. createControlBar();
  1046. createContextMenu();
  1047.  
  1048. // 键值对校验
  1049. function valiKeyValue(value) {
  1050. const keyValuePairs = value.split('\n');
  1051. const regex = /^[a-zA-Z0-9_-]+\s*:\s*[a-zA-Z0-9_-]+$/;
  1052. for (let i = 0; i < keyValuePairs.length; i++) {
  1053. if (!regex.test(keyValuePairs[i])) {
  1054. return false;
  1055. }
  1056. }
  1057. return true;
  1058. }
  1059.  
  1060. // 新增数据
  1061. function onAdd() {
  1062. const styleElement = createWindow();
  1063.  
  1064. const settingMenu = $("#config_edit_menu");
  1065. settingMenu.on("click", "#save", () => {
  1066. const config = {};
  1067. let allFieldsValid = true;
  1068. for (const key in structure) {
  1069. let value = $(key).val();
  1070. if (value || $(key).attr('require') === 'false') {
  1071. config[structure[key]] = $(key).val();
  1072. $(key).removeClass('is_null');
  1073. } else {
  1074. $(key).addClass('is_null');
  1075. allFieldsValid = false;
  1076. }
  1077. }
  1078.  
  1079. // 校验提示
  1080. for (let i = 0, len = checkable.length; i < len; i++) {
  1081. let value = $(checkable[i]).val();
  1082. if (value && !valiKeyValue(value)) {
  1083. if (!$(checkable[i]).prev('span.text-error').length) {
  1084. $(checkable[i]).before('<span class="text-error" style="color: red;">格式不符或存在非法字符</span>');
  1085. }
  1086. allFieldsValid = false;
  1087. } else {
  1088. $(checkable[i]).prev('span.text-error').remove();
  1089. }
  1090. }
  1091.  
  1092. if (!allFieldsValid) return;
  1093. tempConfig.configurations.push(config);
  1094.  
  1095. const list = $("#config_bar_ul");
  1096. createListItemElement(config[structure['#note']]).insertBefore($('#add_button'));
  1097.  
  1098. settingMenu.remove();
  1099. $(styleElement).remove();
  1100. });
  1101.  
  1102. settingMenu.on("click", ".btn-close", () => {
  1103. settingMenu.remove();
  1104. $(styleElement).remove();
  1105. });
  1106. }
  1107.  
  1108. // 编辑数据
  1109. function onEdit() {
  1110. const menu = $("#config_bar_menu");
  1111. menu.css({ display: "none" });
  1112.  
  1113. const list = $("#config_bar_ul");
  1114. const index = Array.from(list.children()).indexOf(this);
  1115.  
  1116. const styleElement = createWindow();
  1117.  
  1118. const settingMenu = $("#config_edit_menu");
  1119. const configAtIndex = tempConfig.configurations[index];
  1120.  
  1121. if (configAtIndex) {
  1122. for (const key in structure) {
  1123. $(key).val(configAtIndex[structure[key]]);
  1124. }
  1125. }
  1126.  
  1127. settingMenu.on("click", "#save", () => {
  1128. const config = {};
  1129. let allFieldsValid = true;
  1130. for (const key in structure) {
  1131. let value = $(key).val();
  1132. if (value || $(key).attr('require') === 'false') {
  1133. config[structure[key]] = $(key).val();
  1134. $(key).removeClass('is_null');
  1135. } else {
  1136. $(key).addClass('is_null');
  1137. allFieldsValid = false;
  1138. }
  1139. }
  1140. // 校验提示
  1141. for (let i = 0, len = checkable.length; i < len; i++) {
  1142. let value = $(checkable[i]).val();
  1143. if (value && !valiKeyValue(value)) {
  1144. if (!$(checkable[i]).prev('span.text-error').length) {
  1145. $(checkable[i]).before('<span class="text-error" style="color: red;">格式不符或存在非法字符</span>');
  1146. }
  1147. allFieldsValid = false;
  1148. } else {
  1149. $(checkable[i]).prev('span.text-error').remove();
  1150. }
  1151. }
  1152.  
  1153. if (!allFieldsValid) return;
  1154. tempConfig.configurations[index] = config;
  1155.  
  1156. settingMenu.remove();
  1157. $(styleElement).remove();
  1158. menu.css({ display: "none" });
  1159.  
  1160. list.children().eq(index).find("label").text(config.note);
  1161. });
  1162.  
  1163. // 关闭按钮
  1164. settingMenu.on("click", ".btn-close", () => {
  1165. settingMenu.remove();
  1166. $(styleElement).remove();
  1167. });
  1168. }
  1169.  
  1170. // 删除数据
  1171. function onDelete() {
  1172. const menu = $("#config_bar_menu");
  1173. menu.css({ display: "none" });
  1174.  
  1175. const list = $("#config_bar_ul");
  1176. const index = Array.from(list.children()).indexOf(this);
  1177.  
  1178. tempConfig.configurations.splice(index, 1);
  1179.  
  1180. list.children().eq(index).remove();
  1181. }
  1182.  
  1183. // 创建编辑窗口
  1184. function createWindow() {
  1185. const styleElement = GM_addStyle(darkenPageStyle2);
  1186. $("body").append(configHTML);
  1187. addDraggable($('#config_edit_menu'));
  1188. return styleElement;
  1189. }
  1190.  
  1191. // 创建控制面板
  1192. function createControlBar() {
  1193. $(element).append(`
  1194. <div id='configControlTip' style='color:red;'></div>
  1195. <div class='config_bar'>
  1196. <div class='config_bar_list' id='config_bar_list'>
  1197. <ul class='config_bar_ul' id='config_bar_ul'></ul>
  1198. </div>
  1199. </div>
  1200. `);
  1201. }
  1202.  
  1203. // 创建右键菜单
  1204. function createContextMenu() {
  1205. const menu = $("<div id='config_bar_menu' style='display: none;'></div>");
  1206. menu.html(`
  1207. <div class='config_bar_menu_item' id='config_bar_menu_edit'>修改</div>
  1208. <div class='config_bar_menu_item' id='config_bar_menu_delete'>删除</div>
  1209. `);
  1210. $("body").append(menu);
  1211. }
  1212.  
  1213. // 创建新的li元素
  1214. function createListItemElement(text) {
  1215. const li = $("<li></li>");
  1216. const radio = $("<input type='radio' name='config_item'></input>").appendTo(li);
  1217. radio.attr("value", counter).attr("id", counter++);
  1218. const label = $("<label class='config_bar_ul_li_text'></label>").text(text).attr("for", radio.attr("value")).appendTo(li);
  1219.  
  1220. // 添加右键菜单
  1221. li.on("contextmenu", function (event) {
  1222. event.preventDefault();
  1223. const menu = $("#config_bar_menu");
  1224. menu.css({ display: "block", left: event.pageX, top: event.pageY });
  1225.  
  1226. const deleteItem = $("#config_bar_menu_delete");
  1227. const editItem = $("#config_bar_menu_edit");
  1228.  
  1229. // 移除旧事件
  1230. deleteItem.off("click");
  1231. editItem.off("click");
  1232.  
  1233. deleteItem.on("click", onDelete.bind(this));
  1234. editItem.on("click", onEdit.bind(this));
  1235.  
  1236. $(document).one("click", (event) => {
  1237. if (!menu.get(0).contains(event.target)) {
  1238. menu.css({ display: "none" });
  1239. deleteItem.off("click", onDelete);
  1240. editItem.off("click", onEdit);
  1241. }
  1242. });
  1243. });
  1244.  
  1245.  
  1246. return li;
  1247. }
  1248.  
  1249. // 渲染列表
  1250. function renderList() {
  1251. const listContainer = $("#config_bar_list");
  1252. const list = $("#config_bar_ul");
  1253. list.empty();
  1254. tempConfig.configurations.forEach((item) => {
  1255. list.append(createListItemElement(item[structure['#note']]));
  1256. });
  1257.  
  1258. list.append(`
  1259. <li id='add_button'>
  1260. <span>+ 添加</span>
  1261. </li>
  1262. `);
  1263. const addItem = $('#add_button');
  1264. addItem.on("click", onAdd);
  1265. };
  1266.  
  1267. renderList();
  1268. return tempConfig;
  1269. }
  1270.  
  1271. const NowcoderBetterSettingMenuHTML = `
  1272. <div id='NowcoderBetter_setting_menu' class='NowcoderBetter_setting_menu'>
  1273. <div class="tool-box">
  1274. <button class="btn-close">×</button>
  1275. </div>
  1276. <h3>基本设置</h3>
  1277. <hr>
  1278. <div class='NowcoderBetter_setting_list'>
  1279. <label for="hoverTargetAreaDisplay">显示目标区域范围</label>
  1280. <div class="help_tip">
  1281. `+ helpCircleHTML + `
  1282. <div class="tip_text">
  1283. <p>开启后当鼠标悬浮在 MD视图/复制/翻译 按钮上时,会显示其目标区域的范围</p>
  1284. </div>
  1285. </div>
  1286. <input type="checkbox" id="hoverTargetAreaDisplay" name="hoverTargetAreaDisplay">
  1287. </div>
  1288. <div class='NowcoderBetter_setting_list'>
  1289. <label for="enableSegmentedTranslation">分段翻译</label>
  1290. <div class="help_tip">
  1291. `+ helpCircleHTML + `
  1292. <div class="tip_text">
  1293. <p>分段翻译会对区域内的每一个&#60;&#112;&#47;&#62;和&#60;&#105;&#47;&#62;标签依次进行翻译,</p>
  1294. <p>这通常在翻译<strong>长篇博客</strong>或者<strong>超长的题目</strong>时很有用。</p>
  1295. <p><u>注意:开启分段翻译会产生如下问题:</u></p>
  1296. <p>- 使得翻译接口无法知晓整个文本的上下文信息,会降低翻译质量。</p>
  1297. <p>- 会有<strong>部分内容不会被翻译</strong>,因为它们不是&#60;&#112;&#47;&#62;或&#60;&#105;&#47;&#62;标签</p>
  1298. </div>
  1299. </div>
  1300. <input type="checkbox" id="enableSegmentedTranslation" name="enableSegmentedTranslation">
  1301. </div>
  1302. <h3>翻译设置</h3>
  1303. <hr>
  1304. <label>
  1305. <input type='radio' name='translation' value='deepl'>
  1306. <span class='NowcoderBetter_setting_menu_label_text'>deepl翻译</span>
  1307. </label>
  1308. <label>
  1309. <input type='radio' name='translation' value='youdao'>
  1310. <span class='NowcoderBetter_setting_menu_label_text'>有道翻译</span>
  1311. </label>
  1312. <label>
  1313. <input type='radio' name='translation' value='google'>
  1314. <span class='NowcoderBetter_setting_menu_label_text'>Google翻译</span>
  1315. </label>
  1316. <label>
  1317. <input type='radio' name='translation' value='openai'>
  1318. <span class='NowcoderBetter_setting_menu_label_text'>使用ChatGPT翻译(API)
  1319. <div class="help_tip">
  1320. `+ helpCircleHTML + `
  1321. <div class="tip_text">
  1322. <p><b>请在下方选定你想使用的配置信息</b></p>
  1323. <p>脚本的所有请求均在本地完成</p>
  1324. </div>
  1325. </div>
  1326. </span>
  1327. </label>
  1328. <div class='NowcoderBetter_setting_menu_input' id='openai' style='display: none;'>
  1329. <div id="chatgpt-config"></div>
  1330. </div>
  1331. <button id='save'>保存</button>
  1332. </div>
  1333. `;
  1334.  
  1335. const chatgptConfigEditHTML = `
  1336. <div class='NowcoderBetter_setting_menu' id='config_edit_menu'>
  1337. <div class="tool-box">
  1338. <button class="btn-close">×</button>
  1339. </div>
  1340. <h4>配置</h4>
  1341. <h5>基本</h5>
  1342. <hr>
  1343. <label for='note'>
  1344. <span class="input_label">备注:</span>
  1345. </label>
  1346. <input type='text' id='note' class='no_default' placeholder='请为该配置取一个备注名' require = true>
  1347. <label for='openai_model'>
  1348. <div style="display: flex;align-items: center;">
  1349. <span class="input_label">模型:</span>
  1350. <div class="help_tip">
  1351. `+ helpCircleHTML + `
  1352. <div class="tip_text">
  1353. <p>留空则默认为:gpt-3.5-turbo</p>
  1354. <p>模型列表请查阅<a target="_blank" href="https://platform.openai.com/docs/models">OpenAI官方文档</a></p>
  1355. <p><strong>此外,如果您使用的是服务商提供的代理API,请确认服务商是否支持对应模型</strong></p>
  1356. </div>
  1357. </div>
  1358. </div>
  1359. </label>
  1360. <input type='text' id='openai_model' placeholder='gpt-3.5-turbo' require = false>
  1361. <label for='openai_key'>
  1362. <div style="display: flex;align-items: center;">
  1363. <span class="input_label">KEY:</span>
  1364. <div class="help_tip">
  1365. `+ helpCircleHTML + `
  1366. <div class="tip_text">
  1367. <p>您需要输入自己的OpenAI key,<a target="_blank" href="https://platform.openai.com/account/usage">官网</a></p>
  1368. <p><b>如果您使用的是服务商提供的代理API,则应该填写服务商提供的 Key</b></p>
  1369. </div>
  1370. </div>
  1371. </div>
  1372. </label>
  1373. <input type='text' id='openai_key' class='no_default' placeholder='请输入KEY' require = true>
  1374. <label for='openai_proxy'>
  1375. <div style="display: flex;align-items: center;">
  1376. <span class="input_label">Proxy API:</span>
  1377. <div class="help_tip">
  1378. `+ helpCircleHTML + `
  1379. <div class="tip_text">
  1380. <p>留空则默认为OpenAI官方API</p>
  1381. <p>您也可以填写指定的API来代理访问OpenAIAPI,</p>
  1382. <p>如果您使用的是服务商提供的代理APIKEY,则这里应该填写其提供的<strong>完整</strong>API地址,详请阅读脚本说明</p>
  1383. <p><strong>由于您指定了自定义的APITampermonkey会对您的跨域请求进行警告,您需要自行授权</strong></p>
  1384. </div>
  1385. </div>
  1386. </div>
  1387. </label>
  1388. <input type='text' id='openai_proxy' placeholder='https://api.openai.com/v1/chat/completions' require = false>
  1389. <h5>高级</h5>
  1390. <hr>
  1391. <label for='_header'>
  1392. <div style="display: flex;align-items: center;">
  1393. <span class="input_label">自定义header</span>
  1394. <div class="help_tip">
  1395. `+ helpCircleHTML + `
  1396. <div class="tip_text">
  1397. <p>格式样例:</p>
  1398. <div style="border: 1px solid #795548; padding: 10px;">
  1399. <p>name1 : 123<br>name2 : cccc</p>
  1400. </div>
  1401. </div>
  1402. </div>
  1403. </div>
  1404. </label>
  1405. <textarea id="_header" placeholder='(选填)您可以在这里填写向请求header中额外添加的键值对' require = false></textarea>
  1406. <label for='_data'>
  1407. <div style="display: flex;align-items: center;">
  1408. <span class="input_label">自定义data</span>
  1409. <div class="help_tip">
  1410. `+ helpCircleHTML + `
  1411. <div class="tip_text">
  1412. <p>格式样例:</p>
  1413. <div style="border: 1px solid #795548; padding: 10px;">
  1414. <p>name1 : 123<br>name2 : cccc</p>
  1415. </div>
  1416. </div>
  1417. </div>
  1418. </div>
  1419. </label>
  1420. <textarea id="_data" placeholder='(选填)您可以在这里填写向请求data中额外添加的键值对' require = false></textarea>
  1421. <button id='save'>保存</button>
  1422. </div>
  1423. `;
  1424.  
  1425. $(document).ready(function () {
  1426. const $settingBtns = $(".NowcoderBetter_setting");
  1427. $settingBtns.click(() => {
  1428. const styleElement = GM_addStyle(darkenPageStyle);
  1429. $settingBtns.prop("disabled", true).addClass("open");
  1430. $("body").append(NowcoderBetterSettingMenuHTML);
  1431.  
  1432. // 窗口初始化
  1433. addDraggable($('#NowcoderBetter_setting_menu'));
  1434. const chatgptStructure = {
  1435. '#note': 'note',
  1436. '#openai_model': 'model',
  1437. '#openai_key': 'key',
  1438. '#openai_proxy': 'proxy',
  1439. '#_header': '_header',
  1440. '#_data': '_data',
  1441. }
  1442. const checkable = [
  1443. '#_header',
  1444. '#_data',
  1445. ]
  1446.  
  1447. // 缓存配置信息
  1448. let tempConfig = GM_getValue('chatgpt-config');
  1449. tempConfig = setupConfigManagement('#chatgpt-config', tempConfig, chatgptStructure, chatgptConfigEditHTML, checkable);
  1450.  
  1451. // 状态切换
  1452. $("#enableSegmentedTranslation").prop("checked", GM_getValue("enableSegmentedTranslation") === true);
  1453. $("#hoverTargetAreaDisplay").prop("checked", GM_getValue("hoverTargetAreaDisplay") === true);
  1454. $("input[name='translation'][value='" + translation + "']").prop("checked", true);
  1455. $("input[name='translation']").css("color", "gray");
  1456. if (translation == "openai") {
  1457. $("#openai").show();
  1458. if (tempConfig) {
  1459. $("input[name='config_item'][value='" + tempConfig.choice + "']").prop("checked", true);
  1460. }
  1461. }
  1462.  
  1463. // 翻译选择情况监听
  1464. $("input[name='translation']").change(function () {
  1465. var selected = $(this).val(); // 获取当前选中的值
  1466. if (selected === "openai") {
  1467. $("#openai").show();
  1468. if (tempConfig) {
  1469. $("input[name='config_item'][value='" + tempConfig.choice + "']").prop("checked", true);
  1470. }
  1471. } else {
  1472. $("#openai").hide();
  1473. }
  1474. });
  1475. // 配置选择情况监听
  1476. $("input[name='config_item']").change(function () {
  1477. var selected = $(this).val(); // 获取当前选中的值
  1478. tempConfig.choice = selected;
  1479. });
  1480.  
  1481. const $settingMenu = $(".NowcoderBetter_setting_menu");
  1482.  
  1483. $("#save").click(debounce(function () {
  1484. const settings = {
  1485. hoverTargetAreaDisplay: $("#hoverTargetAreaDisplay").prop("checked"),
  1486. enableSegmentedTranslation: $("#enableSegmentedTranslation").prop("checked"),
  1487. translation: $("input[name='translation']:checked").val()
  1488. };
  1489. if (settings.translation === "openai") {
  1490. var selectedIndex = $('input[name="config_item"]:checked').closest('li').index();
  1491. if (selectedIndex === -1) {
  1492. $('#configControlTip').text('请选择一项配置!')
  1493. return;
  1494. }
  1495. }
  1496. GM_setValue('chatgpt-config', tempConfig);
  1497. let refreshPage = false; // 是否需要刷新页面
  1498. for (const [key, value] of Object.entries(settings)) {
  1499. if (!refreshPage && !(key == 'enableSegmentedTranslation' || key == 'translation')) {
  1500. if (GM_getValue(key) != value) refreshPage = true;
  1501. }
  1502. GM_setValue(key, value);
  1503. }
  1504. if (refreshPage) location.reload();
  1505. else {
  1506. // 更新配置信息
  1507. enableSegmentedTranslation = settings.enableSegmentedTranslation;
  1508. translation = settings.translation;
  1509. if (settings.translation === "openai") {
  1510. var selectedIndex = $('#config_bar_ul li input[type="radio"]:checked').closest('li').index();
  1511. if (selectedIndex !== opneaiConfig.choice) {
  1512. opneaiConfig = GM_getValue("chatgpt-config");
  1513. const configAtIndex = opneaiConfig.configurations[selectedIndex];
  1514. openai_model = configAtIndex.model;
  1515. openai_key = configAtIndex.key;
  1516. openai_proxy = configAtIndex.proxy;
  1517. openai_header = configAtIndex._header ?
  1518. configAtIndex._header.split("\n").map(header => {
  1519. const [key, value] = header.split(":");
  1520. return { [key.trim()]: value.trim() };
  1521. }) : [];
  1522. openai_data = configAtIndex._data ?
  1523. configAtIndex._data.split("\n").map(header => {
  1524. const [key, value] = header.split(":");
  1525. return { [key.trim()]: value.trim() };
  1526. }) : [];
  1527. }
  1528. }
  1529. }
  1530.  
  1531. $settingMenu.remove();
  1532. $settingBtns.prop("disabled", false).removeClass("open");
  1533. $(styleElement).remove();
  1534. }));
  1535.  
  1536. // 关闭
  1537. $settingMenu.on("click", ".btn-close", () => {
  1538. $settingMenu.remove();
  1539. $settingBtns.prop("disabled", false).removeClass("open");
  1540. $(styleElement).remove();
  1541. });
  1542. });
  1543. });
  1544.  
  1545. // html2md转换/处理规则
  1546. var turndownService = new TurndownService({ bulletListMarker: '-', escape: (text) => text });
  1547. var turndown = turndownService.turndown;
  1548.  
  1549. // 保留原始
  1550. turndownService.keep(['del']);
  1551.  
  1552. turndownService.addRule('removeByClass', {
  1553. filter: function (node) {
  1554. return node.classList.contains('html2md-panel') ||
  1555. node.classList.contains('div-btn-copy') ||
  1556. node.classList.contains('btn-copy') ||
  1557. node.classList.contains('code-copy-btn') ||
  1558. node.classList.contains('overlay')
  1559. },
  1560. replacement: function () {
  1561. return '';
  1562. }
  1563. });
  1564.  
  1565. // inline math
  1566. turndownService.addRule('inline-math1', {
  1567. filter: function (node, options) {
  1568. return node.tagName.toLowerCase() == "span" && node.className == "katex";
  1569. },
  1570. replacement: function (content, node) {
  1571. var text = $(node).find('annotation').text();
  1572. if (text == "") {
  1573. text = $(node).find('math').contents().filter(function () {
  1574. return this.nodeType === Node.TEXT_NODE;
  1575. }).text();
  1576. }
  1577. return "$" + text + "$";
  1578. }
  1579. });
  1580. turndownService.addRule('inline-math2', {
  1581. filter: function (node, options) {
  1582. return node.tagName.toLowerCase() == "span" && node.className == "katex-mathml";
  1583. },
  1584. replacement: function (content, node) {
  1585. var text = "";
  1586. $(node).contents().each(function () {
  1587. if (this.nodeType === Node.TEXT_NODE) {
  1588. text += $(this).text();
  1589. }
  1590. });
  1591. return "$" + text + "$";
  1592. }
  1593. });
  1594.  
  1595. // block math
  1596. turndownService.addRule('block-math', {
  1597. filter: function (node, options) {
  1598. return node.tagName.toLowerCase() == "span" && node.className == "katex-display";
  1599. },
  1600. replacement: function (content, node) {
  1601. return "\n$$\n" + $(node).find('annotation').text() + "\n$$\n";
  1602. }
  1603. });
  1604.  
  1605. // pre
  1606. turndownService.addRule('pre', {
  1607. filter: function (node, options) {
  1608. return node.tagName.toLowerCase() == "pre";
  1609. },
  1610. replacement: function (content, node) {
  1611. return "```\n" + content + "```\n";
  1612. }
  1613. });
  1614.  
  1615. // bordertable
  1616. turndownService.addRule('bordertable', {
  1617. filter: 'table',
  1618. replacement: function (content, node) {
  1619. if (node.classList.contains('table')) {
  1620. var output = [],
  1621. thead = '',
  1622. trs = node.querySelectorAll('tr');
  1623. if (trs.length > 0) {
  1624. var ths = trs[0].querySelectorAll('th');
  1625. if (ths.length > 0) {
  1626. thead = '| ' + Array.from(ths).map(th => turndownService.turndown(th.innerHTML.trim())).join(' | ') + ' |\n'
  1627. + '| ' + Array.from(ths).map(() => ' --- ').join('|') + ' |\n';
  1628. }
  1629. }
  1630. var rows = node.querySelectorAll('tr');
  1631. Array.from(rows).forEach(function (row, i) {
  1632. if (i > 0) {
  1633. var cells = row.querySelectorAll('td,th');
  1634. var trow = '| ' + Array.from(cells).map(cell => turndownService.turndown(cell.innerHTML.trim())).join(' | ') + ' |';
  1635. output.push(trow);
  1636. }
  1637. });
  1638. return thead + output.join('\n');
  1639. } else {
  1640. return content;
  1641. }
  1642. }
  1643. });
  1644.  
  1645.  
  1646. // 随机数生成
  1647. function getRandomNumber(numDigits) {
  1648. let min = Math.pow(10, numDigits - 1);
  1649. let max = Math.pow(10, numDigits) - 1;
  1650. return Math.floor(Math.random() * (max - min + 1)) + min;
  1651. }
  1652.  
  1653. // 按钮面板
  1654. function addButtonPanel(parent, suffix, type, is_simple = false) {
  1655. let htmlString = `<div class='html2md-panel'>
  1656. <button class='html2mdButton html2md-view${suffix}'>MarkDown视图</button>
  1657. <button class='html2mdButton html2md-cb${suffix}'>Copy</button>
  1658. <button class='html2mdButton translateButton${suffix}'>翻译</button>
  1659. </div>`;
  1660. if (type === "this_level") {
  1661. $(parent).before(htmlString);
  1662. } else if (type === "child_level") {
  1663. $(parent).prepend(htmlString);
  1664. }
  1665. if (is_simple) {
  1666. $('.html2md-panel').find('.html2mdButton.html2md-view' + suffix + ', .html2mdButton.html2md-cb' + suffix).remove();
  1667. }
  1668. }
  1669.  
  1670. function addButtonWithHTML2MD(parent, suffix, type) {
  1671. $(document).on("click", ".html2md-view" + suffix, function () {
  1672. var target, removedChildren = $();
  1673. if (type === "this_level") {
  1674. target = $(".html2md-view" + suffix).parent().next().get(0);
  1675. } else if (type === "child_level") {
  1676. target = $(".html2md-view" + suffix).parent().parent().get(0);
  1677. removedChildren = $(".html2md-view" + suffix).parent().parent().children(':first').detach();
  1678. }
  1679. if (target.viewmd) {
  1680. target.viewmd = false;
  1681. $(this).text("MarkDown视图");
  1682. $(this).removeClass("mdViewed");
  1683. $(target).html(target.original_html);
  1684. } else {
  1685. target.viewmd = true;
  1686. if (!target.original_html) {
  1687. target.original_html = $(target).html();
  1688. }
  1689. if (!target.markdown) {
  1690. target.markdown = turndownService.turndown($(target).html());
  1691. }
  1692. $(this).text("原始内容");
  1693. $(this).addClass("mdViewed");
  1694. $(target).html(`<span class="mdViewContent" oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;">${target.markdown}</span>`);
  1695. }
  1696. // 恢复删除的元素
  1697. if (removedChildren) $(target).prepend(removedChildren);
  1698. });
  1699. }
  1700.  
  1701. function addButtonWithHTML2MD(parent, suffix, type) {
  1702. $(document).on("click", ".html2md-view" + suffix, debounce(function () {
  1703. var target, removedChildren = $();
  1704. if (type === "this_level") {
  1705. target = $(".html2md-view" + suffix).parent().next().get(0);
  1706. } else if (type === "child_level") {
  1707. target = $(".html2md-view" + suffix).parent().parent().get(0);
  1708. removedChildren = $(".html2md-view" + suffix).parent().parent().children(':first').detach();
  1709. }
  1710. if (target.viewmd) {
  1711. target.viewmd = false;
  1712. $(this).text("MarkDown视图");
  1713. $(this).removeClass("mdViewed");
  1714. $(target).html(target.original_html);
  1715. } else {
  1716. target.viewmd = true;
  1717. if (!target.original_html) {
  1718. target.original_html = $(target).html();
  1719. }
  1720. if (!target.markdown) {
  1721. target.markdown = turndownService.turndown($(target).html());
  1722. }
  1723. $(this).text("原始内容");
  1724. $(this).addClass("mdViewed");
  1725. $(target).html(`<span class="mdViewContent" oninput="$(this).parent().get(0).markdown=this.value;" style="width:auto; height:auto;">${target.markdown}</span>`);
  1726. }
  1727. // 恢复删除的元素
  1728. if (removedChildren) $(target).prepend(removedChildren);
  1729. }));
  1730.  
  1731. if (hoverTargetAreaDisplay) {
  1732. var previousCSS;
  1733. $(document).on("mouseover", ".html2md-view" + suffix, function () {
  1734. var target;
  1735.  
  1736. if (type === "this_level") {
  1737. target = $(".html2md-view" + suffix).parent().next().get(0);
  1738. } else if (type === "child_level") {
  1739. target = $(".html2md-view" + suffix).parent().parent().get(0);
  1740. }
  1741.  
  1742. $(target).append('<div class="overlay">目标转换区域</div>');
  1743. previousCSS = {
  1744. "position": $(target).css("position"),
  1745. "display": $(target).css("display")
  1746. };
  1747. $(target).css({
  1748. "position": "relative",
  1749. "display": "block"
  1750. });
  1751.  
  1752. $(".html2md-view" + suffix).parent().css({
  1753. "position": "relative",
  1754. "z-index": "99999"
  1755. })
  1756. });
  1757.  
  1758. $(document).on("mouseout", ".html2md-view" + suffix, function () {
  1759. var target;
  1760.  
  1761. if (type === "this_level") {
  1762. target = $(".html2md-view" + suffix).parent().next().get(0);
  1763. } else if (type === "child_level") {
  1764. target = $(".html2md-view" + suffix).parent().parent().get(0);
  1765. }
  1766.  
  1767. $(target).find('.overlay').remove();
  1768. $(target).css(previousCSS);
  1769. $(".html2md-view" + suffix).parent().css({
  1770. "position": "static"
  1771. })
  1772. });
  1773. }
  1774. }
  1775.  
  1776. function addButtonWithCopy(parent, suffix, type) {
  1777. $(document).on("click", ".html2md-cb" + suffix, debounce(function () {
  1778. var target, removedChildren;
  1779. if (type === "this_level") {
  1780. target = $(".translateButton" + suffix).parent().next().eq(0).clone();
  1781. } else if (type === "child_level") {
  1782. target = $(".translateButton" + suffix).parent().parent().eq(0).clone();
  1783. $(target).children(':first').remove();
  1784. }
  1785. if ($(target).find('.mdViewContent').length <= 0) {
  1786. text = turndownService.turndown($(target).html());
  1787. } else {
  1788. text = $(target).find('.mdViewContent').text();
  1789. }
  1790. GM_setClipboard(text);
  1791. $(this).addClass("copied");
  1792. $(this).text("Copied");
  1793. // 更新复制按钮文本
  1794. setTimeout(() => {
  1795. $(this).removeClass("copied");
  1796. $(this).text("Copy");
  1797. }, 2000);
  1798. $(target).remove();
  1799. }));
  1800.  
  1801. if (hoverTargetAreaDisplay) {
  1802. var previousCSS;
  1803. $(document).on("mouseover", ".html2md-cb" + suffix, function () {
  1804. var target;
  1805.  
  1806. if (type === "this_level") {
  1807. target = $(".html2md-cb" + suffix).parent().next().get(0);
  1808. } else if (type === "child_level") {
  1809. target = $(".html2md-cb" + suffix).parent().parent().get(0);
  1810. }
  1811.  
  1812. $(target).append('<div class="overlay">目标复制区域</div>');
  1813. previousCSS = {
  1814. "position": $(target).css("position"),
  1815. "display": $(target).css("display")
  1816. };
  1817.  
  1818. $(target).css({
  1819. "position": "relative",
  1820. "display": "block"
  1821. });
  1822. $(".html2md-cb" + suffix).parent().css({
  1823. "position": "relative",
  1824. "z-index": "99999"
  1825. })
  1826. });
  1827.  
  1828. $(document).on("mouseout", ".html2md-cb" + suffix, function () {
  1829. var target;
  1830.  
  1831. if (type === "this_level") {
  1832. target = $(".html2md-cb" + suffix).parent().next().get(0);
  1833. } else if (type === "child_level") {
  1834. target = $(".html2md-cb" + suffix).parent().parent().get(0);
  1835. }
  1836.  
  1837. $(target).find('.overlay').remove();
  1838. $(target).css(previousCSS);
  1839. $(".html2md-cb" + suffix).parent().css({
  1840. "position": "static"
  1841. })
  1842. });
  1843. }
  1844. }
  1845.  
  1846. async function addButtonWithTranslation(parent, suffix, type) {
  1847. var result;
  1848. $(document).on('click', '.translateButton' + suffix, debounce(async function () {
  1849. $(this).trigger('mouseout');
  1850. $(this).removeClass("translated");
  1851. $(this).text("翻译中");
  1852. $(this).css("cursor", "not-allowed");
  1853. $(this).prop("disabled", true);
  1854. var target, element_node, block, errerNum = 0;
  1855. if (type === "this_level") block = $(".translateButton" + suffix).parent().next();
  1856. else if (type === "child_level") block = $(".translateButton" + suffix).parent().parent();
  1857.  
  1858. // 重新翻译
  1859. if (result) {
  1860. if (result.translateDiv) {
  1861. $(result.translateDiv).remove();
  1862. }
  1863. if (result.copyDiv) {
  1864. $(result.copyDiv).remove();
  1865. }
  1866. if (result.copyButton) {
  1867. $(result.copyButton).remove();
  1868. }
  1869. // 移除旧的事件
  1870. $(document).off("mouseover", ".translateButton" + suffix);
  1871. $(document).off("mouseout", ".translateButton" + suffix);
  1872. // 重新绑定悬停事件
  1873. if (hoverTargetAreaDisplay) bindHoverEvents(suffix, type);
  1874. }
  1875.  
  1876. // 分段翻译
  1877. if (enableSegmentedTranslation) {
  1878. var pElements = block.find("p, li");
  1879. for (let i = 0; i < pElements.length; i++) {
  1880. target = $(pElements[i]).eq(0).clone();
  1881. if (type === "child_level") $(target).children(':first').remove();
  1882. element_node = pElements[i];
  1883. if (type === "child_level") {
  1884. $(pElements[i]).append("<div></div>");
  1885. element_node = $(pElements[i]).find("div:last-child").get(0);
  1886. }
  1887. result = await blockProcessing(target, element_node, $(".translateButton" + suffix));
  1888. if (result.status) errerNum += 1;
  1889. $(target).remove();
  1890. if (translation == "deepl") await new Promise(resolve => setTimeout(resolve, 2000));
  1891. }
  1892. } else {
  1893. target = block.eq(0).clone();
  1894. if (type === "child_level") $(target).children(':first').remove();
  1895. element_node = $(block).get(0);
  1896. if (type === "child_level") {
  1897. $(parent).append("<div></div>");
  1898. element_node = $(parent).find("div:last-child").get(0);
  1899. }
  1900. //是否跳过折叠块
  1901. if ($(target).find('.spoiler').length > 0) {
  1902. const shouldSkip = await skiFoldingBlocks();
  1903. if (shouldSkip) {
  1904. $(target).find('.spoiler').remove();
  1905. } else {
  1906. $(target).find('.html2md-panel').remove();
  1907. }
  1908. }
  1909. result = await blockProcessing(target, element_node, $(".translateButton" + suffix));
  1910. if (result.status) errerNum += 1;
  1911. $(target).remove();
  1912. }
  1913. if (!errerNum) {
  1914. $(this).addClass("translated")
  1915. .text("已翻译")
  1916. .css("cursor", "pointer")
  1917. .removeClass("error")
  1918. .prop("disabled", false);
  1919. } else {
  1920. $(this).prop("disabled", false);
  1921. }
  1922.  
  1923. // 重新翻译
  1924. let currentText, is_error;
  1925. $(document).on("mouseover", ".translateButton" + suffix, function () {
  1926. currentText = $(this).text();
  1927. $(this).text("重新翻译");
  1928. if ($(this).hasClass("error")) {
  1929. is_error = true;
  1930. $(this).removeClass("error");
  1931. }
  1932. });
  1933.  
  1934. $(document).on("mouseout", ".translateButton" + suffix, function () {
  1935. $(this).text(currentText);
  1936. if (is_error) $(this).addClass("error");
  1937. });
  1938. }));
  1939.  
  1940. // 目标区域指示
  1941. function bindHoverEvents(suffix, type) {
  1942. var previousCSS;
  1943.  
  1944. $(document).on("mouseover", ".translateButton" + suffix, function () {
  1945. var target;
  1946.  
  1947. if (type === "this_level") {
  1948. target = $(".translateButton" + suffix).parent().next().get(0);
  1949. } else if (type === "child_level") {
  1950. target = $(".translateButton" + suffix).parent().parent().get(0);
  1951. }
  1952.  
  1953. $(target).append('<div class="overlay">目标翻译区域</div>');
  1954. previousCSS = {
  1955. "position": $(target).css("position"),
  1956. "display": $(target).css("display")
  1957. };
  1958. $(target).css({
  1959. "position": "relative",
  1960. "display": "block"
  1961. });
  1962. $(".translateButton" + suffix).parent().css({
  1963. "position": "relative",
  1964. "z-index": "99999"
  1965. });
  1966. });
  1967.  
  1968. $(document).on("mouseout", ".translateButton" + suffix, function () {
  1969. var target;
  1970.  
  1971. if (type === "this_level") {
  1972. target = $(".translateButton" + suffix).parent().next().get(0);
  1973. } else if (type === "child_level") {
  1974. target = $(".translateButton" + suffix).parent().parent().get(0);
  1975. }
  1976.  
  1977. $(target).find('.overlay').remove();
  1978. if (previousCSS) {
  1979. $(target).css(previousCSS);
  1980. }
  1981. $(".translateButton" + suffix).parent().css({
  1982. "position": "static"
  1983. });
  1984. });
  1985. }
  1986.  
  1987. if (hoverTargetAreaDisplay) bindHoverEvents(suffix, type);
  1988. }
  1989.  
  1990. // 块处理
  1991. async function blockProcessing(target, element_node, button) {
  1992. if (!target.markdown) {
  1993. target.markdown = turndownService.turndown($(target).html());
  1994. }
  1995. const textarea = document.createElement('textarea');
  1996. textarea.value = target.markdown;
  1997. var result = await translateProblemStatement(textarea.value, element_node, $(button));
  1998. //
  1999. if (result.status == 1) {
  2000. $(button).addClass("error")
  2001. .text("翻译中止")
  2002. .css("cursor", "pointer")
  2003. .prop("disabled", false);
  2004. $(result.translateDiv).remove();
  2005. $(target).remove();
  2006. } else if (result.status == 2) {
  2007. result.translateDiv.classList.add("error_translate");
  2008. $(button).addClass("error")
  2009. .text("翻译出错")
  2010. .css("cursor", "pointer")
  2011. .prop("disabled", false);
  2012. $(target).remove();
  2013. }
  2014. return result;
  2015. }
  2016.  
  2017. function addConversionButton() {
  2018. // 添加按钮到题目部分
  2019. $('.subject-question').each(function () {
  2020. let id = "_" + getRandomNumber(8);
  2021. addButtonPanel(this, id, "this_level");
  2022. addButtonWithHTML2MD(this, id, "this_level");
  2023. addButtonWithCopy(this, id, "this_level");
  2024. addButtonWithTranslation(this, id, "this_level");
  2025. });
  2026.  
  2027. // 添加按钮到question-oi-bd部分
  2028. $('.question-oi-bd').each(function () {
  2029. let id = "_question-oi-bd_" + getRandomNumber(8);
  2030. addButtonPanel(this, id, "this_level");
  2031. addButtonWithHTML2MD(this, id, "this_level");
  2032. addButtonWithCopy(this, id, "this_level");
  2033. addButtonWithTranslation(this, id, "this_level");
  2034. });
  2035.  
  2036. // 添加按钮到pre部分
  2037. var selectorList = ['.question-oi-bd', '.CodeMirror'];//排除有这些祖宗节点的pre
  2038. $('pre').each(function () {
  2039. for (var i = 0; i < selectorList.length; i++) {
  2040. if ($(this).closest(selectorList[i]).length > 0) {
  2041. return true;
  2042. }
  2043. }
  2044. let id = "_pre_" + getRandomNumber(8);
  2045. addButtonPanel(this, id, "this_level");
  2046. addButtonWithHTML2MD(this, id, "this_level");
  2047. addButtonWithCopy(this, id, "this_level");
  2048. addButtonWithTranslation(this, id, "this_level");
  2049. });
  2050.  
  2051. // 添加按钮到题解部分
  2052. $('div.nc-post-content').each(function () {
  2053. let id = "_nc-post-content_" + getRandomNumber(8);
  2054. addButtonPanel(this, id, "this_level");
  2055. addButtonWithHTML2MD(this, id, "this_level");
  2056. addButtonWithCopy(this, id, "this_level");
  2057. addButtonWithTranslation(this, id, "this_level");
  2058. });
  2059. // 监听展开按钮
  2060. $(document).on('click', '.more-unfold', function (event) {
  2061. var interval = setInterval(function () {
  2062. if ($("div.nc-post-content:not(div.html2md-panel + div.nc-post-content)").length > 0) {
  2063. $("div.nc-post-content:not(div.html2md-panel + div.nc-post-content)").each(function () {
  2064. let id = "_nc-post-content_" + getRandomNumber(8);
  2065. addButtonPanel(this, id, "this_level");
  2066. addButtonWithHTML2MD(this, id, "this_level");
  2067. addButtonWithCopy(this, id, "this_level");
  2068. addButtonWithTranslation(this, id, "this_level");
  2069. });
  2070. clearInterval(interval);
  2071. }
  2072. }, 1000);
  2073. });
  2074. };
  2075.  
  2076. $(document).ready(function () {
  2077. var tip_SegmentedTranslation = $("<div></div>")
  2078. .addClass("alert alert-danger")
  2079. .html(`
  2080. Nowcoder Better! —— 注意!分段翻译已开启,这会造成负面效果,
  2081. <p>除非你现在需要翻译超长篇的博客或者题目,否则请前往设置关闭分段翻译</p>
  2082. `)
  2083. .css({
  2084. margin: "1em",
  2085. "text-align": "center",
  2086. "font-weight": "600",
  2087. position: "relative",
  2088. });
  2089.  
  2090. if (enableSegmentedTranslation)
  2091. $(".content-board").before(tip_SegmentedTranslation); //显示分段翻译警告
  2092.  
  2093. addConversionButton();
  2094. });
  2095.  
  2096. // 字数超限确认
  2097. function showWordsExceededDialog(button) {
  2098. return new Promise(resolve => {
  2099. const styleElement = GM_addStyle(darkenPageStyle);
  2100. $(button).removeClass("translated");
  2101. $(button).text("字数超限");
  2102. $(button).css("cursor", "not-allowed");
  2103. $(button).prop("disabled", true);
  2104. let htmlString = `
  2105. <div class="wordsExceeded">
  2106. <h3>字数超限!</h3>
  2107. <p>注意,即将翻译的内容字数超过了4950个字符,您可能选择了错误的翻译按钮</p>
  2108. <div style="display:flex; padding:5px 0px; align-items: center;">
  2109. `+ helpCircleHTML + `
  2110. <p>
  2111. 由于实现方式,区域中会出现多个翻译按钮,请点击更小的子区域中的翻译按钮,
  2112. <br>或者在设置面板中开启 分段翻译 后重试。
  2113. </p>
  2114. </div>
  2115. <p>对于免费的接口,大量请求可能导致你的IP被暂时禁止访问,对于GPT,会消耗大量的token</p>
  2116. <p>您确定要继续翻译吗?</p>
  2117. <div style="display:flex; padding-top:10px">
  2118. <button id="continueButton">继续</button><button id="cancelButton">取消</button>
  2119. </div>
  2120. </div>
  2121. `;
  2122. $('body').before(htmlString);
  2123. $("#continueButton").click(function () {
  2124. $(styleElement).remove();
  2125. $('.wordsExceeded').remove();
  2126. resolve(true);
  2127. });
  2128. $("#cancelButton").click(function () {
  2129. $(styleElement).remove();
  2130. $('.wordsExceeded').remove();
  2131. resolve(false);
  2132. });
  2133. });
  2134. }
  2135.  
  2136. // 跳过折叠块确认
  2137. function skiFoldingBlocks() {
  2138. return new Promise(resolve => {
  2139. const styleElement = GM_addStyle(darkenPageStyle);
  2140. let htmlString = `
  2141. <div class="wordsExceeded">
  2142. <h3>是否跳过折叠块?</h3>
  2143. <p></p>
  2144. <div style="display:grid; padding:5px 0px; align-items: center;">
  2145. <p>
  2146. 即将翻译的区域中包含折叠块,可能不需要翻译,现在您需要选择是否跳过这些折叠块,
  2147. </p>
  2148. <p>
  2149. 如果其中有您需要翻译的折叠块,可以稍后再单独点击这些折叠块内的翻译按钮进行翻译
  2150. </p>
  2151. </div>
  2152. <p>要跳过折叠块吗?(建议选择跳过)</p>
  2153. <div style="display:flex; padding-top:10px">
  2154. <button id="cancelButton">否</button><button id="skipButton">跳过</button>
  2155. </div>
  2156. </div>
  2157. `;
  2158. $('body').before(htmlString);
  2159. $("#skipButton").click(function () {
  2160. $(styleElement).remove();
  2161. $('.wordsExceeded').remove();
  2162. resolve(true);
  2163. });
  2164. $("#cancelButton").click(function () {
  2165. $(styleElement).remove();
  2166. $('.wordsExceeded').remove();
  2167. resolve(false);
  2168. });
  2169. });
  2170. }
  2171.  
  2172. // 翻译框/翻译处理器
  2173. var translatedText = "";
  2174. async function translateProblemStatement(text, element_node, button) {
  2175. let status = 0;
  2176. let id = getRandomNumber(8);
  2177. let matches = [];
  2178. let replacements = {};
  2179. // 创建元素并放在element_node的后面
  2180. const translateDiv = document.createElement('div');
  2181. translateDiv.setAttribute('id', id);
  2182. translateDiv.classList.add('translate-problem-statement');
  2183. const spanElement = document.createElement('span');
  2184. translateDiv.appendChild(spanElement);
  2185. element_node.insertAdjacentElement('afterend', translateDiv);
  2186. // 替换latex公式
  2187. if (translation != "openai") {
  2188. // 使用GPT翻译时不必替换latex公式
  2189. let i = 0;
  2190. // 块公式
  2191. matches = matches.concat(text.match(/\$\$([\s\S]*?)\$\$/g));
  2192. try {
  2193. for (i; i < matches.length; i++) {
  2194. let match = matches[i];
  2195. text = text.replace(match, `【${i + 1}】`);
  2196. replacements[`【${i + 1}】`] = match;
  2197. }
  2198. } catch (e) { }
  2199. // 行内公式
  2200. matches = matches.concat(text.match(/\$(.*?)\$/g));
  2201. try {
  2202. for (i; i < matches.length; i++) {
  2203. let match = matches[i];
  2204. text = text.replace(match, `【${i + 1}】`);
  2205. replacements[`【${i + 1}】`] = match;
  2206. }
  2207. } catch (e) { }
  2208. }
  2209. if (text.length > 4950) {
  2210. const shouldContinue = await showWordsExceededDialog(button);
  2211. if (!shouldContinue) {
  2212. status = 1;
  2213. return {
  2214. translateDiv: translateDiv,
  2215. status: status
  2216. };
  2217. }
  2218. }
  2219. // 翻译
  2220. if (translation == "deepl") {
  2221. translateDiv.innerHTML = "正在使用 deepl 翻译中……请稍等";
  2222. translatedText = await translate_deepl(text);
  2223. } else if (translation == "youdao") {
  2224. translateDiv.innerHTML = "正在使用 有道 翻译中……请稍等";
  2225. translatedText = await translate_youdao_mobile(text);
  2226. } else if (translation == "google") {
  2227. translateDiv.innerHTML = "正在使用 google 翻译中……请稍等";
  2228. translatedText = await translate_gg(text);
  2229. } else if (translation == "openai") {
  2230. try {
  2231. translateDiv.innerHTML = "正在使用 ChatGPT 翻译中……" +
  2232. "<br><br>应用的配置:" + opneaiConfig.configurations[opneaiConfig.choice].note +
  2233. "<br><br>使用 ChatGPT 翻译需要很长的时间,请耐心等待";
  2234. translatedText = await translate_openai(text);
  2235. } catch (error) {
  2236. status = 2;
  2237. translatedText = error;
  2238. }
  2239. }
  2240. if (/^翻译出错/.test(translatedText)) status = 2;
  2241. // 还原latex公式
  2242. translatedText = translatedText.replace(/】【/g, '】 【');
  2243. if (translation != "openai") {
  2244. try {
  2245. for (let i = 0; i < matches.length; i++) {
  2246. let match = matches[i];
  2247. let replacement = replacements[`【${i + 1}】`];
  2248. let regex;
  2249. regex = new RegExp(`【\\s*${i + 1}\\s*】`, 'g');
  2250. translatedText = translatedText.replace(regex, replacement);
  2251. regex = new RegExp(`\\[\\s*${i + 1}\\s*\\]`, 'g');
  2252. translatedText = translatedText.replace(regex, replacement);
  2253. regex = new RegExp(`【\\s*${i + 1}(?![】\\d])`, 'g');
  2254. translatedText = translatedText.replace(regex, replacement);
  2255. regex = new RegExp(`(?<![【\\d])${i + 1}\\s*】`, 'g');
  2256. translatedText = translatedText.replace(regex, " " + replacement);
  2257. }
  2258. } catch (e) { }
  2259. }
  2260.  
  2261. // 创建一个隐藏的元素来保存 translatedText 的值
  2262. var textElement = document.createElement("div");
  2263. textElement.style.display = "none";
  2264. textElement.textContent = translatedText;
  2265. translateDiv.parentNode.insertBefore(textElement, translateDiv);
  2266.  
  2267. // 翻译复制按钮
  2268. var copyButton = document.createElement("button");
  2269. copyButton.textContent = "Copy";
  2270. var wrapperDiv = document.createElement("div");
  2271. $(wrapperDiv).css({
  2272. display: "flex",
  2273. justifyContent: "flex-end"
  2274. });
  2275. $(wrapperDiv).append(copyButton);
  2276. $(copyButton).addClass("html2mdButton html2md-cb");
  2277.  
  2278. copyButton.addEventListener("click", function () {
  2279. var translatedText = textElement.textContent;
  2280. GM_setClipboard(translatedText);
  2281. $(this).addClass("copied").text("Copied");
  2282. // 更新复制按钮文本
  2283. setTimeout(() => {
  2284. $(this).removeClass("copied");
  2285. $(this).text("Copy");
  2286. }, 2000);
  2287. });
  2288. translateDiv.parentNode.insertBefore(wrapperDiv, translateDiv);
  2289.  
  2290. // 转义LaTex中的特殊符号
  2291. const escapeRules = [
  2292. { pattern: /(?<!\\)>(?!\s)/g, replacement: " &gt; " }, // >符号
  2293. { pattern: /(?<!\\)</g, replacement: " &lt; " }, // <符号
  2294. { pattern: /(?<!\\)\*/g, replacement: " &#42; " }, // *符号
  2295. { pattern: /(?<!\\)&(?=\s)/g, replacement: "\\&" }, // &符号
  2296. { pattern: /\\&/g, replacement: "\\\\&" }, // &符号
  2297. ];
  2298.  
  2299. let latexMatches = [...translatedText.matchAll(/\$\$([\s\S]*?)\$\$|\$(.*?)\$/g)];
  2300.  
  2301. for (const match of latexMatches) {
  2302. const matchedText = match[0];
  2303.  
  2304. for (const rule of escapeRules) {
  2305. const escapedText = matchedText.replaceAll(rule.pattern, rule.replacement);
  2306. translatedText = translatedText.replace(matchedText, escapedText);
  2307. }
  2308. }
  2309.  
  2310. // 渲染MarkDown
  2311. var md = window.markdownit();
  2312. var html = md.render(translatedText);
  2313. translateDiv.innerHTML = html;
  2314. // 渲染Latex
  2315. if (typeof renderMathInElement === 'function') {
  2316. renderMathInElement(translateDiv, {
  2317. delimiters: [{
  2318. left: "$$",
  2319. right: "$$",
  2320. display: true
  2321. }, {
  2322. left: "$",
  2323. right: "$",
  2324. display: false
  2325. }]
  2326. });
  2327. }
  2328. return {
  2329. translateDiv: translateDiv,
  2330. status: status,
  2331. copyDiv: textElement,
  2332. copyButton: copyButton
  2333. };
  2334.  
  2335. }
  2336.  
  2337. // ChatGPT
  2338. async function translate_openai(raw) {
  2339. var openai_retext = "";
  2340. var data = {
  2341. model: (openai_model !== null && openai_model !== "") ? openai_model : 'gpt-3.5-turbo',
  2342. messages: [{
  2343. role: "user",
  2344. content: "请将下面的文本翻译为中文,这是一道编程竞赛题描述的一部分,注意术语的翻译,注意保持其中的latex公式不翻译,你只需要回复翻译后的内容即可,不要回复任何其他内容:\n\n" + raw
  2345. }],
  2346. temperature: 0.7,
  2347. ...Object.assign({}, ...openai_data)
  2348. };
  2349. return new Promise(function (resolve, reject) {
  2350. GM_xmlhttpRequest({
  2351. method: 'POST',
  2352. url: (openai_proxy !== null && openai_proxy !== "") ? openai_proxy : 'https://api.openai.com/v1/chat/completions',
  2353.  
  2354. data: JSON.stringify(data),
  2355. headers: {
  2356. 'Content-Type': 'application/json',
  2357. 'Authorization': 'Bearer ' + openai_key,
  2358. ...Object.assign({}, ...openai_header)
  2359. },
  2360. responseType: 'json',
  2361. onload: function (response) {
  2362. if (!response.response) {
  2363. reject("发生了未知的错误,如果你启用了代理API,请确认是否填写正确,并确保代理能够正常工作。\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈 请注意打码响应报文的敏感部分\n\n响应报文:" + JSON.stringify(response));
  2364. }
  2365. else if (!response.response.choices || response.response.choices.length < 1 || !response.response.choices[0].message) {
  2366. resolve("翻译出错,请重试\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈\n\n报错信息:" + JSON.stringify(response.response, null, ''));
  2367. } else {
  2368. openai_retext = response.response.choices[0].message.content;
  2369. resolve(openai_retext);
  2370. }
  2371. },
  2372. onerror: function (response) {
  2373. reject("发生了未知的错误,请确认你是否能正常访问OpenAi的接口,如果使用代理API,请检查是否正常工作\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈 请注意打码响应报文的敏感部分\n\n响应报文:" + JSON.stringify(response));
  2374. },
  2375. });
  2376.  
  2377. });
  2378. }
  2379.  
  2380. //--谷歌翻译--start
  2381. async function translate_gg(raw) {
  2382. return new Promise((resolve, reject) => {
  2383. const url = 'https://translate.google.com/m';
  2384. const params = `tl=zh-CN&q=${encodeURIComponent(raw)}`;
  2385.  
  2386. GM_xmlhttpRequest({
  2387. method: 'GET',
  2388. url: `${url}?${params}`,
  2389. onload: function (response) {
  2390. const html = response.responseText;
  2391. const translatedText = $(html).find('.result-container').text();
  2392. resolve(translatedText);
  2393. },
  2394. onerror: function (error) {
  2395. console.error('Error:', error);
  2396. reject(error);
  2397. }
  2398. });
  2399. });
  2400. }
  2401. //--谷歌翻译--end
  2402.  
  2403. //--有道翻译m--start
  2404. async function translate_youdao_mobile(raw) {
  2405. const options = {
  2406. method: "POST",
  2407. url: 'http://m.youdao.com/translate',
  2408. data: "inputtext=" + encodeURIComponent(raw) + "&type=AUTO",
  2409. anonymous: true,
  2410. headers: {
  2411. "Content-Type": "application/x-www-form-urlencoded",
  2412. 'Host': 'm.youdao.com',
  2413. 'Origin': 'http://m.youdao.com',
  2414. 'Referer': 'http://m.youdao.com/translate',
  2415. }
  2416. }
  2417. return await BaseTranslate('有道翻译mobile', raw, options, res => /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
  2418. }
  2419. //--有道翻译m--end
  2420.  
  2421. //--Deepl翻译--start
  2422. function getTimeStamp(iCount) {
  2423. const ts = Date.now();
  2424. if (iCount !== 0) {
  2425. iCount = iCount + 1;
  2426. return ts - (ts % iCount) + iCount;
  2427. } else {
  2428. return ts;
  2429. }
  2430. }
  2431.  
  2432. async function translate_deepl(raw) {
  2433. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  2434. const data = {
  2435. jsonrpc: '2.0',
  2436. method: 'LMT_handle_texts',
  2437. id,
  2438. params: {
  2439. splitting: 'newlines',
  2440. lang: {
  2441. source_lang_user_selected: 'auto',
  2442. target_lang: 'ZH',
  2443. },
  2444. texts: [{
  2445. text: raw,
  2446. requestAlternatives: 3
  2447. }],
  2448. timestamp: getTimeStamp(raw.split('i').length - 1)
  2449. }
  2450. }
  2451. let postData = JSON.stringify(data);
  2452. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  2453. postData = postData.replace('"method":"', '"method" : "');
  2454. } else {
  2455. postData = postData.replace('"method":"', '"method": "');
  2456. }
  2457. const options = {
  2458. method: 'POST',
  2459. url: 'https://www2.deepl.com/jsonrpc',
  2460. data: postData,
  2461. headers: {
  2462. 'Content-Type': 'application/json',
  2463. 'Host': 'www2.deepl.com',
  2464. 'Origin': 'https://www.deepl.com',
  2465. 'Referer': 'https://www.deepl.com/',
  2466. },
  2467. anonymous: true,
  2468. nocache: true,
  2469. }
  2470. return await BaseTranslate('Deepl翻译', raw, options, res => JSON.parse(res).result.texts[0].text)
  2471. }
  2472.  
  2473. //--Deepl翻译--end
  2474.  
  2475. //--异步请求包装工具--start
  2476. async function PromiseRetryWrap(task, options, ...values) {
  2477. const { RetryTimes, ErrProcesser } = options || {};
  2478. let retryTimes = RetryTimes || 5;
  2479. const usedErrProcesser = ErrProcesser || (err => { throw err });
  2480. if (!task) return;
  2481. while (true) {
  2482. try {
  2483. return await task(...values);
  2484. } catch (err) {
  2485. if (!--retryTimes) {
  2486. console.log(err);
  2487. return usedErrProcesser(err);
  2488. }
  2489. }
  2490. }
  2491. }
  2492.  
  2493. async function BaseTranslate(name, raw, options, processer) {
  2494. let errtext;
  2495. const toDo = async () => {
  2496. var tmp;
  2497. try {
  2498. const data = await Request(options);
  2499. tmp = data.responseText;
  2500. const result = await processer(tmp);
  2501. if (result) sessionStorage.setItem(name + '-' + raw, result);
  2502. return result
  2503. } catch (err) {
  2504. errtext = tmp;
  2505. throw {
  2506. responseText: tmp,
  2507. err: err
  2508. }
  2509. }
  2510. }
  2511. return await PromiseRetryWrap(toDo, { RetryTimes: 3, ErrProcesser: () => "翻译出错,请重试或更换翻译接口\n\n如果无法解决,请前往 https://greasyfork.org/zh-CN/scripts/471106/feedback 反馈 请注意打码报错信息的敏感部分\n\n报错信息:" + errtext })
  2512. }
  2513.  
  2514.  
  2515. function Request(options) {
  2516. return new Promise((reslove, reject) => GM_xmlhttpRequest({ ...options, onload: reslove, onerror: reject }))
  2517. }
  2518.  
  2519. //--异步请求包装工具--end
  2520.  
  2521.  
  2522. // 配置自动迁移代码(将在10个小版本后移除-1.20)
  2523. if (GM_getValue("openai_key") || GM_getValue("api2d_key")) {
  2524. const newConfig = { "choice": -1, "configurations": [] };
  2525. if (GM_getValue("openai_key")) {
  2526. let config1 = {
  2527. "note": "我的配置1",
  2528. "model": GM_getValue("openai_model"),
  2529. "key": GM_getValue("openai_key"),
  2530. "proxy": GM_getValue("openai_proxy"),
  2531. "_header": "",
  2532. "_data": ""
  2533. }
  2534. if (GM_getValue("translation") === "openai") newConfig.choice = 0;
  2535. newConfig.configurations.push(config1);
  2536. }
  2537. if (GM_getValue("api2d_key")) {
  2538. let config2 = {
  2539. "note": "api2d",
  2540. "model": GM_getValue("api2d_model"),
  2541. "key": GM_getValue("api2d_key"),
  2542. "proxy": GM_getValue("api2d_request_entry") + '/v1/chat/completions',
  2543. "_header": GM_getValue("x_api2d_no_cache") ? "" : " x-api2d-no-cache : 1",
  2544. "_data": ""
  2545. }
  2546. if (GM_getValue("translation") === "api2d") {
  2547. if (GM_getValue("openai_key")) newConfig.choice = 1;
  2548. else newConfig.choice = 0;
  2549. }
  2550. newConfig.configurations.push(config2);
  2551. }
  2552. GM_setValue("chatgpt-config", newConfig);
  2553. const keysToDelete = ["openai_key", "openai_model", "openai_proxy", "api2d_key", "api2d_model", "api2d_request_entry", "x_api2d_no_cache", "showOpneAiAdvanced"];
  2554. keysToDelete.forEach(key => {
  2555. if (GM_getValue(key) != undefined) GM_deleteValue(key);
  2556. });
  2557. if (GM_getValue("translation") === "api2d") GM_setValue("translation", "openai");
  2558. location.reload();
  2559. }