mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-02-08 08:13:15 +08:00
Adjust Config & Update wanderDetailPage
This commit is contained in:
parent
407ebaaa75
commit
063aca6b94
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -10,7 +10,7 @@
|
|||||||
"name": "Attach to side panel",
|
"name": "Attach to side panel",
|
||||||
"webRoot": "${workspaceFolder}/src/",
|
"webRoot": "${workspaceFolder}/src/",
|
||||||
"port": 9222,
|
"port": 9222,
|
||||||
"urlFilter": "chrome-extension://*"
|
"urlFilter": "chrome-extension://fmalpbpehdilmjhnanhpjmnkgbahopfj/*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,17 +8,14 @@ import { isDev, log, port, r } from './utils';
|
|||||||
* Stub index.html to use Vite in development
|
* Stub index.html to use Vite in development
|
||||||
*/
|
*/
|
||||||
async function stubIndexHtml() {
|
async function stubIndexHtml() {
|
||||||
const views = ['options', 'popup', 'sidepanel'];
|
const views = ['sidepanel'];
|
||||||
|
|
||||||
for (const view of views) {
|
for (const view of views) {
|
||||||
await fs.ensureDir(r(`extension/dist/${view}`));
|
await fs.ensureDir(r(`extension/dist/${view}`));
|
||||||
let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8');
|
let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8');
|
||||||
data = data
|
data = data
|
||||||
.replace('"./main.ts"', `"http://localhost:${port}/${view}/main.ts"`)
|
.replace('"./main.ts"', `"http://localhost:${port}/${view}/main.ts"`)
|
||||||
.replace(
|
.replace('<div id="app"></div>', '<div id="app">Vite server did not start</div>');
|
||||||
'<div id="app"></div>',
|
|
||||||
'<div id="app">Vite server did not start</div>',
|
|
||||||
);
|
|
||||||
await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8');
|
await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8');
|
||||||
log('PRE', `stub ${view}`);
|
log('PRE', `stub ${view}`);
|
||||||
}
|
}
|
||||||
|
|||||||
1
shim.d.ts
vendored
1
shim.d.ts
vendored
@ -4,6 +4,5 @@ declare module 'webext-bridge' {
|
|||||||
export interface ProtocolMap {
|
export interface ProtocolMap {
|
||||||
// define message protocol types
|
// define message protocol types
|
||||||
// see https://github.com/antfu/webext-bridge#type-safe-protocols
|
// see https://github.com/antfu/webext-bridge#type-safe-protocols
|
||||||
'tab-update': { tabId: number };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,20 @@
|
|||||||
import App from './views/App.vue';
|
import App from './views/App.vue';
|
||||||
import { setupApp } from '~/logic/common-setup';
|
import { setupApp } from '~/logic/common-setup';
|
||||||
|
|
||||||
// 是否挂在ContentScript Vue APP
|
// 是否在ContentScript挂载Vue APP
|
||||||
const MOUNT_COMPONENT = false;
|
const MOUNT_COMPONENT = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mount component to context window
|
||||||
|
*/
|
||||||
const mountComponent = () => {
|
const mountComponent = () => {
|
||||||
// mount component to context window
|
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.id = __NAME__;
|
container.id = __NAME__;
|
||||||
const root = document.createElement('div');
|
const root = document.createElement('div');
|
||||||
const styleEl = document.createElement('link');
|
const styleEl = document.createElement('link');
|
||||||
const shadowDOM =
|
const shadowDOM = container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container;
|
||||||
container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) ||
|
|
||||||
container;
|
|
||||||
styleEl.setAttribute('rel', 'stylesheet');
|
styleEl.setAttribute('rel', 'stylesheet');
|
||||||
styleEl.setAttribute(
|
styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/index.css'));
|
||||||
'href',
|
|
||||||
browser.runtime.getURL('dist/contentScripts/index.css'),
|
|
||||||
);
|
|
||||||
shadowDOM.appendChild(styleEl);
|
shadowDOM.appendChild(styleEl);
|
||||||
shadowDOM.appendChild(root);
|
shadowDOM.appendChild(root);
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
@ -33,4 +30,3 @@ const mountComponent = () => {
|
|||||||
mountComponent();
|
mountComponent();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup Vue app
|
||||||
|
* @param app Vue app
|
||||||
|
*/
|
||||||
export function setupApp(app: App) {
|
export function setupApp(app: App) {
|
||||||
// Inject a globally available `$app` object in template
|
// Inject a globally available `$app` object in template
|
||||||
app.config.globalProperties.$app = {
|
app.config.globalProperties.$app = {
|
||||||
|
|||||||
@ -1,13 +1,41 @@
|
|||||||
/**
|
/**
|
||||||
* Execute Script on Document
|
* Executes a provided asynchronous function in the context of a specific browser tab.
|
||||||
* @param tabId
|
* @param tabId - The ID of the browser tab where the script will be executed.
|
||||||
* @param func
|
* @param func - The asynchronous function to execute in the tab's context. This function
|
||||||
* @returns
|
* should be serializable and must not rely on external closures.
|
||||||
|
* @param payload - An optional payload object to pass as an argument to the executed function.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves to the result of the executed function, or `null` if an error occurs.
|
||||||
|
*
|
||||||
|
* @throws This function does not throw directly but logs an error to the console if the script injection fails.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const result = await exec<number, { value: number }>(
|
||||||
|
* tabId,
|
||||||
|
* async (payload) => {
|
||||||
|
* return payload?.value ?? 0;
|
||||||
|
* },
|
||||||
|
* { value: 42 }
|
||||||
|
* );
|
||||||
|
* console.log(result); // Outputs: 42
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function exec<T>(tabId: number, func: () => Promise<T>): Promise<T | null> {
|
export async function exec<T>(tabId: number, func: () => Promise<T>): Promise<T | null>;
|
||||||
|
export async function exec<T, P extends Record<string, unknown>>(
|
||||||
|
tabId: number,
|
||||||
|
func: (payload: P) => Promise<T>,
|
||||||
|
payload: P,
|
||||||
|
): Promise<T | null>;
|
||||||
|
export async function exec<T, P extends Record<string, unknown>>(
|
||||||
|
tabId: number,
|
||||||
|
func: (payload?: P) => Promise<T>,
|
||||||
|
payload?: P,
|
||||||
|
): Promise<T | null> {
|
||||||
const injectResults = await browser.scripting.executeScript({
|
const injectResults = await browser.scripting.executeScript({
|
||||||
target: { tabId },
|
target: { tabId },
|
||||||
func,
|
func,
|
||||||
|
args: payload ? [payload] : undefined,
|
||||||
});
|
});
|
||||||
const ret = injectResults.pop();
|
const ret = injectResults.pop();
|
||||||
if (ret?.error) {
|
if (ret?.error) {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import Emittery from 'emittery';
|
import Emittery from 'emittery';
|
||||||
import type { AmazonGoodsLinkItem, AmazonPageWorker, AmazonPageWorkerEvents } from './types';
|
import type { AmazonGoodsLinkItem, AmazonPageWorker, AmazonPageWorkerEvents } from './types';
|
||||||
import Browser from 'webextension-polyfill';
|
import type { Tabs } from 'webextension-polyfill';
|
||||||
import { exec } from '../execute-script';
|
import { exec } from '../execute-script';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,7 +19,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
|
|||||||
|
|
||||||
readonly channel = new Emittery<AmazonPageWorkerEvents>();
|
readonly channel = new Emittery<AmazonPageWorkerEvents>();
|
||||||
|
|
||||||
private async getCurrentTab(): Promise<Browser.Tabs.Tab> {
|
private async getCurrentTab(): Promise<Tabs.Tab> {
|
||||||
const tab = await browser.tabs
|
const tab = await browser.tabs
|
||||||
.query({ active: true, currentWindow: true })
|
.query({ active: true, currentWindow: true })
|
||||||
.then((tabs) => tabs[0]);
|
.then((tabs) => tabs[0]);
|
||||||
@ -41,7 +41,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async wanderSearchSinglePage(tab: Browser.Tabs.Tab) {
|
private async wanderSearchSinglePage(tab: Tabs.Tab) {
|
||||||
const tabId = tab.id!;
|
const tabId = tab.id!;
|
||||||
// #region Wait for the Next button to appear, indicating that the product items have finished loading
|
// #region Wait for the Next button to appear, indicating that the product items have finished loading
|
||||||
await exec(tabId, async () => {
|
await exec(tabId, async () => {
|
||||||
@ -134,7 +134,7 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
|
|||||||
return { data, hasNextPage };
|
return { data, hasNextPage };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async wanderSearchList(): Promise<void> {
|
public async wanderSearchPage(): Promise<void> {
|
||||||
const tab = await this.getCurrentTab();
|
const tab = await this.getCurrentTab();
|
||||||
let stopSignal = false;
|
let stopSignal = false;
|
||||||
const stop = async (_: unknown): Promise<void> => {
|
const stop = async (_: unknown): Promise<void> => {
|
||||||
@ -240,6 +240,23 @@ class AmazonPageWorkerImpl implements AmazonPageWorker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
//#region Fetch Goods' Images
|
||||||
|
const imageUrls = await exec(tab.id!, async () => {
|
||||||
|
const node = document.evaluate(
|
||||||
|
`//div[@id='imgTagWrapperId']/img`,
|
||||||
|
document,
|
||||||
|
null,
|
||||||
|
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||||
|
null,
|
||||||
|
).singleNodeValue as HTMLImageElement | null;
|
||||||
|
return node ? [node.getAttribute('src')!] : null;
|
||||||
|
});
|
||||||
|
imageUrls &&
|
||||||
|
this.channel.emit('item-images-collected', {
|
||||||
|
asin,
|
||||||
|
urls: imageUrls,
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/logic/page-worker/types.d.ts
vendored
10
src/logic/page-worker/types.d.ts
vendored
@ -26,6 +26,14 @@ interface AmazonPageWorkerEvents {
|
|||||||
category2?: { name: string; rank: number };
|
category2?: { name: string; rank: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event is fired when images collected
|
||||||
|
*/
|
||||||
|
['item-images-collected']: {
|
||||||
|
asin: string;
|
||||||
|
urls: string[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error event that occurs when there is an issue with the Amazon page worker.
|
* Error event that occurs when there is an issue with the Amazon page worker.
|
||||||
*/
|
*/
|
||||||
@ -49,7 +57,7 @@ interface AmazonPageWorker {
|
|||||||
/**
|
/**
|
||||||
* Browsing goods search page and collect links to those goods.
|
* Browsing goods search page and collect links to those goods.
|
||||||
*/
|
*/
|
||||||
wanderSearchList(): Promise<void>;
|
wanderSearchPage(): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browsing goods detail page and collect target information.
|
* Browsing goods detail page and collect target information.
|
||||||
|
|||||||
@ -15,11 +15,6 @@ export async function getManifest() {
|
|||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
action: {
|
action: {
|
||||||
default_icon: './assets/icon-512.png',
|
default_icon: './assets/icon-512.png',
|
||||||
default_popup: './dist/popup/index.html',
|
|
||||||
},
|
|
||||||
options_ui: {
|
|
||||||
page: './dist/options/index.html',
|
|
||||||
open_in_tab: true,
|
|
||||||
},
|
},
|
||||||
background: isFirefox
|
background: isFirefox
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import logo from '~/assets/logo.svg';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main class="option-page">
|
|
||||||
<img :src="logo" class="" alt="extension icon" />
|
|
||||||
<div class="title">Options Page</div>
|
|
||||||
<SharedSubtitle />
|
|
||||||
<div class="footer">
|
|
||||||
Powered by Vite <pixelarticons-zap class="align-middle inline-block" />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.option-page {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: #a3a8d4;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-field {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<base target="_blank" />
|
|
||||||
<title>Options</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="./main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { createApp } from 'vue';
|
|
||||||
import App from './Options.vue';
|
|
||||||
import { setupApp } from '~/logic/common-setup';
|
|
||||||
import '../styles';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
setupApp(app);
|
|
||||||
app.mount('#app');
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
function openOptionsPage() {
|
|
||||||
browser.runtime.openOptionsPage();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main class="popup">
|
|
||||||
<Logo />
|
|
||||||
<div class="title">Popup</div>
|
|
||||||
<SharedSubtitle />
|
|
||||||
<button @click="openOptionsPage">Open Options</button>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.popup {
|
|
||||||
min-width: 240px;
|
|
||||||
min-height: 300px;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background-color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<base target="_blank" />
|
|
||||||
<title>Popup</title>
|
|
||||||
</head>
|
|
||||||
<body style="min-width: 100px">
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="./main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import App from './Popup.vue';
|
|
||||||
import { setupApp } from '~/logic/common-setup';
|
|
||||||
import '../styles';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
setupApp(app);
|
|
||||||
app.mount('#app');
|
|
||||||
@ -72,7 +72,7 @@ const onCollect = async () => {
|
|||||||
message.error(msg);
|
message.error(msg);
|
||||||
});
|
});
|
||||||
await worker.doSearch(keywords.value);
|
await worker.doSearch(keywords.value);
|
||||||
await worker.wanderSearchList();
|
await worker.wanderSearchPage();
|
||||||
message.info('完成');
|
message.info('完成');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -94,8 +94,6 @@ export default defineConfig(({ command }) => ({
|
|||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
options: r('src/options/index.html'),
|
|
||||||
popup: r('src/popup/index.html'),
|
|
||||||
sidepanel: r('src/sidepanel/index.html'),
|
sidepanel: r('src/sidepanel/index.html'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user