// ==UserScript==
// @name Temu-广告数据下载
// @namespace http://tampermonkey.net/
// @version 2025-03-18
// @description 下载Temu-广告数据,作为报表基础表
// @author xx99czj
// @license GPL-3.0
// @match https://us.ads.temu.com/ad-list.html?*
// @icon https://www.google.com/s2/favicons?sz=64&domain=temu.com
// @run-at document-end
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 创建样式
const style = document.createElement('style');
function toStyle () {
style.textContent = `
.toast {
position: fixed;
top: 20px;
right: 20px;
background-color: #4CAF50;
color: white;
padding: 16px;
border-radius: 5px;
opacity: 0;
transition: opacity 0.5s ease;
z-index:10001;
}
.topBar {
position: fixed;
right: 20px;
z-index: 10001;
top: 30px;
cursor:pointer;
}
body {position: relative;}
`;
}
toStyle()
document.head.appendChild(style);
class IndexedDBWrapper {
constructor(dbName, version = 1, storeName = 'dataStore') {
this.dbName = dbName;
this.version = version;
this.storeName = storeName;
this.db = null;
}
// 打开数据库连接
async open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => {
reject(`打开数据库失败: ${event.target.error}`);
};
});
}
// 存储数据
async set(key, value) {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction(this.storeName, 'readwrite');
const store = tx.objectStore(this.storeName);
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = (event) => reject(`存储失败: ${event.target.error}`);
});
}
// 读取数据
async get(key) {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction(this.storeName, 'readonly');
const store = tx.objectStore(this.storeName);
const request = store.get(key);
request.onsuccess = (event) => resolve(event.target.result);
request.onerror = (event) => reject(`读取失败: ${event.target.error}`);
});
}
// 关闭数据库连接
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
}
// ================== 使用示例 ==================
// 初始化数据库 (自动创建对象存储)
const db = new IndexedDBWrapper('AdDatabase', 1, 'adStore');
function showToast(message) {
// 创建 toast 元素
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
// 将 toast 添加到文档
document.body.appendChild(toast);
// 显示 toast
requestAnimationFrame(() => {
toast.style.opacity = '1'; // 显示
});
// 隐藏 toast
setTimeout(() => {
toast.style.opacity = '0'; // 隐藏
// 移除 toast 元素
setTimeout(() => {
document.body.removeChild(toast);
}, 500); // 等待过渡结束后再移除
}, 3000); // 3秒后隐藏
}
// 拦截fetch请求
const originalFetch = window.unsafeWindow.fetch;
window.unsafeWindow.fetch = async function (...args) {
const response = await originalFetch.apply(this, args);
const url = args[0];
// 判断是否需要监听
if (url == '/api/v1/coconut/ad/ads_detail') {
const clone = response.clone();
clone.json().then(async (data)=> {
console.log('拦截到的fetch响应: ', data);
await db.set('data', data);
console.log('数据存储成功');
}).catch(err => {
console.error('解析fetch响应失败:', err);
});
return response;
} else {
return response
}
};
// 拦截 XMLHttpRequest 的 open 方法以捕获 URL
const originalOpen = window.unsafeWindow.XMLHttpRequest.prototype.open;
window.unsafeWindow.XMLHttpRequest.prototype.open = function (...args) {
this._url = args[1]; // 保存 URL
originalOpen.apply(this, args);
};
// 拦截 XMLHttpRequest 的 send 方法
const originalSend = window.unsafeWindow.XMLHttpRequest.prototype.send;
window.unsafeWindow.XMLHttpRequest.prototype.send = function (...args) {
// 判断是否需要监听
if (this._url === '/api/v1/coconut/ad/ads_detail') {
// 修改请求 body(如果需要)
if (args[0]) {
try {
const body = JSON.parse(args[0]);
if (body.page_size) {
body.page_size = 50; // 将 page_size 改为 50
}
args[0] = JSON.stringify(body); // 重新设置 body
} catch (err) {
console.error('解析或修改 body 失败:', err);
}
}
// 监听请求完成事件
this.addEventListener('load', async function () {
console.log('XMLHttpRequest 加载事件 URL:', this._url);
try {
const response = this.responseText;
const data = JSON.parse(response); // 解析响应
console.log('拦截到的 XMLHttpRequest 响应:', data);
// 存储数据到 IndexedDB
await db.set('data', data);
console.log('数据存储成功');
} catch (e) {
console.error('解析 XMLHttpRequest 响应失败:', e);
}
});
}
// 发送请求
originalSend.apply(this, args);
};
// 等待页面完全加载
window.addEventListener('load', () => {
console.log('页面已完全加载');
// 如果在初始加载后动态加载元素, 使用MutationObserver
const observer = new MutationObserver(() => {
});
observer.observe(document.body, { childList: true, subtree: true });
// 5秒后停止观察
setTimeout(() => {
observer.disconnect();
console.log('停止观察DOM变动');
}, 2000);
// 检查页面URL并在特定页面添加按钮
if (window.location.href.startsWith('https://us.ads.temu.com/ad-list.html')) {
console.log('在指定页面, 1秒后添加按钮');
setTimeout(initFixedButton, 1000);
}
});
// 定义点击事件的处理函数
function downloadTextJson(text) {
// 创建一个 Blob 对象,指定类型为 JSON
const blob = new Blob([text], { type: "application/json" });
// 创建一个指向 Blob 的 URL
const url = window.URL.createObjectURL(blob);
// 创建一个隐藏的 标签用于下载
const a = document.createElement("a");
a.href = url; // 设置下载链接
const storeName = localStorage.mall_id;
// 创建一个 Date 对象,表示当前日期和时间
const now = new Date();
// 获取当前的月份(0-11,0 表示 1 月,1 表示 2 月,以此类推)
const month = now.getMonth() + 1; // 加 1 是因为 getMonth() 返回的月份是从 0 开始的
// 获取当前的日期(1-31)
const date = now.getDate();
// 如果需要格式化为两位数(例如:03 表示 3 月,08 表示 8 日)
const formattedMonth = String(month).padStart(2, '0');
const formattedDate = String(date).padStart(2, '0');
a.download = `${storeName}-${formattedMonth}/${formattedDate}.json`; // 设置下载文件名
a.click(); // 触发点击事件,开始下载
a.remove(); // 下载完成后移除 标签
window.URL.revokeObjectURL(url); // 释放 URL 对象
}
// 初始化按钮
function initFixedButton() {
// 创建菜单容器
const menuContainer = document.createElement('div');
menuContainer.setAttribute('id', 'my-buttons');
menuContainer.style.position = 'fixed';
menuContainer.style.bottom = '18px';
menuContainer.style.right = '2px';
menuContainer.style.display = 'flex';
menuContainer.style.flexDirection = 'column';
menuContainer.style.alignItems = 'flex-end';
menuContainer.style.transition = 'opacity 0.3s';
menuContainer.style.opacity = '0'; // 初始隐藏
menuContainer.style.pointerEvents = 'none'; // 初始不可点击
menuContainer.style.zIndex = '10001';
// 创建菜单项
const items = ['data']
// 菜单栏
const bar = document.querySelector(`.index-module__sidebarContainer___2EGa_`)||document.querySelector(`.index-module__sidebarContainer___1HGOI`)||document.querySelector(`._14PJGk-N`);
items.forEach(item => {
const menuItem = document.createElement('div');
menuItem.style.zIndex = '10001';
menuItem.innerText = item;
menuItem.style.padding = '10px';
menuItem.style.backgroundColor = '#007BFF';
menuItem.style.color = 'white';
menuItem.style.borderRadius = '50%';
menuItem.style.margin = '5px 0';
menuItem.style.width = '50px';
menuItem.style.height = '50px';
menuItem.style.display = 'flex';
menuItem.style.alignItems = 'center';
menuItem.style.justifyContent = 'center';
menuItem.style.transition = 'transform 0.3s';
menuItem.style.transform = 'scale(0)'; // 初始隐藏
menuItem.style.cursor = 'pointer';
menuItem.addEventListener('click', async () => {
const res = await db.get('data')
const jsonString = JSON.stringify(res, null, 2); // 使用缩进格式化 JSON 字符串
downloadTextJson(jsonString)
})
menuContainer.appendChild(menuItem);
});
// 创建切换按钮
const toggleButton = document.createElement('button');
toggleButton.style.width = '0';
toggleButton.style.height = '0';
toggleButton.style.borderRadius = '50%';
toggleButton.style.border = '10px solid #FB7701';
toggleButton.style.cursor = 'pointer';
toggleButton.style.position = 'fixed';
toggleButton.style.zIndex = '10001';
toggleButton.style.bottom = '0px';
toggleButton.style.right = '0px';
// 切换菜单显示效果
toggleButton.addEventListener('click', () => {
const isVisible = menuContainer.style.opacity === '1';
menuContainer.style.opacity = isVisible ? '0' : '1';
menuContainer.style.pointerEvents = isVisible ? 'none' : 'auto';
// 显示/隐藏菜单项
const menuItems = menuContainer.children;
for (let i = 0; i < menuItems.length; i++) {
menuItems[i].style.transform = isVisible ? 'scale(0)' : 'scale(1)';
}
});
toggleButton.click();
// 添加元素到文档
document.body.appendChild(menuContainer);
document.body.appendChild(toggleButton);
// 逐个显示菜单项
setTimeout(() => {
const menuItems = menuContainer.children;
for (let i = 0; i < menuItems.length; i++) {
menuItems[i].style.transitionDelay = `${i * 100}ms`;
}
}, 0);
}
window.addEventListener('popstate', (event) => {
const bts = document.querySelector('#my-buttons')
if (bts) {
bts.remove();
}
initFixedButton ();
});
})();