StegLLM

此脚本已不再维护,最新项目详见https://github.com/Rin313/StegLLM。This script is no longer maintenance, please refer to the https://github.com/Rin313/StegLLM for the latest project

  1. // ==UserScript==
  2. // @name StegLLM
  3. // @namespace https://github.com/Rin313
  4. // @version 1.03
  5. // @description 此脚本已不再维护,最新项目详见https://github.com/Rin313/StegLLM。This script is no longer maintenance, please refer to the https://github.com/Rin313/StegLLM for the latest project
  6. // @author Rin
  7. // @match *://*/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_registerMenuCommand
  11. // @license MIT
  12. // @require https://cdn.jsdelivr.net/npm/xxhash-wasm@1.1.0/umd/xxhash-wasm.min.js
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16. const hostname=window.location.hostname;
  17. const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  18. window.onerror = function(message, source, lineno, colno, error) {
  19. if(!source)return;
  20. if(source.includes("StegLLM"))
  21. alert(`Error:${error.message}`);
  22. }
  23. const createElement = (tag, props = {}, styles = {}) => {
  24. const el = Object.assign(document.createElement(tag), props);//创建元素
  25. Object.assign(el.style, styles);//配置styles
  26. return el;
  27. };
  28. let settings = {
  29. prompt: GM_getValue("prompt", '续写这段散文:'),//如果不存在则使用默认值
  30. };
  31. GM_registerMenuCommand('prompt setting', function() {
  32. let customPrompt = prompt("", settings.prompt);
  33. if (customPrompt) {
  34. GM_setValue("prompt", customPrompt);
  35. settings.prompt=customPrompt;
  36. }
  37. });
  38. const gbkDecoder = new TextDecoder('gb18030');//能解码gbk不支持的符号,比如欧元、表意文字
  39. const utf8Encoder= new TextEncoder();
  40. const ranges = [
  41. [0xA1, 0xA9, 0xA1, 0xFE],
  42. [0xB0, 0xF7, 0xA1, 0xFE],
  43. [0x81, 0xA0, 0x40, 0xFE],//从这里开始的三个扩展区,第二个字节要排除0x7F
  44. [0xAA, 0xFE, 0x40, 0xA0],
  45. [0xA8, 0xA9, 0x40, 0xA0],
  46. ];
  47. let codes,table;
  48. const punctuations=["?","?","!","!","。",")",")","……"];//,"\n"
  49. const logitBias=[[" ",false],[" ",false],[" ",false],["\n\n",false],[" \n",false],[" \n",false],["�",false],[" �",false],[".",false],["【",false],["】",false],["〈",false],["〉",false]]
  50. const intercept=2;
  51. const tokens=1;// const tokens=Math.ceil(intercept/0.75);
  52. const probs=10;
  53. function shuffle(array) {
  54. for (let i = array.length - 1; i > 0; i--) {
  55. const j = Math.floor(Math.random() * (i + 1)); // 生成 0 到 i 之间的随机整数
  56. [array[i], array[j]] = [array[j], array[i]]; // 交换元素
  57. }
  58. return array;
  59. }
  60. function encodeToGBK(str) {
  61. if(!codes){
  62. codes=new Uint16Array(22046);//先把全部gbk字符都保存到一个16位整型数组里
  63. let i = 0,t;
  64. for (const [b1Begin, b1End, b2Begin, b2End] of ranges) {
  65. for (let b2 = b2Begin; b2 <= b2End; b2++) {
  66. if (b2 !== 0x7F) {//反过来遍历,减少判断0x7F的次数
  67. t = b2 << 8; //不能用16位的codes[i]
  68. for (let b1 = b1Begin; b1 <= b1End; b1++)
  69. codes[i++] = t | b1;
  70. }
  71. }
  72. }
  73. }
  74. if(!table){
  75. table = new Uint16Array(65509);//gbk包含¤164,将164左移到0也才省一点点空间
  76. const str = gbkDecoder.decode(codes);//解码为包含全部gbk字符的字符串
  77. for (let i = 0; i < str.length; i++){
  78. table[str.charCodeAt(i)] = codes[i];//unicode到gbk的映射
  79. }
  80. }
  81. const buf = new Uint8Array(str.length * 2);
  82. let n = 0;
  83. for (let i = 0; i < str.length; i++) {
  84. const code = str.charCodeAt(i);
  85. if (code < 128)
  86. buf[n++] = code;
  87. else{
  88. const gbk = table[code];
  89. if (gbk === 0)
  90. throw new Error("文本中存在不支持的符号");//有些编码器会用问号替换来避免报错,但这实际已经发生信息丢失了,不能容忍
  91. else {
  92. buf[n++] = gbk;
  93. buf[n++] = gbk >> 8;
  94. }
  95. }
  96. }
  97. return buf.subarray(0, n);
  98. }
  99. async function readStream(stream) {
  100. const reader = stream.getReader();
  101. const chunks = [];
  102. while (true) {
  103. const { done, value } = await reader.read();
  104. if (done) {
  105. break;
  106. }
  107. chunks.push(value);
  108. }
  109. const compressedData = new Uint8Array(chunks.reduce((acc, val) => acc + val.length, 0));
  110. let offset = 0;
  111. for (const chunk of chunks) {
  112. compressedData.set(chunk, offset);
  113. offset += chunk.length;
  114. }
  115. return compressedData;
  116. }
  117. async function decompress(stream) {
  118. const ds = new DecompressionStream("deflate-raw");
  119. const decompressedStream = stream.pipeThrough(ds);
  120. return readStream(decompressedStream).then(data => {
  121. return data; // 或者在这里进行一些额外的处理
  122. });
  123. }
  124. // async function passwordToAesCtrKey(password) {
  125. // const passwordBuffer = utf8Encoder.encode(password);
  126. // // 使用 PBKDF2 算法从密码派生密钥
  127. // const keyMaterial = await crypto.subtle.importKey(
  128. // "raw",
  129. // passwordBuffer,
  130. // { name: "PBKDF2" },
  131. // false,
  132. // ["deriveKey"]
  133. // );
  134. // // 使用 PBKDF2 派生 AES-CTR 密钥
  135. // const aesCtrKey = await crypto.subtle.deriveKey(
  136. // {
  137. // name: "PBKDF2",
  138. // salt: new Uint8Array(0), // 空盐值
  139. // iterations: 1000, // 较低的迭代次数
  140. // hash: "SHA-256",
  141. // },
  142. // keyMaterial,
  143. // { name: "AES-CTR", length: 256 }, // 指定 AES-CTR 算法和密钥长度 (256位)
  144. // true, // 密钥可导出
  145. // ["encrypt", "decrypt"] // 密钥用途
  146. // );
  147. // return aesCtrKey;
  148. // }
  149. async function encryptAesCtr(data, str) {
  150. const buffer=await crypto.subtle.digest('SHA-256', utf8Encoder.encode(str));
  151. const iv=new Uint8Array(buffer).subarray(0, 16);
  152. const key= await crypto.subtle.importKey(
  153. "raw",
  154. buffer,
  155. { name: "AES-CTR", length: 256},
  156. false,
  157. ["encrypt", "decrypt"]
  158. );
  159. const encrypted = await crypto.subtle.encrypt(
  160. {
  161. name: "AES-CTR",
  162. counter: iv,
  163. length: 64, // 计数器块大小(以位为单位),通常为 64 或 128
  164. },
  165. key,data
  166. );
  167. return new Uint8Array(encrypted);
  168. }
  169. async function decryptAesCtr(data, str) {
  170. const buffer=await crypto.subtle.digest('SHA-256', utf8Encoder.encode(str));
  171. const iv=new Uint8Array(buffer).subarray(0, 16);
  172. const key= await crypto.subtle.importKey(
  173. "raw",
  174. buffer,
  175. { name: "AES-CTR", length: 256},
  176. false,
  177. ["encrypt", "decrypt"]
  178. );
  179. const decrypted = await crypto.subtle.decrypt(
  180. {
  181. name: "AES-CTR",
  182. counter: iv,
  183. length: 64,
  184. },
  185. key,
  186. data
  187. );
  188. return new Uint8Array(decrypted);
  189. }
  190. async function chat(str,complete=false) {
  191. const body={//有些参数不生效,响应格式也和llama.cpp的api略有不同//在api中设置system_prompt会导致性能严重下降
  192. // "stream": true,
  193. "n_predict": tokens,//生成的token数,-1-2048
  194. "temperature": 1.4,//影响文本的随机性,0-2//较高的温度会增加计算量,较低的温度会导致重复
  195. // "stop": punctuations,
  196. "repeat_last_n": 256,
  197. "repeat_penalty": 1.18,//重复惩罚,1.0为无惩罚
  198. // "top_p": 0.95,//默认0.95,增大后似乎能增加更多的选词可能性
  199. // "min_p": 0.05,
  200. // "tfs_z": 1,
  201. // "typical_p": 1,
  202. // "presence_penalty": 0,
  203. // "frequency_penalty": 0,
  204. // "mirostat": 0,//关闭mirostat
  205. // "mirostat_tau": 5,
  206. // "mirostat_eta": 0.1,
  207. // "grammar": "",
  208. // "min_keep": 0,
  209. // "image_data": [],
  210. "cache_prompt": true,//提示词复用
  211. "api_key": "",
  212. "slot_id": -1,
  213. "prompt": str,//支持输入多个prompt
  214. // "response_fields": ["content"],//不生效?
  215. "top_k": probs,//选词范围,默认40
  216. "n_probs": probs,//按概率排序的前10个选词,太大或太小都会降低隐写效果
  217. "logit_bias": logitBias//禁用一些不自然的字符,注意空白符有非常多种
  218. }
  219. if(complete){
  220. body["n_predict"]=9;
  221. body["stop"]=punctuations;//动态截断
  222. body["n_probs"]=0;
  223. body["top_k"]=40;
  224. }
  225. const response = await fetch('http://localhost:8080/completion', {
  226. method: 'POST',
  227. body: JSON.stringify(body)
  228. });
  229. if(!response.ok)
  230. throw new Error(`HTTP error! status: ${response.status}`);
  231. const json=(await response.json());
  232. if(complete)
  233. return json.content+json.stopping_word;
  234. const t=json.completion_probabilities[0];
  235. if(!t)return chat(str);
  236. else return shuffle(t.probs);
  237. }
  238. async function encrypt() {
  239. const plainText = (await createCustomPrompt("🔒"));
  240. if(plainText){
  241. const { h32 } = await xxhash();
  242. let bytes= encodeToGBK(plainText);
  243. console.log(bytes);
  244. const stream=new ReadableStream({
  245. start(controller) {
  246. controller.enqueue(bytes);
  247. controller.close();
  248. }
  249. });
  250. const compressedStream = stream.pipeThrough(new CompressionStream("deflate-raw"),);
  251. const result=await readStream(compressedStream);
  252. if(bytes.length>result.length)
  253. bytes=result;
  254. console.log(bytes);
  255. bytes=(await encryptAesCtr(bytes,hostname));
  256. console.log(bytes);
  257. let base2=[];
  258. for (let b of bytes) {
  259. for(let i=7;i>=0;i--){
  260. base2.push(b>>i & 0x01);
  261. }
  262. }
  263. console.log(base2);
  264. const counts=new Uint8Array(base2.length);
  265. let coverText='';
  266. for(let i=0;i<base2.length;i++){
  267. const list=(await chat(settings.prompt+coverText));
  268. let j=0;
  269. aaa:for(;j<list.length;j++){
  270. const t=list[j].tok_str;
  271. if(t.length===2&&!t.includes("\uFFFD")&&h32(t)%2===base2[i]){
  272. coverText+=t;
  273. break;
  274. }
  275. else if(t.length===1){
  276. const list2=(await chat(settings.prompt+coverText+t));
  277. for(let k=0;k<list2.length;k++){
  278. const t2=list2[k].tok_str;
  279. if(t2.length===1&&!t2.includes("\uFFFD")&&h32(t+t2)%2===base2[i]){
  280. coverText+=t+t2;
  281. break aaa;
  282. }
  283. }
  284. }
  285. }
  286. if(j===list.length){
  287. alert("选词失败,请重新再试");
  288. return;
  289. }
  290.  
  291. }
  292. console.log(coverText.length);
  293. if(!punctuations.includes(coverText[coverText.length-1])){
  294. for(;;){
  295. const t=(await chat(settings.prompt+coverText,true));
  296. if(punctuations.includes(t[t.length-1])){
  297. coverText+=t;
  298. break;
  299. }
  300. }
  301. }
  302. showCustomAlert(coverText);
  303. }
  304. }
  305. async function decrypt() {
  306. const userInput=(await createCustomPrompt("🗝️"));//粘贴到prompt会导致空白字符等丢失//粘贴到input会导致换行符丢失
  307. if(userInput){
  308. const { h32 } = await xxhash();
  309. let base2 = [];
  310. let t='';
  311. //console.log(userInput)
  312. console.log(userInput.length);
  313. for (let i = 0; i < userInput.length; i++) {
  314. t+=userInput[i];
  315. if(t.length===intercept){
  316. //console.log(t+" "+t.length)
  317. base2.push(h32(t)%2);
  318. t='';
  319. }
  320. }
  321. let bytes = new Uint8Array(base2.length/8);let k=0;
  322. console.log(base2);
  323. for(let i=0;i<base2.length;){
  324. bytes[k++]=base2[i]*128+base2[i+1]*64+base2[i+2]*32+base2[i+3]*16+base2[i+4]*8+base2[i+5]*4+base2[i+6]*2+base2[i+7];
  325. i+=8;
  326. }
  327. console.log(bytes)
  328. bytes=(await decryptAesCtr(bytes,hostname));
  329. console.log(bytes)
  330. const stream=new ReadableStream({
  331. start(controller) {
  332. controller.enqueue(bytes);
  333. controller.close();
  334. }
  335. });
  336. await decompress(stream)
  337. .then(data=>{bytes=data;})
  338. .catch(error=>{console.log(error)});
  339. console.log(bytes)
  340. alert(gbkDecoder.decode(bytes));
  341. }
  342. }
  343. function swapColors(){
  344. let t=sidebarButton1.style.backgroundColor;
  345. sidebarButton1.style.backgroundColor=sidebarButton2.style.backgroundColor;
  346. sidebarButton2.style.backgroundColor=t;
  347. }
  348. const buttonStyles1 = {
  349. position: 'fixed',
  350. right: '0', //固定右侧
  351. zIndex: '9999', // 确保不被覆盖
  352. cursor: 'pointer',//显示可点击光标
  353. backgroundColor:'#f56c73',
  354. border: 'none',
  355. top: '42%',
  356. height: '25px',
  357. width: '25px',
  358. overflow: 'hidden',
  359. };
  360. const buttonStyles2 = {
  361. position: 'fixed',
  362. right: '0', //固定右侧
  363. zIndex: '9999', // 确保不被覆盖
  364. cursor: 'pointer',//显示可点击光标
  365. backgroundColor:'#d87b83',
  366. border: 'none',
  367. top: '47%',
  368. height: '25px',
  369. width: '25px',
  370. overflow: 'hidden',
  371. };
  372. const sidebarButton1 = createElement('button', {}, buttonStyles1);
  373. const sidebarButton2 = createElement('button', {}, buttonStyles2);
  374. sidebarButton1.addEventListener('mouseenter', () => swapColors() );
  375. sidebarButton2.addEventListener('mouseenter', () => swapColors() );
  376. sidebarButton1.addEventListener('click', () => encrypt());
  377. sidebarButton2.addEventListener('click', () => decrypt());
  378. document.body.append(sidebarButton1, sidebarButton2);
  379. const showCustomAlert = (text) => {
  380. // 创建遮罩层
  381. const overlay = createElement('div', {}, {
  382. position: 'fixed',
  383. top: 0,
  384. left: 0,
  385. width: '100%',
  386. height: '100%',
  387. backgroundColor: 'rgba(0, 0, 0, 0.5)',
  388. display: 'flex',
  389. justifyContent: 'center',
  390. alignItems: 'center',
  391. zIndex: 9999,
  392. });
  393.  
  394. // 创建弹出框容器
  395. const alertBox = createElement('div', {}, {
  396. backgroundColor: '#fff',
  397. padding: '20px',
  398. borderRadius: '8px',
  399. boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
  400. textAlign: 'center',
  401. width: '300px',
  402. });
  403.  
  404. // 创建显示的文本
  405. const message = createElement('p', { textContent: text }, {
  406. margin: '0 0 20px',
  407. fontSize: '16px',
  408. color: '#333',
  409. wordBreak: 'break-word',
  410. });
  411.  
  412. // 创建按钮容器
  413. const buttonContainer = createElement('div', {}, {
  414. display: 'flex',
  415. justifyContent: 'space-between',
  416. marginTop: '20px',
  417. });
  418.  
  419. // 创建复制按钮
  420. const copyButton = createElement('button', { textContent: 'Copy' }, {
  421. padding: '10px 20px',
  422. backgroundColor: '#007bff',
  423. color: '#fff',
  424. border: 'none',
  425. borderRadius: '4px',
  426. cursor: 'pointer',
  427. fontSize: '14px',
  428. flex: '1',
  429. marginRight: '10px',
  430. });
  431.  
  432. // 按钮点击事件 - 复制文本
  433. copyButton.onclick = () => {
  434. navigator.clipboard.writeText(text).then(() => {
  435. alert('Copied to clipboard!');
  436. document.body.removeChild(overlay);
  437. });
  438. };
  439.  
  440. // 创建关闭按钮
  441. const closeButton = createElement('button', { textContent: 'Close' }, {
  442. padding: '10px 20px',
  443. backgroundColor: '#dc3545',
  444. color: '#fff',
  445. border: 'none',
  446. borderRadius: '4px',
  447. cursor: 'pointer',
  448. fontSize: '14px',
  449. flex: '1',
  450. });
  451.  
  452. // 关闭按钮点击事件
  453. closeButton.onclick = () => {
  454. document.body.removeChild(overlay);
  455. };
  456.  
  457. // 组装按钮容器
  458. buttonContainer.appendChild(copyButton);
  459. buttonContainer.appendChild(closeButton);
  460.  
  461. // 组装弹出框
  462. alertBox.appendChild(message);
  463. alertBox.appendChild(buttonContainer);
  464. overlay.appendChild(alertBox);
  465. document.body.appendChild(overlay);
  466. };
  467. const createCustomPrompt = (placeholder = "请输入内容...") => {
  468. return new Promise((resolve) => {
  469. // 创建一个覆盖整个页面的背景遮罩
  470. const overlay = createElement('div', {}, {
  471. position: 'fixed',
  472. top: '0',
  473. left: '0',
  474. width: '100vw',
  475. height: '100vh',
  476. backgroundColor: 'rgba(0, 0, 0, 0.5)',
  477. display: 'flex',
  478. justifyContent: 'center',
  479. alignItems: 'center',
  480. zIndex: '1000',
  481. });
  482.  
  483. // 创建一个容器来放置textarea和按钮
  484. const promptContainer = createElement('div', {}, {
  485. backgroundColor: '#fff',
  486. padding: '20px',
  487. borderRadius: '8px',
  488. boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
  489. display: 'flex',
  490. flexDirection: 'column',
  491. alignItems: 'center',
  492. minWidth: '300px',
  493. });
  494.  
  495. // 创建textarea
  496. const textarea = createElement(
  497. 'textarea',
  498. {
  499. placeholder: placeholder,
  500. },
  501. {
  502. width: '100%',
  503. height: '100px',
  504. marginBottom: '10px',
  505. padding: '10px',
  506. fontSize: '16px',
  507. borderRadius: '4px',
  508. border: '1px solid #ddd',
  509. outline: 'none',
  510. resize: 'none',
  511. }
  512. );
  513.  
  514. // 创建提交按钮
  515. const submitButton = createElement(
  516. 'button',
  517. {
  518. innerText: '提交',
  519. onclick: () => {
  520. const value = textarea.value;
  521. resolve(value); // 当点击提交时,resolve Promise 并返回值
  522. document.body.removeChild(overlay); // 移除遮罩层
  523. },
  524. },
  525. {
  526. padding: '10px 20px',
  527. fontSize: '16px',
  528. backgroundColor: '#007BFF',
  529. color: '#fff',
  530. border: 'none',
  531. borderRadius: '4px',
  532. cursor: 'pointer',
  533. }
  534. );
  535.  
  536. // 提交按钮 hover 样式
  537. submitButton.addEventListener('mouseover', () => {
  538. submitButton.style.backgroundColor = '#0056b3';
  539. });
  540. submitButton.addEventListener('mouseout', () => {
  541. submitButton.style.backgroundColor = '#007BFF';
  542. });
  543. overlay.addEventListener('click', (event) => {
  544. if (event.target === overlay) {
  545. resolve(null); // 用户取消操作时,返回 null
  546. document.body.removeChild(overlay); // 移除遮罩层
  547. }
  548. });
  549. // 把textarea和按钮添加到容器中
  550. promptContainer.appendChild(textarea);
  551. promptContainer.appendChild(submitButton);
  552.  
  553. // 把容器添加到遮罩层中
  554. overlay.appendChild(promptContainer);
  555.  
  556. // 把遮罩层添加到body中
  557. document.body.appendChild(overlay);
  558. });
  559. };
  560. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址