// ==UserScript==
// @name FF14道具仓库批量领取
// @description 最终幻想14道具仓库批量领取
// @namespace https://greasyfork.org/users/129402
// @match https://qu.sdo.com/*
// @grant GM_setValue
// @grant GM_getValue
// @version 2.0.3
// @license GNU General Public License v3.0 or later
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// @run-at document-end
// @downloadURL none
// ==/UserScript==
/* eslint-disable no-magic-numbers */
/* global moment, GM_setValue, GM_getValue */
"use strict";
(() => {
$.ajax({
cache: true,
dataType: "script",
url: "https://qu.sdo.com/static/lib/moment-2.24.0.min.js",
});
const button = $("
");
button.css({
"background-color": "#fff",
border: "gray solid 1px",
"border-radius": "4px",
bottom: "1rem",
cursor: "pointer",
"font-size": "14px",
"font-weight": "700",
"letter-spacing": ".5em",
"line-height": "1.5",
padding: ".5em 0 .5em .5em",
position: "fixed",
right: "1rem",
width: "3em",
"z-index": 73,
});
button.text("批量领取");
button.appendTo("body");
button.on("click", () => {
const container = $("");
container.css({
"background-color": "#fff",
border: "gray solid 1px",
"border-radius": "4px",
bottom: "2rem",
color: "#303133",
"font-size": "14px",
left: "calc(50vw - 600px)",
"line-height": "1.5",
position: "fixed",
top: "2rem",
width: "1200px",
"z-index": 137,
});
container.appendTo("body");
const closeButton = $("");
closeButton.on("click", () => {
if (closeButton.is(":not(:disabled)")) {
container.remove();
}
});
container.append(closeButton);
const title = $("");
title.text("批量领取道具");
title.css({
"border-bottom": "gray solid 1px",
"font-size": "20px",
margin: ".5rem auto",
"text-align": "center",
width: "1100px",
});
container.append(title);
const chooseContainer = $("");
chooseContainer.text("选择仓库:");
chooseContainer.css({
"border-bottom": "gray solid 1px",
left: "50px",
margin: ".5rem auto",
"padding-bottom": ".5rem",
position: "sticky",
"text-align": "center",
top: ".5rem",
width: "1100px",
});
const paidWarehouseInput = $("");
paidWarehouseInput.attr({
id: "3a755830-cfd0-4999-a79b-9dcf62e6669f",
type: "radio",
}).css("margin", "0 .5rem");
const freeWarehouseInput = $("");
freeWarehouseInput.attr({
id: "005c8d3a-4594-4e7b-af4d-865498126a36",
type: "radio",
}).css("margin", "0 .5rem");
const paidWarehouseInputLabel = $("");
paidWarehouseInputLabel.attr("for", "3a755830-cfd0-4999-a79b-9dcf62e6669f").text("购买仓库").css("margin-right", ".5rem");
const freeWarehouseInputLabel = $("");
freeWarehouseInputLabel.attr("for", "005c8d3a-4594-4e7b-af4d-865498126a36").text("奖励仓库").css("margin-right", ".5rem");
paidWarehouseInput.data({
otherInput: freeWarehouseInput,
sourceType: 0,
});
freeWarehouseInput.data({
otherInput: paidWarehouseInput,
sourceType: 1,
});
chooseContainer.append(paidWarehouseInput).append(paidWarehouseInputLabel).append("·").append(freeWarehouseInput).append(freeWarehouseInputLabel);
const submitContainer = $("");
submitContainer.css({
"border-left": "gray solid 1px",
"margin-left": ".5rem",
padding: "0 .5rem",
});
chooseContainer.append(submitContainer);
container.append(chooseContainer);
const resultCotainer = $("");
resultCotainer.css({
margin: "0 auto",
padding: ".5rem",
"text-align": "center",
});
container.append(resultCotainer);
const count = $("");
count.text("道具数量:--");
submitContainer.append(count);
const submitButton = $("");
submitButton.text("开始批量领取").css("margin", "0 .5rem");
submitContainer.append(submitButton);
const selectAllButton = $("");
selectAllButton.text("全选");
selectAllButton.on("click", () => {
if (selectAllButton.is(":not(:disabled)")) {
resultCotainer.find("input").prop("checked", true);
}
});
submitContainer.append(selectAllButton);
const selectNoneButton = $("");
selectNoneButton.text("全不选");
selectNoneButton.on("click", () => {
if (selectNoneButton.is(":not(:disabled)")) {
resultCotainer.find("input").prop("checked", false);
}
});
submitContainer.append(selectNoneButton);
paidWarehouseInput.add(freeWarehouseInput).on("change", async ({ target }) => {
const self = $(target);
self.data("otherInput").prop("checked", false);
resultCotainer.empty().text("加载中……");
try {
const apiResult = await $.ajax({
data: {
_: Math.random,
appId: 100001900,
keyword: "",
order: 0,
page: 1,
pageSize: 100,
sourceType: self.data("sourceType"),
status: 0,
},
type: "GET",
url: "https://sqmallservice.u.sdo.com/api/us/gameItem/list",
xhrFields: {
withCredentials: true,
},
});
resultCotainer.empty();
if (apiResult.resultCode !== 0) {
resultCotainer.text(`请求发生错误!错误信息:[${apiResult.resultCode}] ${apiResult.resultMsg}`);
return;
}
const { propsList, totalCount } = apiResult.data;
if (totalCount > 100) {
resultCotainer.append("仓库中包含超过100件道具,为了避免出现问题,脚本只支持前100件道具的批量领取,请领取完前100件后刷新页面继续。
");
}
if (totalCount === 0) {
resultCotainer.html("该仓库无道具~");
return;
}
const resultTable = $("");
resultTable.css("border-collapse", "collapse");
resultTable.append("序号 | 名称 | 购买日期 | 是否批量领取 |
");
resultCotainer.append($("").css({
margin: "auto",
width: "fit-content",
}).append(resultTable));
const resultTableBody = resultTable.find("tbody");
propsList.forEach(({ propsWarehouseId, productName, purchaseDate, skuId, sourceType }, _index) => {
const index = _index + 1;
const row = $("
");
row.html(' | '.repeat(4));
row.find("td").eq(0).text(index);
const textCol = row.find("td").eq(1);
textCol.text(`${productName} `);
const link = $("");
link.css({
color: "#3273dc",
"text-decoration": "underline",
}).attr({
href: `https://qu.sdo.com/product-detail/${skuId}`,
target: "_blank",
}).text("[商品页面](外链)");
textCol.append(link);
row.find("td").eq(2).text(moment(purchaseDate).format("YYYY年M月D日HH点mm分"));
const input = $("");
input.attr("type", "checkbox").data({ propsWarehouseId, sourceType });
row.find("td").eq(3).append(input);
resultTableBody.append(row);
});
count.text(`道具数量:${propsList.length}`);
}
catch (e) {
resultCotainer.html(`发生网络错误!错误信息:${e}
`);
}
});
submitButton.on("click", async () => {
if (submitButton.is(":not(:disabled)")) {
if (resultCotainer.find("input").length === 0) {
alert("尚未加载数据!");
return;
}
const selected = resultCotainer.find("input:checked").toArray().map((ele) => $(ele).data());
if (selected.length === 0) {
alert("尚未选择道具!");
return;
}
const choosed = {
free: selected.filter(({ sourceType }) => sourceType === 1).map(({ propsWarehouseId }) => propsWarehouseId),
paid: selected.filter(({ sourceType }) => sourceType === 0).map(({ propsWarehouseId }) => propsWarehouseId),
};
let confirmText = "";
if (choosed.paid.length > 0) {
confirmText += `${choosed.paid.length}个购买仓库的道具 和 `;
}
if (choosed.free.length > 0) {
confirmText += `${choosed.free.length}个奖励仓库的道具`;
}
confirmText = confirmText.replace(/ 和 $/, "");
if (!confirm(`你选择了 ${confirmText} ,是否确认批量领取?`)) {
return;
}
submitButton.add(selectAllButton).add(selectNoneButton).add(closeButton).add(resultCotainer.find("input")).attr("disabled", "disabled");
const characterSelectorContainer = $("");
characterSelectorContainer.css({
"background-color": "#fff",
border: "gray solid 1px",
"border-radius": "4px",
"font-size": "14px",
left: "calc(50vw - 300px)",
"line-height": "1.5",
padding: "25px 25px 30px",
position: "fixed",
"text-align": "center",
top: "15vh",
width: "600px",
"z-index": 713,
});
const area = $("");
area.css("margin", ".5rem 0");
const areaSelect = $("");
areaSelect.attr({
disabled: "disabled",
id: "b5970252-6c7e-4603-928f-ae80b7ed1409",
}).html('');
const areaLabel = $("");
areaLabel.attr("for", "b5970252-6c7e-4603-928f-ae80b7ed1409").text("选择游戏大区:");
area.append(areaLabel).append(areaSelect).appendTo(characterSelectorContainer);
const character = $("");
character.css("margin", ".5rem 0");
const characterSelect = $("");
characterSelect.attr({
disabled: "disabled",
id: "f601bc1a-95c8-42ce-81b3-0081bec56f58",
}).html('');
const characterLabel = $("");
characterLabel.attr("for", "f601bc1a-95c8-42ce-81b3-0081bec56f58").text("选择游戏角色:");
character.append(characterLabel).append(characterSelect).appendTo(characterSelectorContainer);
const buttonRow = $("");
buttonRow.css("margin", ".5rem 0");
const confirmButton = $("");
confirmButton.css({
"background-color": "#ce0f30",
border: "1px solid #ce0f30",
"border-radius": "4px",
"box-sizing": "border-box",
color: "#FFF",
cursor: "pointer",
display: "inline-block",
"font-family": "inherit",
"font-size": "14px",
"font-weight": "500",
"line-height": "1",
margin: "0",
outline: "0",
overflow: "visible",
padding: "12px 20px",
"text-align": "center",
"text-transform": "none",
transition: ".1s",
"white-space": "nowrap",
}).text("确认批量领取在此角色");
const cancelButton = $("");
cancelButton.css({
"background-color": "rgb(51, 51, 51)",
border: "1px solid rgb(51, 51, 51)",
"border-radius": "4px",
"box-sizing": "border-box",
color: "#FFF",
cursor: "pointer",
display: "inline-block",
"font-family": "inherit",
"font-size": "14px",
"font-weight": "500",
"line-height": "1",
margin: "0 0 0 10px",
outline: "0",
overflow: "visible",
padding: "12px 20px",
"text-align": "center",
"text-transform": "none",
transition: ".1s",
"white-space": "nowrap",
}).text("取消");
cancelButton.on("click", () => {
submitButton.add(selectAllButton).add(selectNoneButton).add(closeButton).add(resultCotainer.find("input")).removeAttr("disabled");
characterSelectorContainer.remove();
});
buttonRow.append(confirmButton).append(cancelButton).appendTo(characterSelectorContainer);
characterSelectorContainer.appendTo("body");
try {
const areaResult = await $.ajax({
data: {
_: Math.random,
appId: 100001900,
},
type: "GET",
url: "https://sqmallservice.u.sdo.com/api/us/accountInfo/getArea",
xhrFields: {
withCredentials: true,
},
});
if (areaResult.resultCode !== 0) {
alert(`请求发生错误!错误信息:[${areaResult.resultCode}] ${areaResult.resultMsg}`);
cancelButton.click();
return;
}
const areas = JSON.parse(areaResult.data);
const defaultArea = GM_getValue("areaSelected", "-1");
areaSelect.removeAttr("disabled").html(defaultArea === "-1" ? '' : "");
areas.forEach(({ areaId, areaName }) => {
areaSelect.append(``);
});
areaSelect.on("change", async () => {
const areaId = areaSelect.val();
if (areaId === "-1") {
characterSelect.html('');
return;
}
try {
const characterResult = await $.ajax({
data: {
_: Math.random,
appId: 100001900,
areaId: areaId.replace(/-.+$/, ""),
},
type: "GET",
url: "https://sqmallservice.u.sdo.com/api/us/accountInfo/getCharacter",
xhrFields: {
withCredentials: true,
},
});
if (characterResult.resultCode !== 0) {
alert(`请求发生错误!错误信息:[${characterResult.resultCode}] ${characterResult.resultMsg}`);
cancelButton.click();
return;
}
const defaultCharacter = GM_getValue("characterSelected", "-1");
const characters = characterResult.data.roleInfos;
characterSelect.removeAttr("disabled").html(defaultCharacter === "-1" ? '' : "");
characters.forEach(({ characterId, groupId, groupName, roleName }) => {
characterSelect.append(``);
});
characterSelect.val(defaultCharacter);
}
catch (e) {
alert(`发生网络错误!错误信息:${e}`);
cancelButton.click();
return;
}
});
areaSelect.val(defaultArea).change();
confirmButton.on("click", async () => {
const characterSelected = characterSelect.val();
const areaSelected = areaSelect.val();
if (!/([^-]+)-([^-]+)/.test(areaSelected)) {
alert("请选择大区!");
return;
}
if (!/([^-]+)-([^-]+)-([^-]+)-([^-]+)/.test(characterSelected)) {
alert("请选择角色!");
return;
}
const [, areaId, areaName] = Array.from(areaSelected.match(/([^-]+)-([^-]+)/));
const [, groupId, groupName, characterId, roleName] = Array.from(characterSelected.match(/([^-]+)-([^-]+)-([^-]+)-([^-]+)/));
if (!confirm(`确定要将 ${confirmText} 领取到 [${groupName}]${roleName} 上吗?`)) {
return;
}
GM_setValue("characterSelected", characterSelected);
GM_setValue("areaSelected", areaSelected);
submitButton.add(selectAllButton).add(selectNoneButton).add(closeButton).remove().off();
let successCount = 0;
let failedCount = 0;
const log = count.add(characterSelectorContainer);
const target = choosed.paid.concat(choosed.free);
log.text(`成功:0 失败:0 / ${target.length}`);
let startFlag = true;
for (const propsWarehouseId of target) {
if (startFlag) {
startFlag = false;
await new Promise((res) => setTimeout(res, 1500));
}
try {
const apiResult = await $.ajax({
data: {
_: Math.random(),
appId: "100001900",
areaId,
areaName,
characterId,
groupId,
groupName,
propsWarehouseId,
roleName,
},
type: "GET",
url: "https://sqmallservice.u.sdo.com/api/us/gameItem/spend",
xhrFields: {
withCredentials: true,
},
});
if (apiResult.resultCode === 0 && apiResult.data.success) {
successCount++;
}
else {
failedCount++;
}
} catch {
failedCount++;
}
log.text(`成功:${successCount} 失败:${failedCount} / ${target.length}`);
}
setTimeout(() => {
alert(`批量领取完成!共成功 ${successCount} 个,失败 ${failedCount} 个!`);
location.reload(false);
}, 50);
});
}
catch (e) {
alert(`发生网络错误!错误信息:${e}`);
cancelButton.click();
return;
}
}
});
});
})();