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

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

286 lines
7.8 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 1 * * * *
// @grant GM_xmlhttpRequest
// @connect open.feishu.cn
// @connect fxg.jinritemai.com
// ==/UserScript==
const CONFIG = {
id: "cli_a6f25876ea28100d",
secret: "raLC56ZLIara07nKigpysfoDxHTAeyJf",
appId: "GyUUbEzuxajfU4sGieIcNKxvnXd",
tableId: "tblfMlqE1lKBEnR4", // 表格ID
logTableId: "tbl6eZJpt9GkZjWO", // 运行记录表
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`,
fetchComments: (page, pageSize, startTime, endTime) =>
`https://fxg.jinritemai.com/product/tcomment/commentList?rank=0&content_search=0&reply_search=0&appeal_search=0&comment_time_from=${startTime}&comment_time_to=${endTime}&pageSize=${pageSize}&page=${page}`,
},
};
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, commentId) {
const url = CONFIG.urls.bitableSearchRecords(appId, tableId);
const headers = {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
};
const body = JSON.stringify({
field_names: ["评价id"],
filter: {
conjunction: "and",
conditions: [
{
field_name: "评价id",
operator: "is",
value: [commentId],
},
],
},
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 fetchComments(page, pageSize = 50, startTime, endTime) {
const url = CONFIG.urls.fetchComments(page, pageSize, startTime, endTime);
const headers = {
accept: "application/json, text/plain, */*",
};
try {
const response = await sendHttpRequest("GET", url, null, headers);
return response;
} catch (error) {
console.error("Error fetching comments:", error);
throw error;
}
}
async function fetchAllComments() {
let allComments = [];
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 fetchComments(page, 50, startTime, endTime);
if (data.code !== 0) {
result = "Failed";
throw new Error(`Error fetching comments: ${data.msg}`);
}
if (data.data && data.data.length > 0) {
allComments = allComments.concat(data.data);
page++;
} else {
hasMore = false;
}
}
return allComments;
}
async function fetchAndUploadComments() {
try {
log("Fetching comments data...");
const comments = await fetchAllComments();
log("Comments data fetched successfully.");
log("Fetching tenant access token...");
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
log("Tenant access token fetched successfully.");
log("Uploading comments data to bitable...");
for (const comment of comments) {
const exists = await checkIfRecordExists(
accessToken,
CONFIG.appId,
CONFIG.tableId,
comment.id
);
if (!exists) {
const fields = {
评价id: comment.id,
评价时间: comment.comment_time * 1000,
商品id: comment.product_id,
店铺评分: comment.rank_shop,
物流评分: comment.rank_logistic,
商品评分: comment.rank_product,
综合评分: comment.rank,
评价标签: comment.tags.rank_info.name,
SKU: comment.sku,
店铺名: "夸迪官方旗舰店",
订单id: comment.order_id,
商品名称: comment.product.name,
评价内容: comment.content,
};
const itemsToWrite = { fields: fields };
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.tableId,
itemsToWrite
);
}
}
log("Comments 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 fetchAndUploadComments();
} catch (error) {
log("Script execution failed: " + error.message);
} finally {
await logRunResult();
}
})();