269 lines
10 KiB
Vue
269 lines
10 KiB
Vue
<template>
|
|
<el-row>
|
|
<el-col :span="22">
|
|
<el-select-v2 v-model="itemName" :options="itemNameList" clearable filterable placeholder="物品名"
|
|
style="width: 99%"/>
|
|
</el-col>
|
|
<el-col :span="2">
|
|
<el-button :disabled="!itemName" :icon="Search" :loading="listLoading"
|
|
@click="doGetIMarketList(itemName2IdMap[itemName])">查询
|
|
</el-button>
|
|
</el-col>
|
|
</el-row>
|
|
<el-skeleton :loading="listLoading" :rows="5" animated style="margin-top: 1em">
|
|
<template #default>
|
|
<el-table :data="itemOnList" empty-text="暂无数据" style="width: 100%" table-layout="auto">
|
|
<el-table-column label="卖家">
|
|
<template #default="scope">
|
|
<el-link :href="'/bazaar.php?userId=' + scope.row.playerId" :underline="false" target="_blank"
|
|
type="primary" v-text="scope.row.playerName + `[${scope.row.playerId}]`"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="单价">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center"
|
|
v-text="'$' + toThousands(scope.row.price)"></div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="在售" prop="amount"/>
|
|
<el-table-column label="">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.inputAmount" style="width: 12em">
|
|
<template #append>
|
|
<el-button :icon="ShoppingCart" :loading="scope.row.onBuying" type="primary"
|
|
@click="buy(scope.row)">购买
|
|
</el-button>
|
|
</template>
|
|
</el-input>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-row v-if="queryTs">
|
|
<el-col :span="2"><span><el-icon><Timer/></el-icon>{{ timePastFormat(currentTs - queryTs) }}</span>
|
|
</el-col>
|
|
<el-col :span="3">
|
|
<span :style="{ backgroundImage: 'url(/images/items/' + itemId + '/small.png)' }"
|
|
style="display:inline-block;width:38px;height:19px;"></span>
|
|
</el-col>
|
|
</el-row>
|
|
</template>
|
|
</el-skeleton>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
export default {
|
|
name: "MarketHelper"
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import { Search, ShoppingCart, Timer } from '@element-plus/icons-vue';
|
|
import { ElMessage } from "element-plus";
|
|
import { inject, onBeforeUnmount, onMounted, ref } from "vue";
|
|
import { LoggerKey } from "../ts/class/Logger";
|
|
import { ItemHelperKey } from "../ts/class/utils/ItemHelper";
|
|
import { itemNameDict } from "../ts/dictionary/translation";
|
|
import toThousands from "../ts/func/utils/toThousands";
|
|
import Sleep from "../ts/func/utils/Sleep";
|
|
import { MathUtilsKey } from "../ts/class/utils/MathUtils";
|
|
|
|
const logger = inject(LoggerKey);
|
|
const itemHelper = inject(ItemHelperKey);
|
|
const mathUtils = inject(MathUtilsKey)
|
|
|
|
let itemName2IdMap: { [p: string]: number } = null;
|
|
const itemName = ref<string>('');
|
|
const itemNameList = ref<{ value: string, label: string }[]>([]);
|
|
const itemId = ref<number>();
|
|
const queryTs = ref<number>(0);
|
|
type ItemInfo = {
|
|
playerId: string,
|
|
playerName: string,
|
|
price: number,
|
|
amount: number,
|
|
inputAmount: number,
|
|
onBuying: boolean,
|
|
itemId: string,
|
|
};
|
|
const itemOnList = ref<ItemInfo[]>([]);
|
|
const listLoading = ref<boolean>(false);
|
|
const currentTs = ref<number>(Date.now());
|
|
|
|
const timePastFormat = (ts: number): string => {
|
|
// 毫秒
|
|
if (ts < 1000) {
|
|
return ts + 'ms';
|
|
}
|
|
// 秒
|
|
else if (ts < 60000) {
|
|
return (ts / 1000 | 0) + 's';
|
|
}
|
|
// 分
|
|
else if (ts < 3600000) {
|
|
return (ts / 60000 | 0) + 'm';
|
|
}
|
|
// 时
|
|
else {
|
|
return (ts / 3600000 | 0) + 'h';
|
|
}
|
|
};
|
|
|
|
const doGetIMarketList = async (id: number) => {
|
|
itemOnList.value = [];
|
|
listLoading.value = true;
|
|
queryTs.value = 0;
|
|
itemId.value = id;
|
|
const queryItemListHtml = await (await fetch(window.addRFC("https://www.torn.com/imarket.php"), {
|
|
"headers": {
|
|
"accept": "*/*",
|
|
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
"sec-ch-ua-mobile": "?0",
|
|
"sec-fetch-dest": "empty",
|
|
"sec-fetch-mode": "cors",
|
|
"sec-fetch-site": "same-origin",
|
|
"x-requested-with": "XMLHttpRequest"
|
|
},
|
|
"referrer": "https://www.torn.com/imarket.php",
|
|
"referrerPolicy": "strict-origin-when-cross-origin",
|
|
"body": "step=getShopList1&itemID=" + id,
|
|
"method": "POST",
|
|
"mode": "cors",
|
|
"credentials": "include"
|
|
})).text();
|
|
queryTs.value = Date.now();
|
|
let resError = '';
|
|
try {
|
|
const res = JSON.parse(queryItemListHtml);
|
|
if (!res.success) {
|
|
resError = res.error || res.text;
|
|
ElMessage.error(resError);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
listLoading.value = false;
|
|
|
|
// 无法获取bazaar html内容
|
|
if (resError) {
|
|
throw new Error(resError);
|
|
}
|
|
let container = document.createElement('div');
|
|
container.innerHTML = queryItemListHtml;
|
|
const list = container.querySelectorAll('ul.items li.private-bazaar');
|
|
logger.info({ list });
|
|
list.forEach((li, index) => {
|
|
let item = {
|
|
playerId: null,
|
|
playerName: null,
|
|
price: null,
|
|
amount: null,
|
|
inputAmount: null,
|
|
index,
|
|
onBuying: false,
|
|
itemId: null,
|
|
};
|
|
item.playerId = li.querySelector("a.user.name").getAttribute('href').trim().split('XID=')[1];
|
|
item.playerName = li.querySelector("a.user.name span").innerText.trim();
|
|
item.price = Number(li.querySelector("span.cost-price").innerText.trim().replace('$', '').replaceAll(',', ''));
|
|
item.amount = Number(li.querySelector("span.cost-amount").innerText.trim().split(' ')[0].replace('(', "").replaceAll(',', ''));
|
|
item.inputAmount = item.amount;
|
|
item.itemId = id;
|
|
itemOnList.value.push(item);
|
|
});
|
|
container = null;
|
|
};
|
|
const buy = async (itemInfo: ItemInfo) => {
|
|
itemInfo.onBuying = true;
|
|
try {
|
|
// 获取玩家id bazaar信息
|
|
let bazaarId: string;
|
|
const bazaarItemList: { list: { bazaarID: string, itemID: string }[] } = await (await fetch(
|
|
`https://www.torn.com/bazaar.php?sid=bazaarData&step=getBazaarItems&start=0&ID=${ itemInfo.playerId }&order=default&by=asc&categorised=0&limit=1000&searchname=`,
|
|
{
|
|
"headers": {
|
|
"accept": "*/*",
|
|
"sec-ch-ua-mobile": "?0",
|
|
"sec-fetch-dest": "empty",
|
|
"sec-fetch-mode": "cors",
|
|
"sec-fetch-site": "same-origin",
|
|
"x-requested-with": "XMLHttpRequest"
|
|
},
|
|
"referrer": "https://www.torn.com/bazaar.php?userId=" + itemInfo.playerId,
|
|
"referrerPolicy": "strict-origin-when-cross-origin",
|
|
"body": null,
|
|
"method": "GET",
|
|
"mode": "cors",
|
|
"credentials": "include"
|
|
})).json();
|
|
for (let i = 0; i < bazaarItemList.list.length; i++) {
|
|
if (bazaarItemList.list[i].itemID === itemInfo.itemId.toString()) {
|
|
bazaarId = bazaarItemList.list[i].bazaarID;
|
|
break;
|
|
}
|
|
}
|
|
if (!bazaarId) {
|
|
ElMessage.error('上架商品未找到');
|
|
logger.error('物品id未找到对应bazaar项目', bazaarItemList, itemInfo.itemId);
|
|
(() => {
|
|
throw new Error('bazaarId为空')
|
|
})();
|
|
}
|
|
await Sleep(mathUtils.getRandomInt(100, 110))
|
|
// 从baz买货
|
|
let buyResponse = await (await fetch("https://www.torn.com/bazaar.php?sid=bazaarData&step=buyItem", {
|
|
"headers": {
|
|
"accept": "*/*",
|
|
"sec-ch-ua-mobile": "?0",
|
|
"sec-fetch-dest": "empty",
|
|
"sec-fetch-mode": "cors",
|
|
"sec-fetch-site": "same-origin",
|
|
"x-requested-with": "XMLHttpRequest"
|
|
},
|
|
"referrer": "https://www.torn.com/bazaar.php?userId=" + itemInfo.playerId,
|
|
"referrerPolicy": "strict-origin-when-cross-origin",
|
|
"body": (() => {
|
|
const data = new FormData();
|
|
data.append('userID', itemInfo.playerId);
|
|
data.append('id', bazaarId);
|
|
data.append('itemID', itemInfo.itemId.toString());
|
|
data.append('amount', itemInfo.inputAmount.toString());
|
|
data.append('price', itemInfo.price.toString());
|
|
data.append('beforeval', (itemInfo.price * itemInfo.inputAmount).toString());
|
|
return data;
|
|
})(),
|
|
"method": "POST",
|
|
"mode": "cors",
|
|
"credentials": "include"
|
|
})).json();
|
|
if (!buyResponse.success) {
|
|
ElMessage.error('购买失败 ' + buyResponse.text);
|
|
}
|
|
} catch (e) {
|
|
logger.error(e.stack);
|
|
ElMessage.error('出错了 ' + e.message);
|
|
}
|
|
itemInfo.onBuying = false;
|
|
};
|
|
|
|
let intervalId = 0;
|
|
onMounted(async () => {
|
|
intervalId = window.setInterval(() => currentTs.value = Date.now(), 100);
|
|
let map = itemHelper.getItemNameMap();
|
|
if (map.data) {
|
|
itemName2IdMap = map.data;
|
|
} else {
|
|
itemName2IdMap = await map.promise;
|
|
}
|
|
Object.keys(itemName2IdMap).forEach(itemName => {
|
|
itemNameList.value.push({
|
|
value: itemName,
|
|
label: itemNameDict[itemName] ? itemName + ` (${ itemNameDict[itemName] })` : itemName,
|
|
})
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => window.clearInterval(intervalId));
|
|
</script>
|
|
|
|
<style scoped></style>
|