// ==UserScript==
// @name 阿里巴巴国际站询盘数据导出工具-树洞先生
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 更新了买家最近搜索词和询价产品的图片,采集阿里巴巴询盘数据并导出为Excel
// @author You
// @license MPL
// @match https://message.alibaba.com/message/default.htm*
// @grant GM.xmlHttpRequest
// @grant GM_download
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @connect message.alibaba.com
// @connect alicrm.alibaba.com
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js
// ==/UserScript==
(function() {
'use strict';
// 配置常量
const FIELD_WHITELIST = [
"name", "highQualityLevelTag", "levelTag", "appFrom", "feedbackType", "source",
"subject", "tradeId", "createTime", "lastestReplyTime", "readTime", "productId",
"productName", "imageUrl", "url", "ownerName", "registerDate",
"country", "companyName", "companyWebSite", "email", "mobileNumber", "phoneNumber",
"preferredIndustries", "productViewCount", "validInquiryCount", "repliedInquiryCount",
"validRfqCount", "loginDays", "spamInquiryMarkedBySupplierCount", "addedToBlacklistCount",
"totalOrderCount", "totalOrderVolume", "tradeSupplierCount", "searchWords", "latestInquiryProducts"
];
const FIELD_CHINESE_MAP = {
"appFrom": "询盘来源终端", "name": "客户名", "productId": "询盘产品ID",
"productName": "询盘产品标题", "imageUrl": "询盘产品图片", "url": "询盘产品链接",
"feedbackType": "询盘类型", "createTime": "创建时间", "lastestReplyTime": "最新回复时间",
"readTime": "读取时间", "source": "询盘来源", "subject": "询盘标题", "tradeId": "询盘ID",
"ownerName": "业务员", "country": "国家/地区", "levelTag": "买家类型标签",
"registerDate": "注册日期", "companyName": "公司名称", "companyWebSite": "公司网站",
"productViewCount": "产品浏览数", "validInquiryCount": "有效询价数", "repliedInquiryCount": "回复询价数量",
"validRfqCount": "有效RFQ数", "loginDays": "登录天数", "spamInquiryMarkedBySupplierCount": "垃圾询盘数",
"addedToBlacklistCount": "被加为黑名单数", "totalOrderCount": "订单总数", "totalOrderVolume": "订单总金额",
"tradeSupplierCount": "交易供应商数", "highQualityLevelTag": "买家等级标签", "email": "邮箱",
"mobileNumber": "手机号码", "phoneNumber": "电话号码", "preferredIndustries": "最常采购行业",
"searchWords": "最近搜索词", "latestInquiryProducts": "最新询价产品"
};
// 全局变量
let allRows = [];
let currentPage = 1;
let isCollecting = false;
let totalCollected = 0;
// 工具函数
function getCookie(name) {
let pattern = new RegExp(name + "=([^;]*)");
let matches = document.cookie.match(pattern);
return matches ? decodeURIComponent(matches[1]) : undefined;
}
function getPageVarByRegex(regex) {
const html = document.documentElement.innerHTML;
const match = html.match(regex);
return match ? match[1] : '';
}
function getNested(data, ...keys) {
for (const key of keys) {
if (data === null || data === undefined) {
return "";
}
data = data[key];
}
return data !== null && data !== undefined ? data : "";
}
function formatTimestamp(ts) {
try {
ts = parseInt(ts);
if (ts > 1e12) {
ts = Math.floor(ts / 1000);
}
return new Date(ts * 1000).toLocaleString('zh-CN');
} catch (e) {
return ts;
}
}
function formatDate(ts) {
try {
ts = parseInt(ts);
return new Date(ts * 1000).toISOString().split('T')[0];
} catch (e) {
return ts;
}
}
function replaceHiddenValue(value) {
if (value === -1 || value === "-1") {
return "客户隐藏";
}
return value;
}
// 修正 ctoken 获取方式
function getCtoken() {
let xman_us_t = getCookie('xman_us_t');
if (xman_us_t) {
let match = xman_us_t.match(/ctoken=([^&;]+)/);
if (match) return match[1];
}
return undefined;
}
// 临时写死 dmtrack_pageid
function getDmtrackPageId() {
return '6797ac2f2102fbdd1750902252'; // 用你抓包时的值
}
// API请求函数
async function fetchPage(page) {
console.log(`正在请求第 ${page} 页数据...`);
// 动态获取 ctoken 和 dmtrack_pageid
const ctoken = getCtoken();
const dmtrack_pageid = getDmtrackPageId();
console.log('ctoken:', ctoken);
console.log('dmtrack_pageid:', dmtrack_pageid);
const postId = getPageVarByRegex(/postId["']?\s*[:=]\s*["']([^"']+)/) || '';
const params = {
ctoken: ctoken,
dmtrack_pageid: dmtrack_pageid,
};
const paramsJson = {
system: "feedback",
listType: "all",
pageSize: "100",
pagination: { nextPage: page, pageSize: "100" },
filter: { isShowAtm: false },
order: { order: "desc", orderBy: "latest_contact_time" },
search: {}
};
// 拼接URL参数
const url = `https://message.alibaba.com/message/ajax/feedback/subjectList.htm?ctoken=${encodeURIComponent(params.ctoken)}&dmtrack_pageid=${encodeURIComponent(params.dmtrack_pageid)}`;
const data = {
_csrf_token_: getCookie('_csrf_token_'),
postId: postId,
params: JSON.stringify(paramsJson)
};
try {
const response = await new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'POST',
url: url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://message.alibaba.com/message/default.htm',
'Origin': 'https://message.alibaba.com',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
},
data: new URLSearchParams(data).toString(),
responseType: 'text',
onload: resolve,
onerror: reject
});
});
console.log('接口原始返回内容:', response);
let parsedData;
try {
parsedData = JSON.parse(response.responseText);
} catch (e) {
parsedData = {};
}
console.log('接口JSON解析后:', parsedData);
console.log(`第 ${page} 页数据请求成功`);
return parsedData;
} catch (e) {
console.error(`请求第 ${page} 页数据失败:`, e);
return {};
}
}
async function fetchAccountIdEncrypt(secTradeId) {
if (!secTradeId) return "";
const data = {
_csrf_token_: getCookie('_csrf_token_'),
params: JSON.stringify({ secTradeId: secTradeId }),
};
try {
const response = await new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'POST',
url: 'https://message.alibaba.com/message/ajax/feedback/querySummary.htm',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://message.alibaba.com/message/default.htm',
'Origin': 'https://message.alibaba.com',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
},
data: new URLSearchParams(data).toString(),
responseType: 'text',
onload: resolve,
onerror: reject
});
});
let parsedData;
try {
parsedData = JSON.parse(response.responseText);
} catch (e) {
parsedData = {};
}
return parsedData?.data?.contact?.accountIdEncrypt || "";
} catch (e) {
console.error(`获取 accountIdEncrypt 失败:`, e);
return "";
}
}
async function fetchKhtAccessToken(tradeId) {
if (!tradeId) return "";
try {
const response = await new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: `https://message.alibaba.com/message/maDetail.htm?imInquiryId=${tradeId}&hash=`,
responseType: 'text',
onload: resolve,
onerror: reject
});
});
const html = response.responseText;
const match = html.match(/window\.KHTAccessToken\s*=\s*['"]([^'"]+)['"]/);
if (match) {
return match[1];
} else {
console.log(`未找到KHTAccessToken, tradeId=${tradeId}`);
return "";
}
} catch (e) {
console.error(`获取KHTAccessToken失败:`, e);
return "";
}
}
async function fetchQuerySummaryFields(secTradeId) {
if (!secTradeId) return {};
const data = {
_csrf_token_: getCookie('_csrf_token_'),
params: JSON.stringify({ secTradeId: secTradeId }),
};
try {
const response = await new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'POST',
url: 'https://message.alibaba.com/message/ajax/feedback/querySummary.htm',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://message.alibaba.com/message/default.htm',
'Origin': 'https://message.alibaba.com',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
},
data: new URLSearchParams(data).toString(),
responseType: 'text',
onload: resolve,
onerror: reject
});
});
let parsedData;
try {
parsedData = JSON.parse(response.responseText);
} catch (e) {
parsedData = {};
}
const contact = parsedData?.data?.contact || {};
return {
name: contact.name || "",
productId: contact.productId || "",
productName: contact.productName || "",
imageUrl: contact.imageUrl || "",
url: contact.url || ""
};
} catch (e) {
console.error(`获取querySummary字段失败:`, e);
return {};
}
}
function buildCustomerInfoLink(accountIdEncrypt, secTradeId, khtAccessToken) {
const params = {
buyerAccountId: accountIdEncrypt,
secTradeId: secTradeId,
buyerLoginId: '',
secReqToken: khtAccessToken,
clientType: '',
ctoken: getCtoken(),
_tb_token_: getCookie('_tb_token_'),
callback: '',
};
const baseUrl = 'https://alicrm.alibaba.com/jsonp/customerPluginQueryServiceI/queryCustomerInfo.json';
const urlParams = new URLSearchParams(params);
return `${baseUrl}?${urlParams.toString()}`;
}
async function fetchCustomerInfo(customerInfoUrl) {
try {
const response = await new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: customerInfoUrl,
responseType: 'text',
onload: resolve,
onerror: reject
});
});
const text = response.responseText;
const jsonStr = text.replace(/^\w+\((.*)\)$/, '$1');
const data = JSON.parse(jsonStr);
return data?.data || {};
} catch (e) {
console.error(`获取客户信息失败:`, e);
return {};
}
}
// 数据处理函数
async function processItem(item, index, total) {
console.log(`处理第 ${index + 1}/${total} 条记录...`);
const row = {};
const secTradeId = item.secTradeId || "";
// 从item主字段采集
for (const field of FIELD_WHITELIST) {
row[field] = item[field] || "";
}
// 采集 productInfo 里的产品信息(覆盖主字段)
if (Array.isArray(item.productInfo) && item.productInfo.length > 0) {
const p = item.productInfo[0];
row.productId = p.productId || p.id || "";
row.productName = p.productName || "";
row.imageUrl = p.imageUrl || "";
row.url = p.url || "";
}
// 采集querySummary接口的字段(不再覆盖产品相关字段)
const summaryFields = await fetchQuerySummaryFields(secTradeId);
for (const k of ["name"]) {
row[k] = summaryFields[k] || row[k] || "";
}
// 其它 summary 字段如需采集可补充,但不要覆盖 productId/productName/imageUrl/url
row.accountIdEncrypt = await fetchAccountIdEncrypt(secTradeId);
const tradeId = row.tradeId || "";
row.KHTAccessToken = await fetchKhtAccessToken(tradeId);
row.customer_info_link = buildCustomerInfoLink(
row.accountIdEncrypt, row.secTradeId || "", row.KHTAccessToken
);
const customerInfo = await fetchCustomerInfo(row.customer_info_link);
const dataInfo = customerInfo.data || {};
const alicrmInfo = dataInfo.alicrmCustomerInfo || {};
const buyerInfo = dataInfo.buyerInfo || {};
const buyerContact = buyerInfo.buyerContactInfo || {};
const shopBehavior = buyerInfo.buyerShopBehaviorInfo || {};
// 字段采集映射
const fieldMap = {
ownerName: (a, b, c, s) => getNested(a, "ownerName"),
email: (a, b, c, s) => getNested(c, "email") || getNested(a, "email"),
mobileNumber: (a, b, c, s) => getNested(c, "mobileNumber") || getNested(a, "mobileNumber"),
phoneNumber: (a, b, c, s) => getNested(c, "phoneNumber") || getNested(a, "phoneNumber"),
companyName: (a, b, c, s) => getNested(b, "companyName") || getNested(a, "companyName"),
companyWebSite: (a, b, c, s) => getNested(b, "companyWebSite") || getNested(a, "companyWebSite"),
customerGroup: (a, b, c, s) => getNested(a, "customerGroup"),
contractId: (a, b, c, s) => getNested(a, "contractId"),
noteCode: (a, b, c, s) => getNested(a, "noteCode"),
country: (a, b, c, s) => getNested(b, "country"),
levelTag: (a, b, c, s) => getNested(b, "levelTag"),
registerDate: (a, b, c, s) => getNested(b, "registerDate"),
productViewCount: (a, b, c, s) => getNested(b, "productViewCount"),
validInquiryCount: (a, b, c, s) => getNested(b, "validInquiryCount"),
repliedInquiryCount: (a, b, c, s) => getNested(b, "repliedInquiryCount"),
validRfqCount: (a, b, c, s) => getNested(b, "validRfqCount"),
loginDays: (a, b, c, s) => getNested(b, "loginDays"),
spamInquiryMarkedBySupplierCount: (a, b, c, s) => getNested(b, "spamInquiryMarkedBySupplierCount"),
addedToBlacklistCount: (a, b, c, s) => getNested(b, "addedToBlacklistCount"),
totalOrderCount: (a, b, c, s) => getNested(b, "totalOrderCount"),
totalOrderVolume: (a, b, c, s) => getNested(b, "totalOrderVolume"),
tradeSupplierCount: (a, b, c, s) => getNested(b, "tradeSupplierCount"),
isGoldenBuyer: (a, b, c, s) => getNested(b, "isGoldenBuyer"),
highQualityLevelTag: (a, b, c, s) => getNested(b, "highQualityLevelTag"),
visible: (a, b, c, s) => getNested(c, "visible"),
applyStatus: (a, b, c, s) => getNested(c, "applyStatus"),
searchWords: (a, b, c, s) => getNested(b, "searchWords"),
lastestRfqList: (a, b, c, s) => getNested(b, "lastestRfqList"),
latestInquiryProducts: (a, b, c, s) => getNested(b, "latestInquiryProducts"),
productId: (a, b, c, s) => getNested(s, "productId"),
productName: (a, b, c, s) => getNested(s, "productName"),
url: (a, b, c, s) => getNested(s, "url"),
preferredIndustries: (a, b, c, s) => getNested(b, "preferredIndustries"),
imageUrl: (a, b, c, s) => getNested(s, "imageUrl"),
};
for (const field of FIELD_WHITELIST) {
if (fieldMap[field]) {
row[field] = fieldMap[field](alicrmInfo, buyerInfo, buyerContact, shopBehavior) || row[field] || "";
}
}
// 时间戳字段格式化
for (const tsField of ["createTime", "lastestReplyTime", "readTime"]) {
if (row[tsField]) {
row[tsField] = formatTimestamp(row[tsField]);
}
}
// 注册日期格式化
if (row.registerDate) {
row.registerDate = formatDate(row.registerDate);
}
// 替换所有-1为'客户隐藏'
for (const k in row) {
row[k] = replaceHiddenValue(row[k]);
}
return row;
}
// 导出CSV函数
function exportToCSV(data, filename) {
const headers = FIELD_WHITELIST.map(field => FIELD_CHINESE_MAP[field]);
const csvContent = [
headers.join(','),
...data.map(row =>
FIELD_WHITELIST.map(field => {
const value = row[field] || "";
// 处理包含逗号、引号或换行符的值
if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
}).join(',')
)
].join('\n');
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
// 导出XLSX函数
function exportToXLSX(data, filename) {
const headers = FIELD_WHITELIST.map(field => FIELD_CHINESE_MAP[field]);
const rows = data.map(row => FIELD_WHITELIST.map(field => row[field] || ""));
const worksheetData = [headers, ...rows];
const ws = XLSX.utils.aoa_to_sheet(worksheetData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([wbout], { type: "application/octet-stream" });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
// 并发处理工具
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = [];
const executing = [];
for (let i = 0; i < array.length; i++) {
const p = Promise.resolve().then(() => iteratorFn(array[i], i));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
// 悬浮导出按钮
function createExportButton() {
const btn = document.createElement('button');
btn.id = 'inquiry-export-float-btn';
btn.textContent = '导出询盘明细-树洞先生';
btn.style.cssText = `
background: #007bff;
color: #fff;
border: none;
border-radius: 6px;
padding: 6px 14px;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
cursor: pointer;
margin-left: 12px;
`;
btn.onclick = showExportDialog;
// 插入到 reply-info-title 的 options 区域后面
const replyInfoTitle = document.querySelector('.reply-info-title');
if (replyInfoTitle) {
// 找到 options 区域
const optionsDiv = replyInfoTitle.querySelector('.options');
if (optionsDiv) {
optionsDiv.parentNode.insertBefore(btn, optionsDiv.nextSibling);
} else {
replyInfoTitle.appendChild(btn);
}
} else {
document.body.appendChild(btn);
}
}
// 选择页码弹窗
async function showExportDialog() {
if (document.getElementById('inquiry-export-dialog')) return;
// 1. 优先从页面获取用户数量
let totalCount = 0;
let totalPages = 1;
let pageSize = 100;
let statusText = '正在获取总页数...';
const infoDiv = document.querySelector('.op-search-result-info');
if (infoDiv) {
const match = infoDiv.textContent.match(/(\d+)/);
if (match) {
totalCount = parseInt(match[1], 10);
totalPages = Math.ceil(totalCount / pageSize);
if (totalPages < 1) totalPages = 1;
statusText = `共 ${totalCount} 条,约 ${totalPages} 页(基于页面显示)`;
}
}
// 2. 如果页面没有,兜底用接口
if (!totalCount) {
try {
const result = await fetchPage(1);
totalCount = result?.data?.total || 0;
pageSize = result?.data?.pageSize || 100;
totalPages = Math.ceil(totalCount / pageSize);
if (totalPages < 1) totalPages = 1;
statusText = `共 ${totalCount} 条,约 ${totalPages} 页(基于接口)`;
} catch (e) {
statusText = '无法获取总页数';
}
}
const dialog = document.createElement('div');
dialog.id = 'inquiry-export-dialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
z-index: 10001;
padding: 32px 24px 24px 24px;
min-width: 320px;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
<div style="font-size:18px;font-weight:bold;margin-bottom:16px;">导出询盘明细-树洞先生</div>
<div style="margin-bottom:12px;">
<label>采集页码(起始):<input id="export-page-start" type="number" value="1" min="1" style="width:60px;"></label>
</div>
<div style="margin-bottom:12px;">
<label>采集页数:<input id="export-page-count" type="number" value="1" min="1" max="${totalPages}" style="width:60px;"></label>
<span style="color:#888;font-size:12px;margin-left:8px;">最大可采集页码数为 ${totalPages}</span>
</div>
<div style="margin-bottom:18px;">
<span id="inquiry-export-status" style="font-size:12px;color:#666;">${statusText}</span>
</div>
<button id="start-export-btn" style="background:#007bff;color:#fff;border:none;padding:8px 20px;border-radius:4px;cursor:pointer;font-size:15px;">开始采集</button>
<button id="close-export-btn" style="background:#6c757d;color:#fff;border:none;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px;margin-left:12px;">关闭</button>
`;
document.body.appendChild(dialog);
document.getElementById('close-export-btn').onclick = () => dialog.remove();
document.getElementById('start-export-btn').onclick = () => {
const startPage = parseInt(document.getElementById('export-page-start').value, 10) || 1;
let pageCount = parseInt(document.getElementById('export-page-count').value, 10) || 1;
if (pageCount > totalPages) pageCount = totalPages;
startCollectionWithPages(startPage, pageCount, totalPages, dialog);
};
}
// 多页采集主函数,增加 totalPages 和 dialog 参数用于进度显示
async function startCollectionWithPages(startPage, pageCount, totalPages, dialog) {
if (isCollecting) {
alert('正在采集中,请稍候...');
return;
}
isCollecting = true;
allRows = [];
totalCollected = 0;
updateStatus('开始采集数据...', dialog);
try {
for (let currentPage = startPage; currentPage < startPage + pageCount; currentPage++) {
const result = await fetchPage(currentPage);
const dataList = result?.data?.list || [];
const items = [];
for (const sublist of dataList) items.push(...sublist);
updateStatus(`正在采集第 ${currentPage - startPage + 1}/${pageCount} 页(全站约 ${totalPages} 页),共 ${items.length} 条`, dialog);
if (items.length > 0) {
await asyncPool(25, items, async (item, i) => {
updateStatus(`正在采集第 ${currentPage - startPage + 1}/${pageCount} 页(全站约 ${totalPages} 页),第 ${i + 1}/${items.length} 条`, dialog);
const row = await processItem(item, i, items.length);
allRows.push(row);
totalCollected++;
});
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
updateStatus(`采集完成,共 ${allRows.length} 条记录,正在导出...`, dialog);
for (const row of allRows) {
if (Array.isArray(row.preferredIndustries)) {
row.preferredIndustries = row.preferredIndustries.join(', ');
}
if (Array.isArray(row.searchWords)) {
row.searchWords = row.searchWords.join(', ');
}
if (Array.isArray(row.latestInquiryProducts)) {
row.latestInquiryProducts = row.latestInquiryProducts.join(', ');
}
}
const uniqueMap = new Map();
for (const row of allRows) {
const key = `${row.name}||${row.country}||${row.registerDate}`;
if (!uniqueMap.has(key)) {
uniqueMap.set(key, row);
}
}
const uniqueRows = Array.from(uniqueMap.values());
uniqueRows.sort((a, b) => Number(b.createTime) - Number(a.createTime));
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const filename = `询盘明细_${timestamp}.xlsx`;
exportToXLSX(uniqueRows, filename);
updateStatus(`导出完成!文件名: ${filename},共 ${uniqueRows.length} 条记录`, dialog);
} catch (error) {
console.error('采集过程中出错:', error);
updateStatus(`采集出错: ${error.message}`, dialog);
} finally {
isCollecting = false;
}
}
// updateStatus 支持传递 dialog
function updateStatus(message, dialog) {
let statusElement = document.getElementById('inquiry-export-status');
if (!statusElement && dialog) {
statusElement = dialog.querySelector('#inquiry-export-status');
}
if (statusElement) {
statusElement.textContent = message;
}
console.log(message);
}
// 初始化
function init() {
// 只显示悬浮按钮
createExportButton();
}
// 启动脚本
init();
})();