Compare commits

..

20 Commits

Author SHA1 Message Date
kvan7
292e2de2dd fix lint 2024-12-11 17:23:42 -06:00
kvan7
3cda3a74b8 Add lots more testing code 2024-12-11 17:21:18 -06:00
kvan7
32215e5c03 fix name 2024-12-11 17:21:18 -06:00
kvan7
fd4917ca7f style(Update to 2): Update branding
Renamed to Exalted PoE2 Trade
2024-12-11 17:21:18 -06:00
kvan7
c3d6c69d9d change to node 18 2024-12-11 17:21:18 -06:00
Kvan7
0774814912 Update issue templates 2024-12-11 06:34:12 -06:00
kvan7
20b695b1b3 Fixes Config file name updated #14 2024-12-11 06:25:11 -06:00
kvan7
e0a3b34c22 Update Names 2024-12-10 23:46:20 -06:00
kvan7
ed2a93a82f add a lot more logging 2024-12-10 18:28:47 -06:00
kvan7
14ecde0c71 test(Update to 2): Test for more informative logs 2024-12-10 18:16:11 -06:00
Kvan7
dd91dea7b1 Merge pull request #11 from Kvan7:dev
Attempt at bugfix for leagues missing
2024-12-10 17:51:48 -06:00
kvan7
a6ace8e234 build(Update to 2): Version bump 2024-12-10 17:51:10 -06:00
kvan7
68ee7016d1 fix(Update to 2): Remove unused ref
removes unused imports that were added back for some reason
2024-12-10 17:42:43 -06:00
kvan7
46e70400b6 fix(Update to 2): Solving league bug
solve bug by commenting ocut problem code

uhhhh some other stuff may break
2024-12-10 17:42:43 -06:00
kvan7
4a37528500 Merge branch 'dev' of github.com:Kvan7/awakened-poe-trade into dev 2024-12-10 17:16:53 -06:00
kvan7
b36954d0a9 fix(Update to 2): Fixes linting yay
linters, am i right
2024-12-10 17:16:47 -06:00
Alexander Drozdov
932ebd3b44 switch main to npm too 2024-12-10 17:08:03 -06:00
Kvan7
e18ddda81a Update README.md 2024-12-09 22:33:09 -06:00
Kvan7
ef592e0cf4 Update README.md 2024-12-09 22:31:57 -06:00
kvan7
de5444fa04 docs: Update readme 2024-12-09 22:30:02 -06:00
68 changed files with 14584 additions and 14053 deletions

View File

@@ -0,0 +1,24 @@
---
name: Something Broken in PoE2
about: Use this for things that worked in PoE 1 and do not in PoE 2
title: "[PoE2]"
labels: bug
assignees: Kvan7
---
**Describe the problem**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem. This is very helpful for comparing the PoE1 vs 2 problems

View File

@@ -20,8 +20,8 @@ jobs:
working-directory: ./renderer
- run: npm run make-index-files
working-directory: ./renderer
# - run: npm run lint
# working-directory: ./renderer
- run: npm run lint
working-directory: ./renderer
- run: npm run build
working-directory: ./renderer
- uses: actions/upload-artifact@v4
@@ -43,11 +43,11 @@ jobs:
with:
name: renderer-dist
path: ./renderer/dist
- run: yarn --frozen-lockfile
- run: npm ci
working-directory: ./main
- run: yarn build
- run: npm run build
working-directory: ./main
- run: yarn package -p onTagOrDraft
- run: npm run package "--" -p onTagOrDraft
working-directory: ./main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,32 +11,32 @@ Note that these 2 both depend on each other, and one cannot run without the othe
The most up-to-date instructions can always be derived from CI:
[.github/workflows/main.yml](https://github.com/Kvan7/awakened-poe2-trade2/blob/master/.github/workflows/main.yml)
[.github/workflows/main.yml](https://github.com/Kvan7/exiled-exchange-2/blob/master/.github/workflows/main.yml)
Here's what that looks like as of 2023-12-03.
```shell
cd renderer
yarn install
yarn make-index-files
yarn dev
npm install
npm run make-index-files
npm run dev
# In a second shell
cd main
yarn install
yarn dev
npm install
npm run dev
```
# How to build
```shell
cd renderer
yarn install
yarn make-index-files
yarn build
npm install
npm run make-index-files
npm run build
cd ../main
yarn build
npm run build
# We want to sign with a distribution certificate to ensure other users can
# install without errors
CSC_NAME="Certificate name in Keychain" yarn package

View File

@@ -1,11 +1,16 @@
# ![Awakener's Orb](https://web.poecdn.com/image/Art/2DItems/Currency/TransferOrb.png) Awakened PoE2 Trade2
# ![Exalted Orb](./renderer/dist/images/exa.png) Exile's Exchange
[![](https://user-images.githubusercontent.com/4292308/153364874-dde23599-278c-4350-8d86-dadbc4b978b3.svg)](https://somsubhra.github.io/github-release-stats/?username=SnosMe&repository=awakened-poe-trade)
[![](https://user-images.githubusercontent.com/4292308/153364769-e4fe1e82-1bbc-46ac-8a3c-f5a98a5667cc.svg)](https://patreon.com/awakened_poe_trade)
## Moving from POE1
➡ [Download for Windows & Linux](https://snosme.github.io/awakened-poe-trade/download) ⬅
1. Download latest release from [releases](https://github.com/Kvan7/exiled-exchange-2/releases)
- Currently only Windows is supported
- Only available as pre-release right now
2. Run installer
3. Copy `apt-data` from `%APPDATA%\awakened-poe-trade` to `%APPDATA%\exiled-exchange-2` to copy your previous settings
- Resulting directory structure should look like this:
- `%APPDATA%\exiled-exchange-2\apt-data\`
- `config.json`
4. Run Exiled Exchange 2
## Tool showcase
@@ -19,6 +24,7 @@ See [DEVELOPING.md](./DEVELOPING.md)
### Acknowledgments
- [awakened-poe-trade](https://github.com/SnosMe/awakened-poe-trade)
- [libuiohook](https://github.com/kwhat/libuiohook)
- [RePoE](https://github.com/brather1ng/RePoE)
- [poeprices.info](https://www.poeprices.info/)

View File

@@ -1,9 +1,9 @@
import { defineConfig } from 'vitepress'
const BASE = '/awakened-poe2-trade2/'
const BASE = '/exiled-exchange-2/'
export default defineConfig({
title: 'Awakened PoE2 Trade2',
title: 'Exiled Exchange 2',
description: 'App for price-checking items in Path of Exile 2',
base: BASE,
mpa: true,
@@ -22,7 +22,7 @@ export default defineConfig({
// logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401
appVersion: '3.25.101',
github: {
releasesUrl: 'https://github.com/Kvan7/awakened-poe2-trade2/releases'
releasesUrl: 'https://github.com/Kvan7/exiled-exchange-2/releases'
},
socialLinks: [
{
@@ -33,7 +33,7 @@ export default defineConfig({
{
text: 'GitHub',
color: '#181717',
link: 'https://github.com/Kvan7/awakened-poe2-trade2'
link: 'https://github.com/Kvan7/exiled-exchange-2'
}
],
sidebar: [

View File

@@ -8,15 +8,15 @@ import { useData } from 'vitepress'
const { theme } = useData()
</script>
You can download Awakened Poe Trade here. Any other mirrors are not known
You can download Exalted Poe Trade here. Any other mirrors are not known
to the developer, downloading from them may be unsafe.
| Download link | Automatic updates | Startup time |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------ |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-Setup-${theme.appVersion}.exe`">Windows 10+ (installer)</a> | ✔ | Fast |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}.exe`">Windows 10+ (portable)</a> | ❌ | Slower |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}.AppImage`">Linux (AppImage)</a> | ✔ | n/a |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/Awakened-PoE2-Trade2-${theme.appVersion}-universal.dmg`">macOS (dmg)</a> | ❌ | n/a |
| Download link | Automatic updates | Startup time |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------ |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-Setup-${theme.appVersion}.exe`">Windows 10+ (installer)</a> | ✔ | Fast |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${theme.appVersion}.exe`">Windows 10+ (portable)</a> | ❌ | Slower |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${theme.appVersion}.AppImage`">Linux (AppImage)</a> | ✔ | n/a |
| <a :href="`${theme.github.releasesUrl}/download/v${theme.appVersion}/exiled-exchange-2-${theme.appVersion}-universal.dmg`">macOS (dmg)</a> | ❌ | n/a |
Latest version is <span class="bg-gray-100 border rounded px-1">{{ theme.appVersion }}</span>
@@ -36,6 +36,6 @@ warnings on Windows and [macOS](https://support.apple.com/en-us/HT202491#openany
No Administrator rights required, but\
⚠ **If you run PoE client as Admin, OS security boundaries take effect.
In order for Awakened PoE2 Trade2 to have access to the PoE window, it must be started with Administrator rights.**
In order for Exiled Exchange 2 to have access to the PoE window, it must be started with Administrator rights.**
**Not compatible with "GeForce Now" or any other cloud gaming service that do not forward clipboard data.**

View File

@@ -14,7 +14,7 @@ title: Common issues
If Awakened works for you with DirectX11/12 renderer,
then problem is old Vulkan drivers for sure.
4. Delete `%appdata%\awakened-poe2-trade2`
4. Delete `%appdata%\exiled-exchange-2`
If needed, backup `apt-data` folder with your configuration inside.
@@ -22,7 +22,7 @@ title: Common issues
Launch them later one at a time to identify **conflict**.
6. Restart Awakened PoE2 Trade2.
6. Restart Exiled Exchange 2.
*(don't forget to quit first, otherwise launching second instance will do nothing).*

View File

@@ -5,7 +5,7 @@ title: Quick Start
#### First of all, how does it work? {:style="margin-top: 0;"}
When you press `Ctrl + C` Path of Exile 2 copies the item's text (under cursor, if any) to the clipboard.
All that remains is to parse text in Awakened PoE2 Trade2 and show to you in a fancy way.
All that remains is to parse text in Exiled Exchange 2 and show to you in a fancy way.
### Usage

View File

@@ -25,6 +25,7 @@
"dist/": true
},
"editor.tabSize": 2,
"editor.insertSpaces": true,
"conventionalCommits.scopes": [
"Update to 2"
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
main/build/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,6 +1,6 @@
publish:
- "github"
productName: "Awakened PoE2 Trade2"
productName: "Exiled Exchange 2"
npmRebuild: false
files:
- "package.json"

5656
main/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,20 @@
{
"name": "awakened-poe2-trade2",
"version": "0.0.1",
"name": "exiled-exchange-2",
"version": "0.0.9",
"private": true,
"scripts": {
"dev": "node build/script.mjs",
"build": "tsc --noEmit && node build/script.mjs --prod",
"package": "electron-builder build"
"package": "electron-builder build",
"lint": "eslint src",
"fix": "eslint src --fix"
},
"author": {
"name": "Alexander Drozdov"
"name": "Garrett Parker"
},
"repository": {
"type": "git",
"url": "https://github.com/Kvan7/awakened-poe2-trade2.git"
"url": "https://github.com/Kvan7/exiled-exchange-2.git"
},
"main": "dist/main.js",
"dependencies": {
@@ -25,15 +27,12 @@
"@types/ws": "^8.5.3",
"@wokwi/bmp-ts": "^3.0.0",
"comlink": "^4.3.1",
"electron": "31.3.1",
"electron-builder": "24.13.3",
"electron-updater": "^6.1.0",
"esbuild": "^0.23.0",
"ini": "^4.0.0",
"typescript": "5.5.x",
"electron": "33.2.1",
"electron-builder": "25.1.8",
"electron-updater": "^6.3.0",
"esbuild": "^0.24.0",
"ini": "^5.0.0",
"typescript": "5.6.x",
"ws": "^8.16.0"
},
"engines": {
"node": ">=16"
}
}

View File

@@ -23,7 +23,7 @@ export class AppTray {
}
this.tray = new Tray(trayImage);
this.tray.setToolTip(`Awakened PoE2 Trade2 v${app.getVersion()}`);
this.tray.setToolTip(`Exiled Exchange 2 v${app.getVersion()}`);
this.rebuildMenu();
server.onEventAnyClient("CLIENT->MAIN::user-action", ({ action }) => {

View File

@@ -12,25 +12,25 @@ const POSSIBLE_PATH =
? [
path.join(
app.getPath("documents"),
"My Games\\Path of Exile 2\\production_Config.ini"
"My Games\\Path of Exile 2\\poe2_production_Config.ini"
),
]
: process.platform === "linux"
? [
path.join(
app.getPath("documents"),
"My Games/Path of Exile 2/production_Config.ini"
"My Games/Path of Exile 2/poe2_production_Config.ini"
),
path.join(
app.getPath("home"),
".local/share/Steam/steamapps/compatdata/238960/pfx/drive_c/users/steamuser/Documents/My Games/Path of Exile 2/production_Config.ini"
".local/share/Steam/steamapps/compatdata/238960/pfx/drive_c/users/steamuser/Documents/My Games/Path of Exile 2/poe2_production_Config.ini"
),
]
: process.platform === "darwin"
? [
path.join(
app.getPath("appData"),
"Path of Exile 2/Preferences/production_Config.ini"
"Path of Exile 2/Preferences/poe2_production_Config.ini"
),
]
: [];

View File

@@ -1,6 +1,7 @@
import type { Server } from 'http'
import { app, net } from 'electron'
import type { Logger } from './RemoteLogger'
import { exec } from 'child_process';
const PROXY_HOSTS = [
{ host: 'www.pathofexile.com', official: true },
@@ -9,6 +10,7 @@ const PROXY_HOSTS = [
{ host: 'poe.game.daum.net', official: true },
{ host: 'poe.ninja', official: false },
{ host: 'www.poeprices.info', official: false },
{ host: 'kvan.dev', official: false },
]
export class HttpProxy {
@@ -18,11 +20,18 @@ export class HttpProxy {
) {
server.addListener('request', (req, res) => {
if (!req.url?.startsWith('/proxy/')) return
const fullPath = req.url.slice('/proxy/'.length)
const host = req.url.split('/', 3)[2]
if (fullPath.startsWith('www.pathofexile.com/api/trade2/search/Standard') || fullPath.startsWith('kvan.dev')) {
this.executeCurl(fullPath, logger)
}
const official = PROXY_HOSTS.find(entry => entry.host === host)?.official
if (official === undefined) return req.destroy()
this.pingHost(host, logger); // Add this line to use ping
for (const key in req.headers) {
if (key.startsWith('sec-') || key === 'host' || key === 'origin' || key === 'content-length') {
delete req.headers[key]
@@ -39,17 +48,68 @@ export class HttpProxy {
useSessionCookies: true
})
proxyReq.addListener('response', (proxyRes) => {
logger.write(`response [proxy] ${proxyRes.statusCode} ${proxyRes.statusMessage} (${host})`)
const resHeaders = { ...proxyRes.headers }
// `net.request` returns an already decoded body
delete resHeaders['content-encoding']
res.writeHead(proxyRes.statusCode, proxyRes.statusMessage, resHeaders)
;(proxyRes as unknown as NodeJS.ReadableStream).pipe(res)
})
proxyReq.addListener('error', (err) => {
logger.write(`error [cors-proxy] ${err.message} (${host})`)
logger.write(`error [proxy] ${err.message} (${host})`)
logger.write(`error-[proxy]-(${err.name}) ${err.stack}`)
res.destroy(err)
})
req.pipe(proxyReq as unknown as NodeJS.WritableStream)
logger.write(`Full request details: ${JSON.stringify(proxyReq, null, 2)}`);
})
}
pingHost(host: string, logger: Logger) {
const pingCommand = process.platform === 'win32' ? `ping -n 1 ${host}` : `ping -c 1 ${host}`;
exec(pingCommand, (error, stdout, stderr) => {
if (error) {
logger.write(`ping error [${host}] ${error.message}`)
return;
}
if (stderr) {
logger.write(`ping stderr [${host}] ${stderr}`)
return;
}
logger.write(`ping success [${host}] ${stdout}`)
})
}
executeCurl(path: string, logger: Logger) {
const postData = {
"query": {
"status": { "option": "online" },
"stats": [{ "type": "and", "filters": [] }],
"filters": {
"trade_filters": { "filters": { "collapse": { "option": "true" }}},
"type_filters": { "filters": {
"rarity": { "option": "nonunique" },
"category": { "option": "accessory.ring" }}},
"misc_filters": { "filters": {
"corrupted": { "option": "false" },
"mirrored": { "option": "false" }}}
}
},
"sort": { "price": "asc" }
};
const postDataStr = JSON.stringify(postData);
const curlCommand = `curl -X POST --data '${postDataStr}' https://${path}`;
exec(curlCommand, (error, stdout, stderr) => {
if (error) {
logger.write(`curl error [${path}] ${error.message}`);
return;
}
if (stderr) {
logger.write(`curl stderr [${path}] ${stderr}`);
return;
}
logger.write(`curl output [${path}] ${stdout}`);
});
}
}

View File

@@ -93,7 +93,7 @@ export async function startServer (
socket.on('close', () => {
const clients = websocketServer.clients
if (clients.size === 1) {
lastActiveClient = clients.values().next().value
lastActiveClient = clients.values().next().value!
evBus.emit('CLIENT->MAIN::used-recently', { isOverlay: true })
}
})

View File

@@ -161,7 +161,7 @@ export class OverlayWindow {
// ----------------------
"Path of Exile 2 is running with administrator rights.\n" +
"\n" +
"You need to restart Awakened PoE2 Trade2 with administrator rights."
"You need to restart Exiled Exchange 2 with administrator rights."
);
} else {
this.server.sendEventTo("broadcast", {

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1,56 @@
module.exports = {
root: true,
root: true,
env: {
node: true
},
plugins: [
'@typescript-eslint',
// 'only-warn'
],
extends: [
'plugin:vue/base',
'standard-with-typescript',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'quote-props': ['error', 'consistent-as-needed'],
'no-labels': ['error', { allowLoop: true }],
'multiline-ternary': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/prefer-readonly': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
'@typescript-eslint/no-invalid-void-type': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'import/first': 'off',
'import/no-duplicates': 'off',
'func-call-spacing': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off',
},
overrides: [{
files: ['src/main/**/*'],
env: {
node: true
},
plugins: [
'@typescript-eslint',
'prettier'
// 'only-warn'
],
extends: [
'plugin:vue/base',
'standard-with-typescript',
'plugin:prettier/recommended',
'eslint-plugin-prettier/recommended',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'quote-props': ['error', 'consistent-as-needed'],
'no-labels': ['error', { allowLoop: true }],
'multiline-ternary': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/prefer-readonly': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/prefer-reduce-type-parameter': 'off',
'@typescript-eslint/no-invalid-void-type': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'import/first': 'off',
'import/no-duplicates': 'off',
'func-call-spacing': 'off',
// TODO: refactor IPC and enable
'@typescript-eslint/consistent-type-assertions': 'off',
"indent": ["error", "tab"]
},
overrides: [{
files: ['src/main/**/*'],
env: {
node: true
}
}, {
files: ['*.ts'],
parserOptions: {
project: './tsconfig.json'
}
}],
parserOptions: {
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue']
node: true
}
}, {
files: ['*.ts'],
parserOptions: {
project: './tsconfig.json'
}
}],
parserOptions: {
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue']
}
}

View File

@@ -1,7 +0,0 @@
{
"singleQuote": true,
"endOfLine": "lf",
"tabWidth": 2,
"useTabs": true,
"trailingComma": "es5"
}

View File

@@ -1,15 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="color-scheme" content="dark">
<link rel="icon" href="/icon.ico">
<title>Awakened PoE Trade2</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="color-scheme" content="dark">
<link rel="icon" href="/icon.ico">
<title>Exiled Exchange 2</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

12906
renderer/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +1,63 @@
{
"name": "awakened-poe2-trade2",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite",
"lint": "eslint --ext .ts,.vue src",
"build": "vue-tsc --noEmit && vite build",
"make-index-files": "node src/assets/make-index-files.mjs"
},
"dependencies": {
"@fortawesome/fontawesome-free": "6.x.x",
"@sindresorhus/fnv1a": "^3.0.0",
"@vueuse/core": "^11.0.0",
"animate.css": "^4.1.1",
"apexcharts": "^4.0.0",
"dot-prop": "9.x.x",
"fast-deep-equal": "^3.1.3",
"fastest-levenshtein": "^1.0.16",
"luxon": "3.x.x",
"neverthrow": "^8.0.0",
"object-hash": "^3.0.0",
"sockette": "^2.0.6",
"tailwindcss": "3.x.x",
"tippy.js": "^6.2.7",
"vue": "3.2.37",
"vue-i18n": "^10.0.0",
"vue3-apexcharts": "^1.1.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@types/luxon": "^3.0.0",
"@types/node": "^20.0.0",
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"postcss": "^8.2.14",
"prettier": "3.4.2",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
},
"optionalDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.21.0",
"eslint-config-standard-with-typescript": "^31.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.1.1"
},
"postcss": {
"plugins": {
"tailwindcss/nesting": {},
"tailwindcss": {},
"autoprefixer": {}
}
},
"browserslist": [
"chrome >= 101"
]
"name": "exiled-exchange-2",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"lint": "eslint --ext .ts,.vue src",
"lint-fix": "eslint --ext .ts,.vue src --fix",
"build": "vue-tsc --noEmit && vite build",
"make-index-files": "node src/assets/make-index-files.mjs"
},
"dependencies": {
"@fortawesome/fontawesome-free": "6.x.x",
"@sindresorhus/fnv1a": "^3.0.0",
"@vueuse/core": "^11.0.0",
"animate.css": "^4.1.1",
"apexcharts": "^4.0.0",
"dot-prop": "9.x.x",
"fast-deep-equal": "^3.1.3",
"fastest-levenshtein": "^1.0.16",
"luxon": "3.x.x",
"neverthrow": "^8.0.0",
"object-hash": "^3.0.0",
"sockette": "^2.0.6",
"tailwindcss": "3.x.x",
"tippy.js": "^6.2.7",
"vue": "3.2.37",
"vue-i18n": "^10.0.0",
"vue3-apexcharts": "^1.1.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@types/luxon": "^3.0.0",
"@types/node": "^20.0.0",
"@types/object-hash": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.0.2",
"postcss": "^8.2.14",
"typescript": "5.6.x",
"vite": "^5.0.0",
"vue-tsc": "^2.0.0"
},
"optionalDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.21.0",
"eslint-config-standard-with-typescript": "^31.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.1.1"
},
"postcss": {
"plugins": {
"tailwindcss/nesting": {},
"tailwindcss": {},
"autoprefixer": {}
}
},
"browserslist": [
"chrome >= 101"
]
}

View File

@@ -269,7 +269,7 @@
"stack": "Stack"
},
"settings": {
"title": "Settings - Awakened PoE2 Trade2",
"title": "Settings - Exiled Exchange 2",
"language": "Language",
"private_league": "or Private League",
"account_name": "Account name",

View File

@@ -266,7 +266,7 @@
"stack": "스택"
},
"settings": {
"title": "세팅 - Awakened PoE2 Trade2",
"title": "세팅 - Exiled Exchange 2",
"language": "언어",
"private_league": "개인리그",
"account_name": "계정명",

View File

@@ -282,7 +282,7 @@
"stack": "Стак"
},
"settings": {
"title": "Настройки - Awakened PoE2 Trade2",
"title": "Настройки - Exiled Exchange 2",
"language": "Язык",
"private_league": "или Приватная лига",
"account_name": "Имя учетной записи",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -299,8 +299,8 @@ function parseNamePlate (section: string[]) {
const item: ParserState = {
rarity: undefined,
category: undefined,
name: name,
baseType: baseType,
name,
baseType,
isUnidentified: false,
isCorrupted: false,
newMods: [],

View File

@@ -1,97 +1,97 @@
export enum ItemCategory {
Map = 'Map',
CapturedBeast = 'Captured Beast',
MetamorphSample = 'Metamorph Sample',
Helmet = 'Helmet',
BodyArmour = 'Body Armour',
Gloves = 'Gloves',
Boots = 'Boots',
Shield = 'Shield',
Amulet = 'Amulet',
Belt = 'Belt',
Ring = 'Ring',
Flask = 'Flask',
AbyssJewel = 'Abyss Jewel',
Jewel = 'Jewel',
Quiver = 'Quiver',
Claw = 'Claw',
Bow = 'Bow',
Sceptre = 'Sceptre',
Wand = 'Wand',
FishingRod = 'Fishing Rod',
Staff = 'Staff',
Warstaff = 'Warstaff',
Dagger = 'Dagger',
RuneDagger = 'Rune Dagger',
OneHandedAxe = 'One-Handed Axe',
TwoHandedAxe = 'Two-Handed Axe',
OneHandedMace = 'One-Handed Mace',
TwoHandedMace = 'Two-Handed Mace',
OneHandedSword = 'One-Handed Sword',
TwoHandedSword = 'Two-Handed Sword',
ClusterJewel = 'Cluster Jewel',
HeistBlueprint = 'Heist Blueprint',
HeistContract = 'Heist Contract',
HeistTool = 'Heist Tool',
HeistBrooch = 'Heist Brooch',
HeistGear = 'Heist Gear',
HeistCloak = 'Heist Cloak',
Trinket = 'Trinket',
Invitation = 'Invitation',
Gem = 'Gem',
Currency = 'Currency',
DivinationCard = 'Divination Card',
Voidstone = 'Voidstone',
Sentinel = 'Sentinel',
MemoryLine = 'Memory Line',
SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture',
Charm = 'Charm',
Crossbow = 'Crossbow',
Map = 'Map',
CapturedBeast = 'Captured Beast',
MetamorphSample = 'Metamorph Sample',
Helmet = 'Helmet',
BodyArmour = 'Body Armour',
Gloves = 'Gloves',
Boots = 'Boots',
Shield = 'Shield',
Amulet = 'Amulet',
Belt = 'Belt',
Ring = 'Ring',
Flask = 'Flask',
AbyssJewel = 'Abyss Jewel',
Jewel = 'Jewel',
Quiver = 'Quiver',
Claw = 'Claw',
Bow = 'Bow',
Sceptre = 'Sceptre',
Wand = 'Wand',
FishingRod = 'Fishing Rod',
Staff = 'Staff',
Warstaff = 'Warstaff',
Dagger = 'Dagger',
RuneDagger = 'Rune Dagger',
OneHandedAxe = 'One-Handed Axe',
TwoHandedAxe = 'Two-Handed Axe',
OneHandedMace = 'One-Handed Mace',
TwoHandedMace = 'Two-Handed Mace',
OneHandedSword = 'One-Handed Sword',
TwoHandedSword = 'Two-Handed Sword',
ClusterJewel = 'Cluster Jewel',
HeistBlueprint = 'Heist Blueprint',
HeistContract = 'Heist Contract',
HeistTool = 'Heist Tool',
HeistBrooch = 'Heist Brooch',
HeistGear = 'Heist Gear',
HeistCloak = 'Heist Cloak',
Trinket = 'Trinket',
Invitation = 'Invitation',
Gem = 'Gem',
Currency = 'Currency',
DivinationCard = 'Divination Card',
Voidstone = 'Voidstone',
Sentinel = 'Sentinel',
MemoryLine = 'Memory Line',
SanctumRelic = 'Sanctum Relic',
Tincture = 'Tincture',
Charm = 'Charm',
Crossbow = 'Crossbow',
}
export const WEAPON_ONE_HANDED_MELEE = new Set([
ItemCategory.OneHandedAxe,
ItemCategory.OneHandedMace,
ItemCategory.OneHandedSword,
ItemCategory.Claw,
ItemCategory.Dagger,
ItemCategory.RuneDagger,
ItemCategory.Sceptre,
]);
ItemCategory.OneHandedAxe,
ItemCategory.OneHandedMace,
ItemCategory.OneHandedSword,
ItemCategory.Claw,
ItemCategory.Dagger,
ItemCategory.RuneDagger,
ItemCategory.Sceptre
])
export const WEAPON_ONE_HANDED = new Set([
ItemCategory.Wand,
...WEAPON_ONE_HANDED_MELEE,
]);
ItemCategory.Wand,
...WEAPON_ONE_HANDED_MELEE
])
export const WEAPONE_TWO_HANDED_MELEE = new Set([
ItemCategory.TwoHandedAxe,
ItemCategory.TwoHandedMace,
ItemCategory.TwoHandedSword,
ItemCategory.Staff,
ItemCategory.Warstaff,
]);
ItemCategory.TwoHandedAxe,
ItemCategory.TwoHandedMace,
ItemCategory.TwoHandedSword,
ItemCategory.Staff,
ItemCategory.Warstaff
])
export const WEAPON = new Set([
ItemCategory.FishingRod,
ItemCategory.Bow,
...WEAPON_ONE_HANDED,
...WEAPONE_TWO_HANDED_MELEE,
]);
ItemCategory.FishingRod,
ItemCategory.Bow,
...WEAPON_ONE_HANDED,
...WEAPONE_TWO_HANDED_MELEE
])
export const ARMOUR = new Set([
ItemCategory.BodyArmour,
ItemCategory.Boots,
ItemCategory.Gloves,
ItemCategory.Helmet,
ItemCategory.Shield,
]);
ItemCategory.BodyArmour,
ItemCategory.Boots,
ItemCategory.Gloves,
ItemCategory.Helmet,
ItemCategory.Shield
])
export const ACCESSORY = new Set([
ItemCategory.Amulet,
ItemCategory.Belt,
ItemCategory.Ring,
ItemCategory.Trinket,
// ItemCategory.Quiver
]);
ItemCategory.Amulet,
ItemCategory.Belt,
ItemCategory.Ring,
ItemCategory.Trinket
// ItemCategory.Quiver
])

View File

@@ -109,7 +109,7 @@ export function translateStatWithRoll (
calc.sources.some(s => s.stat.stat.ref === calc.stat.ref && s.stat.roll!.dp)
: undefined
return { string: translation.string, negate: translation.negate || false, dp: dp }
return { string: translation.string, negate: translation.negate || false, dp }
}
export enum ModifierType {

File diff suppressed because it is too large Load Diff

View File

@@ -1,118 +1,117 @@
import { computed, shallowRef, readonly } from 'vue';
import { createGlobalState } from '@vueuse/core';
import { AppConfig, poeWebApi } from '@/web/Config';
import { Host } from './IPC';
import { computed, shallowRef, readonly } from 'vue'
import { createGlobalState } from '@vueuse/core'
import { AppConfig } from '@/web/Config'
// pc-ggg, pc-garena
// const PERMANENT_SC = ['Standard', '標準模式']
const PERMANENT_HC = ['Hardcore', '專家模式'];
const PERMANENT_HC = ['Hardcore', '專家模式']
interface ApiLeague {
id: string;
event?: boolean;
rules: Array<{ id: string }>;
id: string
event?: boolean
rules: Array<{ id: string }>
}
const DEFAULT_POE2_LEAGUES: ApiLeague[] = [
{ id: 'Standard', rules: [] },
{
id: 'Hardcore',
rules: [
{
id: 'Hardcore',
},
],
},
];
interface League {
id: string;
isPopular: boolean;
id: string
isPopular: boolean
}
export const useLeagues = createGlobalState(() => {
const isLoading = shallowRef(false);
const error = shallowRef<string | null>(null);
const tradeLeagues = shallowRef<League[]>([]);
const isLoading = shallowRef(false)
const error = shallowRef<string | null>(null)
const tradeLeagues = shallowRef<League[]>([])
const selectedId = computed<string | undefined>({
get() {
return tradeLeagues.value.length ? AppConfig().leagueId : undefined;
},
set(id) {
AppConfig().leagueId = id;
},
});
const DEFAULT_POE2_LEAGUES: ApiLeague[] = [
{ id: 'Standard', rules: [] },
{
id: 'Hardcore',
rules: [
{
id: 'Hardcore'
}
]
}
]
const selected = computed(() => {
const { leagueId } = AppConfig();
if (!tradeLeagues.value || !leagueId) return undefined;
const listed = tradeLeagues.value.find((league) => league.id === leagueId);
return {
id: leagueId,
realm: AppConfig().realm,
isPopular: !isPrivateLeague(leagueId) && Boolean(listed?.isPopular),
};
});
const selectedId = computed<string | undefined>({
get () {
return tradeLeagues.value.length ? AppConfig().leagueId : undefined
},
set (id) {
AppConfig().leagueId = id
}
})
async function load() {
isLoading.value = true;
error.value = null;
const selected = computed(() => {
const { leagueId } = AppConfig()
if (!tradeLeagues.value || !leagueId) return undefined
const listed = tradeLeagues.value.find((league) => league.id === leagueId)
return {
id: leagueId,
realm: AppConfig().realm,
isPopular: !isPrivateLeague(leagueId) && Boolean(listed?.isPopular)
}
})
try {
const response = await Host.proxy(
`${poeWebApi()}/api/leagues?type=main&realm=pc`
);
if (!response.ok)
throw new Error(JSON.stringify(Object.fromEntries(response.headers)));
// const leagues: ApiLeague[] = await response.json();
const leagues: ApiLeague[] = DEFAULT_POE2_LEAGUES;
tradeLeagues.value = leagues
.filter(
(league) =>
!PERMANENT_HC.includes(league.id) &&
!league.rules.some(
(rule) =>
rule.id === 'NoParties' ||
(rule.id === 'HardMode' && !league.event)
)
)
.map((league) => {
return { id: league.id, isPopular: true };
});
async function load () {
isLoading.value = true
error.value = null
const leagueIsAlive = tradeLeagues.value.some(
(league) => league.id === selectedId.value
);
if (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 1;
selectedId.value = tradeLeagues.value[TMP_CHALLENGE].id;
} else {
const STANDARD = 0;
selectedId.value = tradeLeagues.value[STANDARD].id;
}
}
} catch (e) {
error.value = (e as Error).message;
} finally {
isLoading.value = false;
}
}
try {
// const response = await Host.proxy(
// `${poeWebApi()}/api/leagues?type=main&realm=pc`
// );
// if (!response.ok)
// throw new Error(JSON.stringify(Object.fromEntries(response.headers)));
// const leagues: ApiLeague[] = await response.json();
const leagues: ApiLeague[] = DEFAULT_POE2_LEAGUES
tradeLeagues.value = leagues
.filter(
(league) =>
!PERMANENT_HC.includes(league.id) &&
!league.rules.some(
(rule) =>
rule.id === 'NoParties' ||
(rule.id === 'HardMode' && !league.event)
)
)
.map((league) => {
return { id: league.id, isPopular: true }
})
return {
isLoading,
error,
selectedId,
selected,
list: readonly(tradeLeagues),
load,
};
});
const leagueIsAlive = tradeLeagues.value.some(
(league) => league.id === selectedId.value
)
if (!leagueIsAlive && !isPrivateLeague(selectedId.value ?? '')) {
if (tradeLeagues.value.length > 1) {
const TMP_CHALLENGE = 1
selectedId.value = tradeLeagues.value[TMP_CHALLENGE].id
} else {
const STANDARD = 0
selectedId.value = tradeLeagues.value[STANDARD].id
}
}
} catch (e) {
error.value = (e as Error).message
} finally {
isLoading.value = false
}
}
function isPrivateLeague(id: string) {
if (id.includes('Ruthless')) {
return true;
}
return /\(PL\d+\)$/.test(id);
return {
isLoading,
error,
selectedId,
selected,
list: readonly(tradeLeagues),
load
}
})
function isPrivateLeague (id: string) {
if (id.includes('Ruthless')) {
return true
}
return /\(PL\d+\)$/.test(id)
}

View File

@@ -19,7 +19,7 @@ import type { ItemCheckWidget } from './widget.js'
import Widget from '../overlay/Widget.vue'
import MapCheck from '../map-check/MapCheck.vue'
import ItemInfo from './ItemInfo.vue'
import ConversionWarningBanner from "../conversion-warn-banner/ConversionWarningBanner.vue";
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
const props = defineProps<{
config: ItemCheckWidget

View File

@@ -1,54 +1,53 @@
import { Host } from '@/web/background/IPC';
import { AppConfig } from '@/web/Config';
import { ParsedItem, parseClipboard } from '@/parser';
import { Host } from '@/web/background/IPC'
import { AppConfig } from '@/web/Config'
import { ParsedItem, parseClipboard } from '@/parser'
const POEDB_LANGS = { en: 'us', ru: 'ru', 'cmn-Hant': 'tw', ko: 'kr' };
const POEDB_LANGS = { 'en': 'us', 'ru': 'ru', 'cmn-Hant': 'tw', 'ko': 'kr' }
export function registerActions() {
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
if (
![
'open-wiki',
'open-craft-of-exile',
'open-poedb',
'search-similar',
].includes(e.target)
)
return;
const parsed = parseClipboard(e.clipboard);
if (!parsed.isOk()) return;
export function registerActions () {
Host.onEvent('MAIN->CLIENT::item-text', (e) => {
if (
![
'open-wiki',
'open-craft-of-exile',
'open-poedb',
'search-similar'
].includes(e.target)
) { return }
const parsed = parseClipboard(e.clipboard)
if (!parsed.isOk()) return
if (e.target === 'open-wiki') {
openWiki(parsed.value);
} else if (e.target === 'open-craft-of-exile') {
openCoE(parsed.value);
} else if (e.target === 'open-poedb') {
openPoedb(parsed.value);
} else if (e.target === 'search-similar') {
findSimilarItems(parsed.value);
}
});
if (e.target === 'open-wiki') {
openWiki(parsed.value)
} else if (e.target === 'open-craft-of-exile') {
openCoE(parsed.value)
} else if (e.target === 'open-poedb') {
openPoedb(parsed.value)
} else if (e.target === 'search-similar') {
findSimilarItems(parsed.value)
}
})
}
export function openWiki(item: ParsedItem) {
window.open(`https://www.poe2wiki.net/wiki/${item.info.refName}`);
export function openWiki (item: ParsedItem) {
window.open(`https://www.poe2wiki.net/wiki/${item.info.refName}`)
}
export function openPoedb(item: ParsedItem) {
window.open(
`https://poe2db.tw/${POEDB_LANGS[AppConfig().language]}/search?q=${item.info.refName}`
);
export function openPoedb (item: ParsedItem) {
window.open(
`https://poe2db.tw/${POEDB_LANGS[AppConfig().language]}/search?q=${item.info.refName}`
)
}
export function openCoE(item: ParsedItem) {
const encodedClipboard = encodeURIComponent(item.rawText);
window.open(
`https://craftofexile.com/?game=poe2&eimport=${encodedClipboard}`
);
export function openCoE (item: ParsedItem) {
const encodedClipboard = encodeURIComponent(item.rawText)
window.open(
`https://craftofexile.com/?game=poe2&eimport=${encodedClipboard}`
)
}
export function findSimilarItems(item: ParsedItem) {
const text = JSON.stringify(item.info.name);
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'stash-search', text },
});
export function findSimilarItems (item: ParsedItem) {
const text = JSON.stringify(item.info.name)
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'stash-search', text }
})
}

View File

@@ -4,7 +4,7 @@
<div :class="$style.widget" v-if="show">
<div :class="$style.box">
<div class="py-2 px-4">
<div class="text-base">Awakened PoE2 Trade2</div>
<div class="text-base">Exiled Exchange 2</div>
<p>{{ t('app_is_ready') }}</p>
</div>
</div>
@@ -25,7 +25,9 @@ const show = shallowRef(false)
Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
if (!show.value && AppConfig().showAttachNotification) {
show.value = true
setTimeout(() => { show.value = false }, 2500)
setTimeout(() => {
show.value = false
}, 2500)
}
})
</script>
@@ -51,7 +53,7 @@ Host.onEvent('MAIN->OVERLAY::overlay-attached', () => {
.box::before {
position: absolute;
content: '';
background: url('/images/TransferOrb.png') no-repeat top right/contain;
background: url('/images/exa.png') no-repeat top right/contain;
right: 100%;
width: 100%;
height: 100%;

View File

@@ -61,7 +61,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const wm = inject<WidgetManager>('wm')!
const widgets = computed(() => {
@@ -79,17 +79,17 @@ export default defineComponent({
return {
t,
widgets,
createOfType(type: string) {
createOfType (type: string) {
wm.create(type)
},
toggle(widget: IWidget) {
toggle (widget: IWidget) {
if (widget.wmWants === 'hide') {
wm.show(widget.wmId)
} else {
wm.hide(widget.wmId)
}
},
handleItemPaste(e: Event) {
handleItemPaste (e: Event) {
const target = e.target as HTMLInputElement
const inputRect = target.getBoundingClientRect()
Host.selfDispatch({

View File

@@ -71,7 +71,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
const leagues = useLeagues()
@@ -177,7 +177,7 @@ export default defineComponent({
props.item.info.unique == null)
})
function handleSearchMouseenter(e: MouseEvent) {
function handleSearchMouseenter (e: MouseEvent) {
if ((filtersComponent.value.$el as HTMLElement).contains(e.relatedTarget as HTMLElement)) {
doSearch.value = true
@@ -216,10 +216,10 @@ export default defineComponent({
showSupportLinks,
presets: computed(() => presets.value.presets.map(preset =>
({ id: preset.id, active: (preset.id === presets.value.active) }))),
selectPreset(id: string) {
selectPreset (id: string) {
presets.value.active = id
},
makeTradeLink() {
makeTradeLink () {
return `https://${getTradeEndpoint()}/trade2/search/poe2/${itemFilters.value.trade.league}?q=${JSON.stringify(createTradeRequest(itemFilters.value, itemStats.value, props.item))}`
}
}

View File

@@ -1,23 +1,52 @@
<template>
<div style="top: 0; left: 0; height: 100%; width: 100%; position: absolute;"
class="flex grow h-full pointer-events-none" :class="{
<div
style="top: 0; left: 0; height: 100%; width: 100%; position: absolute"
class="flex grow h-full pointer-events-none"
:class="{
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory',
}">
<div v-if="!isBrowserShown" class="layout-column shrink-0" style="width: var(--game-panel);">
</div>
<div id="price-window" class="layout-column shrink-0 text-gray-200 pointer-events-auto" style="width: 28.75rem;">
}"
>
<div
v-if="!isBrowserShown"
class="layout-column shrink-0"
style="width: var(--game-panel)"
></div>
<div
id="price-window"
class="layout-column shrink-0 text-gray-200 pointer-events-auto"
style="width: 28.75rem"
>
<ConversionWarningBanner />
<AppTitleBar @close="closePriceCheck" @click="openLeagueSelection" :title="title">
<ui-popover v-if="stableOrbCost" trigger="click" boundary="#price-window">
<AppTitleBar
@close="closePriceCheck"
@click="openLeagueSelection"
:title="title"
>
<ui-popover
v-if="stableOrbCost"
trigger="click"
boundary="#price-window"
>
<template #target>
<button><i class="fas fa-exchange-alt" /> {{ stableOrbCost }}</button>
<button>
<i class="fas fa-exchange-alt" /> {{ stableOrbCost }}
</button>
</template>
<template #content>
<item-quick-price class="text-base" :price="{ min: stableOrbCost, max: stableOrbCost, currency: 'chaos' }"
item-img="/images/divine.png" />
<item-quick-price
class="text-base"
:price="{
min: stableOrbCost,
max: stableOrbCost,
currency: 'chaos',
}"
item-img="/images/divine.png"
/>
<div v-for="i in 9" :key="i">
<div class="pl-1">{{ i / 10 }} div {{ Math.round(stableOrbCost * i / 10) }} c</div>
<div class="pl-1">
{{ i / 10 }} div {{ Math.round((stableOrbCost * i) / 10) }} c
</div>
</div>
</template>
</ui-popover>
@@ -26,33 +55,61 @@
</AppTitleBar>
<div class="grow layout-column min-h-0 bg-gray-800">
<background-info />
<check-position-circle v-if="showCheckPos" :position="checkPosition" style="z-index: -1;" />
<check-position-circle
v-if="showCheckPos"
:position="checkPosition"
style="z-index: -1"
/>
<template v-if="item?.isErr()">
<ui-error-box class="m-4">
<template #name>{{ t(item.error.name) }}</template>
<p>{{ t(item.error.message) }}</p>
</ui-error-box>
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{ item.error.rawText }}</pre>
<pre class="bg-gray-900 rounded m-4 overflow-x-hidden p-2">{{
item.error.rawText
}}</pre>
</template>
<template v-else-if="item?.isOk()">
<unidentified-resolver :item="item.value" @identify="handleIdentification($event)" />
<checked-item v-if="isLeagueSelected" :item="item.value" :advanced-check="advancedCheck" />
<unidentified-resolver
:item="item.value"
@identify="handleIdentification($event)"
/>
<checked-item
v-if="isLeagueSelected"
:item="item.value"
:advanced-check="advancedCheck"
/>
</template>
<div v-if="isBrowserShown" class="bg-gray-900 px-6 py-2 truncate">
<i18n-t keypath="app.toggle_browser_hint" tag="div">
<span class="bg-gray-400 text-gray-900 rounded px-1">{{ overlayKey }}</span>
<span class="bg-gray-400 text-gray-900 rounded px-1">{{
overlayKey
}}</span>
</i18n-t>
</div>
</div>
</div>
<webview v-if="isBrowserShown" ref="iframeEl" class="pointer-events-auto flex-1" width="100%" height="100%" />
<webview
v-if="isBrowserShown"
ref="iframeEl"
class="pointer-events-auto flex-1"
width="100%"
height="100%"
/>
<div v-else class="layout-column flex-1 min-w-0">
<div class="flex" :class="{
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory'
}">
<related-items v-if="item?.isOk()" class="pointer-events-auto" :item="item.value"
:click-position="clickPosition" />
<div
class="flex"
:class="{
'flex-row': clickPosition === 'stash',
'flex-row-reverse': clickPosition === 'inventory',
}"
>
<related-items
v-if="item?.isOk()"
class="pointer-events-auto"
:item="item.value"
:click-position="clickPosition"
/>
<rate-limiter-state class="pointer-events-auto" />
</div>
</div>
@@ -79,9 +136,13 @@ import CheckPositionCircle from './CheckPositionCircle.vue'
import AppTitleBar from '@/web/ui/AppTitlebar.vue'
import ItemQuickPrice from '@/web/ui/ItemQuickPrice.vue'
import { PriceCheckWidget, WidgetManager } from '../overlay/interfaces'
import ConversionWarningBanner from "../conversion-warn-banner/ConversionWarningBanner.vue";
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
type ParseError = { name: string; message: string; rawText: ParsedItem['rawText'] }
type ParseError = {
name: string;
message: string;
rawText: ParsedItem['rawText'];
};
export default defineComponent({
components: {
@@ -103,7 +164,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const wm = inject<WidgetManager>('wm')!
const { xchgRate, initialLoading: xchgRateLoading, queuePricesFetch } = usePoeninja()
@@ -122,9 +183,13 @@ export default defineComponent({
if (Host.isElectron && !e.focusOverlay) {
// everything in CSS pixels
const width = 28.75 * AppConfig().fontSize
const screenX = ((e.position.x - window.screenX) > window.innerWidth / 2)
? (window.screenX + window.innerWidth) - wm.poePanelWidth.value - width
: window.screenX + wm.poePanelWidth.value
const screenX =
e.position.x - window.screenX > window.innerWidth / 2
? window.screenX +
window.innerWidth -
wm.poePanelWidth.value -
width
: window.screenX + wm.poePanelWidth.value
MainProcess.sendEvent({
name: 'OVERLAY->MAIN::track-area',
payload: {
@@ -146,13 +211,18 @@ export default defineComponent({
checkPosition.value = e.position
advancedCheck.value = e.focusOverlay
item.value = (e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard))
.andThen(item => (
(item.category === ItemCategory.HeistContract && item.rarity !== ItemRarity.Unique) ||
(item.category === ItemCategory.Sentinel && item.rarity !== ItemRarity.Unique))
? err('item.unknown')
: ok(item))
.mapErr(err => ({
item.value = (
e.item ? ok(e.item as ParsedItem) : parseClipboard(e.clipboard)
)
.andThen((item) =>
(item.category === ItemCategory.HeistContract &&
item.rarity !== ItemRarity.Unique) ||
(item.category === ItemCategory.Sentinel &&
item.rarity !== ItemRarity.Unique)
? err('item.unknown')
: ok(item)
)
.mapErr((err) => ({
name: `${err}`,
message: `${err}_help`,
rawText: e.clipboard
@@ -163,7 +233,7 @@ export default defineComponent({
}
})
function handleIdentification(identified: ParsedItem) {
function handleIdentification (identified: ParsedItem) {
item.value = ok(identified)
}
@@ -171,14 +241,17 @@ export default defineComponent({
wm.hide(props.config.wmId)
})
watch(() => props.config.wmWants, (state) => {
if (state === 'hide') {
closeBrowser()
watch(
() => props.config.wmWants,
(state) => {
if (state === 'hide') {
closeBrowser()
}
}
})
)
const leagues = useLeagues()
const title = computed(() => leagues.selectedId.value || 'Awakened PoE2 Trade2')
const title = computed(() => leagues.selectedId.value || 'Exiled Exchange 2')
const stableOrbCost = computed(() => (xchgRate.value) ? Math.round(xchgRate.value) : null)
const isBrowserShown = computed(() => props.config.wmFlags.includes('has-browser'))
const overlayKey = computed(() => AppConfig().overlayKey)
@@ -205,7 +278,7 @@ export default defineComponent({
}
})
function closePriceCheck() {
function closePriceCheck () {
if (isBrowserShown.value || !Host.isElectron) {
wm.hide(props.config.wmId)
} else {
@@ -213,7 +286,7 @@ export default defineComponent({
}
}
function openLeagueSelection() {
function openLeagueSelection () {
const settings = wm.widgets.value.find(w => w.wmType === 'settings')!
wm.setFlag(settings.wmId, `settings:widget:${props.config.wmId}`, true)
wm.show(settings.wmId)
@@ -221,14 +294,14 @@ export default defineComponent({
const iframeEl = shallowRef<HTMLIFrameElement | null>(null)
function showBrowser(url: string) {
function showBrowser (url: string) {
wm.setFlag(props.config.wmId, 'has-browser', true)
nextTick(() => {
iframeEl.value!.src = url
})
}
function closeBrowser() {
function closeBrowser () {
wm.setFlag(props.config.wmId, 'has-browser', false)
}

View File

@@ -63,7 +63,7 @@ const stats = computed(() => {
if (!parsed.roll) {
return {
text: parsed.translation.string,
contribution: contribution,
contribution,
contributes: true
}
}

View File

@@ -176,7 +176,7 @@ export function createFilters (
}
filters.searchRelaxed = {
category: item.category,
disabled: disabled
disabled
}
}
}

View File

@@ -177,7 +177,7 @@ export function calculatedStatToFilter (
? FilterTag.Enchant
: FilterTag.Variant,
oils: decodeOils(calc),
sources: sources,
sources,
option: {
value: sources[0].contributes!.value
},
@@ -197,7 +197,7 @@ export function calculatedStatToFilter (
text: translation.string,
tag: (type as unknown) as FilterTag,
oils: decodeOils(calc),
sources: sources,
sources,
roll: undefined,
disabled: true
}
@@ -288,7 +288,7 @@ export function calculatedStatToFilter (
bounds: (item.rarity === ItemRarity.Unique && roll.min !== roll.max && calc.stat.better !== StatBetter.NotComparable)
? filterBounds
: undefined,
dp: dp,
dp,
isNegated: false,
tradeInvert: calc.stat.trade.inverted
}
@@ -427,10 +427,10 @@ function applyClusterJewelRules (filters: StatFilter[]) {
// 4 is [_, 5]
if (filter.roll!.value === 4) {
filter.roll!.max = 5
// 5 is [5, 5]
// 5 is [5, 5]
} else if (filter.roll!.value === 5) {
filter.roll!.min = filter.roll!.default.min
// 3, 6, 10, 11, 12 are [n, _]
// 3, 6, 10, 11, 12 are [n, _]
} else if (
filter.roll!.value === 3 ||
filter.roll!.value === 6 ||

View File

@@ -335,7 +335,7 @@ export function filterPseudo (ctx: FiltersCreationContext) {
const filter = calculatedStatToFilter({
stat: STAT_BY_REF(rule.pseudo)!,
type: ModifierType.Pseudo,
sources: sources
sources
}, ctx.searchInRange, ctx.item)
filter.disabled = rule.disabled ?? true

View File

@@ -257,7 +257,7 @@ function propToFilter (opts: {
better: StatBetter.PositiveRoll
}
const filter = calculatedStatToFilter({
stat: stat,
stat,
type: ModifierType.Pseudo,
sources: [{
modifier: {
@@ -265,7 +265,7 @@ function propToFilter (opts: {
stats: []
},
stat: {
stat: stat,
stat,
translation: stat.matchers[0],
roll: {
dp: opts.dp ?? false,

View File

@@ -1,144 +1,144 @@
import { ParsedItem } from '@/parser';
import { useLeagues } from '@/web/background/Leagues';
import { Host } from '@/web/background/IPC';
import { Cache } from '../trade/Cache';
import { usePoeninja } from '@/web/background/Prices';
import { ParsedItem } from '@/parser'
import { useLeagues } from '@/web/background/Leagues'
import { Host } from '@/web/background/IPC'
import { Cache } from '../trade/Cache'
import { usePoeninja } from '@/web/background/Prices'
const cache = new Cache();
const cache = new Cache()
interface PoepricesApiResponse {
/* eslint-disable camelcase */ currency: 'chaos' | 'divine' | 'exalt';
error: number;
error_msg: string;
warning_msg: string;
max: number;
min: number;
pred_confidence_score: number;
pred_explanation: Array<[string, number]>;
/* eslint-disable camelcase */ currency: 'chaos' | 'divine' | 'exalt'
error: number
error_msg: string
warning_msg: string
max: number
min: number
pred_confidence_score: number
pred_explanation: Array<[string, number]>
}
export interface RareItemPrice {
max: number;
min: number;
confidence: number;
currency: 'chaos' | 'div';
explanation: Array<{
name: string;
contrib: number;
}>;
max: number
min: number
confidence: number
currency: 'chaos' | 'div'
explanation: Array<{
name: string
contrib: number
}>
}
export async function requestPoeprices(
item: ParsedItem
export async function requestPoeprices (
item: ParsedItem
): Promise<RareItemPrice> {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade', // might be required name here
});
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade' // might be required name here
})
let data = cache.get<PoepricesApiResponse>(query);
if (!data) {
const response = await Host.proxy(`www.poeprices.info/api?${query}`);
try {
data = (await response.json()) as PoepricesApiResponse;
} catch (e) {
throw new Error(
`${response.status}, poeprices.info API is under load or down.`
);
}
let data = cache.get<PoepricesApiResponse>(query)
if (!data) {
const response = await Host.proxy(`www.poeprices.info/api?${query}`)
try {
data = (await response.json()) as PoepricesApiResponse
} catch (e) {
throw new Error(
`${response.status}, poeprices.info API is under load or down.`
)
}
if (data.error !== 0) {
throw new Error(data.error_msg);
}
if (data.error !== 0) {
throw new Error(data.error_msg)
}
cache.set<PoepricesApiResponse>(query, data, 300);
}
cache.set<PoepricesApiResponse>(query, data, 300)
}
if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja();
const xchgExalted = findPriceByQuery({
ns: 'ITEM',
name: 'Exalted Orb',
variant: undefined,
});
if (!xchgExalted) {
throw new Error('poeprices.info gave the price in Exalted Orbs.');
}
const converted = autoCurrency([
data.min * xchgExalted.chaos,
data.max * xchgExalted.chaos,
]);
data.min = converted.min;
data.max = converted.max;
data.currency = converted.currency === 'div' ? 'divine' : 'chaos';
} else if (data.currency !== 'divine' && data.currency !== 'chaos') {
throw new Error('poeprices.info gave the price in unknown currency.');
}
if (data.currency === 'exalt') {
const { findPriceByQuery, autoCurrency } = usePoeninja()
const xchgExalted = findPriceByQuery({
ns: 'ITEM',
name: 'Exalted Orb',
variant: undefined
})
if (!xchgExalted) {
throw new Error('poeprices.info gave the price in Exalted Orbs.')
}
const converted = autoCurrency([
data.min * xchgExalted.chaos,
data.max * xchgExalted.chaos
])
data.min = converted.min
data.max = converted.max
data.currency = converted.currency === 'div' ? 'divine' : 'chaos'
} else if (data.currency !== 'divine' && data.currency !== 'chaos') {
throw new Error('poeprices.info gave the price in unknown currency.')
}
return {
currency: data.currency === 'divine' ? 'div' : 'chaos',
min: data.min,
max: data.max,
confidence: Math.round(data.pred_confidence_score),
explanation: data.pred_explanation.map((expl) => ({
name: expl[0],
contrib: Math.round(expl[1] * 100),
})),
};
return {
currency: data.currency === 'divine' ? 'div' : 'chaos',
min: data.min,
max: data.max,
confidence: Math.round(data.pred_confidence_score),
explanation: data.pred_explanation.map((expl) => ({
name: expl[0],
contrib: Math.round(expl[1] * 100)
}))
}
}
export function getExternalLink(item: ParsedItem): string {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade',
w: 1,
});
return `https://www.poeprices.info/api?${query}`;
export function getExternalLink (item: ParsedItem): string {
const query = querystring({
i: utf8ToBase64(transformItemText(item.rawText)),
l: useLeagues().selectedId.value,
s: 'awakened-poe-trade',
w: 1
})
return `https://www.poeprices.info/api?${query}`
}
export async function sendFeedback(
feedback: { text: string; option: 'fair' | 'low' | 'high' },
prediction: Pick<PoepricesApiResponse, 'min' | 'max' | 'currency'>,
item: ParsedItem
export async function sendFeedback (
feedback: { text: string, option: 'fair' | 'low' | 'high' },
prediction: Pick<PoepricesApiResponse, 'min' | 'max' | 'currency'>,
item: ParsedItem
): Promise<void> {
const body = new FormData();
body.append('selector', feedback.option);
body.append('feedbacktxt', feedback.text);
body.append('qitem_txt', utf8ToBase64(transformItemText(item.rawText)));
body.append('source', 'awakened-poe-trade');
body.append('min', String(prediction.min));
body.append('max', String(prediction.max));
body.append('currency', prediction.currency);
body.append('league', useLeagues().selectedId.value!);
// body.append('debug', String(1))
const body = new FormData()
body.append('selector', feedback.option)
body.append('feedbacktxt', feedback.text)
body.append('qitem_txt', utf8ToBase64(transformItemText(item.rawText)))
body.append('source', 'awakened-poe-trade')
body.append('min', String(prediction.min))
body.append('max', String(prediction.max))
body.append('currency', prediction.currency)
body.append('league', useLeagues().selectedId.value!)
// body.append('debug', String(1))
const response = await Host.proxy('www.poeprices.info/send_feedback', {
method: 'POST',
body,
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const text = await response.text();
// console.assert(text === `"${feedback.option}"`)
const response = await Host.proxy('www.poeprices.info/send_feedback', {
method: 'POST',
body
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const text = await response.text()
// console.assert(text === `"${feedback.option}"`)
}
function utf8ToBase64(value: string) {
return btoa(unescape(encodeURIComponent(value)));
function utf8ToBase64 (value: string) {
return btoa(unescape(encodeURIComponent(value)))
}
function querystring(q: Record<string, any>) {
return Object.entries(q)
.map((pair) => pair.map(encodeURIComponent).join('='))
.join('&');
function querystring (q: Record<string, any>) {
return Object.entries(q)
.map((pair) => pair.map(encodeURIComponent).join('='))
.join('&')
}
/**
* @deprecated TODO blocked by poeprices.info not supporting advanced text
*/
function transformItemText(rawText: string) {
// this may not account for all cases
return rawText
.replace(/(?<=\d)(\([^)]+\))/gm, '')
.replace(/^\{.+\}$\n/gm, '');
function transformItemText (rawText: string) {
// this may not account for all cases
return rawText
.replace(/(?<=\d)(\([^)]+\))/gm, '')
.replace(/^\{.+\}$\n/gm, '')
}

View File

@@ -111,7 +111,7 @@ import TradeLinks from './TradeLinks.vue'
const slowdown = artificialSlowdown(900)
function useBulkApi() {
function useBulkApi () {
type BulkSearchExtended = Record<'xchgChaos' | 'xchgStable', {
listed: Ref<BulkSearch | null>
listedLazy: ComputedRef<PricingResult[]>
@@ -121,7 +121,7 @@ function useBulkApi() {
const error = shallowRef<string | null>(null)
const result = shallowRef<BulkSearchExtended | null>(null)
async function search(item: ParsedItem, filters: ItemFilters) {
async function search (item: ParsedItem, filters: ItemFilters) {
try {
searchId += 1
error.value = null
@@ -133,8 +133,8 @@ function useBulkApi() {
const have = (item.info.refName === 'Chaos Orb')
? ['divine']
: (item.info.refName === 'Divine Orb')
? ['chaos']
: ['divine', 'chaos']
? ['chaos']
: ['divine', 'chaos']
const optimisticSearch = await execBulkSearch(
item, filters, have, { accountName: AppConfig().accountName })
@@ -149,7 +149,7 @@ function useBulkApi() {
}
}
function getResultsByHave(
function getResultsByHave (
item: ParsedItem,
filters: ItemFilters,
preloaded: Array<BulkSearch | null>,
@@ -203,7 +203,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
const { error, result, search } = useBulkApi()
@@ -236,7 +236,7 @@ export default defineComponent({
}
})
function makeTradeLink(_have?: string[]) {
function makeTradeLink (_have?: string[]) {
const have = _have ?? ((selectedCurr.value === 'xchgStable') ? ['divine'] : ['chaos'])
const httpPostBody = createTradeRequest(props.filters, props.item, have)
const httpGetQuery = { exchange: httpPostBody.query }
@@ -254,7 +254,7 @@ export default defineComponent({
execSearch: () => { search(props.item, props.filters) },
showSeller: computed(() => widget.value.showSeller),
makeTradeLink,
openTradeLink() {
openTradeLink () {
showBrowser(makeTradeLink(['mirror']))
}
}

View File

@@ -105,7 +105,7 @@ const API_FETCH_LIMIT = 100
const MIN_NOT_GROUPED = 7
const MIN_GROUPED = 10
function useTradeApi() {
function useTradeApi () {
let searchId = 0
const error = shallowRef<string | null>(null)
const searchResult = shallowRef<SearchResult | null>(null)
@@ -143,7 +143,7 @@ function useTradeApi() {
return out
})
async function search(filters: ItemFilters, stats: StatFilter[], item: ParsedItem) {
async function search (filters: ItemFilters, stats: StatFilter[], item: ParsedItem) {
try {
searchId += 1
error.value = null
@@ -174,7 +174,7 @@ function useTradeApi() {
}
let fetched = 20
async function fetchMore(): Promise<void> {
async function fetchMore (): Promise<void> {
if (_searchId !== searchId) return
const totalGrouped = groupedResults.value.length
const totalNotGrouped = groupedResults.value.reduce((len, res) =>
@@ -215,7 +215,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const widget = computed(() => AppConfig<PriceCheckWidget>('price-check')!)
watch(() => props.item, (item) => {
@@ -226,7 +226,7 @@ export default defineComponent({
const showBrowser = inject<(url: string) => void>('builtin-browser')!
function makeTradeLink() {
function makeTradeLink () {
return (searchResult.value)
? `https://${getTradeEndpoint()}/trade2/search/poe2/${props.filters.trade.league}/${searchResult.value.id}`
: `https://${getTradeEndpoint()}/trade2/search/poe2/${props.filters.trade.league}?q=${JSON.stringify(createTradeRequest(props.filters, props.stats, props.item))}`
@@ -253,7 +253,7 @@ export default defineComponent({
error,
showSeller: computed(() => widget.value.showSeller),
makeTradeLink,
openTradeLink() {
openTradeLink () {
showBrowser(makeTradeLink())
}
}

View File

@@ -1,225 +1,225 @@
import { DateTime } from 'luxon';
import { Host } from '@/web/background/IPC';
import { DateTime } from 'luxon'
import { Host } from '@/web/background/IPC'
import {
TradeResponse,
Account,
getTradeEndpoint,
RATE_LIMIT_RULES,
adjustRateLimits,
tradeTag,
preventQueueCreation,
} from './common';
import { RateLimiter } from './RateLimiter';
import { ItemFilters } from '../filters/interfaces';
import { ParsedItem } from '@/parser';
import { Cache } from './Cache';
TradeResponse,
Account,
getTradeEndpoint,
RATE_LIMIT_RULES,
adjustRateLimits,
tradeTag,
preventQueueCreation
} from './common'
import { RateLimiter } from './RateLimiter'
import { ItemFilters } from '../filters/interfaces'
import { ParsedItem } from '@/parser'
import { Cache } from './Cache'
interface TradeRequest {
/* eslint-disable camelcase */ engine: 'new';
query: {
status: { option: 'online' | 'onlineleague' | 'any' };
have: string[];
want: string[];
minimum?: number;
fulfillable?: null;
};
sort: { have: 'asc' };
/* eslint-disable camelcase */ engine: 'new'
query: {
status: { option: 'online' | 'onlineleague' | 'any' }
have: string[]
want: string[]
minimum?: number
fulfillable?: null
}
sort: { have: 'asc' }
}
interface SearchResult {
id: string;
result: Record<string, FetchResult>;
total: number;
id: string
result: Record<string, FetchResult>
total: number
}
interface FetchResult {
id: string;
listing: {
indexed: string;
offers: Array<{
exchange: {
currency: string;
amount: number;
};
item: {
amount: number;
stock: number;
};
}>;
account: Account;
};
id: string
listing: {
indexed: string
offers: Array<{
exchange: {
currency: string
amount: number
}
item: {
amount: number
stock: number
}
}>
account: Account
}
}
export interface PricingResult {
id: string;
relativeDate: string;
exchangeAmount: number;
itemAmount: number;
stock: number;
accountStatus: 'offline' | 'online' | 'afk';
isMine: boolean;
accountName: string;
ign: string;
id: string
relativeDate: string
exchangeAmount: number
itemAmount: number
stock: number
accountStatus: 'offline' | 'online' | 'afk'
isMine: boolean
accountName: string
ign: string
}
const cache = new Cache();
const cache = new Cache()
async function requestTradeResultList(
body: TradeRequest,
leagueId: string
async function requestTradeResultList (
body: TradeRequest,
leagueId: string
): Promise<SearchResult> {
let data = cache.get<SearchResult>([body, leagueId]);
let data = cache.get<SearchResult>([body, leagueId])
if (!data) {
preventQueueCreation([{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }]);
if (!data) {
preventQueueCreation([{ count: 1, limiters: RATE_LIMIT_RULES.EXCHANGE }])
await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE);
await RateLimiter.waitMulti(RATE_LIMIT_RULES.EXCHANGE)
const response = await Host.proxy(
`${getTradeEndpoint()}/api/trade2/exchange/${leagueId}`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
);
adjustRateLimits(RATE_LIMIT_RULES.EXCHANGE, response.headers);
const response = await Host.proxy(
`${getTradeEndpoint()}/api/trade2/exchange/${leagueId}`,
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
)
adjustRateLimits(RATE_LIMIT_RULES.EXCHANGE, response.headers)
const _data = (await response.json()) as TradeResponse<SearchResult>;
if (_data.error) {
throw new Error(_data.error.message);
} else {
data = _data;
}
const _data = (await response.json()) as TradeResponse<SearchResult>
if (_data.error) {
throw new Error(_data.error.message)
} else {
data = _data
}
cache.set<SearchResult>(
[body, leagueId],
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE)
);
}
cache.set<SearchResult>(
[body, leagueId],
data,
Cache.deriveTtl(...RATE_LIMIT_RULES.EXCHANGE)
)
}
return data;
return data
}
function toPricingResult(
result: FetchResult,
opts: { accountName: string },
offer: number
function toPricingResult (
result: FetchResult,
opts: { accountName: string },
offer: number
): PricingResult {
return {
id: result.id,
relativeDate:
DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ??
'',
exchangeAmount: result.listing.offers[offer].exchange.amount,
itemAmount: result.listing.offers[offer].item.amount,
stock: result.listing.offers[offer].item.stock,
isMine: result.listing.account.name === opts.accountName,
ign: result.listing.account.lastCharacterName,
accountName: result.listing.account.name,
accountStatus: result.listing.account.online
? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline',
};
return {
id: result.id,
relativeDate:
DateTime.fromISO(result.listing.indexed).toRelative({ style: 'short' }) ??
'',
exchangeAmount: result.listing.offers[offer].exchange.amount,
itemAmount: result.listing.offers[offer].item.amount,
stock: result.listing.offers[offer].item.stock,
isMine: result.listing.account.name === opts.accountName,
ign: result.listing.account.lastCharacterName,
accountName: result.listing.account.name,
accountStatus: result.listing.account.online
? result.listing.account.online.status === 'afk'
? 'afk'
: 'online'
: 'offline'
}
}
export interface BulkSearch {
queryId: string;
haveTag: string;
total: number;
listed: PricingResult[];
queryId: string
haveTag: string
total: number
listed: PricingResult[]
}
export function createTradeRequest(
filters: ItemFilters,
item: ParsedItem,
have: string[]
export function createTradeRequest (
filters: ItemFilters,
item: ParsedItem,
have: string[]
): TradeRequest {
return {
engine: 'new',
query: {
have: have,
want: [tradeTag(item)!],
status: {
option: filters.trade.offline
? 'any'
: filters.trade.onlineInLeague
? 'onlineleague'
: 'online',
},
minimum:
filters.stackSize && !filters.stackSize.disabled
? filters.stackSize.value
: undefined,
// fulfillable: null
},
sort: { have: 'asc' },
};
return {
engine: 'new',
query: {
have,
want: [tradeTag(item)!],
status: {
option: filters.trade.offline
? 'any'
: filters.trade.onlineInLeague
? 'onlineleague'
: 'online'
},
minimum:
filters.stackSize && !filters.stackSize.disabled
? filters.stackSize.value
: undefined
// fulfillable: null
},
sort: { have: 'asc' }
}
}
const SHOW_RESULTS = 20;
const API_FETCH_LIMIT = 100;
const SHOW_RESULTS = 20
const API_FETCH_LIMIT = 100
export async function execBulkSearch(
item: ParsedItem,
filters: ItemFilters,
have: string[],
opts: { accountName: string }
export async function execBulkSearch (
item: ParsedItem,
filters: ItemFilters,
have: string[],
opts: { accountName: string }
): Promise<Array<BulkSearch | null>> {
const query = await requestTradeResultList(
createTradeRequest(filters, item, have),
filters.trade.league
);
const query = await requestTradeResultList(
createTradeRequest(filters, item, have),
filters.trade.league
)
const offer = 0;
const results = Object.values(query.result).filter(
(result) => result.listing.offers.length === 1
);
const offer = 0
const results = Object.values(query.result).filter(
(result) => result.listing.offers.length === 1
)
const resultByHave = have.map((tradeTag) => {
const resultsTag = results.filter(
(result) => result.listing.offers[offer].exchange.currency === tradeTag
);
const resultByHave = have.map((tradeTag) => {
const resultsTag = results.filter(
(result) => result.listing.offers[offer].exchange.currency === tradeTag
)
const loadedOnDemand =
tradeTag === 'chaos' &&
resultsTag.length < SHOW_RESULTS &&
query.total > API_FETCH_LIMIT;
if (loadedOnDemand) return null;
const loadedOnDemand =
tradeTag === 'chaos' &&
resultsTag.length < SHOW_RESULTS &&
query.total > API_FETCH_LIMIT
if (loadedOnDemand) return null
const listed = resultsTag
.sort(
(a, b) =>
a.listing.offers[offer].exchange.amount /
a.listing.offers[offer].item.amount -
b.listing.offers[offer].exchange.amount /
b.listing.offers[offer].item.amount
)
.slice(0, SHOW_RESULTS)
.map((result) => toPricingResult(result, opts, offer));
const listed = resultsTag
.sort(
(a, b) =>
a.listing.offers[offer].exchange.amount /
a.listing.offers[offer].item.amount -
b.listing.offers[offer].exchange.amount /
b.listing.offers[offer].item.amount
)
.slice(0, SHOW_RESULTS)
.map((result) => toPricingResult(result, opts, offer))
const chaosIsLoaded =
tradeTag === 'divine' &&
resultsTag.length < results.length &&
(results.length - resultsTag.length >= SHOW_RESULTS ||
query.total <= API_FETCH_LIMIT);
const chaosIsLoaded =
tradeTag === 'divine' &&
resultsTag.length < results.length &&
(results.length - resultsTag.length >= SHOW_RESULTS ||
query.total <= API_FETCH_LIMIT)
return {
queryId: query.id,
haveTag: tradeTag,
// this is a best guess when making request with multiple `have` currencies
total: chaosIsLoaded
? resultsTag.length
: query.total - (results.length - resultsTag.length),
listed: listed,
};
});
return {
queryId: query.id,
haveTag: tradeTag,
// this is a best guess when making request with multiple `have` currencies
total: chaosIsLoaded
? resultsTag.length
: query.total - (results.length - resultsTag.length),
listed
}
})
return resultByHave;
return resultByHave
}

File diff suppressed because it is too large Load Diff

View File

@@ -104,7 +104,7 @@ export default defineComponent({
: autoCurrency(trend.chaos)
return {
price: price,
price,
change: deltaFromGraph(trend.graph),
url: trend.url
}

View File

@@ -28,7 +28,7 @@ export default defineComponent({
default: null
}
},
setup(props, ctx) {
setup (props, ctx) {
const identifiedVariants = computed(() => {
const baseType = props.item!.info.refName
const possible: BaseType[] = []
@@ -55,10 +55,10 @@ export default defineComponent({
!props.item.info.unique
})
function select(info: BaseType) {
function select (info: BaseType) {
const newItem: ParsedItem = {
...props.item!,
info: info
info
}
ctx.emit('identify', newItem)
}

View File

@@ -69,7 +69,7 @@ import SettingsStopwatch from './stopwatch.vue'
import SettingsItemSearch from '../item-search/settings-item-search.vue'
import ConversionWarningBanner from '../conversion-warn-banner/ConversionWarningBanner.vue'
function shuffle<T>(array: T[]): T[] {
function shuffle<T> (array: T[]): T[] {
let currentIndex = array.length
while (currentIndex !== 0) {
const randomIndex = Math.floor(Math.random() * currentIndex)
@@ -80,7 +80,7 @@ function shuffle<T>(array: T[]): T[] {
return array
}
function quit() {
function quit () {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'quit' }
@@ -95,7 +95,7 @@ export default defineComponent({
required: true
}
},
setup(props) {
setup (props) {
const wm = inject<WidgetManager>('wm')!
const { t } = useI18n()
@@ -143,7 +143,7 @@ export default defineComponent({
menuByType(configWidget.value?.wmType)
.map(group => group.map(component => ({
name: t(component.name!),
select() { selectedComponent.value = component },
select () { selectedComponent.value = component },
isSelected: (selectedComponent.value === component),
type: 'menu-item' as const
}))),
@@ -152,14 +152,14 @@ export default defineComponent({
return {
t,
save() {
save () {
updateConfig(configClone.value!)
saveConfig()
pushHostConfig()
wm.hide(props.config.wmId)
},
cancel() {
cancel () {
wm.hide(props.config.wmId)
},
quit,
@@ -178,13 +178,13 @@ export default defineComponent({
})
}),
podiumVisible,
showPodium() { podiumVisible.value = true },
hidePodium() { podiumVisible.value = false }
showPodium () { podiumVisible.value = true },
hidePodium () { podiumVisible.value = false }
}
}
})
function menuByType(type?: string) {
function menuByType (type?: string) {
switch (type) {
case 'stash-search':
return [[SettingsStashSearch]]
@@ -206,7 +206,7 @@ function menuByType(type?: string) {
}
}
function flatJoin<T, J>(arr: T[][], joinEl: () => J) {
function flatJoin<T, J> (arr: T[][], joinEl: () => J) {
const out: Array<T | J> = []
for (const nested of arr) {
out.push(...nested)

View File

@@ -2,12 +2,12 @@
<div class="p-2 flex flex-col h-full items-center">
<div class="flex flex-col items-center p-2 mb-4">
<img class="w-12 h-12" src="/images/TransferOrb.png">
<p class="text-base">Awakened PoE2 Trade2</p>
<p class="text-base">Exiled Exchange 2</p>
<p class="">{{ t('app.version', [version]) }}</p>
<div class="flex gap-2">
<a class="border-b" href="https://github.com/Kvan7/awakened-poe2-trade2/releases" target="_blank">{{
<a class="border-b" href="https://github.com/Kvan7/exiled-exchange-2/releases" target="_blank">{{
t('app.release_notes') }}</a>
<a class="border-b" href="https://github.com/Kvan7/awakened-poe2-trade2/issues" target="_blank">{{
<a class="border-b" href="https://github.com/Kvan7/exiled-exchange-2/issues" target="_blank">{{
t('app.report_bug') }}</a>
</div>
</div>
@@ -35,51 +35,82 @@ import { useI18n } from 'vue-i18n'
import { Host } from '@/web/background/IPC'
import { DateTime } from 'luxon'
function checkForUpdates() {
function checkForUpdates () {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'check-for-update' }
})
}
function openDownloadPage() {
window.open('https://snosme.github.io/awakened-poe-trade/download')
function openDownloadPage () {
window.open('https://github.com/Kvan7/exiled-exchange-2/releases')
}
function quitAndInstall() {
function quitAndInstall () {
Host.sendEvent({
name: 'CLIENT->MAIN::user-action',
payload: { action: 'update-and-restart' }
})
}
function fmtTime(millis: number) {
function fmtTime (millis: number) {
return DateTime.fromMillis(millis).toRelative({ style: 'long' }) ?? 'n/a'
}
export default defineComponent({
name: 'settings.about',
inheritAttrs: false,
setup() {
setup () {
const { t } = useI18n()
const info = computed(() => {
const rawInfo = Host.updateInfo.value
switch (rawInfo.state) {
case 'initial':
return { str1: t('updates.maybe_outdated'), str2: t('updates.never_checked'), action: checkForUpdates, actionText: t('updates.check_now') }
return {
str1: t('updates.maybe_outdated'),
str2: t('updates.never_checked'),
action: checkForUpdates,
actionText: t('updates.check_now')
}
case 'checking-for-update':
return { str1: t('updates.checking'), str2: t('please_wait') }
case 'update-not-available':
return { str1: t('updates.latest'), str2: t('updates.last_checked', [fmtTime(rawInfo.checkedAt)]), action: checkForUpdates, actionText: t('updates.check_now') }
return {
str1: t('updates.latest'),
str2: t('updates.last_checked', [fmtTime(rawInfo.checkedAt)]),
action: checkForUpdates,
actionText: t('updates.check_now')
}
case 'error':
return { str1: t('updates.maybe_outdated'), str2: t('updates.error'), action: openDownloadPage, actionText: t('updates.downloads_page') }
return {
str1: t('updates.maybe_outdated'),
str2: t('updates.error'),
action: openDownloadPage,
actionText: t('updates.downloads_page')
}
case 'update-downloaded':
return { str1: t('updates.available', [rawInfo.version]), str2: t('updates.installed_on_exit'), action: quitAndInstall, actionText: t('updates.install_now') }
return {
str1: t('updates.available', [rawInfo.version]),
str2: t('updates.installed_on_exit'),
action: quitAndInstall,
actionText: t('updates.install_now')
}
case 'update-available':
return (rawInfo.noDownloadReason)
? { str1: t('updates.available', [rawInfo.version]), str2: (rawInfo.noDownloadReason === 'not-supported') ? t('updates.download_manually') : t('updates.download_disabled'), action: openDownloadPage, actionText: t('updates.downloads_page') }
: { str1: t('updates.available', [rawInfo.version]), str2: t('updates.downloading') }
return rawInfo.noDownloadReason
? {
str1: t('updates.available', [rawInfo.version]),
str2:
rawInfo.noDownloadReason === 'not-supported'
? t('updates.download_manually')
: t('updates.download_disabled'),
action: openDownloadPage,
actionText: t('updates.downloads_page')
}
: {
str1: t('updates.available', [rawInfo.version]),
str2: t('updates.downloading')
}
}
})

View File

@@ -33,7 +33,7 @@
<div class="mb-4">
<div class="flex-1 mb-1">{{ t(':poe_cfg_file') }}</div>
<input v-model.trim="gameConfig"
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/My Games/Path of Exile 2/production_Config.ini">
class="rounded bg-gray-900 px-1 block w-full font-sans" placeholder="...?/My Games/Path of Exile 2/poe2_production_Config.ini">
</div>
<hr class="mb-4 mx-8 border-gray-700">
<div class="mb-2">

File diff suppressed because it is too large Load Diff

22
testUpdate.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Remove ./renderer/dist if it exists
rm -rf ./renderer/dist
# Remove ./main/dist if it exists
rm -rf ./main/dist
cd ./renderer
npm install
npm run make-index-files
npm run build
cd ..
cd ./main
npm install
npm run build
npm run package