This commit is contained in:
Liwanyi 2023-06-26 18:13:47 +08:00
parent 8146b165f9
commit e7effb0881
11 changed files with 369 additions and 22 deletions

View File

@ -5,6 +5,14 @@
# CHANGE # CHANGE
## 1.0.6
2023年06月26日
### 添加
- 物品功能标签
## 1.0.5 ## 1.0.5
2023年06月19日 2023年06月19日

View File

@ -1,6 +1,6 @@
{ {
"name": "wuhu-torn-helper", "name": "wuhu-torn-helper",
"version": "1.0.5", "version": "1.0.6",
"description": "芜湖助手", "description": "芜湖助手",
"scripts": { "scripts": {
"release": "cross-env NODE_ENV=production rollup -c && node build.mjs", "release": "cross-env NODE_ENV=production rollup -c && node build.mjs",

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@ import TravelItem, { TravelItemKey } from "./action/TravelItem";
import QuickGymTrain, { QuickGymTrainKey } from "./action/QuickGymTrain"; import QuickGymTrain, { QuickGymTrainKey } from "./action/QuickGymTrain";
import QuickFlyBtnHandler, { QuickFlyBtnHandlerKey } from "./handler/QuickFlyBtnHandler"; import QuickFlyBtnHandler, { QuickFlyBtnHandlerKey } from "./handler/QuickFlyBtnHandler";
import ItemHelper, { ItemHelperKey } from "./utils/ItemHelper"; import ItemHelper, { ItemHelperKey } from "./utils/ItemHelper";
import MathUtils, { MathUtilsKey } from "./utils/MathUtils";
@ClassName("IconHelper") @ClassName("IconHelper")
@Injectable() @Injectable()
@ -31,6 +32,7 @@ export default class IconHelper {
private readonly quickGymTrain: QuickGymTrain, private readonly quickGymTrain: QuickGymTrain,
private readonly quickFlyBtnHandler: QuickFlyBtnHandler, private readonly quickFlyBtnHandler: QuickFlyBtnHandler,
private readonly itemHelper: ItemHelper, private readonly itemHelper: ItemHelper,
private readonly mathUtils: MathUtils,
) { ) {
this._element = document.createElement('div'); this._element = document.createElement('div');
} }
@ -47,6 +49,7 @@ export default class IconHelper {
app.config.warnHandler = (err) => this.logger.warn('[VUE警告]', err); app.config.warnHandler = (err) => this.logger.warn('[VUE警告]', err);
app.provide(LoggerKey, this.logger); app.provide(LoggerKey, this.logger);
app.provide(CommonUtilsKey, this.commonUtils); app.provide(CommonUtilsKey, this.commonUtils);
app.provide(MathUtilsKey, this.mathUtils);
app.provide(TravelItemKey, this.travelItem); app.provide(TravelItemKey, this.travelItem);
app.provide(PopupWrapperKey, this.popupWrapper); app.provide(PopupWrapperKey, this.popupWrapper);
app.provide(LocalConfigWrapperKey, this.localConfigWrapper); app.provide(LocalConfigWrapperKey, this.localConfigWrapper);

View File

@ -374,8 +374,7 @@ export default class TranslateNew extends Provider implements ResponseInject {
name.innerText = `${ name.innerText } ${ nameZh }`; name.innerText = `${ name.innerText } ${ nameZh }`;
} }
// 操作按钮 // 操作按钮
let actions = elem.querySelectorAll('.icon-h'); elem.querySelectorAll('.icon-h').forEach(action => {
actions.forEach(action => {
let attrTitle = action.getAttribute('title'); let attrTitle = action.getAttribute('title');
// TODO // TODO
let zh = itemPageDict[attrTitle]; let zh = itemPageDict[attrTitle];

View File

@ -1,5 +1,6 @@
import ClassName from "../../container/ClassName"; import ClassName from "../../container/ClassName";
import { Injectable } from "../../container/Injectable"; import { Injectable } from "../../container/Injectable";
import { InjectionKey } from "vue";
@ClassName('MathUtils') @ClassName('MathUtils')
@Injectable() @Injectable()
@ -14,3 +15,5 @@ export default class MathUtils {
return Math.floor(Math.random() * (max - min)) + min; return Math.floor(Math.random() * (max - min)) + min;
} }
} }
export const MathUtilsKey = Symbol('MathUtilsKey') as InjectionKey<MathUtils>

View File

@ -0,0 +1,5 @@
const Sleep = (ms: number = 0) => {
return new Promise(resolve => window.setTimeout(resolve, ms))
}
export default Sleep

View File

@ -0,0 +1,21 @@
const equipItem = (itemId: string, armoryId: string, type: number) => {
return fetch(window.addRFC("https://www.torn.com/item.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/item.php",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `step=actionForm&item_id=${ itemId }&armour-from-set=&type=${ type }&action=equip&item=${ itemId }&id=${ armoryId }&confirm=1`,
"method": "POST",
"mode": "cors",
"credentials": "include"
})
}
export default equipItem

View File

@ -15,9 +15,9 @@
</el-badge> </el-badge>
</el-button> </el-button>
</el-button-group> </el-button-group>
<el-dialog v-model="drawer" :destroy-on-close="false" :fullscreen="isMobilePhone" :lock-scroll="true" <el-dialog v-model="drawer" :fullscreen="isMobilePhone" :lock-scroll="true"
:title="editableTabsValue" width="65%"> width="65%">
<el-tabs v-model="editableTabsValue" closable type="card" @tab-remove="removeTab"> <el-tabs v-model="editableTabsValue" closable style="margin-top: -1em" type="border-card" @tab-remove="removeTab">
<el-tab-pane v-for="item in editableTabs" :key="item.name" :label="item.title" :name="item.name"> <el-tab-pane v-for="item in editableTabs" :key="item.name" :label="item.title" :name="item.name">
<component :is="item.content"/> <component :is="item.content"/>
</el-tab-pane> </el-tab-pane>
@ -82,20 +82,20 @@
<el-menu-item @click="quickGymTrain.doTrain(BATTLE_STAT.DEX)">闪避 <el-menu-item @click="quickGymTrain.doTrain(BATTLE_STAT.DEX)">闪避
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu>
<el-menu-item v-for="(item, i) in menuItemList" :index="(3 + i).toString()" @click="menuClick(item)"> <el-sub-menu index="3">
<el-icon>{{ item.title.slice(0, 2) }}</el-icon>
<span>{{ item.title.slice(2, item.title.length) }}</span>
</el-menu-item>
<el-sub-menu :index="2 + menuItemList.length + 1 + ''">
<template #title> <template #title>
<el-icon>🪓</el-icon> <el-icon>🪓</el-icon>
<span>老功能</span> <span>老功能</span>
</template> </template>
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 1" @click="_adHelper">📜 传单助手</el-menu-item> <el-menu-item index="3-1" @click="_adHelper">📜 传单助手</el-menu-item>
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 2" @click="_safeKeeper">🛡 守望者</el-menu-item> <el-menu-item index="3-2" @click="_safeKeeper">🛡 守望者</el-menu-item>
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 3" @click="MUZHUANG">🌲 寻找木桩</el-menu-item> <el-menu-item index="3-3" @click="MUZHUANG">🌲 寻找木桩</el-menu-item>
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 4" @click="_setting"> 助手设置</el-menu-item> <el-menu-item index="3-4" @click="_setting"> 助手设置</el-menu-item>
</el-sub-menu> </el-sub-menu>
<el-menu-item v-for="(item, i) in menuItemList" :index="(4 + i).toString()" @click="menuClick(item)">
<el-icon>{{ item.title.slice(0, 2) }}</el-icon>
<span>{{ item.title.slice(2, item.title.length) }}</span>
</el-menu-item>
</el-menu> </el-menu>
</el-drawer> </el-drawer>
</el-config-provider> </el-config-provider>
@ -127,6 +127,7 @@ import PTMarketView from "./PTMarketView.vue";
import QuickCrime from "./QuickCrime.vue"; import QuickCrime from "./QuickCrime.vue";
import UpdateDate from "./UpdateScript.vue"; import UpdateDate from "./UpdateScript.vue";
import VirusProgramming from "./VirusProgramming.vue"; import VirusProgramming from "./VirusProgramming.vue";
import InventoryView from "./InventoryView.vue";
const logger = inject(LoggerKey); const logger = inject(LoggerKey);
const quickGymTrain = inject(QuickGymTrainKey); const quickGymTrain = inject(QuickGymTrainKey);
@ -208,6 +209,10 @@ const menuItemList: MenuItem[] = [
title: '🫵 关闭店铺(双击开启)', title: '🫵 关闭店铺(双击开启)',
handler: () => bazaarControl.method(), handler: () => bazaarControl.method(),
}, },
{
title: '📦 物品',
template: InventoryView,
},
{ {
title: '🚀 更新历史', title: '🚀 更新历史',
handler: () => { handler: () => {

300
src/vue/InventoryView.vue Normal file
View File

@ -0,0 +1,300 @@
<script lang="ts" setup>
import { Check, Delete, Promotion } from "@element-plus/icons-vue"
import { ElMessage } from "element-plus"
import { inject, onMounted, ref } from "vue"
import { LoggerKey } from "../ts/class/Logger"
import { itemNameDict } from "../ts/dictionary/translation"
import Sleep from "../ts/func/utils/Sleep"
import equipItem from "../ts/func/utils/equipItem"
import useItem from "../ts/func/utils/useItem"
const logger = inject(LoggerKey)
type Item = {
id: number
name: string
nameZh: string
amount: number
isEquipped: boolean
details: ItemDetails
isDetailsLoading: boolean
armoryId: number
damage: number
accuracy: number
defence: number
type: number
}
type ItemDetails = {
itemType: string
itemCost: string
itemValue: string
itemRareTitle: string
extras: { icon: string; cl: string; type: string; title: string; value: string }[]
itemInfo: string
itemSell: string
itemID: number
itemName: string
itemInfoContent: string
itemCirculation: string
armoryID: boolean
itemRareIcon: string
}
const itemList = ref<Item[]>([])
const disabledInfiniteLoading = ref(false)
const categorySelected = ref('Drug')
const categories = [
{ label: '📦 ', value: '' },
{ label: '主', value: 'Primary' },
{ label: '副', value: 'Secondary' },
{ label: '🔪 ', value: 'Melee' },
{ label: '💣 ', value: 'Temporary' },
{ label: '🛡 ', value: 'Defensive' },
{ label: '👔 ', value: 'Clothing' },
{ label: '🏥 ', value: 'Medical' },
{ label: '💊 ', value: 'Drug' },
{ label: '🥤 ', value: 'Energy+Drink' },
{ label: '🍺 ', value: 'Alcohol' },
{ label: '🍬 ', value: 'Candy' },
{ label: '⬆️ ', value: 'Booster' },
{ label: '🪄 ', value: 'Enhancer' },
{ label: '📦 ', value: 'Supply+Pack' },
{ label: '🔌 ', value: 'Electronic' },
{ label: '💎 ', value: 'Jewelry' },
{ label: '🌹 ', value: 'Flower' },
{ label: '🧸 ', value: 'Plushie' },
{ label: '🚗 ', value: 'Car' },
{ label: '🦠 ', value: 'Virus' },
{ label: '📖 ', value: 'Book' },
{ label: '🌟', value: 'Special' },
{ label: '🛍 ', value: 'Other' },
// TODO
{ label: '🗼 ', value: '' },
{ label: '🏆 ', value: '' },
]
const fetchList = async (start: number = 0): Promise<string> => {
let json: { html?: string, error?: string, text?: string }
try {
json = await (await fetch(window.addRFC("https://www.torn.com/item.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/item.php",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": categorySelected.value ? `step=getCategoryList&itemName=${ categorySelected.value }&start=${ start }&test=true&prevtotal=0` : `step=getNotAllItemsListWithoutGroups&start=${ start }&queue=All`,
"method": "POST",
"mode": "cors",
"credentials": "include"
})).json()
} catch (e) {
logger.error(e.stack)
ElMessage.error('物品获取失败 ' + e.message)
throw e
}
if (json.html) return json.html
else {
const err = json.error || json.text || '获取物品列表时没有内容'
logger.error(err)
ElMessage.error('获取物品失败 ' + err)
throw new Error(err)
}
}
const fetchItemDetails = async (id: number): Promise<ItemDetails> => {
let ret: ItemDetails = null
try {
ret = await (await fetch(window.addRFC("https://www.torn.com/page.php?sid=inventory"), {
"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/item.php",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "itemID=" + id,
"method": "POST",
"mode": "cors",
"credentials": "include"
})).json()
} catch (e) {
logger.error(e.stack)
ElMessage.error('物品详情获取失败 ' + e.message)
throw e
}
return ret
}
const parseListHtml = (html: string) => {
const ret: Item[] = []
let tmp = document.createElement('div')
tmp.innerHTML = html
tmp.childNodes.forEach(li => {
if (li.nodeType === 1) {
const item: Item = {
type: 0,
defence: 0,
accuracy: 0, damage: 0,
armoryId: 0,
details: undefined,
isDetailsLoading: false,
amount: -1, isEquipped: false, id: -1, name: "", nameZh: ""
}
let elem = li as HTMLElement
//
let name: HTMLElement = elem.querySelector('.name-wrap .name')
item.name = name.innerText.trim()
item.nameZh = itemNameDict[item.name]
//
item.amount = Number(elem.getAttribute('data-qty'))
// id
item.id = Number(elem.getAttribute('data-item'))
//
item.isEquipped = elem.getAttribute('data-equipped').trim() === 'true'
// id
item.armoryId = Number(elem.getAttribute('data-armoryid'))
//
item.damage = Number(elem.querySelector('.bonus-attachment-item-damage-bonus')?.nextElementSibling.innerText?.trim())
//
item.accuracy = Number(elem.querySelector('.bonus-attachment-item-accuracy-bonus')?.nextElementSibling.innerText?.trim())
//
item.defence = Number(elem.querySelector('.bonus-attachment-item-defence-bonus')?.nextElementSibling.innerText?.trim())
//
item.type = Number(elem.querySelector('ul.actions-wrap li[data-type]')?.getAttribute('data-type').trim())
ret.push(item)
}
})
return ret
}
let mouseOverItemId: number
const itemHover = async (item: Item) => {
const thisId = item.id
mouseOverItemId = item.id
await Sleep(300)
if (thisId === mouseOverItemId && !item.isDetailsLoading && !item.details) {
item.isDetailsLoading = true
try {
item.details = await fetchItemDetails(item.id)
} catch (e) {
}
item.isDetailsLoading = false
}
}
const loadMore = async () => {
if (disabledInfiniteLoading.value) return
disabledInfiniteLoading.value = true
let newItems: Item[]
try {
newItems = parseListHtml(await fetchList(itemList.value.length))
} catch (e) {
if (e.message !== '获取物品列表时没有内容') {
ElMessage.error(e.message)
logger.error(e.stack)
throw e
}
}
newItems.forEach(item => itemList.value.push(item))
window.setTimeout(() => disabledInfiniteLoading.value = newItems.length < 1, 500)
}
const loadCategory = async (type: string) => {
disabledInfiniteLoading.value = true
categorySelected.value = type
itemList.value = []
itemList.value = parseListHtml(await fetchList(itemList.value.length))
window.setTimeout(() => disabledInfiniteLoading.value = false, 500)
}
const _equipItem = async (item: Item) => {
await equipItem(String(item.id), String(item.armoryId), item.type)
item.isEquipped = !item.isEquipped
}
onMounted(async () => {
itemList.value = parseListHtml(await fetchList())
})
</script>
<template>
<el-button-group>
<el-button v-for="item in categories" :disabled="item.value === categorySelected" @click="loadCategory(item.value)">
{{ item.label }}
</el-button>
</el-button-group>
<el-space size="default" wrap>
<div v-for="item in itemList" v-infinite-scroll="loadMore" :infinite-scroll-delay="500"
:infinite-scroll-disabled="disabledInfiniteLoading">
<el-badge :hidden="item.amount < 2" :value="item.amount">
<el-popover :show-after="300" :width="240" trigger="hover">
<template #reference>
<el-card :body-style="{ padding: '0px' }" :style="item.isEquipped ? { borderColor: '#32CD32' } : null"
shadow="hover">
<el-image :alt="item.id.toString()" :mouseover="itemHover(item)"
:src="`/images/items/${item.id}/medium.png`" style="width: 60px;height: 30px"/>
<el-row v-if="item.damage > 0 && item.accuracy > 0">
<el-col :span="12">
<el-tag>{{ item.damage }}</el-tag>
</el-col>
<el-col :span="12">
<el-tag>{{ item.accuracy }}</el-tag>
</el-col>
</el-row>
<el-row v-if="item.defence > 0">
<el-col :span="24">
<el-tag>{{ item.defence }}</el-tag>
</el-col>
</el-row>
</el-card>
</template>
<el-row>
<el-text v-if="item.nameZh" size="large">{{ item.nameZh }}</el-text>
</el-row>
<el-row>
<el-text>{{ item.name }}<span v-if="item.armoryId"> - {{ item.armoryId }}</span></el-text>
</el-row>
<el-row v-if="item.damage">
<el-text>攻击: {{ item.damage }}</el-text>
</el-row>
<el-row v-if="item.accuracy">
<el-text>命中: {{ item.accuracy }}</el-text>
</el-row>
<el-row v-if="item.defence">
<el-text>防御: {{ item.defence }}</el-text>
</el-row>
<el-row :gutter="4">
<el-col :span="8">
<el-button :icon="Check" @click="item.armoryId ? _equipItem(item) : useItem(item.id.toString())"/>
</el-col>
<el-col :span="8">
<el-button :icon="Delete" disabled/>
</el-col>
<el-col :span="8">
<el-button :icon="Promotion" disabled/>
</el-col>
</el-row>
<div v-if="item.details || item.isDetailsLoading" v-loading="item.isDetailsLoading"
v-html="item.details?.itemInfoContent || ''"></div>
<el-row v-if="item.details">
<el-text>估值: {{ item.details.itemValue }}</el-text>
</el-row>
</el-popover>
</el-badge>
</div>
</el-space>
</template>
<style scoped></style>

View File

@ -65,9 +65,12 @@ import { LoggerKey } from "../ts/class/Logger";
import { ItemHelperKey } from "../ts/class/utils/ItemHelper"; import { ItemHelperKey } from "../ts/class/utils/ItemHelper";
import { itemNameDict } from "../ts/dictionary/translation"; import { itemNameDict } from "../ts/dictionary/translation";
import toThousands from "../ts/func/utils/toThousands"; 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 logger = inject(LoggerKey);
const itemHelper = inject(ItemHelperKey); const itemHelper = inject(ItemHelperKey);
const mathUtils = inject(MathUtilsKey)
let itemName2IdMap: { [p: string]: number } = null; let itemName2IdMap: { [p: string]: number } = null;
const itemName = ref<string>(''); const itemName = ref<string>('');
@ -205,7 +208,7 @@ const buy = async (itemInfo: ItemInfo) => {
throw new Error('bazaarId为空') throw new Error('bazaarId为空')
})(); })();
} }
await Sleep(mathUtils.getRandomInt(100, 110))
// baz // baz
let buyResponse = await (await fetch("https://www.torn.com/bazaar.php?sid=bazaarData&step=buyItem", { let buyResponse = await (await fetch("https://www.torn.com/bazaar.php?sid=bazaarData&step=buyItem", {
"headers": { "headers": {