commit bdc3d0d512d95db36e6113f5b019e1569924278e
Author: johnathan <952508490@qq.com>
Date: Sat Apr 12 17:26:36 2025 +0800
Initial Repo
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..453b76d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.DS_Store
+.idea/
+.vite-ssg-dist
+.vite-ssg-temp
+*.crx
+*.local
+*.log
+*.pem
+*.xpi
+*.zip
+dist
+dist-ssr
+extension/manifest.json
+node_modules
+src/auto-imports.d.ts
+src/components.d.ts
+.eslintcache
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..5ee7abd
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+pnpm exec lint-staged
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..009aa06
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+shamefully-hoist=true
+auto-install-peers=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..1b8ac88
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+# Ignore artifacts:
+build
+coverage
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..544138b
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "singleQuote": true
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..96f65d5
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+ "recommendations": [
+ "vue.volar"
+ // "antfu.iconify",
+ // "antfu.unocss",
+ // "dbaeumer.vscode-eslint",
+ // "csstools.postcss"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..fb068a7
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "cSpell.words": ["Vitesse"],
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "vite.autoStart": false,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "files.associations": {
+ "*.css": "postcss"
+ },
+ "prettier.tabWidth": 2
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9b031a2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Anthony Fu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README_Template.md b/README_Template.md
new file mode 100644
index 0000000..ba226cf
--- /dev/null
+++ b/README_Template.md
@@ -0,0 +1,138 @@
+# WebExtension Vite Starter
+
+A [Vite](https://vitejs.dev/) powered WebExtension ([Chrome](https://developer.chrome.com/docs/extensions/reference/), [FireFox](https://addons.mozilla.org/en-US/developers/), etc.) starter template.
+
+
+Popup
+
+Options Page
+
+Inject Vue App into the Content Script
+
+
+
+## Features
+
+- ⚡️ **Instant HMR** - use **Vite** on dev (no more refresh!)
+- 🥝 Vue 3 - Composition API, [`
+
+
+
+
+
+
+
+
diff --git a/src/env.ts b/src/env.ts
new file mode 100644
index 0000000..bd19ad3
--- /dev/null
+++ b/src/env.ts
@@ -0,0 +1,14 @@
+const forbiddenProtocols = [
+ 'chrome-extension://',
+ 'chrome-search://',
+ 'chrome://',
+ 'devtools://',
+ 'edge://',
+ 'https://chrome.google.com/webstore',
+];
+
+export function isForbiddenUrl(url: string): boolean {
+ return forbiddenProtocols.some((protocol) => url.startsWith(protocol));
+}
+
+export const isFirefox = navigator.userAgent.includes('Firefox');
diff --git a/src/global.d.ts b/src/global.d.ts
new file mode 100644
index 0000000..e3a1e02
--- /dev/null
+++ b/src/global.d.ts
@@ -0,0 +1,10 @@
+///
+
+declare const __DEV__: boolean;
+/** Extension name, defined in packageJson.name */
+declare const __NAME__: string;
+
+declare module '*.vue' {
+ const component: any;
+ export default component;
+}
diff --git a/src/logic/common-setup.ts b/src/logic/common-setup.ts
new file mode 100644
index 0000000..7d98d66
--- /dev/null
+++ b/src/logic/common-setup.ts
@@ -0,0 +1,15 @@
+import type { App } from 'vue';
+
+export function setupApp(app: App) {
+ // Inject a globally available `$app` object in template
+ app.config.globalProperties.$app = {
+ context: '',
+ };
+
+ // Provide access to `app` in script setup with `const app = inject('app')`
+ app.provide('app', app.config.globalProperties.$app);
+
+ // Here you can install additional plugins for all contexts: popup, options page and content-script.
+ // example: app.use(i18n)
+ // example excluding content-script context: if (context !== 'content-script') app.use(i18n)
+}
diff --git a/src/logic/index.ts b/src/logic/index.ts
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/logic/index.ts
@@ -0,0 +1 @@
+
diff --git a/src/logic/page-worker/index.ts b/src/logic/page-worker/index.ts
new file mode 100644
index 0000000..b4a4c63
--- /dev/null
+++ b/src/logic/page-worker/index.ts
@@ -0,0 +1,64 @@
+class AmazonPageWorkerImpl implements AmazonPageWorker {
+ public async doSearch(keywords: string): Promise {
+ const url = new URL('https://www.amazon.com/s');
+ url.searchParams.append('k', keywords);
+
+ const tab = await browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then((tabs) => tabs[0]);
+ const currentUrl = new URL(tab.url!);
+ if (
+ currentUrl.hostname !== url.hostname ||
+ currentUrl.searchParams.get('k') !== keywords
+ ) {
+ await browser.tabs.update(tab.id, { url: url.toString() });
+ }
+ return url.toString();
+ }
+
+ public async wanderSearchList(): Promise {
+ const tab = await browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then((tabs) => tabs[0]);
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tab.id! },
+ func: async () => {
+ try {
+ await new Promise((resolve) =>
+ setTimeout(resolve, 500 + ~~(500 * Math.random())),
+ );
+ while (!document.querySelector('.s-pagination-strip')) {
+ window.scrollBy(0, ~~(Math.random() * 500) + 500);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ }
+ const nextButton =
+ document.querySelector('.s-pagination-next');
+ if (
+ nextButton &&
+ !nextButton.classList.contains('s-pagination-disabled')
+ ) {
+ await new Promise((resolve) =>
+ setTimeout(resolve, 500 + ~~(500 * Math.random())),
+ );
+ nextButton.click();
+ }
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ });
+ console.log('results', results);
+ return new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+}
+
+class PageWorkerFactory {
+ public createAmazonPageWorker(): AmazonPageWorker {
+ return new AmazonPageWorkerImpl();
+ }
+}
+
+const pageWorkerFactory = new PageWorkerFactory();
+
+export default pageWorkerFactory;
diff --git a/src/logic/page-worker/types.d.ts b/src/logic/page-worker/types.d.ts
new file mode 100644
index 0000000..4741766
--- /dev/null
+++ b/src/logic/page-worker/types.d.ts
@@ -0,0 +1,14 @@
+interface AmazonPageWorker {
+ /**
+ * Search for a list of items on Amazon
+ * @param keywords - The keywords to search for on Amazon.
+ * @returns A promise that resolves to a string representing the search URL.
+ */
+ doSearch(keywords: string): Promise;
+
+ /**
+ * Browsing item search page and collect links to those items.
+ * @param entryUrl - The URL of the Amazon search page to start from.
+ */
+ wanderSearchList(): Promise;
+}
diff --git a/src/logic/storage.ts b/src/logic/storage.ts
new file mode 100644
index 0000000..84b4e5b
--- /dev/null
+++ b/src/logic/storage.ts
@@ -0,0 +1,3 @@
+import { useWebExtensionStorage } from '~/composables/useWebExtensionStorage';
+
+export const keywords = useWebExtensionStorage('keywords', '');
\ No newline at end of file
diff --git a/src/manifest.ts b/src/manifest.ts
new file mode 100644
index 0000000..e626f87
--- /dev/null
+++ b/src/manifest.ts
@@ -0,0 +1,81 @@
+import fs from 'fs-extra';
+import type { Manifest } from 'webextension-polyfill';
+import type PkgType from '../package.json';
+import { isDev, isFirefox, port, r } from '../scripts/utils';
+
+export async function getManifest() {
+ const pkg = (await fs.readJSON(r('package.json'))) as typeof PkgType;
+
+ // update this file to update this manifest.json
+ // can also be conditional based on your need
+ const manifest: Manifest.WebExtensionManifest = {
+ manifest_version: 3,
+ name: pkg.displayName || pkg.name,
+ version: pkg.version,
+ description: pkg.description,
+ action: {
+ 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
+ ? {
+ scripts: ['dist/background/index.mjs'],
+ type: 'module',
+ }
+ : {
+ service_worker: './dist/background/index.mjs',
+ },
+ icons: {
+ 16: './assets/icon-512.png',
+ 48: './assets/icon-512.png',
+ 128: './assets/icon-512.png',
+ },
+ permissions: ['tabs', 'storage', 'activeTab', 'sidePanel', 'scripting'],
+ host_permissions: ['*://*/*'],
+ content_scripts: [
+ {
+ matches: [''],
+ js: ['dist/contentScripts/index.global.js'],
+ },
+ ],
+ web_accessible_resources: [
+ {
+ resources: ['dist/contentScripts/index.css'],
+ matches: [''],
+ },
+ ],
+ content_security_policy: {
+ extension_pages: isDev
+ ? // this is required on dev for Vite script to load
+ `script-src \'self\' http://localhost:${port}; object-src \'self\'`
+ : "script-src 'self'; object-src 'self'",
+ },
+ };
+
+ // add sidepanel
+ if (isFirefox) {
+ manifest.sidebar_action = {
+ default_panel: 'dist/sidepanel/index.html',
+ };
+ } else {
+ // the sidebar_action does not work for chromium based
+ (manifest as any).side_panel = {
+ default_path: 'dist/sidepanel/index.html',
+ };
+ }
+
+ // FIXME: not work in MV3
+ if (isDev && false) {
+ // for content script, as browsers will cache them for each reload,
+ // we use a background script to always inject the latest version
+ // see src/background/contentScriptHMR.ts
+ delete manifest.content_scripts;
+ manifest.permissions?.push('webNavigation');
+ }
+
+ return manifest;
+}
diff --git a/src/options/Options.vue b/src/options/Options.vue
new file mode 100644
index 0000000..29b5196
--- /dev/null
+++ b/src/options/Options.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+