|
|
@@ -142,6 +142,7 @@ export class XiaohongshuAdapter extends BasePlatformAdapter {
|
|
|
|
|
|
/**
|
|
|
* 获取账号信息
|
|
|
+ * 通过拦截 API 响应获取准确数据
|
|
|
*/
|
|
|
async getAccountInfo(cookies: string): Promise<AccountProfile> {
|
|
|
try {
|
|
|
@@ -167,64 +168,98 @@ export class XiaohongshuAdapter extends BasePlatformAdapter {
|
|
|
fans?: number;
|
|
|
notes?: number;
|
|
|
};
|
|
|
+ homeData?: {
|
|
|
+ fans?: number;
|
|
|
+ notes?: number;
|
|
|
+ };
|
|
|
} = {};
|
|
|
|
|
|
+ // 用于等待 API 响应的 Promise
|
|
|
+ let resolvePersonalInfo: () => void;
|
|
|
+ let resolveNotesCount: () => void;
|
|
|
+ const personalInfoPromise = new Promise<void>((resolve) => { resolvePersonalInfo = resolve; });
|
|
|
+ const notesCountPromise = new Promise<void>((resolve) => { resolveNotesCount = resolve; });
|
|
|
+
|
|
|
+ // 设置超时自动 resolve
|
|
|
+ setTimeout(() => resolvePersonalInfo(), 10000);
|
|
|
+ setTimeout(() => resolveNotesCount(), 10000);
|
|
|
+
|
|
|
// 设置 API 响应监听器
|
|
|
this.page.on('response', async (response) => {
|
|
|
const url = response.url();
|
|
|
try {
|
|
|
- // 监听用户信息 API
|
|
|
- if (url.includes('/api/galaxy/creator/home/personal_info') ||
|
|
|
- url.includes('/api/sns/web/v1/user/selfinfo') ||
|
|
|
- url.includes('/user/selfinfo')) {
|
|
|
+ // 监听用户信息 API - personal_info 接口
|
|
|
+ // URL: https://creator.xiaohongshu.com/api/galaxy/creator/home/personal_info
|
|
|
+ // 返回结构: { data: { name, avatar, fans_count, red_num, follow_count, faved_count } }
|
|
|
+ if (url.includes('/api/galaxy/creator/home/personal_info')) {
|
|
|
const data = await response.json();
|
|
|
- logger.info(`[Xiaohongshu API] User info response:`, JSON.stringify(data).slice(0, 500));
|
|
|
+ logger.info(`[Xiaohongshu API] Personal info:`, JSON.stringify(data).slice(0, 1000));
|
|
|
|
|
|
- const userInfo = data?.data?.user_info || data?.data || data;
|
|
|
- if (userInfo) {
|
|
|
+ if (data?.data) {
|
|
|
+ const info = data.data;
|
|
|
capturedData.userInfo = {
|
|
|
- nickname: userInfo.nickname || userInfo.name || userInfo.userName,
|
|
|
- avatar: userInfo.image || userInfo.avatar || userInfo.images,
|
|
|
- userId: userInfo.user_id || userInfo.userId,
|
|
|
- redId: userInfo.red_id || userInfo.redId,
|
|
|
- fans: userInfo.fans || userInfo.fansCount,
|
|
|
- notes: userInfo.notes || userInfo.noteCount,
|
|
|
+ nickname: info.name,
|
|
|
+ avatar: info.avatar,
|
|
|
+ userId: info.red_num, // 小红书号
|
|
|
+ redId: info.red_num,
|
|
|
+ fans: info.fans_count,
|
|
|
};
|
|
|
- logger.info(`[Xiaohongshu API] Captured user info:`, capturedData.userInfo);
|
|
|
+ logger.info(`[Xiaohongshu API] Captured personal info:`, capturedData.userInfo);
|
|
|
}
|
|
|
+ resolvePersonalInfo();
|
|
|
}
|
|
|
|
|
|
- // 监听创作者主页数据
|
|
|
- if (url.includes('/api/galaxy/creator/home/home_page') ||
|
|
|
- url.includes('/api/galaxy/creator/data')) {
|
|
|
+ // 监听笔记列表 API (获取作品数) - 新版 edith API
|
|
|
+ // URL: https://edith.xiaohongshu.com/web_api/sns/v5/creator/note/user/posted
|
|
|
+ // 返回结构: { data: { tags: [{ name: "所有笔记", notes_count: 1 }] } }
|
|
|
+ if (url.includes('edith.xiaohongshu.com') && url.includes('/creator/note/user/posted')) {
|
|
|
const data = await response.json();
|
|
|
- logger.info(`[Xiaohongshu API] Creator home response:`, JSON.stringify(data).slice(0, 500));
|
|
|
-
|
|
|
- if (data?.data) {
|
|
|
- const homeData = data.data;
|
|
|
- if (homeData.fans_count !== undefined) {
|
|
|
- capturedData.userInfo = capturedData.userInfo || {};
|
|
|
- capturedData.userInfo.fans = homeData.fans_count;
|
|
|
- }
|
|
|
- if (homeData.note_count !== undefined) {
|
|
|
- capturedData.userInfo = capturedData.userInfo || {};
|
|
|
- capturedData.userInfo.notes = homeData.note_count;
|
|
|
+ logger.info(`[Xiaohongshu API] Posted notes (edith):`, JSON.stringify(data).slice(0, 800));
|
|
|
+
|
|
|
+ if (data?.data?.tags && Array.isArray(data.data.tags)) {
|
|
|
+ // 从 tags 数组中找到 "所有笔记" 的 notes_count
|
|
|
+ const allNotesTag = data.data.tags.find((tag: { id?: string; name?: string; notes_count?: number }) =>
|
|
|
+ tag.id?.includes('note_time') || tag.name === '所有笔记'
|
|
|
+ );
|
|
|
+ if (allNotesTag?.notes_count !== undefined) {
|
|
|
+ capturedData.homeData = capturedData.homeData || {};
|
|
|
+ capturedData.homeData.notes = allNotesTag.notes_count;
|
|
|
+ logger.info(`[Xiaohongshu API] Total notes from edith API: ${allNotesTag.notes_count}`);
|
|
|
}
|
|
|
}
|
|
|
+ resolveNotesCount();
|
|
|
}
|
|
|
- } catch {
|
|
|
+ } catch (e) {
|
|
|
// 忽略非 JSON 响应
|
|
|
+ logger.debug(`[Xiaohongshu API] Failed to parse response: ${url}`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 访问创作者中心
|
|
|
- logger.info('[Xiaohongshu] Navigating to creator center...');
|
|
|
- await this.page.goto(this.creatorHomeUrl, {
|
|
|
- waitUntil: 'domcontentloaded',
|
|
|
+ // 1. 先访问创作者首页获取用户信息
|
|
|
+ // URL: https://creator.xiaohongshu.com/new/home
|
|
|
+ // API: /api/galaxy/creator/home/personal_info
|
|
|
+ logger.info('[Xiaohongshu] Navigating to creator home...');
|
|
|
+ await this.page.goto('https://creator.xiaohongshu.com/new/home', {
|
|
|
+ waitUntil: 'networkidle',
|
|
|
timeout: 30000,
|
|
|
});
|
|
|
|
|
|
- await this.page.waitForTimeout(3000);
|
|
|
+ // 等待 personal_info API 响应
|
|
|
+ await Promise.race([personalInfoPromise, this.page.waitForTimeout(5000)]);
|
|
|
+ logger.info(`[Xiaohongshu] After home page, capturedData.userInfo:`, capturedData.userInfo);
|
|
|
+
|
|
|
+ // 2. 再访问笔记管理页面获取作品数
|
|
|
+ // URL: https://creator.xiaohongshu.com/new/note-manager
|
|
|
+ // API: https://edith.xiaohongshu.com/web_api/sns/v5/creator/note/user/posted
|
|
|
+ logger.info('[Xiaohongshu] Navigating to note manager...');
|
|
|
+ await this.page.goto('https://creator.xiaohongshu.com/new/note-manager', {
|
|
|
+ waitUntil: 'networkidle',
|
|
|
+ timeout: 30000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待 notes API 响应
|
|
|
+ await Promise.race([notesCountPromise, this.page.waitForTimeout(5000)]);
|
|
|
+ logger.info(`[Xiaohongshu] After note manager, capturedData.homeData:`, capturedData.homeData);
|
|
|
|
|
|
// 检查是否需要登录
|
|
|
const currentUrl = this.page.url();
|
|
|
@@ -240,84 +275,59 @@ export class XiaohongshuAdapter extends BasePlatformAdapter {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 等待 API 响应
|
|
|
- await this.page.waitForTimeout(3000);
|
|
|
-
|
|
|
// 使用捕获的数据
|
|
|
if (capturedData.userInfo) {
|
|
|
- if (capturedData.userInfo.nickname) {
|
|
|
- accountName = capturedData.userInfo.nickname;
|
|
|
- }
|
|
|
- if (capturedData.userInfo.avatar) {
|
|
|
- avatarUrl = capturedData.userInfo.avatar;
|
|
|
- }
|
|
|
- if (capturedData.userInfo.userId) {
|
|
|
- accountId = `xiaohongshu_${capturedData.userInfo.userId}`;
|
|
|
- } else if (capturedData.userInfo.redId) {
|
|
|
- accountId = `xiaohongshu_${capturedData.userInfo.redId}`;
|
|
|
- }
|
|
|
- if (capturedData.userInfo.fans) {
|
|
|
- fansCount = capturedData.userInfo.fans;
|
|
|
- }
|
|
|
- if (capturedData.userInfo.notes) {
|
|
|
- worksCount = capturedData.userInfo.notes;
|
|
|
- }
|
|
|
+ if (capturedData.userInfo.nickname) accountName = capturedData.userInfo.nickname;
|
|
|
+ if (capturedData.userInfo.avatar) avatarUrl = capturedData.userInfo.avatar;
|
|
|
+ if (capturedData.userInfo.userId) accountId = `xiaohongshu_${capturedData.userInfo.userId}`;
|
|
|
+ else if (capturedData.userInfo.redId) accountId = `xiaohongshu_${capturedData.userInfo.redId}`;
|
|
|
+ if (capturedData.userInfo.fans !== undefined) fansCount = capturedData.userInfo.fans;
|
|
|
}
|
|
|
|
|
|
- // 尝试获取作品列表
|
|
|
- try {
|
|
|
- await this.page.goto(this.contentManageUrl, {
|
|
|
- waitUntil: 'domcontentloaded',
|
|
|
- timeout: 30000,
|
|
|
- });
|
|
|
- await this.page.waitForTimeout(3000);
|
|
|
-
|
|
|
- worksList = await this.page.evaluate(() => {
|
|
|
- const items: WorkItem[] = [];
|
|
|
- const cards = document.querySelectorAll('[class*="note-item"], [class*="content-item"]');
|
|
|
-
|
|
|
- cards.forEach((card) => {
|
|
|
- try {
|
|
|
- const coverImg = card.querySelector('img');
|
|
|
- const coverUrl = coverImg?.src || '';
|
|
|
-
|
|
|
- const titleEl = card.querySelector('[class*="title"], [class*="desc"]');
|
|
|
- const title = titleEl?.textContent?.trim() || '无标题';
|
|
|
-
|
|
|
- const timeEl = card.querySelector('[class*="time"], [class*="date"]');
|
|
|
- const publishTime = timeEl?.textContent?.trim() || '';
|
|
|
-
|
|
|
- const statusEl = card.querySelector('[class*="status"]');
|
|
|
- const status = statusEl?.textContent?.trim() || '';
|
|
|
-
|
|
|
- // 获取数据指标
|
|
|
- const statsEl = card.querySelector('[class*="stats"], [class*="data"]');
|
|
|
- const statsText = statsEl?.textContent || '';
|
|
|
-
|
|
|
- const likeMatch = statsText.match(/(\d+)\s*赞/);
|
|
|
- const commentMatch = statsText.match(/(\d+)\s*评/);
|
|
|
- const collectMatch = statsText.match(/(\d+)\s*藏/);
|
|
|
-
|
|
|
- items.push({
|
|
|
- title,
|
|
|
- coverUrl,
|
|
|
- duration: '',
|
|
|
- publishTime,
|
|
|
- status,
|
|
|
- playCount: 0,
|
|
|
- likeCount: likeMatch ? parseInt(likeMatch[1]) : 0,
|
|
|
- commentCount: commentMatch ? parseInt(commentMatch[1]) : 0,
|
|
|
- shareCount: collectMatch ? parseInt(collectMatch[1]) : 0,
|
|
|
- });
|
|
|
- } catch {}
|
|
|
- });
|
|
|
+ // homeData.notes 来自笔记列表 API,直接使用(优先级最高)
|
|
|
+ if (capturedData.homeData) {
|
|
|
+ if (capturedData.homeData.notes !== undefined) {
|
|
|
+ worksCount = capturedData.homeData.notes;
|
|
|
+ logger.info(`[Xiaohongshu] Using notes count from API: ${worksCount}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- return items;
|
|
|
+ // 如果 API 没捕获到,尝试从页面 DOM 获取
|
|
|
+ if (fansCount === 0 || worksCount === 0) {
|
|
|
+ const statsData = await this.page.evaluate(() => {
|
|
|
+ const result = { fans: 0, notes: 0, name: '', avatar: '' };
|
|
|
+
|
|
|
+ // 获取页面文本
|
|
|
+ const allText = document.body.innerText;
|
|
|
+
|
|
|
+ // 尝试匹配粉丝数
|
|
|
+ const fansMatch = allText.match(/粉丝[::\s]*(\d+(?:\.\d+)?[万亿]?)|(\d+(?:\.\d+)?[万亿]?)\s*粉丝/);
|
|
|
+ if (fansMatch) {
|
|
|
+ const numStr = fansMatch[1] || fansMatch[2];
|
|
|
+ result.fans = Math.floor(parseFloat(numStr) * (numStr.includes('万') ? 10000 : numStr.includes('亿') ? 100000000 : 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试匹配笔记数
|
|
|
+ const notesMatch = allText.match(/笔记[::\s]*(\d+)|(\d+)\s*篇?笔记|共\s*(\d+)\s*篇/);
|
|
|
+ if (notesMatch) {
|
|
|
+ result.notes = parseInt(notesMatch[1] || notesMatch[2] || notesMatch[3]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取用户名
|
|
|
+ const nameEl = document.querySelector('[class*="nickname"], [class*="user-name"], [class*="creator-name"]');
|
|
|
+ if (nameEl) result.name = nameEl.textContent?.trim() || '';
|
|
|
+
|
|
|
+ // 获取头像
|
|
|
+ const avatarEl = document.querySelector('[class*="avatar"] img, [class*="user-avatar"] img');
|
|
|
+ if (avatarEl) result.avatar = (avatarEl as HTMLImageElement).src || '';
|
|
|
+
|
|
|
+ return result;
|
|
|
});
|
|
|
-
|
|
|
- logger.info(`[Xiaohongshu] Fetched ${worksList.length} works`);
|
|
|
- } catch (e) {
|
|
|
- logger.warn('[Xiaohongshu] Failed to fetch works list:', e);
|
|
|
+
|
|
|
+ if (fansCount === 0 && statsData.fans > 0) fansCount = statsData.fans;
|
|
|
+ if (worksCount === 0 && statsData.notes > 0) worksCount = statsData.notes;
|
|
|
+ if ((!accountName || accountName === '小红书账号') && statsData.name) accountName = statsData.name;
|
|
|
+ if (!avatarUrl && statsData.avatar) avatarUrl = statsData.avatar;
|
|
|
}
|
|
|
|
|
|
await this.closeBrowser();
|
|
|
@@ -1187,6 +1197,222 @@ export class XiaohongshuAdapter extends BasePlatformAdapter {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 删除已发布的作品
|
|
|
+ * 使用小红书笔记管理页面: https://creator.xiaohongshu.com/new/note-manager
|
|
|
+ */
|
|
|
+ async deleteWork(
|
|
|
+ cookies: string,
|
|
|
+ noteId: string,
|
|
|
+ onCaptchaRequired?: (captchaInfo: { taskId: string; imageUrl?: string }) => Promise<string>
|
|
|
+ ): Promise<{ success: boolean; errorMessage?: string }> {
|
|
|
+ try {
|
|
|
+ // 使用无头浏览器后台运行
|
|
|
+ await this.initBrowser({ headless: true });
|
|
|
+ await this.setCookies(cookies);
|
|
|
+
|
|
|
+ if (!this.page) throw new Error('Page not initialized');
|
|
|
+
|
|
|
+ logger.info(`[Xiaohongshu Delete] Starting delete for note: ${noteId}`);
|
|
|
+
|
|
|
+ // 访问笔记管理页面(新版)
|
|
|
+ const noteManagerUrl = 'https://creator.xiaohongshu.com/new/note-manager';
|
|
|
+ await this.page.goto(noteManagerUrl, {
|
|
|
+ waitUntil: 'networkidle',
|
|
|
+ timeout: 60000,
|
|
|
+ });
|
|
|
+
|
|
|
+ await this.page.waitForTimeout(3000);
|
|
|
+
|
|
|
+ // 检查是否需要登录
|
|
|
+ const currentUrl = this.page.url();
|
|
|
+ if (currentUrl.includes('login') || currentUrl.includes('passport')) {
|
|
|
+ throw new Error('登录已过期,请重新登录');
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(`[Xiaohongshu Delete] Current URL: ${currentUrl}`);
|
|
|
+
|
|
|
+ // 截图用于调试
|
|
|
+ try {
|
|
|
+ const screenshotPath = `uploads/debug/xhs_delete_page_${Date.now()}.png`;
|
|
|
+ await this.page.screenshot({ path: screenshotPath, fullPage: true });
|
|
|
+ logger.info(`[Xiaohongshu Delete] Page screenshot: ${screenshotPath}`);
|
|
|
+ } catch {}
|
|
|
+
|
|
|
+ // 在笔记管理页面找到对应的笔记行
|
|
|
+ // 页面结构:
|
|
|
+ // - 每条笔记是 div.note 元素
|
|
|
+ // - 笔记ID在 data-impression 属性的 JSON 中: noteId: "xxx"
|
|
|
+ // - 删除按钮是 span.control.data-del 内的 <span>删除</span>
|
|
|
+ let deleteClicked = false;
|
|
|
+
|
|
|
+ // 方式1: 通过 data-impression 属性找到对应笔记,然后点击其删除按钮
|
|
|
+ logger.info(`[Xiaohongshu Delete] Looking for note with ID: ${noteId}`);
|
|
|
+
|
|
|
+ // 查找所有笔记卡片
|
|
|
+ const noteCards = this.page.locator('div.note');
|
|
|
+ const noteCount = await noteCards.count();
|
|
|
+ logger.info(`[Xiaohongshu Delete] Found ${noteCount} note cards`);
|
|
|
+
|
|
|
+ for (let i = 0; i < noteCount; i++) {
|
|
|
+ const card = noteCards.nth(i);
|
|
|
+ const impression = await card.getAttribute('data-impression').catch(() => '');
|
|
|
+
|
|
|
+ // 检查 data-impression 中是否包含目标 noteId
|
|
|
+ if (impression && impression.includes(noteId)) {
|
|
|
+ logger.info(`[Xiaohongshu Delete] Found target note at index ${i}`);
|
|
|
+
|
|
|
+ // 在该笔记卡片内查找删除按钮 (span.data-del)
|
|
|
+ const deleteBtn = card.locator('span.data-del, span.control.data-del').first();
|
|
|
+ if (await deleteBtn.count() > 0) {
|
|
|
+ await deleteBtn.click();
|
|
|
+ deleteClicked = true;
|
|
|
+ logger.info(`[Xiaohongshu Delete] Clicked delete button for note ${noteId}`);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 方式2: 如果方式1没找到,尝试直接用 evaluate 在 DOM 中查找
|
|
|
+ if (!deleteClicked) {
|
|
|
+ logger.info('[Xiaohongshu Delete] Trying evaluate method to find note by data-impression...');
|
|
|
+ deleteClicked = await this.page.evaluate((nid: string) => {
|
|
|
+ // 查找所有 div.note 元素
|
|
|
+ const notes = document.querySelectorAll('div.note');
|
|
|
+ console.log(`[XHS Delete] Found ${notes.length} note elements`);
|
|
|
+
|
|
|
+ for (const note of notes) {
|
|
|
+ const impression = note.getAttribute('data-impression') || '';
|
|
|
+ if (impression.includes(nid)) {
|
|
|
+ console.log(`[XHS Delete] Found note with ID ${nid}`);
|
|
|
+
|
|
|
+ // 查找删除按钮
|
|
|
+ const deleteBtn = note.querySelector('span.data-del') ||
|
|
|
+ note.querySelector('.control.data-del');
|
|
|
+ if (deleteBtn) {
|
|
|
+ console.log(`[XHS Delete] Clicking delete button`);
|
|
|
+ (deleteBtn as HTMLElement).click();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }, noteId);
|
|
|
+
|
|
|
+ if (deleteClicked) {
|
|
|
+ logger.info('[Xiaohongshu Delete] Delete button clicked via evaluate');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 方式3: 如果还没找到,尝试点击第一个可见的删除按钮
|
|
|
+ if (!deleteClicked) {
|
|
|
+ logger.info('[Xiaohongshu Delete] Trying to click first visible delete button...');
|
|
|
+
|
|
|
+ const allDeleteBtns = this.page.locator('span.data-del');
|
|
|
+ const btnCount = await allDeleteBtns.count();
|
|
|
+ logger.info(`[Xiaohongshu Delete] Found ${btnCount} delete buttons on page`);
|
|
|
+
|
|
|
+ for (let i = 0; i < btnCount; i++) {
|
|
|
+ const btn = allDeleteBtns.nth(i);
|
|
|
+ if (await btn.isVisible().catch(() => false)) {
|
|
|
+ await btn.click();
|
|
|
+ deleteClicked = true;
|
|
|
+ logger.info(`[Xiaohongshu Delete] Clicked delete button ${i}`);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!deleteClicked) {
|
|
|
+ // 截图调试
|
|
|
+ try {
|
|
|
+ const screenshotPath = `uploads/debug/xhs_delete_no_btn_${Date.now()}.png`;
|
|
|
+ await this.page.screenshot({ path: screenshotPath, fullPage: true });
|
|
|
+ logger.info(`[Xiaohongshu Delete] No delete button found, screenshot: ${screenshotPath}`);
|
|
|
+ } catch {}
|
|
|
+ throw new Error('未找到删除按钮');
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.page.waitForTimeout(1000);
|
|
|
+
|
|
|
+ // 检查是否需要验证码
|
|
|
+ const captchaVisible = await this.page.locator('[class*="captcha"], [class*="verify"]').count() > 0;
|
|
|
+
|
|
|
+ if (captchaVisible && onCaptchaRequired) {
|
|
|
+ logger.info('[Xiaohongshu Delete] Captcha required');
|
|
|
+
|
|
|
+ // 点击发送验证码
|
|
|
+ const sendCodeBtn = this.page.locator('button:has-text("发送验证码"), button:has-text("获取验证码")').first();
|
|
|
+ if (await sendCodeBtn.count() > 0) {
|
|
|
+ await sendCodeBtn.click();
|
|
|
+ logger.info('[Xiaohongshu Delete] Verification code sent');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通过回调获取验证码
|
|
|
+ const taskId = `delete_xhs_${noteId}_${Date.now()}`;
|
|
|
+ const code = await onCaptchaRequired({ taskId });
|
|
|
+
|
|
|
+ if (code) {
|
|
|
+ // 输入验证码
|
|
|
+ const codeInput = this.page.locator('input[placeholder*="验证码"], input[type="text"]').first();
|
|
|
+ if (await codeInput.count() > 0) {
|
|
|
+ await codeInput.fill(code);
|
|
|
+ logger.info('[Xiaohongshu Delete] Verification code entered');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击确认按钮
|
|
|
+ const confirmBtn = this.page.locator('button:has-text("确定"), button:has-text("确认")').first();
|
|
|
+ if (await confirmBtn.count() > 0) {
|
|
|
+ await confirmBtn.click();
|
|
|
+ await this.page.waitForTimeout(2000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认删除(可能有二次确认弹窗)
|
|
|
+ const confirmDeleteSelectors = [
|
|
|
+ 'button:has-text("确认删除")',
|
|
|
+ 'button:has-text("确定")',
|
|
|
+ 'button:has-text("确认")',
|
|
|
+ '[class*="modal"] button[class*="primary"]',
|
|
|
+ '[class*="dialog"] button[class*="confirm"]',
|
|
|
+ '.d-button.red:has-text("确")',
|
|
|
+ ];
|
|
|
+
|
|
|
+ for (const selector of confirmDeleteSelectors) {
|
|
|
+ const confirmBtn = this.page.locator(selector).first();
|
|
|
+ if (await confirmBtn.count() > 0 && await confirmBtn.isVisible()) {
|
|
|
+ await confirmBtn.click();
|
|
|
+ logger.info(`[Xiaohongshu Delete] Confirm button clicked via: ${selector}`);
|
|
|
+ await this.page.waitForTimeout(1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待删除完成
|
|
|
+ await this.page.waitForTimeout(2000);
|
|
|
+
|
|
|
+ // 检查是否删除成功(页面刷新或出现成功提示)
|
|
|
+ const successToast = await this.page.locator('[class*="success"]:has-text("成功"), [class*="toast"]:has-text("删除成功")').count();
|
|
|
+ if (successToast > 0) {
|
|
|
+ logger.info('[Xiaohongshu Delete] Delete success toast found');
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info('[Xiaohongshu Delete] Delete completed');
|
|
|
+ await this.closeBrowser();
|
|
|
+
|
|
|
+ return { success: true };
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('[Xiaohongshu Delete] Error:', error);
|
|
|
+ await this.closeBrowser();
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ errorMessage: error instanceof Error ? error.message : '删除失败',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 获取数据统计
|
|
|
*/
|
|
|
async getAnalytics(cookies: string, dateRange: DateRange): Promise<AnalyticsData> {
|