mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2025-12-19 06:25:45 +00:00
refactor: remove exceptions from parser
This commit is contained in:
@@ -30,6 +30,7 @@ module.exports = {
|
|||||||
'@typescript-eslint/no-floating-promises': 'off',
|
'@typescript-eslint/no-floating-promises': 'off',
|
||||||
'@typescript-eslint/no-misused-promises': 'off',
|
'@typescript-eslint/no-misused-promises': 'off',
|
||||||
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
|
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
|
||||||
|
'@typescript-eslint/no-invalid-void-type': 'off',
|
||||||
// TODO: refactor IPC and enable
|
// TODO: refactor IPC and enable
|
||||||
'@typescript-eslint/consistent-type-assertions': 'off'
|
'@typescript-eslint/consistent-type-assertions': 'off'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fastest-levenshtein": "^1.0.16",
|
"fastest-levenshtein": "^1.0.16",
|
||||||
"luxon": "3.x.x",
|
"luxon": "3.x.x",
|
||||||
|
"neverthrow": "^6.0.0",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"tailwindcss": "3.x.x",
|
"tailwindcss": "3.x.x",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Result, ok, err } from 'neverthrow'
|
||||||
import {
|
import {
|
||||||
CLIENT_STRINGS as _$,
|
CLIENT_STRINGS as _$,
|
||||||
CLIENT_STRINGS_REF as _$REF,
|
CLIENT_STRINGS_REF as _$REF,
|
||||||
@@ -20,9 +21,9 @@ type SectionParseResult =
|
|||||||
| 'PARSER_SKIPPED'
|
| 'PARSER_SKIPPED'
|
||||||
|
|
||||||
type ParserFn = (section: string[], item: ParserState) => SectionParseResult
|
type ParserFn = (section: string[], item: ParserState) => SectionParseResult
|
||||||
type VirtualParserFn = (item: ParserState) => void
|
type VirtualParserFn = (item: ParserState) => Result<never, string> | void
|
||||||
|
|
||||||
export interface ParserState extends ParsedItem {
|
interface ParserState extends ParsedItem {
|
||||||
name: string
|
name: string
|
||||||
baseType: string | undefined
|
baseType: string | undefined
|
||||||
infoVariants: BaseType[]
|
infoVariants: BaseType[]
|
||||||
@@ -70,11 +71,51 @@ const parsers: Array<ParserFn | { virtual: VirtualParserFn }> = [
|
|||||||
{ virtual: calcBasePercentile }
|
{ virtual: calcBasePercentile }
|
||||||
]
|
]
|
||||||
|
|
||||||
export function parseClipboard (clipboard: string) {
|
export function parseClipboard (clipboard: string): Result<ParsedItem, string> {
|
||||||
const lines = clipboard.split(/\r?\n/)
|
try {
|
||||||
|
let sections = itemTextToSections(clipboard)
|
||||||
|
|
||||||
|
if (sections[0][2] === _$.CANNOT_USE_ITEM) {
|
||||||
|
sections[0].pop() // remove CANNOT_USE_ITEM line
|
||||||
|
sections[1].unshift(...sections[0]) // prepend item class & rarity into second section
|
||||||
|
sections.shift() // remove first section where CANNOT_USE_ITEM line was
|
||||||
|
}
|
||||||
|
const parsed = parseNamePlate(sections[0])
|
||||||
|
if (!parsed.isOk()) return parsed
|
||||||
|
|
||||||
|
sections.shift()
|
||||||
|
parsed.value.rawText = clipboard
|
||||||
|
|
||||||
|
// each section can be parsed at most by one parser
|
||||||
|
for (const parser of parsers) {
|
||||||
|
if (typeof parser === 'object') {
|
||||||
|
const error = parser.virtual(parsed.value)
|
||||||
|
if (error) return error
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
const result = parser(section, parsed.value)
|
||||||
|
if (result === 'SECTION_PARSED') {
|
||||||
|
sections = sections.filter(s => s !== section)
|
||||||
|
break
|
||||||
|
} else if (result === 'PARSER_SKIPPED') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.freeze(parsed)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return err('item.parse_error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemTextToSections (text: string) {
|
||||||
|
const lines = text.split(/\r?\n/)
|
||||||
lines.pop()
|
lines.pop()
|
||||||
|
|
||||||
let sections: string[][] = [[]]
|
const sections: string[][] = [[]]
|
||||||
lines.reduce((section, line) => {
|
lines.reduce((section, line) => {
|
||||||
if (line !== '--------') {
|
if (line !== '--------') {
|
||||||
section.push(line)
|
section.push(line)
|
||||||
@@ -85,40 +126,7 @@ export function parseClipboard (clipboard: string) {
|
|||||||
return section
|
return section
|
||||||
}
|
}
|
||||||
}, sections[0])
|
}, sections[0])
|
||||||
sections = sections.filter(section => section.length)
|
return sections.filter(section => section.length)
|
||||||
|
|
||||||
if (sections[0][2] === _$.CANNOT_USE_ITEM) {
|
|
||||||
sections[0].pop() // remove CANNOT_USE_ITEM line
|
|
||||||
sections[1].unshift(...sections[0]) // prepend item class & rarity into second section
|
|
||||||
sections.shift() // remove first section where CANNOT_USE_ITEM line was
|
|
||||||
}
|
|
||||||
const parsed = parseNamePlate(sections[0])
|
|
||||||
if (!parsed) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
sections.shift()
|
|
||||||
parsed.rawText = clipboard
|
|
||||||
|
|
||||||
// each section can be parsed at most by one parser
|
|
||||||
for (const parser of parsers) {
|
|
||||||
if (typeof parser === 'object') {
|
|
||||||
parser.virtual(parsed)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const section of sections) {
|
|
||||||
const result = parser(section, parsed)
|
|
||||||
if (result === 'SECTION_PARSED') {
|
|
||||||
sections = sections.filter(s => s !== section)
|
|
||||||
break
|
|
||||||
} else if (result === 'PARSER_SKIPPED') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.freeze(parsed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeName (item: ParserState) {
|
function normalizeName (item: ParserState) {
|
||||||
@@ -180,7 +188,7 @@ function findInDatabase (item: ParserState) {
|
|||||||
info = ITEM_BY_REF('ITEM', item.baseType ?? item.name)
|
info = ITEM_BY_REF('ITEM', item.baseType ?? item.name)
|
||||||
}
|
}
|
||||||
if (!info?.length) {
|
if (!info?.length) {
|
||||||
throw new Error('UNKNOWN_ITEM')
|
return err('item.unknown')
|
||||||
}
|
}
|
||||||
if (info[0].unique) {
|
if (info[0].unique) {
|
||||||
info = info.filter(info => info.unique!.base === item.baseType)
|
info = info.filter(info => info.unique!.base === item.baseType)
|
||||||
@@ -267,7 +275,7 @@ function parseNamePlate (section: string[]) {
|
|||||||
if (section.length < 3 ||
|
if (section.length < 3 ||
|
||||||
!section[0].startsWith(_$.ITEM_CLASS) ||
|
!section[0].startsWith(_$.ITEM_CLASS) ||
|
||||||
!section[1].startsWith(_$.RARITY)) {
|
!section[1].startsWith(_$.RARITY)) {
|
||||||
return null
|
return err('item.parse_error')
|
||||||
}
|
}
|
||||||
|
|
||||||
const item: ParserState = {
|
const item: ParserState = {
|
||||||
@@ -311,10 +319,10 @@ function parseNamePlate (section: string[]) {
|
|||||||
item.rarity = ItemRarity.Unique
|
item.rarity = ItemRarity.Unique
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return null
|
return err('item.unknown')
|
||||||
}
|
}
|
||||||
|
|
||||||
return item
|
return ok(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInfluence (section: string[], item: ParsedItem) {
|
function parseInfluence (section: string[], item: ParsedItem) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
|||||||
if (e.target !== 'item-check') return
|
if (e.target !== 'item-check') return
|
||||||
|
|
||||||
checkPosition.value = e.position
|
checkPosition.value = e.position
|
||||||
item.value = parseClipboard(e.clipboard)
|
item.value = parseClipboard(e.clipboard).unwrapOr(null)
|
||||||
if (item.value) {
|
if (item.value) {
|
||||||
wm.show(props.config.wmId)
|
wm.show(props.config.wmId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ const POEDB_LANGS = { 'en': 'us', 'ru': 'ru', 'cmn-Hant': 'tw' }
|
|||||||
export function registerActions () {
|
export function registerActions () {
|
||||||
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
|
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
|
||||||
if (!['open-wiki', 'open-craft-of-exile', 'open-poedb', 'search-similar'].includes(e.target)) return
|
if (!['open-wiki', 'open-craft-of-exile', 'open-poedb', 'search-similar'].includes(e.target)) return
|
||||||
const item = parseClipboard(e.clipboard)
|
const parsed = parseClipboard(e.clipboard)
|
||||||
if (!item) return
|
if (!parsed.isOk()) return
|
||||||
|
|
||||||
if (e.target === 'open-wiki') {
|
if (e.target === 'open-wiki') {
|
||||||
openWiki(item)
|
openWiki(parsed.value)
|
||||||
} else if (e.target === 'open-craft-of-exile') {
|
} else if (e.target === 'open-craft-of-exile') {
|
||||||
openCoE(item)
|
openCoE(parsed.value)
|
||||||
} else if (e.target === 'open-poedb') {
|
} else if (e.target === 'open-poedb') {
|
||||||
openPoedb(item)
|
openPoedb(parsed.value)
|
||||||
} else if (e.target === 'search-similar') {
|
} else if (e.target === 'search-similar') {
|
||||||
findSimilarItems(item)
|
findSimilarItems(parsed.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,17 @@
|
|||||||
<background-info />
|
<background-info />
|
||||||
<check-position-circle v-if="showCheckPos"
|
<check-position-circle v-if="showCheckPos"
|
||||||
:position="checkPosition" style="z-index: -1;" />
|
:position="checkPosition" style="z-index: -1;" />
|
||||||
<template v-if="item && ('error' in item)">
|
<template v-if="item?.isErr()">
|
||||||
<ui-error-box class="m-4">
|
<ui-error-box class="m-4">
|
||||||
<template #name>{{ t(item.error.name) }}</template>
|
<template #name>{{ t(item.error.name) }}</template>
|
||||||
<p>{{ t(item.error.message) }}</p>
|
<p>{{ t(item.error.message) }}</p>
|
||||||
</ui-error-box>
|
</ui-error-box>
|
||||||
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.rawText }}</pre>
|
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.error.rawText }}</pre>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else-if="item?.isOk()">
|
||||||
<unidentified-resolver :item="item" @identify="item = $event" />
|
<unidentified-resolver :item="item.value" @identify="handleIdentification($event)" />
|
||||||
<checked-item v-if="isLeagueSelected && item"
|
<checked-item v-if="isLeagueSelected"
|
||||||
:item="item" :advanced-check="advancedCheck" />
|
:item="item.value" :advanced-check="advancedCheck" />
|
||||||
</template>
|
</template>
|
||||||
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
|
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
|
||||||
<i18n-t keypath="app.toggle_browser_hint" tag="div">
|
<i18n-t keypath="app.toggle_browser_hint" tag="div">
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
'flex-row': clickPosition === 'stash',
|
'flex-row': clickPosition === 'stash',
|
||||||
'flex-row-reverse': clickPosition === 'inventory'
|
'flex-row-reverse': clickPosition === 'inventory'
|
||||||
}">
|
}">
|
||||||
<related-items v-if="item && !('error' in item)" class="pointer-events-auto"
|
<related-items v-if="item?.isOk()" class="pointer-events-auto"
|
||||||
:item="item" :click-position="clickPosition" />
|
:item="item.value" :click-position="clickPosition" />
|
||||||
<rate-limiter-state class="pointer-events-auto" />
|
<rate-limiter-state class="pointer-events-auto" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, PropType, shallowRef, watch, computed, nextTick, provide } from 'vue'
|
import { defineComponent, inject, PropType, shallowRef, watch, computed, nextTick, provide } from 'vue'
|
||||||
|
import { Result, ok, err } from 'neverthrow'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import CheckedItem from './CheckedItem.vue'
|
import CheckedItem from './CheckedItem.vue'
|
||||||
import BackgroundInfo from './BackgroundInfo.vue'
|
import BackgroundInfo from './BackgroundInfo.vue'
|
||||||
@@ -83,13 +84,7 @@ import CheckPositionCircle from './CheckPositionCircle.vue'
|
|||||||
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
|
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
|
||||||
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces'
|
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces'
|
||||||
|
|
||||||
interface ParseError {
|
type ParseError = { name: string; message: string; rawText: ParsedItem['rawText'] }
|
||||||
error: {
|
|
||||||
name: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
rawText: ParsedItem['rawText']
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -116,7 +111,7 @@ export default defineComponent({
|
|||||||
props.config.wmFlags = ['hide-on-blur', 'skip-menu']
|
props.config.wmFlags = ['hide-on-blur', 'skip-menu']
|
||||||
})
|
})
|
||||||
|
|
||||||
const item = shallowRef<ParsedItem | ParseError | null>(null)
|
const item = shallowRef<null | Result<ParsedItem, ParseError>>(null)
|
||||||
const advancedCheck = shallowRef(false)
|
const advancedCheck = shallowRef(false)
|
||||||
const checkPosition = shallowRef({ x: 1, y: 1 })
|
const checkPosition = shallowRef({ x: 1, y: 1 })
|
||||||
|
|
||||||
@@ -149,28 +144,28 @@ export default defineComponent({
|
|||||||
wm.show(props.config.wmId)
|
wm.show(props.config.wmId)
|
||||||
checkPosition.value = e.position
|
checkPosition.value = e.position
|
||||||
advancedCheck.value = e.focusOverlay
|
advancedCheck.value = e.focusOverlay
|
||||||
try {
|
|
||||||
const parsed = parseClipboard(e.clipboard)
|
item.value = parseClipboard(e.clipboard)
|
||||||
if (parsed != null && (
|
.andThen(item => (
|
||||||
(parsed.category === ItemCategory.HeistContract && parsed.rarity !== ItemRarity.Unique) ||
|
(item.category === ItemCategory.HeistContract && item.rarity !== ItemRarity.Unique) ||
|
||||||
(parsed.category === ItemCategory.Sentinel && parsed.rarity !== ItemRarity.Unique)
|
(item.category === ItemCategory.Sentinel && item.rarity !== ItemRarity.Unique))
|
||||||
)) {
|
? err('item.unknown')
|
||||||
throw new Error('UNKNOWN_ITEM')
|
: ok(item))
|
||||||
} else {
|
.mapErr(err => ({
|
||||||
item.value = parsed
|
name: `${err}`,
|
||||||
|
message: `${err}_help`,
|
||||||
|
rawText: e.clipboard
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (item.value.isOk()) {
|
||||||
queuePricesFetch()
|
queuePricesFetch()
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
|
||||||
const strings = (err instanceof Error && err.message === 'UNKNOWN_ITEM')
|
|
||||||
? 'item.unknown'
|
|
||||||
: 'item.parse_error'
|
|
||||||
|
|
||||||
item.value = {
|
|
||||||
error: { name: `${strings}`, message: `${strings}_help` },
|
|
||||||
rawText: e.clipboard
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function handleIdentification (identified: ParsedItem) {
|
||||||
|
item.value = ok(identified)
|
||||||
|
}
|
||||||
|
|
||||||
MainProcess.onEvent('MAIN->OVERLAY::hide-exclusive-widget', () => {
|
MainProcess.onEvent('MAIN->OVERLAY::hide-exclusive-widget', () => {
|
||||||
wm.hide(props.config.wmId)
|
wm.hide(props.config.wmId)
|
||||||
})
|
})
|
||||||
@@ -253,6 +248,7 @@ export default defineComponent({
|
|||||||
checkPosition,
|
checkPosition,
|
||||||
item,
|
item,
|
||||||
advancedCheck,
|
advancedCheck,
|
||||||
|
handleIdentification,
|
||||||
overlayKey,
|
overlayKey,
|
||||||
isLeagueSelected,
|
isLeagueSelected,
|
||||||
openLeagueSelection
|
openLeagueSelection
|
||||||
|
|||||||
@@ -1693,6 +1693,11 @@ natural-compare@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||||
|
|
||||||
|
neverthrow@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-6.0.0.tgz#bacd7661cade296ccc5c35760bb3b679214155b6"
|
||||||
|
integrity sha512-kPZKRs4VkdloCGQXPoP84q4sT/1Z+lYM61AXyV8wWa2hnuo5KpPBF2S3crSFnMrOgUISmEBP8Vo/ngGZX60NhA==
|
||||||
|
|
||||||
node-releases@^2.0.6:
|
node-releases@^2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
||||||
|
|||||||
Reference in New Issue
Block a user