22k4已购游戏搜索

已购买游戏详情页添加搜索功能

  1. // ==UserScript==
  2. // @name 22k4已购游戏搜索
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-10-07
  5. // @description 已购买游戏详情页添加搜索功能
  6. // @author 口吃者
  7. // @match https://22k4.com/member/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=22k4.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. /*
  13. 1. 多次快速点击分页按钮造成卡死
  14. 2. 可以增加保存配置功能,在没有购买新游戏时,可以保存配置,下次直接加载
  15. */
  16. class ProductCatalog {
  17. constructor() {
  18. this.pages = []; // 存储每页的商品列表
  19. this.productByName = new Map(); // 存储商品名称到商品对象的映射
  20. }
  21.  
  22. // 添加商品到指定页
  23. addProductToPage(pageIndex, product) {
  24. if (!this.pages[pageIndex]) {
  25. this.pages[pageIndex] = [];
  26. }
  27.  
  28. // 检查商品名称是否已存在
  29. const existingProduct = this.productByName.get(product.name.toLowerCase());
  30. if (existingProduct) {
  31. console.log(`Product with name "${product.name}" already exists.`);
  32. return; // 商品名称已存在,不添加
  33. }
  34.  
  35. // 添加商品到对应页,并更新映射
  36. this.pages[pageIndex].push(product);
  37. this.productByName.set(product.name.toLowerCase(), product);
  38. }
  39.  
  40. // 根据商品名称查询商品
  41. findProductByName(name) {
  42. const lowerCaseName = name.toLowerCase();
  43. return this.productByName.get(lowerCaseName);
  44. }
  45. // 模糊查询商品
  46. searchProductsByPartialName(query) {
  47. const results = [];
  48.  
  49. // 遍历所有商品
  50. this.productByName.forEach((product, name) => {
  51. if (name.includes(query.toLowerCase())) {
  52. results.push(product);
  53. }
  54. });
  55.  
  56. return results;
  57. }
  58.  
  59. // 获取所有商品
  60. getAllProducts() {
  61. return this.productByName.values();
  62. }
  63.  
  64. // 获取某一页的所有商品
  65. getProductsOnPage(pageIndex) {
  66. return this.pages[pageIndex] || [];
  67. }
  68. }
  69. const catalog = new ProductCatalog();
  70. var requestsCompleted = false;
  71. var pickUpGameHref = '#';
  72. var searchGameHref = '#';
  73. (function() {
  74. 'use strict';
  75. addPanel();
  76. // Your code here...
  77. })();
  78. window.onload = function() {
  79. // 示例:检查请求是否全部完成
  80. let btnSwitch = document.querySelector('#btnSwitch');
  81. checkTimer = setTimeout(() => {
  82. btnSwitch.textContent = "正在获取游戏中..."
  83. }, 1000); // 这只是一个示例,实际应用中应该根据实际场景决定何时检查
  84.  
  85. //获取总页数
  86. let pageCount = document.querySelector("#PageCount").value;
  87. let pageSize = document.querySelector("#PageSize").value;
  88. let totalPages = getTotalPages(pageCount, pageSize);
  89. console.log(totalPages);
  90. const urls = Array.from({ length: totalPages }, (_, i) =>
  91. `https://22k4.com/member/product.php?page=${i + 1}`
  92. );
  93.  
  94. // 创建一个 Promise 数组
  95. const fetchPromises = urls.map(url =>
  96. fetch(url, {
  97. method: 'GET',
  98. credentials: 'include', // 使得请求会发送 Cookie
  99. headers: {
  100. 'Content-Type': 'text/html;charset=utf-8',
  101. }
  102. }).then(response => {
  103. if (!response.ok) {
  104. throw new Error("HTTP error " + response.status);
  105. }
  106. return response.text(); // 读取为文本
  107. })
  108. );
  109.  
  110. // 使用 Promise.all 来等待所有请求完成
  111. Promise.all(fetchPromises)
  112. .then(htmlTexts => {
  113. // 处理每个 HTML 文档
  114. htmlTexts.forEach((htmlText, index) => {
  115. // 创建一个范围较小的虚拟文档环境
  116. const parser = new DOMParser();
  117. const doc = parser.parseFromString(htmlText, "text/html");
  118. getAllItemDetails(doc, index + 1);
  119. requestsCompleted = true;
  120. clearTimeout(checkTimer);
  121. btnSwitch.textContent = "面板"
  122. });
  123. })
  124. .catch(error => {
  125. console.error('There was a problem with one of the fetch operations:', error);
  126. });
  127.  
  128. //输入框
  129. document.getElementById('iptName').addEventListener('input', function(event) {
  130. let input = event.target.value;
  131. // 显示建议列表
  132. displaySuggestions(input, 1);
  133. })
  134. // 初始化分页图标
  135. updatePaginationIcons(1, 1); // 假设初始页码为1,总页数为1(待更新)
  136. var cssText = `
  137. #input_suggestion li {
  138. color: aliceblue;
  139. cursor: pointer;
  140. }
  141. #input_suggestion li:hover {
  142. color: cadetblue;
  143. background-color: #f0f0f0;
  144. }
  145. #pagination span {
  146. display: inline-block;
  147. width: 25px;
  148. height: 25px;
  149. line-height: 25px;
  150. text-align: center;
  151. border: 1px solid #ccc;
  152. cursor: pointer;
  153. background: azure;
  154. margin-inline-end: 5px;
  155. margin-inline-start: 5px;
  156. }
  157. `
  158. GMaddStyle(cssText);
  159.  
  160. }
  161. function addPanel(){
  162. function genDiv(cls, id){
  163. let d = document.createElement('div');
  164. d.style.cssText = 'vertical-align:middle;';
  165. if(cls){ d.className = cls };
  166. if(id){ d.id = id };
  167. return d;
  168. }
  169. //创建一个可以自由弹出的面板
  170. function genPanel(name, visiable){
  171. let panel = genDiv(name, name);
  172. panel.style.cssText = 'background: rgba(58, 58, 58, 0.8);position: fixed;top: 50%;right: 0px;';
  173. panel.style.cssText += 'text-align: center;transform: translate(0px, -50%);z-index: 100;';
  174. panel.style.cssText += 'border: 1px solid rgb(83, 83, 83);padding: 5px;border-radius: 10px 0 0 10px;';
  175. panel.style.cssText += 'transition:right 0.8s;right:-300px;'
  176. return panel;
  177. }
  178. function genButton(test, foo, id, fooParams = {}){
  179. let b = document.createElement('button');
  180. b.textContent = test;
  181. b.style.verticalAlign = 'inherit';
  182. // 使用箭头函数创建闭包来保存 fooParams 并传递给 foo
  183. b.addEventListener('click', () => {
  184. foo.call(b, ...Object.values(fooParams)); // 使用 call 方法确保 this 指向按钮对象
  185. });
  186. if(id){ b.id = id };
  187. return b;
  188. }
  189. function genInput(id, value, tips, number) {
  190. let i = document.createElement('input');
  191. i.id = id;
  192. i.style.cssText = 'border:none;border-radius:0;margin:0px 5px;width:60%;text-align:center;';
  193. i.style.cssText += 'color:#000;background:#fff;vertical-align:inherit;';
  194. if (value) { i.value = value; }
  195. if (tips) { i.placeholder = tips; }
  196. if (number) {
  197. i.type = 'number';
  198. i.step = 0.01;
  199. i.min = 0;
  200. }
  201. return i;
  202. }
  203. function genUl(items, id) {
  204. // 创建一个新的<ul>元素
  205. let ul = document.createElement('ul');
  206. // 如果提供了类名,添加到<ul>元素上
  207. if (id) {
  208. ul.id = id;
  209. }
  210. // // 遍历items数组,为每个项目创建一个<li>元素并添加到<ul>中
  211. // items.forEach(function(item) {
  212. // let li = document.createElement('li');
  213. // // 这里简单地将item的值设置为<li>的文本内容
  214. // // 根据实际需求,这里可以进一步定制化,比如添加事件监听器等
  215. // li.textContent = item;
  216. // // 将<li>添加到<ul>中
  217. // ul.appendChild(li);
  218. // });
  219. return ul;
  220. }
  221. function genSpan(id, textContentSpan) {
  222. let s = document.createElement('span');
  223. s.id = id;
  224. s.textContent = textContentSpan;
  225. return s;
  226. }
  227. //展开指定面板
  228. function showPanel(panelId){
  229. let panel = document.getElementById(panelId);
  230. if (panel.style.right == '-300px') {
  231. panel.style.right = '0';
  232. } else {
  233. panel.style.right = '-300px';
  234. }
  235. }
  236. let btnSwitch = genButton('面板', showPanel, 'btnSwitch', {param1: 'autoSell'});
  237. let panelFunc = genPanel('autoSell', false); //初始隐藏
  238. //加入位置
  239. let lBtnArea = document.querySelector('body > div.page > div.container.m_top_30 > div');
  240. lBtnArea.insertBefore(btnSwitch, lBtnArea.children[0]);
  241. lBtnArea.insertBefore(panelFunc, lBtnArea.children[0]);
  242. //自动出售(加入到面板中) 搜索
  243. let divName = genDiv();
  244. let divPagination = genDiv(null, 'pagination');
  245. let button01 = genButton('搜索', searchGame, 'searchGame');
  246. let button02 = genButton('提货', pickUpGame, 'pickUpGame');
  247. let iptName = genInput('iptName', '', ' 游戏名称', false);
  248. let inputSuggestion = genUl(['无'], 'input_suggestion');
  249. let prePageSpan = genSpan('prePageSpan', '<');
  250. let nextPageSpan = genSpan('nextPageSpan', '>');
  251.  
  252. divName.appendChild(button01);
  253. divName.appendChild(button02);
  254. divName.appendChild(iptName);
  255. divName.appendChild(inputSuggestion);
  256.  
  257. divPagination.append(prePageSpan);
  258. divPagination.append(nextPageSpan);
  259.  
  260. panelFunc.appendChild(divName);
  261. panelFunc.appendChild(divPagination);
  262. //todo 可优化成bilibili增强的样式表写法
  263. let h5Element = document.querySelector("body > div.page > div.container.m_top_30 > div > h5");
  264. btnSwitch.style.cssText += 'display: inline-block; margin-right: 10px;'
  265. btnSwitch.style.cssText += 'font-weight: bold;font-size: 16px;'
  266. h5Element.style.cssText += 'display: inline-block; margin-right: 10px;'
  267. }
  268. function searchGame(){
  269. // 使用 window.open 方法打开链接
  270. var url = this.getAttribute('data-url');// 链接动态变化,添加格外属性,每次点击重新获取
  271. window.open(url, '_blank'); // '_blank' 表示在一个新的浏览器标签页中打开
  272. }
  273. function pickUpGame(event){
  274. // 使用 window.open 方法打开链接
  275. var url = this.getAttribute('data-url');// 链接动态变化,添加格外属性,每次点击重新获取
  276. window.open(url, '_blank'); // '_blank' 表示在一个新的浏览器标签页中打开
  277. }
  278. function getTotalPages(input, eachPage) {
  279. // 将字符串转换为整数,如果是数字类型则直接使用
  280. let num = typeof input === 'string' ? parseInt(input, 10) : input;
  281. let eachPage01 = typeof eachPage === 'string' ? parseInt(eachPage, 10) : eachPage;
  282.  
  283. // 检查转换后的值是否为 NaN 或者小于等于 0
  284. if (isNaN(num) || num <= 0 || isNaN(eachPage01) || eachPage01 <= 0) {
  285. throw new Error('getTotalPages error:Invalid input: input must be a positive integer.');
  286. }
  287.  
  288. // 计算总页数
  289. // Math.ceil() 用于向上取整
  290. const totalPages = Math.ceil(num / eachPage01);
  291.  
  292. return totalPages;
  293. }
  294. function getAllItemDetails(pageDocument, page){
  295. let itemList = pageDocument.querySelectorAll('tbody > tr');
  296. itemList.forEach(item => {
  297. let itemId = item.id;
  298. let itemImg = item.querySelector('img').src;
  299. let itemName = item.querySelector('a').textContent;
  300. let itemPrice = item.querySelector('span').textContent;
  301. let itemOrderNo = item.querySelector('td:nth-child(3) > p:nth-child(1)').textContent;
  302. let itemOrderTime = item.querySelector('td:nth-child(3) > p:nth-child(2)').textContent;
  303. let itemPickup = item.querySelector('td:nth-child(4) > p > a').href;
  304. let itemSearch = item.querySelector('td:nth-child(2) > p:nth-child(1) > b > a').href;
  305. // 创建一个新的 product 对象
  306. const product = {
  307. id: itemId, // 假设这是一个生成唯一 ID 的方法
  308. name: itemName,
  309. imageUrl: itemImg,
  310. price: itemPrice,
  311. orderNo: itemOrderNo,
  312. orderTime: itemOrderTime,
  313. pickUp: itemPickup,
  314. searchGame: itemSearch
  315. };
  316. catalog.addProductToPage(page - 1, product);
  317. })
  318. }
  319. function displaySuggestions(input, page) {
  320. const suggestions = catalog.searchProductsByPartialName(input).map(product => product.name);
  321.  
  322. let suggestionsList = document.getElementById('input_suggestion');
  323. suggestionsList.innerHTML = ''; // 清空现有建议
  324.  
  325. // 根据输入过滤建议
  326. let filteredSuggestions = suggestions;
  327.  
  328. const itemsPerPage = 7; // 每页显示的建议数
  329. var totalPages = Math.ceil(filteredSuggestions.length / itemsPerPage);
  330. const start = (page - 1) * itemsPerPage;
  331. const end = Math.min(start + itemsPerPage, filteredSuggestions.length);
  332.  
  333. // 显示过滤后的建议
  334. if (filteredSuggestions.length > 0 && filteredSuggestions.length >= start) {
  335. suggestionsList.style.display = 'block';
  336. // 获取输入框元素
  337. const iptName = document.getElementById('iptName');
  338. // 显示当前页的建议
  339. for (let i = start; i < end; i++) {
  340. let item = document.createElement('li');
  341. item.textContent = filteredSuggestions[i];
  342.  
  343. if(item._clickHandler){
  344. item.removeEventListener('click', handler);
  345. delete item._clickHandler; // 清理保存的引用
  346. }
  347. // 创建一个通用的事件处理函数,并保存其引用
  348. const handler = () => handleSuggestionClick(item);
  349. item._clickHandler = handler; // 保存函数引用
  350. item.addEventListener('click', handler);
  351. suggestionsList.appendChild(item);
  352. }
  353. } else {
  354. suggestionsList.style.display = 'none'; // 如果没有建议则隐藏列表
  355. }
  356.  
  357. // 更新分页图标的状态
  358. updatePaginationIcons(page, totalPages);
  359. }
  360.  
  361. function updatePaginationIcons(currentPage, totalPages) {
  362. const prevPageIcon = document.getElementById('prePageSpan');
  363. const nextPageIcon = document.getElementById('nextPageSpan');
  364.  
  365. if(currentPage === -1 && totalPages === -1){
  366. prevPageIcon.style.display = 'none';
  367. nextPageIcon.style.display = 'none';
  368. return;
  369. }
  370.  
  371. prevPageIcon.style.display = currentPage === 1 ? 'none' : 'inline-block';
  372. nextPageIcon.style.display = currentPage === totalPages ? 'none' : 'inline-block';
  373. if(prevPageIcon._clickHandler){
  374. prevPageIcon.removeEventListener('click', prevPageIcon._clickHandler);
  375. delete prevPageIcon._clickHandler;
  376. }
  377. if(nextPageIcon._clickHandler){
  378. nextPageIcon.removeEventListener('click', nextPageIcon._clickHandler);
  379. delete nextPageIcon._clickHandler;
  380. }
  381. const preHandler = () => displaySuggestions(document.getElementById('iptName').value, currentPage - 1);
  382. const nextHandler = () => displaySuggestions(document.getElementById('iptName').value, currentPage + 1);
  383. prevPageIcon._clickHandler = preHandler;
  384. nextPageIcon._clickHandler = nextHandler;
  385. prevPageIcon.addEventListener('click', preHandler);
  386. nextPageIcon.addEventListener('click', nextHandler);
  387. }
  388. function GMaddStyle(css){
  389. var myStyle = document.createElement('style');
  390. myStyle.textContent = css;
  391. var doc = document.head || document.documentElement;
  392. doc.appendChild(myStyle);
  393. }
  394. function handleSuggestionClick(suggestion) {
  395. const iptName = document.getElementById('iptName');
  396. iptName.value = suggestion.textContent;
  397.  
  398. pickUpGameHref = catalog.findProductByName(suggestion.textContent).pickUp;
  399. searchGameHref = catalog.findProductByName(suggestion.textContent).searchGame;
  400.  
  401. document.querySelector('#pickUpGame').setAttribute('data-url', pickUpGameHref);
  402. document.querySelector('#searchGame').setAttribute('data-url', searchGameHref);
  403.  
  404. // 防止点击建议后再点击分页卡死
  405. const prevPageIcon = document.getElementById('prePageSpan');
  406. const nextPageIcon = document.getElementById('nextPageSpan');
  407. prevPageIcon.style.display = 'none';
  408. nextPageIcon.style.display = 'none';
  409.  
  410. const suggestionsList = document.getElementById('input_suggestion');
  411. suggestionsList.style.display = 'none'; // 关闭建议列表
  412. }

QingJ © 2025

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