|
|
本帖最后由 173470 于 2026-8-4 21:30 编辑
初学油猴脚本记录三:SteamDB加购物车魔改版
https://keylol.com/t861551-1-1
(出处: 其乐 Keylol)
生生不息,以+1养+1——我的阿区号余额增值方法(2023.01更新)
https://keylol.com/t658360-1-1
(出处: 其乐 Keylol)
ai太好用了
- // ==UserScript==
- // @name SteamDB Sales 加入 Steam 购物车
- // @namespace https://greasyfork.org/users/935206-shiquda
- // @version 2.5.0
- // @description 在新版 SteamDB Sales 中单个或批量加入 Steam 购物车,优先使用 package/sub。
- // @author shiquda(原作者 jklujklu),页面兼容修复
- // @match https://steamdb.info/sales/*
- // @match http://steamdb.info/sales/*
- // @icon https://steamdb.info/static/logos/32px.png
- // @grant GM_xmlhttpRequest
- // @grant GM_addStyle
- // @connect store.steampowered.com
- // @connect api.steampowered.com
- // @run-at document-end
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- const STEAM_STORE = 'https://store.steampowered.com/';
- const STEAM_APP = 'https://store.steampowered.com/app/';
- const STEAM_CART_API = 'https://api.steampowered.com/IAccountCartService/';
- const skipMultiSubs = false; // true 按自动选择规则加入;false 时手动选择。
- const autoChooseHighestDiscount = false; // true 自动选折扣最高;false 自动选价格最低。
- const addCartInterval = 200; // 批量加入间隔(毫秒)。
- let steamApiToken = '';
- let steamCountry = '';
- let batchRunning = false;
- let batchCancelled = false;
- GM_addStyle(`
- .steamdb-cart-add, .steamdb-cart-batch { margin-left: 8px; padding: 2px 7px; border: 1px solid #5c7e10; border-radius: 3px; color: #d2efa9; background: #365000; cursor: pointer; font: inherit; line-height: 1.4; }
- .steamdb-cart-add:hover:not(:disabled) { background: #527a00; }
- .steamdb-cart-add:disabled { cursor: wait; opacity: .7; }
- .steamdb-cart-tools { display: inline-flex; align-items: center; gap: 6px; margin-left: 8px; }
- .steamdb-cart-tools label { display: inline-flex; align-items: center; gap: 4px; }
- .steamdb-cart-select { margin-right: 7px; vertical-align: middle; }
- #steamdb-cart-toast { position: fixed; right: 20px; bottom: 20px; z-index: 2147483647; max-width: 360px; padding: 10px 14px; border: 1px solid #4e7c1e; border-radius: 4px; color: #fff; background: #213145; box-shadow: 0 3px 12px #0008; }
- #steamdb-cart-sub-modal { position: fixed; inset: 0; z-index: 2147483646; display: grid; place-items: center; background: #0009; }
- #steamdb-cart-sub-modal > div { width: min(860px, calc(100vw - 32px)); max-height: calc(100vh - 32px); overflow: auto; padding: 18px; border: 1px solid #4e7c1e; border-radius: 4px; background: #213145; color: #fff; }
- #steamdb-cart-sub-modal table { width: 100%; margin-top: 12px; border-collapse: collapse; }
- #steamdb-cart-sub-modal th, #steamdb-cart-sub-modal td { padding: 7px; border-bottom: 1px solid #3d536b; text-align: left; }
- #steamdb-cart-sub-modal .steamdb-cart-package-contents { color: #c7d4e2; font-size: 13px; }
- #steamdb-cart-sub-modal button { padding: 5px 10px; cursor: pointer; color: #d2efa9; background: #365000; border: 1px solid #5c7e10; border-radius: 3px; }
- #steamdb-cart-sub-modal .steamdb-cart-cancel { display: block; margin-top: 12px; color: #ddd; background: #3b3b3b; border-color: #666; }
- `);
- // 显示短暂的操作结果提示。
- function showToast(message, isError) {
- let toast = document.querySelector('#steamdb-cart-toast');
- if (!toast) {
- toast = document.createElement('div');
- toast.id = 'steamdb-cart-toast';
- document.body.appendChild(toast);
- }
- toast.textContent = message;
- toast.style.borderColor = isError ? '#b23b3b' : '#4e7c1e';
- clearTimeout(showToast.timer);
- showToast.timer = window.setTimeout(() => toast.remove(), 4000);
- }
- // 向 Steam 发起跨域请求并统一处理网络错误。
- function steamRequest(details) {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- timeout: 15000,
- ...details,
- onload: resolve,
- onerror: () => reject(new Error('网络请求失败')),
- ontimeout: () => reject(new Error('请求超时,已跳过该游戏')),
- onabort: () => reject(new Error('请求已取消'))
- });
- });
- }
- // 读取 Steam Web API 所需的短期令牌与当前商店地区。
- async function getSteamApiAuth() {
- if (steamApiToken && steamCountry) return { token: steamApiToken, country: steamCountry };
- const response = await steamRequest({ method: 'GET', url: STEAM_STORE });
- const token = response.responseText.match(/g_wapit\s*=\s*"([^"]+)"/);
- const country = response.responseText.match(/g_strCountryCode\s*=\s*"([A-Z]{2})"/i);
- if (!token) throw new Error('未能获取 Steam 购物车授权,请确认已登录 Steam 商店');
- steamApiToken = token[1];
- steamCountry = country?.[1]?.toUpperCase() || 'US';
- return { token: steamApiToken, country: steamCountry };
- }
- // 将无符号整数编码为 protobuf varint。
- function encodeVarint(value) {
- let number = BigInt(value);
- if (number < 0n) throw new Error('protobuf 整数不能为负数');
- const bytes = [];
- do {
- let byte = Number(number & 0x7fn);
- number >>= 7n;
- if (number) byte |= 0x80;
- bytes.push(byte);
- } while (number);
- return bytes;
- }
- // 将 protobuf varint 从字节数组中解码。
- function decodeVarint(bytes, offset) {
- let value = 0n;
- let shift = 0n;
- while (offset < bytes.length) {
- const byte = bytes[offset++];
- value |= BigInt(byte & 0x7f) << shift;
- if (!(byte & 0x80)) return { value, offset };
- shift += 7n;
- if (shift > 70n) break;
- }
- throw new Error('Steam 返回了无效的 protobuf 整数');
- }
- // 生成 protobuf 长度分隔字段。
- function encodeLengthField(field, bytes) {
- return [...encodeVarint((field << 3) | 2), ...encodeVarint(bytes.length), ...bytes];
- }
- // 将字节数组转换为 Web API 接受的 Base64 protobuf 文本。
- function bytesToBase64(bytes) {
- let binary = '';
- for (const byte of bytes) binary += String.fromCharCode(byte);
- return btoa(binary);
- }
- // 从 Web API 的 protobuf 加购响应中读取新建购物车行 ID。
- function getLineItemId(response) {
- let text = '';
- try {
- text = response.responseText || '';
- } catch {
- // 二进制响应模式下部分脚本管理器禁止读取 responseText。
- }
- try {
- const json = JSON.parse(text);
- const ids = json.response?.line_item_ids || json.line_item_ids;
- if (ids?.length) return String(ids[0]);
- } catch {
- // Web API 默认返回 protobuf,继续使用二进制解析。
- }
- const bytes = new Uint8Array(response.response || []);
- let offset = 0;
- while (offset < bytes.length) {
- const tag = decodeVarint(bytes, offset);
- offset = tag.offset;
- const field = Number(tag.value >> 3n);
- const wireType = Number(tag.value & 7n);
- if (field === 1 && wireType === 0) return String(decodeVarint(bytes, offset).value);
- if (wireType === 0) {
- offset = decodeVarint(bytes, offset).offset;
- continue;
- }
- if (wireType !== 2) throw new Error('Steam 返回了无法识别的购物车数据');
- const length = decodeVarint(bytes, offset);
- offset = length.offset;
- const end = offset + Number(length.value);
- if (end > bytes.length) throw new Error('Steam 返回了截断的购物车数据');
- if (field === 1) return String(decodeVarint(bytes, offset).value);
- offset = end;
- }
- throw new Error('Steam 未返回新购物车项目 ID');
- }
- // 向新版 Steam 购物车 Web API 提交 protobuf 请求。
- async function cartApiRequest(method, protobuf) {
- const { token } = await getSteamApiAuth();
- const boundary = `----SteamDBCart${Date.now().toString(36)}`;
- const data = `--${boundary}\r\nContent-Disposition: form-data; name="input_protobuf_encoded"\r\n\r\n${bytesToBase64(protobuf)}\r\n--${boundary}--\r\n`;
- const response = await steamRequest({
- method: 'POST',
- url: `${STEAM_CART_API}${method}/v1?access_token=${encodeURIComponent(token)}`,
- data,
- responseType: 'arraybuffer',
- headers: {
- 'Content-Type': `multipart/form-data; boundary=${boundary}`,
- Origin: STEAM_STORE.slice(0, -1),
- Referer: STEAM_STORE
- }
- });
- if (response.status < 200 || response.status >= 300) throw new Error(`Steam 购物车请求失败(HTTP ${response.status})`);
- return response;
- }
- // 将商店价格统一为千位逗号与小数点的格式。
- function formatPrice(value) {
- const match = value.match(/^(\D*)([\d.,\s]+)(.*)$/);
- if (!match) return value;
- const number = match[2].replace(/\s/g, '');
- const separator = Math.max(number.lastIndexOf('.'), number.lastIndexOf(','));
- const fraction = separator >= 0 && number.length - separator - 1 === 2 ? number.slice(separator + 1) : '';
- const integer = (fraction ? number.slice(0, separator) : number).replace(/[.,]/g, '');
- if (!integer) return value;
- return `${match[1]}${integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${fraction ? `.${fraction}` : ''}${match[3]}`;
- }
- // 将 Steam 原始价格文本转换为用于比较的数值。
- function getPriceValue(value) {
- const match = value.match(/[\d.,\s]+/);
- if (!match) return Number.POSITIVE_INFINITY;
- const number = match[0].replace(/\s/g, '');
- const separator = Math.max(number.lastIndexOf('.'), number.lastIndexOf(','));
- const fraction = separator >= 0 && number.length - separator - 1 === 2 ? number.slice(separator + 1) : '';
- const integer = (fraction ? number.slice(0, separator) : number).replace(/[.,]/g, '');
- const parsed = Number(`${integer}${fraction ? `.${fraction}` : ''}`);
- return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
- }
- // 将 Steam 显示的折扣文本转换为便于比较的百分比数值。
- function getDiscountValue(discount) {
- return Number(discount.match(/\d+(?:\.\d+)?/)?.[0] || 0);
- }
- // 读取一个 package/sub 内包含的 Steam 应用名称。
- async function getPackageContents(subId) {
- const response = await steamRequest({
- method: 'GET',
- url: `${STEAM_STORE}api/packagedetails?packageids=${encodeURIComponent(subId)}`
- });
- const result = JSON.parse(response.responseText);
- const data = result[subId]?.data;
- if (!data?.apps?.length) throw new Error('Steam 未返回该 package 的应用列表');
- return data.apps.map(app => app.name).filter(Boolean);
- }
- // 从 package/sub 或 bundle 的独立商店页读取实际显示名称。
- async function getPurchaseTitle(option) {
- const path = option.type === 'bundle' ? 'bundle' : 'sub';
- const response = await steamRequest({ method: 'GET', url: `${STEAM_STORE}${path}/${encodeURIComponent(option.id)}/` });
- const page = new DOMParser().parseFromString(response.responseText, 'text/html');
- const title = page.querySelector('.pageheader, #appHubAppName, h1[id^="add_bundle_to_cart_title_"], h1[id^="bundle_label_"]')?.textContent.trim().replace(/^(购买|Buy)\s*/i, '');
- if (!title) throw new Error(`未能读取 ${path} ${option.id} 的商店名称`);
- return title;
- }
- // 从商店页提取可购买项及其在选择面板中显示的信息。
- async function getPurchaseOptions(appId) {
- const response = await steamRequest({ method: 'GET', url: STEAM_APP + appId });
- if (response.responseText.includes('agegate_birthday_selector')) return 'agegate';
- const page = new DOMParser().parseFromString(response.responseText, 'text/html');
- const options = [];
- const forms = page.querySelectorAll('form input[name="subid"], form input[name="bundleid"]');
- for (const input of forms) {
- const id = input.value;
- const type = input.name === 'subid' ? 'sub' : 'bundle';
- if (!id || options.some(option => option.id === id && option.type === type)) continue;
- const wrapper = input.closest('.game_area_purchase_game_wrapper') || input.closest('.game_area_purchase_game') || input.closest('form')?.parentElement;
- const title = wrapper?.querySelector('.game_area_purchase_game h1, h1')?.textContent.trim().replace(/^(购买|Purchase)\s*/i, '') || page.querySelector('#appHubAppName')?.textContent.trim() || `Package ${id}`;
- const discount = wrapper?.querySelector('.discount_pct')?.textContent.trim() || '-0%';
- const rawPrice = wrapper?.querySelector('.discount_final_price, .game_purchase_price')?.textContent.trim() || '-';
- const price = formatPrice(rawPrice);
- options.push({ id, type, title, discount, price, priceValue: getPriceValue(rawPrice) });
- }
- return options;
- }
- // 在关闭 skipMultiSubs 时展示原脚本风格的购买项选择表格。
- function chooseSub(options) {
- return new Promise(resolve => {
- const modal = document.createElement('div');
- modal.id = 'steamdb-cart-sub-modal';
- const panel = document.createElement('div');
- const title = document.createElement('strong');
- title.textContent = '请选择要加入购物车的购买项';
- panel.appendChild(title);
- const table = document.createElement('table');
- const head = document.createElement('thead');
- const headRow = document.createElement('tr');
- for (const text of ['游戏', '折扣', '价格', 'Sub类型', 'SubId', '']) {
- const cell = document.createElement('th');
- cell.textContent = text;
- headRow.appendChild(cell);
- }
- head.appendChild(headRow);
- table.appendChild(head);
- const body = document.createElement('tbody');
- for (const option of options) {
- const row = document.createElement('tr');
- for (const [index, text] of [option.title, option.discount, option.price, option.type === 'sub' ? 'game' : 'bundle', option.id].entries()) {
- const cell = document.createElement('td');
- cell.textContent = text;
- row.appendChild(cell);
- if (index === 0) {
- cell.textContent = '读取商店名称...';
- getPurchaseTitle(option).then(name => {
- option.title = name;
- cell.textContent = name;
- }).catch(() => {
- cell.textContent = option.title;
- });
- }
- }
- const action = document.createElement('td');
- const button = document.createElement('button');
- button.type = 'button';
- button.textContent = 'Add';
- button.addEventListener('click', () => {
- modal.remove();
- resolve(option);
- });
- action.appendChild(button);
- const contents = document.createElement('button');
- contents.type = 'button';
- contents.textContent = '内容';
- contents.style.marginLeft = '6px';
- contents.addEventListener('click', async () => {
- if (option.type !== 'sub') {
- showToast('Bundle 的内容请在 Steam 商店页确认', true);
- return;
- }
- const existing = row.nextElementSibling;
- if (existing?.classList.contains('steamdb-cart-package-contents')) {
- existing.remove();
- return;
- }
- contents.disabled = true;
- contents.textContent = '加载中...';
- try {
- const names = await getPackageContents(option.id);
- const details = document.createElement('tr');
- details.className = 'steamdb-cart-package-contents';
- const cell = document.createElement('td');
- cell.colSpan = 6;
- cell.textContent = `包含 ${names.length} 项:${names.join('、')}`;
- details.appendChild(cell);
- row.after(details);
- contents.textContent = '收起';
- } catch (error) {
- contents.textContent = '内容';
- showToast(error.message, true);
- } finally {
- contents.disabled = false;
- }
- });
- action.appendChild(contents);
- row.appendChild(action);
- body.appendChild(row);
- }
- table.appendChild(body);
- panel.appendChild(table);
- const cancel = document.createElement('button');
- cancel.type = 'button';
- cancel.className = 'steamdb-cart-cancel';
- cancel.textContent = '取消';
- cancel.addEventListener('click', () => {
- modal.remove();
- resolve(null);
- });
- panel.appendChild(cancel);
- modal.appendChild(panel);
- document.body.appendChild(modal);
- });
- }
- // 按配置在可购买项目中自动挑选折扣最高或价格最低的一项。
- function chooseAutomaticOption(options) {
- const subs = options.filter(item => item.type === 'sub');
- const candidates = subs.length ? subs : options;
- return candidates.reduce((best, current) => {
- if (autoChooseHighestDiscount) return getDiscountValue(current.discount) > getDiscountValue(best.discount) ? current : best;
- return current.priceValue < best.priceValue ? current : best;
- });
- }
- // 将指定 package/sub 或 bundle 加入当前 Steam 购物车,并保存新行项目 ID。
- async function addToCart(appId) {
- const options = await getPurchaseOptions(appId);
- if (options === 'agegate') {
- window.open(STEAM_APP + appId, '_blank', 'noopener');
- throw new Error('该游戏需要年龄验证,已在新标签页打开商店页面');
- }
- if (!options.length) throw new Error('未找到可购买的 package');
- const subs = options.filter(item => item.type === 'sub');
- let option = chooseAutomaticOption(options);
- if (!skipMultiSubs && subs.length > 1) {
- option = await chooseSub(options);
- if (!option) throw new Error('已取消选择 package/sub');
- }
- const { country } = await getSteamApiAuth();
- const item = option.type === 'sub'
- ? [...encodeVarint(8), ...encodeVarint(option.id)]
- : [...encodeVarint(16), ...encodeVarint(option.id)];
- const request = [
- ...encodeLengthField(1, Array.from(country, char => char.charCodeAt(0))),
- ...encodeLengthField(2, item)
- ];
- const response = await cartApiRequest('AddItemsToCart', request);
- return { ...option, lineItemId: getLineItemId(response), country };
- }
- // 使用加购时返回的行项目 ID 从当前 Steam 购物车移除项目。
- async function removeFromCart(lineItemId, country) {
- const auth = await getSteamApiAuth();
- const request = [
- ...encodeVarint(8), ...encodeVarint(lineItemId),
- ...encodeLengthField(2, Array.from(country || auth.country, char => char.charCodeAt(0)))
- ];
- await cartApiRequest('RemoveItemFromCart', request);
- }
- // 为一行游戏创建单个加入购物车按钮。
- function createAddButton(appId) {
- const button = document.createElement('button');
- button.className = 'steamdb-cart-add';
- button.type = 'button';
- button.textContent = '加入购物车';
- button.title = `将 App ${appId} 加入 Steam 购物车`;
- button.addEventListener('click', async event => {
- event.preventDefault();
- event.stopPropagation();
- button.disabled = true;
- button.textContent = '处理中...';
- try {
- if (button.dataset.cartLineItemId) {
- await removeFromCart(button.dataset.cartLineItemId, button.dataset.cartCountry);
- delete button.dataset.cartLineItemId;
- delete button.dataset.cartCountry;
- button.textContent = '加入购物车';
- button.title = `将 App ${appId} 加入 Steam 购物车`;
- showToast(`App ${appId} 已从 Steam 购物车移除`);
- } else {
- const option = await addToCart(appId);
- button.dataset.cartLineItemId = option.lineItemId;
- button.dataset.cartCountry = option.country;
- button.textContent = '已加入';
- button.title = `点击将 App ${appId} 从 Steam 购物车移除`;
- showToast(`App ${appId} 已加入 Steam 购物车`);
- }
- } catch (error) {
- showToast(error.message, true);
- } finally {
- button.disabled = false;
- if (button.textContent === '处理中...') button.textContent = button.dataset.cartLineItemId ? '已加入' : '加入购物车';
- }
- });
- return button;
- }
- // 同步全选框状态,避免筛选或翻页后显示过期状态。
- function syncSelectAll() {
- const selectAll = document.querySelector('#steamdb-cart-select-all');
- const boxes = document.querySelectorAll('.steamdb-cart-select');
- if (!selectAll || !boxes.length) return;
- selectAll.checked = Array.from(boxes).every(box => box.checked);
- selectAll.indeterminate = !selectAll.checked && Array.from(boxes).some(box => box.checked);
- }
- // 将当前勾选的游戏按顺序加入购物车,避免短时间内请求过多。
- async function addSelectedToCart() {
- const batchButton = document.querySelector('#steamdb-cart-batch-add');
- if (batchRunning) {
- batchCancelled = true;
- batchButton.textContent = '正在停止...';
- return;
- }
- const rows = Array.from(document.querySelectorAll('#DataTables_Table_0 tbody tr[data-appid]'))
- .filter(row => row.querySelector('.steamdb-cart-select')?.checked);
- if (!rows.length) {
- showToast('请先勾选要加入购物车的游戏', true);
- return;
- }
- batchRunning = true;
- batchCancelled = false;
- batchButton.textContent = '获取 Steam 购物车授权...';
- try {
- await getSteamApiAuth();
- } catch (error) {
- batchRunning = false;
- batchButton.textContent = '加入已选';
- showToast(error.message, true);
- return;
- }
- let success = 0;
- let processed = 0;
- for (const [index, row] of rows.entries()) {
- if (batchCancelled) break;
- batchButton.textContent = `处理中 ${index + 1}/${rows.length}(点击停止)`;
- try {
- const option = await addToCart(row.dataset.appid);
- if (!batchCancelled) {
- success += 1;
- const button = row.querySelector('.steamdb-cart-add');
- if (button) {
- button.disabled = true;
- button.dataset.cartLineItemId = option.lineItemId;
- button.dataset.cartCountry = option.country;
- button.textContent = '已加入';
- button.title = `点击将 App ${row.dataset.appid} 从 Steam 购物车移除`;
- }
- }
- } catch (error) {
- console.warn(`App ${row.dataset.appid}: ${error.message}`);
- }
- processed += 1;
- if (batchCancelled) break;
- if (index + 1 < rows.length) await new Promise(resolve => window.setTimeout(resolve, addCartInterval));
- }
- const wasCancelled = batchCancelled;
- batchRunning = false;
- batchCancelled = false;
- batchButton.textContent = '加入已选';
- showToast(
- wasCancelled
- ? `已停止:已处理 ${processed}/${rows.length},成功加入 ${success} 款游戏`
- : `已加入 ${success}/${rows.length} 款游戏${success === rows.length ? '' : ',失败项目请单独重试'}`,
- success !== rows.length
- );
- }
- // 在当前 Sales 筛选工具栏插入全选与批量加入控件。
- function initTools() {
- if (document.querySelector('#steamdb-cart-tools')) return;
- const host = document.querySelector('#js-filters') || document.querySelector('#DataTables_Table_0')?.parentElement;
- if (!host) return;
- const tools = document.createElement('span');
- tools.id = 'steamdb-cart-tools';
- tools.className = 'steamdb-cart-tools';
- tools.innerHTML = '<label title="全选当前筛选结果页"><input id="steamdb-cart-select-all" type="checkbox">全选当前页</label><button id="steamdb-cart-batch-add" class="steamdb-cart-batch" type="button">加入已选</button>';
- tools.querySelector('#steamdb-cart-select-all').addEventListener('change', event => {
- document.querySelectorAll('.steamdb-cart-select').forEach(box => {
- box.checked = event.target.checked;
- });
- syncSelectAll();
- });
- tools.querySelector('#steamdb-cart-batch-add').addEventListener('click', addSelectedToCart);
- host.appendChild(tools);
- }
- // 为新版 Sales 表格中尚未处理的游戏行插入复选框和操作按钮。
- function bindRows() {
- initTools();
- const rows = document.querySelectorAll('#DataTables_Table_0 tbody tr[data-appid]');
- for (const row of rows) {
- if (row.querySelector('.steamdb-cart-add')) continue;
- const appId = row.dataset.appid;
- const nameCell = row.querySelector('td:nth-child(3)') || row.querySelector('td');
- if (!appId || !nameCell) continue;
- const select = document.createElement('input');
- select.className = 'steamdb-cart-select';
- select.type = 'checkbox';
- select.title = `选择 App ${appId}`;
- select.addEventListener('change', syncSelectAll);
- nameCell.prepend(select);
- nameCell.appendChild(createAddButton(appId));
- }
- syncSelectAll();
- }
- // 监听页面的筛选、排序和翻页造成的表格重绘。
- function observeTable() {
- bindRows();
- const observer = new MutationObserver(bindRows);
- observer.observe(document.body, { childList: true, subtree: true });
- }
- // 等待 SteamDB 异步初始化 Sales 表格。
- function waitForTable() {
- if (document.querySelector('#DataTables_Table_0 tbody')) {
- observeTable();
- return;
- }
- window.setTimeout(waitForTable, 250);
- }
- waitForTable();
- })();
复制代码
|
1、转载或引用本网站内容,必须注明本文网址:https://keylol.com/t1045640-1-1。如发文者注明禁止转载,则请勿转载
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利
4、所有帖子仅代表作者本人意见,不代表本社区立场
|