1
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// Amazon:action(编排逻辑放这里),注入函数放 amazon_tool.js
|
// Amazon:action(编排逻辑放这里),注入函数放 amazon_tool.js
|
||||||
|
|
||||||
import { create_tab_task } from '../libs/tabs.js';
|
import { create_tab_task } from '../libs/tabs.js';
|
||||||
|
import { fail_response, ok_response, response_code } from '../libs/action_response.js';
|
||||||
import {
|
import {
|
||||||
injected_amazon_homepage_search,
|
injected_amazon_homepage_search,
|
||||||
injected_amazon_product_detail,
|
injected_amazon_product_detail,
|
||||||
@@ -10,7 +11,6 @@ import {
|
|||||||
injected_amazon_validate_captcha_continue,
|
injected_amazon_validate_captcha_continue,
|
||||||
normalize_product_url,
|
normalize_product_url,
|
||||||
try_solve_amazon_validate_captcha,
|
try_solve_amazon_validate_captcha,
|
||||||
wait_tab_complete,
|
|
||||||
wait_until_search_list_url,
|
wait_until_search_list_url,
|
||||||
} from './amazon_tool.js';
|
} from './amazon_tool.js';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const AMAZON_HOME_FOR_LANG =
|
|||||||
'https://www.amazon.com/customer-preferences/edit?ie=UTF8&preferencesReturnUrl=%2F&ref_=topnav_lang_ais&language=zh_CN¤cy=HKD';
|
'https://www.amazon.com/customer-preferences/edit?ie=UTF8&preferencesReturnUrl=%2F&ref_=topnav_lang_ais&language=zh_CN¤cy=HKD';
|
||||||
|
|
||||||
export function amazon_search_list(data, sendResponse) {
|
export function amazon_search_list(data, sendResponse) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const category_keyword = data && data.category_keyword ? String(data.category_keyword).trim() : '';
|
const category_keyword = data && data.category_keyword ? String(data.category_keyword).trim() : '';
|
||||||
const sort_by = data && data.sort_by ? String(data.sort_by).trim() : '';
|
const sort_by = data && data.sort_by ? String(data.sort_by).trim() : '';
|
||||||
const keep_tab_open = data && data.keep_tab_open === true;
|
const keep_tab_open = data && data.keep_tab_open === true;
|
||||||
@@ -53,114 +53,82 @@ export function amazon_search_list(data, sendResponse) {
|
|||||||
.set_target('__amazon_search_list');
|
.set_target('__amazon_search_list');
|
||||||
|
|
||||||
let url = AMAZON_ZH_HOME_URL;
|
let url = AMAZON_ZH_HOME_URL;
|
||||||
try {
|
tab_task.open_async()
|
||||||
const tab = await tab_task.open_async();
|
.then((tab) => {
|
||||||
let running = false;
|
tab.on_update_complete(async () => {
|
||||||
let resolved = false;
|
await tab.execute_script(injected_amazon_search_list, [{ category_keyword, sort_by, debug: true }], 'document_idle');
|
||||||
|
await try_solve_amazon_validate_captcha(tab, 3);
|
||||||
|
|
||||||
tab.on_update_complete(async (t) => {
|
const home_ret = await tab.execute_script(injected_amazon_homepage_search, [{ keyword }], 'document_idle');
|
||||||
// 刷新/导航完成后也能看到注入轨迹(不重复跑主流程)
|
const home_ok = Array.isArray(home_ret) ? home_ret[0] : home_ret;
|
||||||
await t.execute_script(injected_amazon_search_list, [{ category_keyword, sort_by, debug: true }], 'document_idle');
|
if (!home_ok || !home_ok.ok) {
|
||||||
|
throw new Error((home_ok && home_ok.error) || '首页搜索提交失败');
|
||||||
if (running) return;
|
|
||||||
running = true;
|
|
||||||
try {
|
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
|
||||||
|
|
||||||
const home_ret = await t.execute_script(injected_amazon_homepage_search, [{ keyword }], 'document_idle');
|
|
||||||
const home_ok = Array.isArray(home_ret) ? home_ret[0] : home_ret;
|
|
||||||
if (!home_ok || !home_ok.ok) {
|
|
||||||
throw new Error((home_ok && home_ok.error) || '首页搜索提交失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
url = await wait_until_search_list_url(t.id, 45000);
|
|
||||||
await t.wait_complete();
|
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
|
||||||
|
|
||||||
if (sort_s) {
|
|
||||||
const u = new URL(url);
|
|
||||||
u.searchParams.set('s', sort_s);
|
|
||||||
url = u.toString();
|
|
||||||
await new Promise((resolve_nav, reject_nav) => {
|
|
||||||
chrome.tabs.update(t.id, { url, active: true }, () => {
|
|
||||||
if (chrome.runtime.lastError) return reject_nav(new Error(chrome.runtime.lastError.message));
|
|
||||||
resolve_nav(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await t.wait_complete();
|
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unique_map = new Map();
|
|
||||||
let next_url = url;
|
|
||||||
for (let page = 1; page <= 10 && unique_map.size < limit; page += 1) {
|
|
||||||
if (page > 1) {
|
|
||||||
await new Promise((resolve_nav, reject_nav) => {
|
|
||||||
chrome.tabs.update(t.id, { url: next_url, active: true }, () => {
|
|
||||||
if (chrome.runtime.lastError) return reject_nav(new Error(chrome.runtime.lastError.message));
|
|
||||||
resolve_nav(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await t.wait_complete();
|
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
|
||||||
}
|
|
||||||
const injected_result_list = await t.execute_script(
|
|
||||||
injected_amazon_search_list,
|
|
||||||
[{ url: next_url, category_keyword, sort_by }],
|
|
||||||
'document_idle',
|
|
||||||
);
|
|
||||||
const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null;
|
|
||||||
const items = injected_result && Array.isArray(injected_result.items) ? injected_result.items : [];
|
|
||||||
items.forEach((it) => {
|
|
||||||
const k = it && (it.asin || it.url) ? String(it.asin || it.url) : null;
|
|
||||||
if (!k) return;
|
|
||||||
if (!unique_map.has(k)) unique_map.set(k, it);
|
|
||||||
});
|
|
||||||
if (unique_map.size >= limit) break;
|
|
||||||
next_url = injected_result && injected_result.next_url ? String(injected_result.next_url) : null;
|
|
||||||
if (!next_url) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const list_result = { stage: 'list', limit, total: unique_map.size, items: Array.from(unique_map.values()).slice(0, limit) };
|
|
||||||
const result = { code: 0, status: true, message: 'ok', data: { tab_id: t.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: list_result } };
|
|
||||||
|
|
||||||
send_action('amazon_search_list', result);
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true;
|
|
||||||
resolve({ tab_id: t.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: list_result });
|
|
||||||
}
|
|
||||||
if (!keep_tab_open) {
|
|
||||||
t.remove(0);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
send_action('amazon_search_list', {
|
|
||||||
code: 30,
|
|
||||||
status: false,
|
|
||||||
message: (err && err.message) || String(err),
|
|
||||||
data: null,
|
|
||||||
documentURI: url || AMAZON_ZH_HOME_URL,
|
|
||||||
});
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true;
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
if (!keep_tab_open) {
|
|
||||||
t.remove(0);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url = await wait_until_search_list_url(tab.id, 45000);
|
||||||
|
await tab.wait_complete();
|
||||||
|
|
||||||
|
if (sort_s) {
|
||||||
|
const u = new URL(url);
|
||||||
|
u.searchParams.set('s', sort_s);
|
||||||
|
url = u.toString();
|
||||||
|
await tab.navigate(url);
|
||||||
|
await tab.wait_complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
const unique_map = new Map();
|
||||||
|
let next_url = url;
|
||||||
|
for (let page = 1; page <= 10 && unique_map.size < limit; page += 1) {
|
||||||
|
if (page > 1) {
|
||||||
|
await tab.navigate(next_url);
|
||||||
|
await tab.wait_complete();
|
||||||
|
}
|
||||||
|
const injected_result_list = await tab.execute_script(
|
||||||
|
injected_amazon_search_list,
|
||||||
|
[{ url: next_url, category_keyword, sort_by }],
|
||||||
|
'document_idle',
|
||||||
|
);
|
||||||
|
const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null;
|
||||||
|
const items = injected_result && Array.isArray(injected_result.items) ? injected_result.items : [];
|
||||||
|
items.forEach((it) => {
|
||||||
|
const k = it && (it.asin || it.url) ? String(it.asin || it.url) : null;
|
||||||
|
if (!k) return;
|
||||||
|
if (!unique_map.has(k)) unique_map.set(k, it);
|
||||||
|
});
|
||||||
|
if (unique_map.size >= limit) break;
|
||||||
|
next_url = injected_result && injected_result.next_url ? String(injected_result.next_url) : null;
|
||||||
|
if (!next_url) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list_result = { stage: 'list', limit, total: unique_map.size, items: Array.from(unique_map.values()).slice(0, limit) };
|
||||||
|
const result = ok_response({ tab_id: tab.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: list_result });
|
||||||
|
|
||||||
|
send_action('amazon_search_list', result);
|
||||||
|
resolve({ tab_id: tab.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: list_result });
|
||||||
|
if (!keep_tab_open) {
|
||||||
|
tab.remove(0);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
once: true,
|
||||||
|
on_error: (err) => {
|
||||||
|
send_action('amazon_search_list', fail_response((err && err.message) || String(err), {
|
||||||
|
code: response_code.runtime_error,
|
||||||
|
documentURI: url || AMAZON_ZH_HOME_URL,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
if (!keep_tab_open) {
|
||||||
|
tab.remove(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
})
|
||||||
send_action('amazon_search_list', {
|
.catch((err) => {
|
||||||
code: 30,
|
send_action('amazon_search_list', fail_response((err && err.message) || String(err), {
|
||||||
status: false,
|
code: response_code.runtime_error,
|
||||||
message: (err && err.message) || String(err),
|
|
||||||
data: null,
|
|
||||||
documentURI: url || AMAZON_ZH_HOME_URL,
|
documentURI: url || AMAZON_ZH_HOME_URL,
|
||||||
});
|
}));
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +141,7 @@ amazon_search_list.params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function amazon_set_language(data, sendResponse) {
|
export function amazon_set_language(data, sendResponse) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const mapping = {
|
const mapping = {
|
||||||
EN: 'en_US',
|
EN: 'en_US',
|
||||||
ES: 'es_US',
|
ES: 'es_US',
|
||||||
@@ -198,31 +166,44 @@ export function amazon_set_language(data, sendResponse) {
|
|||||||
const tab_task = create_tab_task(AMAZON_HOME_FOR_LANG)
|
const tab_task = create_tab_task(AMAZON_HOME_FOR_LANG)
|
||||||
.set_latest(false)
|
.set_latest(false)
|
||||||
.set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
.set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
||||||
try {
|
tab_task.open_async()
|
||||||
const tab = await tab_task.open_async();
|
.then((tab) => {
|
||||||
tab.on_update_complete(async (t) => {
|
tab.on_update_complete(async () => {
|
||||||
// 首次 complete 也会触发:在回调里完成注入与结果采集
|
// 首次 complete 也会触发:在回调里完成注入与结果采集
|
||||||
await t.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
await tab.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
||||||
const raw = await t.execute_script(injected_amazon_switch_language, [{ lang: code }], 'document_idle');
|
const raw = await tab.execute_script(injected_amazon_switch_language, [{ lang: code }], 'document_idle');
|
||||||
const inj = Array.isArray(raw) ? raw[0] : raw;
|
const inj = Array.isArray(raw) ? raw[0] : raw;
|
||||||
if (!inj || !inj.ok) {
|
if (!inj || !inj.ok) {
|
||||||
throw new Error((inj && inj.error) || 'switch_language_failed');
|
throw new Error((inj && inj.error) || 'switch_language_failed');
|
||||||
}
|
}
|
||||||
const final_url = await new Promise((res, rej) => {
|
const final_url = await new Promise((res, rej) => {
|
||||||
chrome.tabs.get(t.id, (tt) => {
|
chrome.tabs.get(tab.id, (tt) => {
|
||||||
if (chrome.runtime.lastError) return rej(new Error(chrome.runtime.lastError.message));
|
if (chrome.runtime.lastError) return rej(new Error(chrome.runtime.lastError.message));
|
||||||
res(tt && tt.url ? tt.url : '');
|
res(tt && tt.url ? tt.url : '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const result = { code: 0, status: true, message: 'ok', data: { tab_id: t.id, lang: inj.lang, url: final_url } };
|
const result = ok_response({ tab_id: tab.id, lang: inj.lang, url: final_url });
|
||||||
send_action('amazon_set_language', result);
|
send_action('amazon_set_language', result);
|
||||||
resolve({ tab_id: t.id, lang: inj.lang, url: final_url });
|
resolve({ tab_id: tab.id, lang: inj.lang, url: final_url });
|
||||||
t.remove(0);
|
tab.remove(0);
|
||||||
}, { once: true });
|
}, {
|
||||||
} catch (err) {
|
once: true,
|
||||||
send_action('amazon_set_language', { code: 30, status: false, message: (err && err.message) || String(err), data: null, documentURI: AMAZON_HOME_FOR_LANG });
|
on_error: (err) => {
|
||||||
|
send_action('amazon_set_language', fail_response((err && err.message) || String(err), {
|
||||||
|
code: response_code.runtime_error,
|
||||||
|
documentURI: AMAZON_HOME_FOR_LANG,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
send_action('amazon_set_language', fail_response((err && err.message) || String(err), {
|
||||||
|
code: response_code.runtime_error,
|
||||||
|
documentURI: AMAZON_HOME_FOR_LANG,
|
||||||
|
}));
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,30 +252,45 @@ function run_pdp_action(product_url, injected_fn, inject_args, action_name, send
|
|||||||
sendResponse.log && sendResponse.log(payload);
|
sendResponse.log && sendResponse.log(payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let url = product_url;
|
let url = product_url;
|
||||||
try {
|
Promise.resolve()
|
||||||
url = normalize_product_url(product_url);
|
.then(() => normalize_product_url(product_url))
|
||||||
} catch (e) {
|
.then((normalized_url) => {
|
||||||
send_action(action_name, { code: 10, status: false, message: e.message, data: null });
|
url = normalized_url;
|
||||||
return reject(e);
|
const tab_task = create_tab_task(url).set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
||||||
}
|
return tab_task.open_async();
|
||||||
const tab_task = create_tab_task(url).set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
})
|
||||||
try {
|
.then((tab) => {
|
||||||
const tab = await tab_task.open_async();
|
tab.on_update_complete(async () => {
|
||||||
tab.on_update_complete(async (t) => {
|
await tab.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
||||||
await t.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
await try_solve_amazon_validate_captcha(tab, 3);
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
const raw_list = await tab.execute_script(injected_fn, inject_args || [], 'document_idle');
|
||||||
const raw_list = await t.execute_script(injected_fn, inject_args || [], 'document_idle');
|
|
||||||
const result = Array.isArray(raw_list) ? raw_list[0] : raw_list;
|
const result = Array.isArray(raw_list) ? raw_list[0] : raw_list;
|
||||||
send_action(action_name, { code: 0, status: true, message: 'ok', data: { tab_id: t.id, product_url: url, result } });
|
send_action(action_name, ok_response({ tab_id: tab.id, product_url: url, result }));
|
||||||
resolve({ tab_id: t.id, product_url: url, result });
|
resolve({ tab_id: tab.id, product_url: url, result });
|
||||||
t.remove(0);
|
tab.remove(0);
|
||||||
}, { once: true });
|
}, {
|
||||||
} catch (err) {
|
once: true,
|
||||||
send_action(action_name, { code: 30, status: false, message: (err && err.message) || String(err), data: null, documentURI: url });
|
on_error: (err) => {
|
||||||
reject(err);
|
send_action(action_name, fail_response((err && err.message) || String(err), {
|
||||||
}
|
code: response_code.runtime_error,
|
||||||
|
documentURI: url,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
const is_bad_request = (err && err.message) === '缺少 product_url'
|
||||||
|
|| (err && err.message) === 'product_url 需为亚马逊域名'
|
||||||
|
|| (err && err.message) === 'product_url 需包含 /dp/ASIN 或 /gp/product/ASIN';
|
||||||
|
send_action(action_name, fail_response((err && err.message) || String(err), {
|
||||||
|
code: is_bad_request ? response_code.bad_request : response_code.runtime_error,
|
||||||
|
documentURI: is_bad_request ? undefined : url,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,38 +301,52 @@ function run_pdp_action_multi(product_url, steps, action_name, sendResponse) {
|
|||||||
sendResponse.log && sendResponse.log(payload);
|
sendResponse.log && sendResponse.log(payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let url = product_url;
|
let url = product_url;
|
||||||
try {
|
Promise.resolve()
|
||||||
url = normalize_product_url(product_url);
|
.then(() => normalize_product_url(product_url))
|
||||||
} catch (e) {
|
.then((normalized_url) => {
|
||||||
send_action(action_name, { code: 10, status: false, message: e.message, data: null });
|
url = normalized_url;
|
||||||
return reject(e);
|
const tab_task = create_tab_task(url).set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
||||||
}
|
return tab_task.open_async();
|
||||||
|
})
|
||||||
const tab_task = create_tab_task(url).set_bounds({ top: 20, left: 20, width: 1280, height: 900 });
|
.then((tab) => {
|
||||||
try {
|
tab.on_update_complete(async () => {
|
||||||
const tab = await tab_task.open_async();
|
await tab.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
||||||
tab.on_update_complete(async (t) => {
|
await try_solve_amazon_validate_captcha(tab, 3);
|
||||||
await t.execute_script(injected_amazon_validate_captcha_continue, [], 'document_idle');
|
|
||||||
await try_solve_amazon_validate_captcha(t, 3);
|
|
||||||
|
|
||||||
const results = {};
|
const results = {};
|
||||||
for (const step of steps || []) {
|
for (const step of steps || []) {
|
||||||
if (!step || !step.name || typeof step.injected_fn !== 'function') continue;
|
if (!step || !step.name || typeof step.injected_fn !== 'function') continue;
|
||||||
const raw_list = await t.execute_script(step.injected_fn, step.inject_args || [], 'document_idle');
|
const raw_list = await tab.execute_script(step.injected_fn, step.inject_args || [], 'document_idle');
|
||||||
const result = Array.isArray(raw_list) ? raw_list[0] : raw_list;
|
const result = Array.isArray(raw_list) ? raw_list[0] : raw_list;
|
||||||
results[step.name] = result;
|
results[step.name] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_action(action_name, { code: 0, status: true, message: 'ok', data: { tab_id: t.id, product_url: url, result: results } });
|
send_action(action_name, ok_response({ tab_id: tab.id, product_url: url, result: results }));
|
||||||
resolve({ tab_id: t.id, product_url: url, result: results });
|
resolve({ tab_id: tab.id, product_url: url, result: results });
|
||||||
t.remove(0);
|
tab.remove(0);
|
||||||
}, { once: true });
|
}, {
|
||||||
} catch (err) {
|
once: true,
|
||||||
send_action(action_name, { code: 30, status: false, message: (err && err.message) || String(err), data: null, documentURI: url });
|
on_error: (err) => {
|
||||||
reject(err);
|
send_action(action_name, fail_response((err && err.message) || String(err), {
|
||||||
}
|
code: response_code.runtime_error,
|
||||||
|
documentURI: url,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
const is_bad_request = (err && err.message) === '缺少 product_url'
|
||||||
|
|| (err && err.message) === 'product_url 需为亚马逊域名'
|
||||||
|
|| (err && err.message) === 'product_url 需包含 /dp/ASIN 或 /gp/product/ASIN';
|
||||||
|
send_action(action_name, fail_response((err && err.message) || String(err), {
|
||||||
|
code: is_bad_request ? response_code.bad_request : response_code.runtime_error,
|
||||||
|
documentURI: is_bad_request ? undefined : url,
|
||||||
|
}));
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,28 +96,63 @@ export async function try_solve_amazon_validate_captcha(tab, max_round) {
|
|||||||
export function injected_amazon_homepage_search(params) {
|
export function injected_amazon_homepage_search(params) {
|
||||||
const keyword = params && params.keyword ? String(params.keyword).trim() : '';
|
const keyword = params && params.keyword ? String(params.keyword).trim() : '';
|
||||||
if (!keyword) return { ok: false, error: 'empty_keyword' };
|
if (!keyword) return { ok: false, error: 'empty_keyword' };
|
||||||
const input =
|
|
||||||
document.querySelector('#twotabsearchtextbox') ||
|
function wait_query(selectors, timeout_ms) {
|
||||||
document.querySelector('input#nav-search-keywords') ||
|
const list = Array.isArray(selectors) ? selectors : [];
|
||||||
document.querySelector('input[name="field-keywords"]');
|
const deadline = Date.now() + (Number.isFinite(timeout_ms) ? timeout_ms : 5000);
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
for (const sel of list) {
|
||||||
|
const el = document.querySelector(sel);
|
||||||
|
if (!el) continue;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
if (r.width > 0 && r.height > 0) return el;
|
||||||
|
}
|
||||||
|
const t0 = performance.now();
|
||||||
|
while (performance.now() - t0 < 40) { }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = wait_query([
|
||||||
|
'#twotabsearchtextbox',
|
||||||
|
'input#nav-search-keywords',
|
||||||
|
'input[name="field-keywords"]',
|
||||||
|
'input[type="search"][name="field-keywords"]',
|
||||||
|
], 7000);
|
||||||
if (!input) return { ok: false, error: 'no_search_input' };
|
if (!input) return { ok: false, error: 'no_search_input' };
|
||||||
input.focus();
|
input.focus();
|
||||||
input.value = keyword;
|
input.value = keyword;
|
||||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
const btn =
|
const btn = wait_query([
|
||||||
document.querySelector('#nav-search-submit-button') ||
|
'#nav-search-submit-button',
|
||||||
document.querySelector('#nav-search-bar-form input[type="submit"]') ||
|
'#nav-search-bar-form input[type="submit"]',
|
||||||
document.querySelector('form[role="search"] input[type="submit"]');
|
'#nav-search-bar-form button[type="submit"]',
|
||||||
|
'form[role="search"] input[type="submit"]',
|
||||||
|
'form[role="search"] button[type="submit"]',
|
||||||
|
'input.nav-input[type="submit"]',
|
||||||
|
], 2000);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
return { ok: dispatch_human_click(btn) };
|
return { ok: dispatch_human_click(btn) };
|
||||||
}
|
}
|
||||||
const form = input.closest('form');
|
const form = input.form || input.closest('form');
|
||||||
if (form) {
|
if (form && typeof form.requestSubmit === 'function') {
|
||||||
form.submit();
|
form.requestSubmit();
|
||||||
return { ok: true };
|
return { ok: true, method: 'request_submit' };
|
||||||
}
|
}
|
||||||
return { ok: false, error: 'no_submit' };
|
if (form && typeof form.submit === 'function') {
|
||||||
|
form.submit();
|
||||||
|
return { ok: true, method: 'form_submit' };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
||||||
|
return { ok: true, method: 'keyboard_enter' };
|
||||||
|
} catch (_) {
|
||||||
|
// ignore and return explicit error
|
||||||
|
}
|
||||||
|
return { ok: false, error: 'no_submit', keyword };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function injected_amazon_switch_language(params) {
|
export function injected_amazon_switch_language(params) {
|
||||||
|
|||||||
32
mv2_simple_crx/src/libs/action_response.js
Normal file
32
mv2_simple_crx/src/libs/action_response.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const RESPONSE_CODE_OK = 0;
|
||||||
|
const RESPONSE_CODE_BAD_REQUEST = 10;
|
||||||
|
const RESPONSE_CODE_RUNTIME_ERROR = 30;
|
||||||
|
|
||||||
|
export function ok_response(data) {
|
||||||
|
return {
|
||||||
|
code: RESPONSE_CODE_OK,
|
||||||
|
status: true,
|
||||||
|
message: 'ok',
|
||||||
|
data: data == null ? null : data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fail_response(message, options) {
|
||||||
|
const opts = options && typeof options === 'object' ? options : {};
|
||||||
|
const code = Number.isFinite(opts.code) ? Number(opts.code) : RESPONSE_CODE_RUNTIME_ERROR;
|
||||||
|
const data = Object.prototype.hasOwnProperty.call(opts, 'data') ? opts.data : null;
|
||||||
|
const documentURI = Object.prototype.hasOwnProperty.call(opts, 'documentURI') ? opts.documentURI : undefined;
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
status: false,
|
||||||
|
message: message ? String(message) : 'error',
|
||||||
|
data,
|
||||||
|
...(documentURI ? { documentURI } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const response_code = {
|
||||||
|
ok: RESPONSE_CODE_OK,
|
||||||
|
bad_request: RESPONSE_CODE_BAD_REQUEST,
|
||||||
|
runtime_error: RESPONSE_CODE_RUNTIME_ERROR,
|
||||||
|
};
|
||||||
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
import { execute_script } from './inject.js';
|
import { execute_script } from './inject.js';
|
||||||
|
|
||||||
|
function update_tab(tab_id, update_props) {
|
||||||
|
return new Promise((resolve_update, reject_update) => {
|
||||||
|
chrome.tabs.update(tab_id, update_props, (updated_tab) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
return reject_update(new Error(chrome.runtime.lastError.message));
|
||||||
|
}
|
||||||
|
resolve_update(updated_tab || true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function attach_tab_helpers(tab) {
|
function attach_tab_helpers(tab) {
|
||||||
if (!tab) return tab;
|
if (!tab) return tab;
|
||||||
|
|
||||||
@@ -17,6 +28,13 @@ function attach_tab_helpers(tab) {
|
|||||||
return await execute_script(tab.id, fn, args, run_at);
|
return await execute_script(tab.id, fn, args, run_at);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tab.navigate = async function navigate(url, options) {
|
||||||
|
const nav_options = options && typeof options === 'object' ? options : {};
|
||||||
|
const active = Object.prototype.hasOwnProperty.call(nav_options, 'active') ? nav_options.active === true : true;
|
||||||
|
const update_props = { url: String(url), active };
|
||||||
|
return await update_tab(tab.id, update_props);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 等待 tab 页面加载完成(status=complete)
|
* 等待 tab 页面加载完成(status=complete)
|
||||||
* - 作为 tab 方法,避免业务层到处传 tab_id
|
* - 作为 tab 方法,避免业务层到处传 tab_id
|
||||||
@@ -56,20 +74,25 @@ function attach_tab_helpers(tab) {
|
|||||||
|
|
||||||
let running = false;
|
let running = false;
|
||||||
const once = !!(options && options.once === true);
|
const once = !!(options && options.once === true);
|
||||||
|
const on_error = options && typeof options.on_error === 'function' ? options.on_error : null;
|
||||||
const listener = async (updated_tab_id, change_info, updated_tab) => {
|
const listener = async (updated_tab_id, change_info, updated_tab) => {
|
||||||
if (updated_tab_id !== tab.id) return;
|
if (updated_tab_id !== tab.id) return;
|
||||||
if (!change_info || change_info.status !== 'complete') return;
|
if (!change_info || change_info.status !== 'complete') return;
|
||||||
if (running) return;
|
if (running) return;
|
||||||
running = true;
|
running = true;
|
||||||
|
const tab_obj = attach_tab_helpers(updated_tab || tab);
|
||||||
try {
|
try {
|
||||||
const tab_obj = attach_tab_helpers(updated_tab || tab);
|
|
||||||
await fn(tab_obj, change_info);
|
await fn(tab_obj, change_info);
|
||||||
if (once) {
|
if (once) {
|
||||||
tab.off_update_complete && tab.off_update_complete();
|
tab.off_update_complete && tab.off_update_complete();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line no-console
|
if (on_error) {
|
||||||
console.warn('[tab_on_update] fail', { tab_id: tab.id, error: (err && err.message) || String(err) });
|
on_error(err, tab_obj, change_info);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[tab_on_update] fail', { tab_id: tab.id, error: (err && err.message) || String(err) });
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
@@ -138,7 +161,11 @@ export function open_tab(url, options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
chrome.tabs.onUpdated.addListener(on_updated);
|
chrome.tabs.onUpdated.addListener(on_updated);
|
||||||
chrome.tabs.update(tab_id, { url });
|
update_tab(tab_id, { url })
|
||||||
|
.catch((err) => {
|
||||||
|
chrome.tabs.onUpdated.removeListener(on_updated);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -210,12 +237,7 @@ export function create_tab_task(url) {
|
|||||||
throw new Error('popup window 创建失败');
|
throw new Error('popup window 创建失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await update_tab(tab0.id, { url: this.url, active: this.active !== false });
|
||||||
chrome.tabs.update(tab0.id, { url: this.url, active: this.active !== false }, () => {
|
|
||||||
if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const tab_done = await new Promise((resolve) => {
|
const tab_done = await new Promise((resolve) => {
|
||||||
const on_updated = (tab_id, change_info, tab) => {
|
const on_updated = (tab_id, change_info, tab) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user