Fully add support for multiple currencies?

This commit is contained in:
kvan7
2025-10-11 11:01:31 -05:00
parent e95ebe6576
commit 1431ce6a06
10 changed files with 117 additions and 73 deletions

View File

@@ -228,7 +228,7 @@
"currency_only_chaos": "Chaos Orb", "currency_only_chaos": "Chaos Orb",
"currency_only_div": "Divine Orb", "currency_only_div": "Divine Orb",
"currency_chaos_div": "Both Orbs", "currency_chaos_div": "Both Orbs",
"currency_exalted_div": "Both Orbs", "currency_exalted_div": "Ex/Div Orbs",
"currency_only_exalted": "Exalted Orbs", "currency_only_exalted": "Exalted Orbs",
"ratio_tooltip": "Ratio used for sorting locally\nDefault on trade site is 7.5:1" "ratio_tooltip": "Ratio used for sorting locally\nDefault on trade site is 7.5:1"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -28,8 +28,8 @@ export interface CurrencyValue {
currency: "chaos" | "exalted" | "div"; currency: "chaos" | "exalted" | "div";
} }
interface CoreCurrency { export interface CoreCurrency {
id: string; id: "exalted" | "chaos";
abbrev: string; abbrev: string;
ref: string; ref: string;
text: string; text: string;
@@ -79,10 +79,20 @@ export const usePoeninja = createGlobalState(() => {
return listed; return listed;
}); });
/**
* core/div
*/
const xchgRate = shallowRef<number | undefined>(undefined); const xchgRate = shallowRef<number | undefined>(undefined);
/**
* Current core currency
*/
const xchgRateCurrency = shallowRef<"chaos" | "exalted" | undefined>( const xchgRateCurrency = shallowRef<"chaos" | "exalted" | undefined>(
undefined, undefined,
); );
/**
* exalted/div
*/
const exaltXchgRate = shallowRef<number | undefined>(undefined);
const isLoading = shallowRef(false); const isLoading = shallowRef(false);
let PRICES_DB: PriceDatabase = []; let PRICES_DB: PriceDatabase = [];
@@ -150,24 +160,24 @@ export const usePoeninja = createGlobalState(() => {
ns: "ITEM", ns: "ITEM",
name: "Divine Orb", name: "Divine Orb",
}); });
const preferred = const preferred = selectedCoreCurrency.value;
AppConfig<PriceCheckWidget>("price-check")!.coreCurrency;
if (divine && divine.exalted >= 30) { if (divine && divine.exalted >= 30) {
if (preferred === "exalted") { exaltXchgRate.value = divine.exalted;
if (!preferred || preferred.id === "exalted") {
xchgRate.value = divine.exalted; xchgRate.value = divine.exalted;
xchgRateCurrency.value = "exalted"; xchgRateCurrency.value = "exalted";
} else { } else {
const ex = divine.exalted; const ninjaPreferred = findPriceByQuery({
if (preferred === "chaos") {
const chaos = findPriceByQuery({
ns: "ITEM", ns: "ITEM",
name: "Chaos Orb", name: preferred.ref,
}); });
if (chaos && ex / chaos.exalted >= 5) { if (
xchgRate.value = ex / chaos.exalted; ninjaPreferred &&
xchgRateCurrency.value = "chaos"; exaltXchgRate.value / ninjaPreferred.exalted >= 5
} ) {
xchgRate.value = exaltXchgRate.value / ninjaPreferred.exalted;
xchgRateCurrency.value = preferred.id;
} }
} }
} }
@@ -230,34 +240,69 @@ export const usePoeninja = createGlobalState(() => {
return null; return null;
} }
function autoCurrency(value: number | [number, number]): CurrencyValue { /**
* Converts item value from exalts to stable or current core currency
* @param value item value in exalts
* @returns Value in stable or core currency
*/
function autoCurrency(
value: number | [number, number],
useOnlyCore: boolean = false,
): CurrencyValue {
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value[1] > (xchgRate.value || 9999)) { if (value[1] > (exaltXchgRate.value || 9999) && !useOnlyCore) {
return { return {
min: exaltedToStable(value[0]), min: exaltToStable(value[0]),
max: exaltedToStable(value[1]), max: exaltToStable(value[1]),
currency: "div", currency: "div",
}; };
} }
if (selectedCoreCurrency.value?.id) {
// NOTE: This if should catch everything
return {
min: exaltToCore(value[0]),
max: exaltToCore(value[1]),
currency: selectedCoreCurrency.value.id,
};
}
// this should never run, assuming we have loaded a currency
return { min: value[0], max: value[1], currency: "exalted" }; return { min: value[0], max: value[1], currency: "exalted" };
} }
if (value > (xchgRate.value || 9999) * 0.94) { if (value > (exaltXchgRate.value || 9999) * 0.94 && !useOnlyCore) {
if (value < (xchgRate.value || 9999) * 1.06) { if (value < (exaltXchgRate.value || 9999) * 1.06) {
return { min: 1, max: 1, currency: "div" }; return { min: 1, max: 1, currency: "div" };
} else { } else {
return { return {
min: exaltedToStable(value), min: exaltToStable(value),
max: exaltedToStable(value), max: exaltToStable(value),
currency: "div", currency: "div",
}; };
} }
} }
if (selectedCoreCurrency.value?.id) {
// NOTE: This if should catch everything
return {
min: exaltToCore(value),
max: exaltToCore(value),
currency: selectedCoreCurrency.value.id,
};
}
// this should never run, assuming we have loaded a currency
return { min: value, max: value, currency: "exalted" }; return { min: value, max: value, currency: "exalted" };
} }
function exaltedToStable(count: number) { // eslint-disable-next-line @typescript-eslint/no-unused-vars
function coreToStable(count: number) {
return count / (xchgRate.value || 9999); return count / (xchgRate.value || 9999);
} }
function exaltToStable(count: number) {
return count / (exaltXchgRate.value || 9999);
}
function exaltToCore(count: number) {
// ex -> core
// ex => ex * (div/ex) * (core/div) = core
return (count / (exaltXchgRate.value || 9999)) * (xchgRate.value || 9999);
}
function cachedCurrencyByQuery(query: DbQuery, count: number) { function cachedCurrencyByQuery(query: DbQuery, count: number) {
const key = { ns: query.ns, name: query.name, count }; const key = { ns: query.ns, name: query.name, count };
@@ -265,7 +310,8 @@ export const usePoeninja = createGlobalState(() => {
return priceCache.get(key)!; return priceCache.get(key)!;
} }
const price = findPriceByQuery(query); const price = // since ex aren't currently in db
query.name === "Exalted Orb" ? { exalted: 1 } : findPriceByQuery(query);
if (!price) { if (!price) {
return; return;
} }

View File

@@ -50,7 +50,7 @@
:title="title" :title="title"
> >
<ui-popover <ui-popover
v-if="stableOrbCost" v-if="stableOrbCost && xchgRateCurrency?.id"
trigger="click" trigger="click"
boundary="#price-window" boundary="#price-window"
> >
@@ -65,13 +65,14 @@
:price="{ :price="{
min: stableOrbCost, min: stableOrbCost,
max: stableOrbCost, max: stableOrbCost,
currency: 'exalted', currency: xchgRateCurrency.id,
}" }"
item-img="/images/divine.png" item-img="/images/divine.png"
/> />
<div v-for="i in 9" :key="i"> <div v-for="i in 9" :key="i">
<div class="pl-1"> <div class="pl-1">
{{ i / 10 }} div ⇒ {{ Math.round((stableOrbCost * i) / 10) }} c {{ i / 10 }} div ⇒ {{ Math.round((stableOrbCost * i) / 10) }}
{{ xchgRateCurrency.abbrev }}
</div> </div>
</div> </div>
</template> </template>
@@ -271,6 +272,7 @@ export default defineComponent({
const wm = inject<WidgetManager>("wm")!; const wm = inject<WidgetManager>("wm")!;
const { const {
xchgRate, xchgRate,
xchgRateCurrency,
initialLoading: xchgRateLoading, initialLoading: xchgRateLoading,
queuePricesFetch, queuePricesFetch,
} = usePoeninja(); } = usePoeninja();
@@ -452,6 +454,7 @@ export default defineComponent({
closePriceCheck, closePriceCheck,
title, title,
stableOrbCost, stableOrbCost,
xchgRateCurrency,
xchgRateLoading, xchgRateLoading,
showCheckPos, showCheckPos,
checkPosition, checkPosition,

View File

@@ -42,14 +42,10 @@ export default defineComponent({
function getPriceFor(n: number) { function getPriceFor(n: number) {
const one = findPriceByQuery(getDetailsId(props.item)!)!; const one = findPriceByQuery(getDetailsId(props.item)!)!;
const price = const price = autoCurrency(
props.item.info.refName === "Divine Orb" n * one.exalted,
? { props.item.info.refName === "Divine Orb",
min: n * one.exalted, );
max: n * one.exalted,
currency: "exalted" as const,
}
: autoCurrency(n * one.exalted);
return `${displayRounding(price.min)} ${price.currency}`; return `${displayRounding(price.min)} ${price.currency}`;
} }

View File

@@ -5,6 +5,7 @@ const MIN_TTL = 300;
export class Cache { export class Cache {
private cached = new Map<string, unknown>(); private cached = new Map<string, unknown>();
private currency = "";
get<T = unknown>(key: unknown): T | undefined { get<T = unknown>(key: unknown): T | undefined {
const _key = hash.sha1(JSON.parse(JSON.stringify(key))); const _key = hash.sha1(JSON.parse(JSON.stringify(key)));
@@ -20,6 +21,13 @@ export class Cache {
}, ttl * 1000); }, ttl * 1000);
} }
purgeIfDifferentCurrency(currency: string | undefined) {
if (!currency || this.currency === currency) return;
this.currency = currency;
this.cached.clear();
console.log("Purged cache");
}
static deriveTtl(...limits: RateLimiter[]): number { static deriveTtl(...limits: RateLimiter[]): number {
return Math.max(MIN_TTL, ...limits.map((limit) => limit.window)); return Math.max(MIN_TTL, ...limits.map((limit) => limit.window));
} }

View File

@@ -84,9 +84,9 @@
<ui-radio v-model="filters.trade.currency" value="exalted">{{ <ui-radio v-model="filters.trade.currency" value="exalted">{{
t(":currency_only_exalted") t(":currency_only_exalted")
}}</ui-radio> }}</ui-radio>
<!-- <ui-radio v-model="filters.trade.currency" value="chaos">{{ <ui-radio v-model="filters.trade.currency" value="chaos">{{
t(":currency_only_chaos") t(":currency_only_chaos")
}}</ui-radio> --> }}</ui-radio>
<ui-radio v-model="filters.trade.currency" value="divine">{{ <ui-radio v-model="filters.trade.currency" value="divine">{{
t(":currency_only_div") t(":currency_only_div")
}}</ui-radio> }}</ui-radio>

View File

@@ -12,16 +12,11 @@
}" }"
>{{ result.priceAmount }} {{ result.priceCurrency >{{ result.priceAmount }} {{ result.priceCurrency
}}{{ }}{{
result.priceCurrency !== "exalted" && result.normalizedPriceCurrency &&
result.priceCurrency !== result.normalizedPriceCurrency.id &&
result.priceCurrency !== "divine" && result.priceCurrency !== "divine" &&
result.normalizedPrice result.normalizedPrice
? ` (${result.normalizedPrice} ${ ? ` (${result.normalizedPrice} ${result.normalizedPriceCurrency.abbrev})`
result.normalizedPriceCurrency! === "exalted"
? "ex"
: result.normalizedPriceCurrency! === "chaos"
? "c"
: result.normalizedPriceCurrency!
})`
: "" : ""
}}</span }}</span
> >
@@ -31,18 +26,6 @@
><span class="font-sans">×</span> {{ result.listedTimes }}</span ><span class="font-sans">×</span> {{ result.listedTimes }}</span
><i v-else-if="!result.hasNote" class="fas fa-question" /> ><i v-else-if="!result.hasNote" class="fas fa-question" />
</td> </td>
<!-- <td class="px-2">
<div v-if="result.priceCurrencyRank" class="my-2 flex flex-row">
<div
v-for="i in result.priceCurrencyRank"
class="account-status mr-1"
:class="{
'rank-2': result.priceCurrencyRank === 2,
'rank-3': result.priceCurrencyRank === 3,
}"
></div>
</div>
</td> -->
<td v-if="item.stackSize" class="px-2 text-right"> <td v-if="item.stackSize" class="px-2 text-right">
{{ result.stackSize }} {{ result.stackSize }}
</td> </td>

View File

@@ -310,7 +310,13 @@ export interface PricingResult {
priceCurrency: string; priceCurrency: string;
priceCurrencyRank?: number; priceCurrencyRank?: number;
normalizedPrice?: string; normalizedPrice?: string;
normalizedPriceCurrency?: string; normalizedPriceCurrency?: {
id: "exalted" | "chaos";
abbrev: string;
ref: string;
text: string;
icon: string;
};
isMine: boolean; isMine: boolean;
hasNote: boolean; hasNote: boolean;
isInstantBuyout: boolean; isInstantBuyout: boolean;
@@ -953,8 +959,11 @@ export async function requestResults(
resultIds: string[], resultIds: string[],
opts: { accountName: string }, opts: { accountName: string },
): Promise<PricingResult[]> { ): Promise<PricingResult[]> {
const { cachedCurrencyByQuery, xchgRateCurrency } = usePoeninja();
// Solves cached results showing random incorrect values
cache.purgeIfDifferentCurrency(xchgRateCurrency.value?.id);
let data = cache.get<FetchResult[]>(resultIds); let data = cache.get<FetchResult[]>(resultIds);
const { cachedCurrencyByQuery } = usePoeninja();
if (!data) { if (!data) {
await RateLimiter.waitMulti(RATE_LIMIT_RULES.FETCH); await RateLimiter.waitMulti(RATE_LIMIT_RULES.FETCH);
@@ -1053,21 +1062,15 @@ export async function requestResults(
const query = getCurrencyDetailsId( const query = getCurrencyDetailsId(
result.listing.price?.currency ?? "no price", result.listing.price?.currency ?? "no price",
); );
const normalizedCurrency = const normalizedCurrency = cachedCurrencyByQuery(
result.listing.price?.currency === "exalted" query,
? // exalts aren't in db since they are the stable currency result.listing.price?.amount ?? 0,
{ );
min: result.listing.price.amount,
max: result.listing.price.amount,
currency: "exalted",
}
: // otherwise convert to stable
cachedCurrencyByQuery(query, result.listing.price?.amount ?? 0);
const normalizedPrice = const normalizedPrice =
normalizedCurrency !== undefined normalizedCurrency !== undefined
? displayRounding(normalizedCurrency.min) ? displayRounding(normalizedCurrency.min)
: undefined; : undefined;
const normalizedPriceCurrency = normalizedCurrency?.currency; const normalizedPriceCurrency = xchgRateCurrency.value;
return { return {
id: result.id, id: result.id,

View File

@@ -40,6 +40,11 @@
src="/images/chaos.png" src="/images/chaos.png"
class="max-w-full max-h-full" class="max-w-full max-h-full"
/> />
<img
v-else-if="price?.currency === 'annul'"
src="/images/annul.png"
class="max-w-full max-h-full"
/>
<img v-else src="/images/exa.png" class="max-w-full max-h-full" /> <img v-else src="/images/exa.png" class="max-w-full max-h-full" />
</div> </div>
</div> </div>
@@ -56,7 +61,7 @@ export default defineComponent({
type: Object as PropType<{ type: Object as PropType<{
min: number; min: number;
max: number; max: number;
currency: "chaos" | "div" | "exalted"; currency: string;
}>, }>,
default: undefined, default: undefined,
}, },