scriptCat/douyinShop/douyinAftersale/douyinAftersale.js
intelligrow 3c3ecf8947 feat: 新增多个脚本用于监控抖音直播、店铺评价、售后及体验分数据
新增了多个脚本文件,用于监控抖音直播间的弹幕、店铺评价、售后数据及商家体验分。这些脚本通过飞书多维表格进行数据存储,并支持定时任务自动更新数据。具体包括:
1. 直播间弹幕监控脚本
2. 店铺评价监控脚本
3. 售后数据监控脚本
4. 商家体验分监控脚本
5. 竞品、行业及跨行业热门千川素材获取脚本

这些脚本通过飞书API进行数据写入,并支持去重和定时任务调度。
2025-04-25 15:15:29 +08:00

288 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name 抖音店铺售后监控
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.2.0
// @description 每小时获取前2小时的店铺售后数据并去重后上传到飞书多维表格
// @author wanxi
// @crontab 5 * * * *
// @grant GM_xmlhttpRequest
// ==/UserScript==
const CONFIG = {
id: "",
secret: "",
appId: "",
tableId: "", // 表格ID
logTableId: "", // 运行记录表
urls: {
tenantAccessToken:
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
bitableUpdateRecords: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records`,
bitableSearchRecords: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records/search`,
fetchAfterSales: () => `https://fxg.jinritemai.com/after_sale/pc/list`,
},
};
let logs = [];
let result = "Success";
function log(message) {
logs.push(message);
console.log(message);
}
// 重试函数
const retry = async (fn, retries = 3, delay = 1000) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i < retries - 1) {
console.warn(`Retrying... (${i + 1}/${retries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
async function sendHttpRequest(
method,
url,
body = null,
headers = { "Content-Type": "application/json" }
) {
return retry(
() =>
new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url,
data: body,
headers: headers,
onload: function (response) {
if (response.status >= 200 && response.status < 300) {
try {
const jsonResponse = JSON.parse(response.responseText);
if (jsonResponse.msg) {
log(jsonResponse.msg);
}
resolve(jsonResponse);
} catch (e) {
reject(`Failed to parse JSON: ${e}`);
}
} else {
reject(`Error: ${response.status} ${response.statusText}`);
}
},
onerror: function (error) {
reject(`Network Error: ${error}`);
},
});
})
);
}
async function fetchTenantAccessToken(id, secret) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.tenantAccessToken,
JSON.stringify({
app_id: id,
app_secret: secret,
})
);
return response.tenant_access_token;
} catch (error) {
throw new Error(`Error fetching Tenant Access Token: ${error}`);
}
}
// 更新
async function updateBitableRecords(accessToken, appId, tableId, items) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.bitableUpdateRecords(appId, tableId),
JSON.stringify(items),
{
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
}
);
log("Updated record: " + JSON.stringify(response));
return response;
} catch (error) {
throw new Error(`Failed to update record in Bitable: ${error}`);
}
}
async function checkIfRecordExists(accessToken, appId, tableId, afterSaleId) {
const url = CONFIG.urls.bitableSearchRecords(appId, tableId);
const headers = {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
};
const body = JSON.stringify({
field_names: ["售后单号"],
filter: {
conjunction: "and",
conditions: [
{
field_name: "售后单号",
operator: "is",
value: [afterSaleId],
},
],
},
automatic_fields: false,
});
try {
const response = await sendHttpRequest("POST", url, body, headers);
return response.data.total > 0;
} catch (error) {
console.error("Error querying Bitable:", error);
throw new Error(`Failed to query Bitable: ${error.message}`);
}
}
async function fetchAfterSales(page, pageSize = 50, startTime, endTime) {
const url = CONFIG.urls.fetchAfterSales();
const headers = {
accept: "application/json, text/plain, */*",
"content-type": "application/json;charset=UTF-8",
};
const body = JSON.stringify({
pageSize: pageSize,
page: page,
apply_time_start: startTime,
apply_time_end: endTime,
});
try {
const response = await sendHttpRequest("POST", url, body, headers);
return response;
} catch (error) {
console.error("Error fetching after sales:", error);
throw error;
}
}
async function fetchAllAfterSales() {
let allAfterSales = [];
let page = 0;
let hasMore = true;
//秒级时间戳
const endTime = Math.floor(Date.now() / 1000);
const startTime = endTime - 2 * 3600;
//const startTime = 1721516400;
//const endTime = 1721523600;
while (hasMore) {
const data = await fetchAfterSales(page, 50, startTime, endTime);
if (data.code !== 0) {
result = "Failed";
throw new Error(`Error fetching after sales: ${data.msg}`);
}
if (data.data && data.data.items.length > 0) {
allAfterSales = allAfterSales.concat(data.data.items);
page++;
hasMore = data.data.has_more;
} else {
hasMore = false;
}
}
return allAfterSales;
}
async function fetchAndUploadAfterSales() {
try {
log("Fetching after sales data...");
const afterSales = await fetchAllAfterSales();
log("After sales data fetched successfully.");
log("Fetching tenant access token...");
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
log("Tenant access token fetched successfully.");
log("Uploading after sales data to bitable...");
for (const afterSale of afterSales) {
const exists = await checkIfRecordExists(
accessToken,
CONFIG.appId,
CONFIG.tableId,
afterSale.after_sale_info.after_sale_id
);
if (!exists) {
const fields = {
售后单号: afterSale.after_sale_info.after_sale_id,
售后类型: afterSale.after_sale_info.after_sale_tags[0].text,
售后原因: afterSale.text_part.reason_text,
申请时间: afterSale.after_sale_info.apply_time * 1000,
退款金额: afterSale.after_sale_info.refund_amount / 100,
订单创建时间:
afterSale.order_info.related_order_info[0].create_time * 1000,
订单id: afterSale.order_info.related_order_info[0].sku_order_id,
订单商品id: afterSale.order_info.related_order_info[0].product_id,
订单商品名称: afterSale.order_info.related_order_info[0].product_name,
店铺: "夸迪官方旗舰店",
};
const itemsToWrite = { fields: fields };
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.tableId,
itemsToWrite
);
}
}
log("After sales data uploaded successfully.");
} catch (error) {
log("Error: " + error.message);
result = "Failed";
throw error;
}
}
async function logRunResult() {
try {
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
const runTime = new Date().getTime();
const logData = {
fields: {
表名: "售后",
表格id: CONFIG.tableId,
最近运行时间: runTime,
运行结果: result,
日志: logs.join("\n"),
},
};
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.logTableId,
logData
);
log("Run result logged successfully.");
} catch (error) {
log("Failed to log run result: " + error.message);
}
}
(async function () {
try {
await fetchAndUploadAfterSales();
} catch (error) {
log("Script execution failed: " + error.message);
} finally {
await logRunResult();
}
})();