// ==UserScript==
// @name 22k4已购游戏搜索
// @namespace http://tampermonkey.net/
// @version 2024-10-07
// @description 已购买游戏详情页添加搜索功能
// @author 口吃者
// @match https://22k4.com/member/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=22k4.com
// @grant none
// @license MIT
// ==/UserScript==
/*
1. 多次快速点击分页按钮造成卡死
2. 可以增加保存配置功能,在没有购买新游戏时,可以保存配置,下次直接加载
*/
class ProductCatalog {
constructor() {
this.pages = []; // 存储每页的商品列表
this.productByName = new Map(); // 存储商品名称到商品对象的映射
}
// 添加商品到指定页
addProductToPage(pageIndex, product) {
if (!this.pages[pageIndex]) {
this.pages[pageIndex] = [];
}
// 检查商品名称是否已存在
const existingProduct = this.productByName.get(product.name.toLowerCase());
if (existingProduct) {
console.log(`Product with name "${product.name}" already exists.`);
return; // 商品名称已存在,不添加
}
// 添加商品到对应页,并更新映射
this.pages[pageIndex].push(product);
this.productByName.set(product.name.toLowerCase(), product);
}
// 根据商品名称查询商品
findProductByName(name) {
const lowerCaseName = name.toLowerCase();
return this.productByName.get(lowerCaseName);
}
// 模糊查询商品
searchProductsByPartialName(query) {
const results = [];
// 遍历所有商品
this.productByName.forEach((product, name) => {
if (name.includes(query.toLowerCase())) {
results.push(product);
}
});
return results;
}
// 获取所有商品
getAllProducts() {
return this.productByName.values();
}
// 获取某一页的所有商品
getProductsOnPage(pageIndex) {
return this.pages[pageIndex] || [];
}
}
const catalog = new ProductCatalog();
var requestsCompleted = false;
var pickUpGameHref = '#';
var searchGameHref = '#';
(function() {
'use strict';
addPanel();
// Your code here...
})();
window.onload = function() {
// 示例:检查请求是否全部完成
let btnSwitch = document.querySelector('#btnSwitch');
checkTimer = setTimeout(() => {
btnSwitch.textContent = "正在获取游戏中..."
}, 1000); // 这只是一个示例,实际应用中应该根据实际场景决定何时检查
//获取总页数
let pageCount = document.querySelector("#PageCount").value;
let pageSize = document.querySelector("#PageSize").value;
let totalPages = getTotalPages(pageCount, pageSize);
console.log(totalPages);
const urls = Array.from({ length: totalPages }, (_, i) =>
`https://22k4.com/member/product.php?page=${i + 1}`
);
// 创建一个 Promise 数组
const fetchPromises = urls.map(url =>
fetch(url, {
method: 'GET',
credentials: 'include', // 使得请求会发送 Cookie
headers: {
'Content-Type': 'text/html;charset=utf-8',
}
}).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text(); // 读取为文本
})
);
// 使用 Promise.all 来等待所有请求完成
Promise.all(fetchPromises)
.then(htmlTexts => {
// 处理每个 HTML 文档
htmlTexts.forEach((htmlText, index) => {
// 创建一个范围较小的虚拟文档环境
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, "text/html");
getAllItemDetails(doc, index + 1);
requestsCompleted = true;
clearTimeout(checkTimer);
btnSwitch.textContent = "面板"
});
})
.catch(error => {
console.error('There was a problem with one of the fetch operations:', error);
});
//输入框
document.getElementById('iptName').addEventListener('input', function(event) {
let input = event.target.value;
// 显示建议列表
displaySuggestions(input, 1);
})
// 初始化分页图标
updatePaginationIcons(1, 1); // 假设初始页码为1,总页数为1(待更新)
var cssText = `
#input_suggestion li {
color: aliceblue;
cursor: pointer;
}
#input_suggestion li:hover {
color: cadetblue;
background-color: #f0f0f0;
}
#pagination span {
display: inline-block;
width: 25px;
height: 25px;
line-height: 25px;
text-align: center;
border: 1px solid #ccc;
cursor: pointer;
background: azure;
margin-inline-end: 5px;
margin-inline-start: 5px;
}
`
GMaddStyle(cssText);
}
function addPanel(){
function genDiv(cls, id){
let d = document.createElement('div');
d.style.cssText = 'vertical-align:middle;';
if(cls){ d.className = cls };
if(id){ d.id = id };
return d;
}
//创建一个可以自由弹出的面板
function genPanel(name, visiable){
let panel = genDiv(name, name);
panel.style.cssText = 'background: rgba(58, 58, 58, 0.8);position: fixed;top: 50%;right: 0px;';
panel.style.cssText += 'text-align: center;transform: translate(0px, -50%);z-index: 100;';
panel.style.cssText += 'border: 1px solid rgb(83, 83, 83);padding: 5px;border-radius: 10px 0 0 10px;';
panel.style.cssText += 'transition:right 0.8s;right:-300px;'
return panel;
}
function genButton(test, foo, id, fooParams = {}){
let b = document.createElement('button');
b.textContent = test;
b.style.verticalAlign = 'inherit';
// 使用箭头函数创建闭包来保存 fooParams 并传递给 foo
b.addEventListener('click', () => {
foo.call(b, ...Object.values(fooParams)); // 使用 call 方法确保 this 指向按钮对象
});
if(id){ b.id = id };
return b;
}
function genInput(id, value, tips, number) {
let i = document.createElement('input');
i.id = id;
i.style.cssText = 'border:none;border-radius:0;margin:0px 5px;width:60%;text-align:center;';
i.style.cssText += 'color:#000;background:#fff;vertical-align:inherit;';
if (value) { i.value = value; }
if (tips) { i.placeholder = tips; }
if (number) {
i.type = 'number';
i.step = 0.01;
i.min = 0;
}
return i;
}
function genUl(items, id) {
// 创建一个新的<ul>元素
let ul = document.createElement('ul');
// 如果提供了类名,添加到<ul>元素上
if (id) {
ul.id = id;
}
// // 遍历items数组,为每个项目创建一个<li>元素并添加到<ul>中
// items.forEach(function(item) {
// let li = document.createElement('li');
// // 这里简单地将item的值设置为<li>的文本内容
// // 根据实际需求,这里可以进一步定制化,比如添加事件监听器等
// li.textContent = item;
// // 将<li>添加到<ul>中
// ul.appendChild(li);
// });
return ul;
}
function genSpan(id, textContentSpan) {
let s = document.createElement('span');
s.id = id;
s.textContent = textContentSpan;
return s;
}
//展开指定面板
function showPanel(panelId){
let panel = document.getElementById(panelId);
if (panel.style.right == '-300px') {
panel.style.right = '0';
} else {
panel.style.right = '-300px';
}
}
let btnSwitch = genButton('面板', showPanel, 'btnSwitch', {param1: 'autoSell'});
let panelFunc = genPanel('autoSell', false); //初始隐藏
//加入位置
let lBtnArea = document.querySelector('body > div.page > div.container.m_top_30 > div');
lBtnArea.insertBefore(btnSwitch, lBtnArea.children[0]);
lBtnArea.insertBefore(panelFunc, lBtnArea.children[0]);
//自动出售(加入到面板中) 搜索
let divName = genDiv();
let divPagination = genDiv(null, 'pagination');
let button01 = genButton('搜索', searchGame, 'searchGame');
let button02 = genButton('提货', pickUpGame, 'pickUpGame');
let iptName = genInput('iptName', '', ' 游戏名称', false);
let inputSuggestion = genUl(['无'], 'input_suggestion');
let prePageSpan = genSpan('prePageSpan', '<');
let nextPageSpan = genSpan('nextPageSpan', '>');
divName.appendChild(button01);
divName.appendChild(button02);
divName.appendChild(iptName);
divName.appendChild(inputSuggestion);
divPagination.append(prePageSpan);
divPagination.append(nextPageSpan);
panelFunc.appendChild(divName);
panelFunc.appendChild(divPagination);
//todo 可优化成bilibili增强的样式表写法
let h5Element = document.querySelector("body > div.page > div.container.m_top_30 > div > h5");
btnSwitch.style.cssText += 'display: inline-block; margin-right: 10px;'
btnSwitch.style.cssText += 'font-weight: bold;font-size: 16px;'
h5Element.style.cssText += 'display: inline-block; margin-right: 10px;'
}
function searchGame(){
// 使用 window.open 方法打开链接
var url = this.getAttribute('data-url');// 链接动态变化,添加格外属性,每次点击重新获取
window.open(url, '_blank'); // '_blank' 表示在一个新的浏览器标签页中打开
}
function pickUpGame(event){
// 使用 window.open 方法打开链接
var url = this.getAttribute('data-url');// 链接动态变化,添加格外属性,每次点击重新获取
window.open(url, '_blank'); // '_blank' 表示在一个新的浏览器标签页中打开
}
function getTotalPages(input, eachPage) {
// 将字符串转换为整数,如果是数字类型则直接使用
let num = typeof input === 'string' ? parseInt(input, 10) : input;
let eachPage01 = typeof eachPage === 'string' ? parseInt(eachPage, 10) : eachPage;
// 检查转换后的值是否为 NaN 或者小于等于 0
if (isNaN(num) || num <= 0 || isNaN(eachPage01) || eachPage01 <= 0) {
throw new Error('getTotalPages error:Invalid input: input must be a positive integer.');
}
// 计算总页数
// Math.ceil() 用于向上取整
const totalPages = Math.ceil(num / eachPage01);
return totalPages;
}
function getAllItemDetails(pageDocument, page){
let itemList = pageDocument.querySelectorAll('tbody > tr');
itemList.forEach(item => {
let itemId = item.id;
let itemImg = item.querySelector('img').src;
let itemName = item.querySelector('a').textContent;
let itemPrice = item.querySelector('span').textContent;
let itemOrderNo = item.querySelector('td:nth-child(3) > p:nth-child(1)').textContent;
let itemOrderTime = item.querySelector('td:nth-child(3) > p:nth-child(2)').textContent;
let itemPickup = item.querySelector('td:nth-child(4) > p > a').href;
let itemSearch = item.querySelector('td:nth-child(2) > p:nth-child(1) > b > a').href;
// 创建一个新的 product 对象
const product = {
id: itemId, // 假设这是一个生成唯一 ID 的方法
name: itemName,
imageUrl: itemImg,
price: itemPrice,
orderNo: itemOrderNo,
orderTime: itemOrderTime,
pickUp: itemPickup,
searchGame: itemSearch
};
catalog.addProductToPage(page - 1, product);
})
}
function displaySuggestions(input, page) {
const suggestions = catalog.searchProductsByPartialName(input).map(product => product.name);
let suggestionsList = document.getElementById('input_suggestion');
suggestionsList.innerHTML = ''; // 清空现有建议
// 根据输入过滤建议
let filteredSuggestions = suggestions;
const itemsPerPage = 7; // 每页显示的建议数
var totalPages = Math.ceil(filteredSuggestions.length / itemsPerPage);
const start = (page - 1) * itemsPerPage;
const end = Math.min(start + itemsPerPage, filteredSuggestions.length);
// 显示过滤后的建议
if (filteredSuggestions.length > 0 && filteredSuggestions.length >= start) {
suggestionsList.style.display = 'block';
// 获取输入框元素
const iptName = document.getElementById('iptName');
// 显示当前页的建议
for (let i = start; i < end; i++) {
let item = document.createElement('li');
item.textContent = filteredSuggestions[i];
if(item._clickHandler){
item.removeEventListener('click', handler);
delete item._clickHandler; // 清理保存的引用
}
// 创建一个通用的事件处理函数,并保存其引用
const handler = () => handleSuggestionClick(item);
item._clickHandler = handler; // 保存函数引用
item.addEventListener('click', handler);
suggestionsList.appendChild(item);
}
} else {
suggestionsList.style.display = 'none'; // 如果没有建议则隐藏列表
}
// 更新分页图标的状态
updatePaginationIcons(page, totalPages);
}
function updatePaginationIcons(currentPage, totalPages) {
const prevPageIcon = document.getElementById('prePageSpan');
const nextPageIcon = document.getElementById('nextPageSpan');
if(currentPage === -1 && totalPages === -1){
prevPageIcon.style.display = 'none';
nextPageIcon.style.display = 'none';
return;
}
prevPageIcon.style.display = currentPage === 1 ? 'none' : 'inline-block';
nextPageIcon.style.display = currentPage === totalPages ? 'none' : 'inline-block';
if(prevPageIcon._clickHandler){
prevPageIcon.removeEventListener('click', prevPageIcon._clickHandler);
delete prevPageIcon._clickHandler;
}
if(nextPageIcon._clickHandler){
nextPageIcon.removeEventListener('click', nextPageIcon._clickHandler);
delete nextPageIcon._clickHandler;
}
const preHandler = () => displaySuggestions(document.getElementById('iptName').value, currentPage - 1);
const nextHandler = () => displaySuggestions(document.getElementById('iptName').value, currentPage + 1);
prevPageIcon._clickHandler = preHandler;
nextPageIcon._clickHandler = nextHandler;
prevPageIcon.addEventListener('click', preHandler);
nextPageIcon.addEventListener('click', nextHandler);
}
function GMaddStyle(css){
var myStyle = document.createElement('style');
myStyle.textContent = css;
var doc = document.head || document.documentElement;
doc.appendChild(myStyle);
}
function handleSuggestionClick(suggestion) {
const iptName = document.getElementById('iptName');
iptName.value = suggestion.textContent;
pickUpGameHref = catalog.findProductByName(suggestion.textContent).pickUp;
searchGameHref = catalog.findProductByName(suggestion.textContent).searchGame;
document.querySelector('#pickUpGame').setAttribute('data-url', pickUpGameHref);
document.querySelector('#searchGame').setAttribute('data-url', searchGameHref);
// 防止点击建议后再点击分页卡死
const prevPageIcon = document.getElementById('prePageSpan');
const nextPageIcon = document.getElementById('nextPageSpan');
prevPageIcon.style.display = 'none';
nextPageIcon.style.display = 'none';
const suggestionsList = document.getElementById('input_suggestion');
suggestionsList.style.display = 'none'; // 关闭建议列表
}