// ==UserScript== // @name 自动获取竞品、行业、跨行业热门千川素材 // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.2.0 // @description try to take over the world! // @author wanxi // @crontab * 8 once * * // @match https://yuntu.oceanengine.com/yuntu_ng/content/creative/content_lab* // @match https://yuntu.oceanengine.com/yuntu_brand/content/creative/content_lab* // @icon https://www.google.com/s2/favicons?domain=oceanengine.com // @grant GM_xmlhttpRequest // ==/UserScript== const CONFIG = { id: "cli_a6f25876ea28100d", secret: "raLC56ZLIara07nKigpysfoDxHTAeyJf", appId: 'EGKVbQPMCarVmWsJEPgcOQ38nKh', tableId1: 'tblSW2DBXwYBWHaT', // 竞品表 tableId2: 'tblgwfXH6HD0nMN7', // 同行业 tableId3: 'tbl0hQsktecuZP2W', // 跨行业 logTableId: 'tblutCXYQl4WYmwY', // 运行记录表 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`, fetchCompetitorUrl: (brandIdList) => { const industryId = 12; const dateType = 200; const endDate = getTMinus2Date(); const startDate = getTMinus2Date(); const brandId = 10209634; const competitorType = 1; const itemType = 3; const level1TriggerPointId = 150000; const level2TriggerPointId = 150200; const level3TriggerPointId = 150202; const firstOrderIndex = 10001; const secondOrderIndex = 10001; const secondOrderBy = 'asc'; const queryCnt = 20; const baseUrl = 'https://yuntu.oceanengine.com/yuntu_ng/api/v1/CompetitorHotItems'; return `${baseUrl}?aadvid=1710507483282439&industry_id=${industryId}&date_type=${dateType}&end_date=${endDate}&start_date=${startDate}&brand_id=${brandId}&brand_id_list=${brandIdList}&competitor_type=${competitorType}&item_type=${itemType}&level_1_trigger_point_id=${level1TriggerPointId}&level_2_trigger_point_id=${level2TriggerPointId}&level_3_trigger_point_id=${level3TriggerPointId}&first_order_index=${firstOrderIndex}&second_order_index=${secondOrderIndex}&second_order_by=${secondOrderBy}&query_cnt=${queryCnt}`; }, fetchIndustryUrl: (noCheckIndustry) => { const dateType = 200; const endDate = getTMinus2Date(); const startDate = getTMinus2Date(); const itemType = 3; const level1TriggerPointId = 150000; const level2TriggerPointId = 150200; const level3TriggerPointId = 150202; const firstOrderIndex = 10001; const secondOrderIndex = 10001; const secondOrderBy = 'asc'; const queryCnt = 20; const baseUrl = 'https://yuntu.oceanengine.com/yuntu_ng/api/v1/IndustryHotItems'; return `${baseUrl}?aadvid=1710507483282439&no_check_industry=${noCheckIndustry}&date_type=${dateType}&end_date=${endDate}&start_date=${startDate}&item_type=${itemType}&level_1_trigger_point_id=${level1TriggerPointId}&level_2_trigger_point_id=${level2TriggerPointId}&level_3_trigger_point_id=${level3TriggerPointId}&first_order_index=${firstOrderIndex}&second_order_index=${secondOrderIndex}&second_order_by=${secondOrderBy}&query_cnt=${queryCnt}`; } } }; 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, videoId) { 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": [videoId] }] }, "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}`); } } const brandDict = [{ brandId: 533686, brandName: "PROYA/珀莱雅" }, { brandId: 885679, brandName: "KANS/韩束" }]; const industryDict = [{ industry_id: 1202, industry_name: "护肤" }, { industry_id: 13, industry_name: "个护清洁(日化)" }, { industry_id: 27, industry_name: "医疗保健" }]; const getTMinus2Date = () => { const date = new Date(); date.setDate(date.getDate() - 2); return date.toISOString().split('T')[0]; }; const fetchData = async (url, getName, isCompetitor) => { console.log(`Fetching data from URL: ${url}`); const fetchDataWithRetry = async () => { const response = await sendHttpRequest('GET', url, null, { 'accept': 'application/json, text/plain, */*' }); if (response.status === 0 && response.data.hot_items) { return response.data.hot_items .filter(item => item.video_id && item.title && item.id) // 过滤掉任意一个字段为空的条目 .map(item => ({ 视频后台id: item.video_id, [isCompetitor ? '品牌' : '行业']: getName(), 日期: getTMinus2Date(), 视频标题: item.title, 播放量: item.show_cnt, 前台id: item.id, 点击率: item.click_rate, 互动率: item.interact_rate, 完播率: item.play_over_rate })); } else { throw new Error('Invalid response'); } }; try { return await retry(fetchDataWithRetry); } catch (error) { console.error('Error:', error); return []; } }; const fetchVideoScript = async (video_id, id) => { const fetchVideoScriptWithRetry = async () => { const response = await sendHttpRequest('POST', "https://yuntu.oceanengine.com/yuntu_ng/api/v1/GetContentFormulaAndScript?aadvid=1710507483282439", JSON.stringify({ vid: video_id, item_id: id }), { "accept": "application/json, text/plain, */*", "content-type": "application/json" }); return response.data.ori_script.text; }; try { return await retry(fetchVideoScriptWithRetry); } catch (error) { console.error('Error:', error); return '文案获取失败'; } }; const executeTasks = async () => { console.log('Executing tasks immediately.'); const fetchCompetitorData = brandDict.map(brand => fetchData(CONFIG.urls.fetchCompetitorUrl(brand.brandId), () => brand.brandName, true) ); const fetchIndustryData = industryDict .filter(industry => industry.industry_id == 1202) // 不包括护肤行业 .map(industry => fetchData(CONFIG.urls.fetchIndustryUrl(industry.industry_id), () => industry.industry_name, false) ); const fetchCrossIndustryData = industryDict .filter(industry => industry.industry_id !== 1202) // 不包括护肤行业 .map(industry => fetchData(CONFIG.urls.fetchIndustryUrl(industry.industry_id), () => industry.industry_name, false) ); const [competitorDataArray, industryDataArray, crossIndustryDataArray] = await Promise.all([ Promise.all(fetchCompetitorData), Promise.all(fetchIndustryData), Promise.all(fetchCrossIndustryData) ]); const competitorData = competitorDataArray.flat(); const industryData = industryDataArray.flat(); const crossIndustryData = crossIndustryDataArray.flat(); const addScripts = async (data) => { for (const item of data) { item.文案 = await fetchVideoScript(item.视频后台id, item.前台id); } }; await addScripts(competitorData); await addScripts(industryData); await addScripts(crossIndustryData); console.log('Competitor Data:', JSON.stringify(competitorData, null, 2)); console.log('Industry Data:', JSON.stringify(industryData, null, 2)); console.log('Cross-Industry Data:', JSON.stringify(crossIndustryData, null, 2)); const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret); for (const item of competitorData) { const itemsToWrite = {}; const exists = await checkIfRecordExists(accessToken, CONFIG.appId, CONFIG.tableId1, item.视频后台id); if (exists === 0) { itemsToWrite.fields = { "视频后台id": item.视频后台id, "品牌/行业": item.品牌 || item.行业, "日期": Math.floor(new Date(item.日期).getTime()), "视频标题": item.视频标题, "播放量": item.播放量, "视频前台id": item.前台id, "点击率": item.点击率, "互动率": item.互动率, "完播率": item.完播率, "文案": item.文案 }; } if (Object.keys(itemsToWrite).length > 0) { console.log(itemsToWrite); await updateBitableRecords(accessToken, CONFIG.appId, CONFIG.tableId1, itemsToWrite); } } for (const item of industryData) { const itemsToWrite = {}; const exists = await checkIfRecordExists(accessToken, CONFIG.appId, CONFIG.tableId2, item.视频后台id); if (exists === 0) { itemsToWrite.fields = { "视频后台id": item.视频后台id, "品牌/行业": item.品牌 || item.行业, "日期": Math.floor(new Date(item.日期).getTime()), "视频标题": item.视频标题, "播放量": item.播放量, "视频前台id": item.前台id, "点击率": item.点击率, "互动率": item.互动率, "完播率": item.完播率, "文案": item.文案 }; } if (Object.keys(itemsToWrite).length > 0) { console.log(itemsToWrite); await updateBitableRecords(accessToken, CONFIG.appId, CONFIG.tableId2, itemsToWrite); } } for (const item of crossIndustryData) { const itemsToWrite = {}; const exists = await checkIfRecordExists(accessToken, CONFIG.appId, CONFIG.tableId3, item.视频后台id); if (exists === 0) { itemsToWrite.fields = { "视频后台id": item.视频后台id, "品牌/行业": item.品牌 || item.行业, "日期": Math.floor(new Date(item.日期).getTime()), "视频标题": item.视频标题, "播放量": item.播放量, "视频前台id": item.前台id, "点击率": item.点击率, "互动率": item.互动率, "完播率": item.完播率, "文案": item.文案 }; } if (Object.keys(itemsToWrite).length > 0) { console.log(itemsToWrite); await updateBitableRecords(accessToken, CONFIG.appId, CONFIG.tableId3, itemsToWrite); } } }; async function logRunResult() { try { const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret); const runTime = new Date().getTime(); const logEntries = [{ "表名": "竞品表", "表格id": CONFIG.tableId1, "最近运行时间": runTime, "运行结果": result, "日志": logs.join('\n') }, { "表名": "同行业表", "表格id": CONFIG.tableId2, "最近运行时间": runTime, "运行结果": result, "日志": logs.join('\n') }, { "表名": "跨行业表", "表格id": CONFIG.tableId3, "最近运行时间": runTime, "运行结果": result, "日志": logs.join('\n') }]; for (const logData of logEntries) { await updateBitableRecords(accessToken, CONFIG.appId, CONFIG.logTableId, { fields: logData }); log(`Run result for ${logData.表名} logged successfully.`); } } catch (error) { log('Failed to log run result: ' + error.message); } } (async function() { try { await executeTasks(); } catch (error) { log('Script execution failed: ' + error.message); result = "Failed"; } finally { await logRunResult(); } })();