From a3decd63de1b0398749bd2d3ac2c66b1cbe0b190 Mon Sep 17 00:00:00 2001 From: NAME Date: Tue, 12 May 2026 08:33:40 +0000 Subject: [PATCH] D --- src/app.controller.ts | 14 +++++-- src/notify.service.ts | 35 ++++++++++++---- src/sqs-module/sqs.poster.worker.ts | 3 ++ src/x-cache/x-cache.service.ts | 22 +++++++++- src/x-poster/utils/x-headers.util.ts | 14 ------- src/x-poster/x-browser.service.ts | 53 ++++++++++++++++++------ src/x-poster/x-cookie.service.ts | 9 +++-- src/x-poster/x-poster.controller.ts | 54 ++++--------------------- src/x-poster/x-poster.router.service.ts | 28 +++++++++++-- src/xbot-follow/xbot-follow.service.ts | 2 + 10 files changed, 143 insertions(+), 91 deletions(-) diff --git a/src/app.controller.ts b/src/app.controller.ts index 15c01b2..2792727 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,15 +1,23 @@ -import {Controller, Get} from '@nestjs/common'; +import {Controller, Get, Post} from '@nestjs/common'; import {AppService} from './app.service'; +import {XCookieAccountDto} from "./x-poster/dto/x-cookie-account.dto"; +import {XBrowserService} from "./x-poster/x-browser.service"; @Controller() export class AppController { constructor( private readonly appService: AppService, + private readonly xBrowserService: XBrowserService, ) { } @Get() - getHello(): string { - return this.appService.getHello(); + getHello() { + return this.xBrowserService.verifyCookie(); + } + + @Post('/set-x-cookies') + setXCookies(dto: XCookieAccountDto) { + } } diff --git a/src/notify.service.ts b/src/notify.service.ts index ce09dae..eec0011 100644 --- a/src/notify.service.ts +++ b/src/notify.service.ts @@ -10,16 +10,35 @@ export class NotifyService { const CHAT_ID = process.env.TELEGRAM_CHAT_ID!; await axios.post( - `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, - { - chat_id: CHAT_ID, - text: message, - parse_mode: 'HTML' - } - ); + `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, + { + chat_id: CHAT_ID, + text: message, + parse_mode: 'HTML' + } + ); } - async sendMessageToTeleByChatId(chatId:number,message: string): Promise { + + async sendUrgentMessageToTele(message: string): Promise { + const axios = require('axios'); + + const BOT_TOKEN = process.env.TELEGRAM_URGENT_BOT_TOKEN!; + const CHAT_ID = process.env.TELEGRAM_CHAT_ID!; + const X_USERNAME = process.env.X_USERNAME!; + + await axios.post( + `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, + { + chat_id: CHAT_ID, + text: `X:${X_USERNAME}==>${message}`, + parse_mode: 'HTML' + } + ); + + } + + async sendMessageToTeleByChatId(chatId: number, message: string): Promise { const axios = require('axios'); const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!; diff --git a/src/sqs-module/sqs.poster.worker.ts b/src/sqs-module/sqs.poster.worker.ts index b19ace1..19183fd 100644 --- a/src/sqs-module/sqs.poster.worker.ts +++ b/src/sqs-module/sqs.poster.worker.ts @@ -22,6 +22,9 @@ export class SqsPosterWorker { console.log(`🚀 Worker started for ${await this.sqs.getQueueName()}`); await this.notifyService.sendMessageToTele(`🚀 Worker started for ${await this.sqs.getQueueName()}`) + //check cookie + this.xRouterService.verifyCookie(); + let ReceiptHandle = ''; while (true) { try { diff --git a/src/x-cache/x-cache.service.ts b/src/x-cache/x-cache.service.ts index e3ce014..a697ae2 100644 --- a/src/x-cache/x-cache.service.ts +++ b/src/x-cache/x-cache.service.ts @@ -26,7 +26,7 @@ export class XCacheService { } async getCacheTwRefreshToken() { - return this.cacheManager.get('tw_app_refresh_token'); + return this.cacheManager.get('tw_app_refresh_token'); } async setCacheTwAccessToken(token: string) { @@ -57,4 +57,24 @@ export class XCacheService { async incrCountCollectNewsapi() { //this.cacheManager.c } + + async changeStateXCookiesIsAlive() { + const cacheKey = 'state_xcookie_status'; + const currentState = await this.isXCookiesAlive(); + return this.cacheManager.set(cacheKey, currentState ? 0 : 1, 365 * 24 * 3600); + } + + async setStateXCookiesIsDie() { + const cacheKey = 'state_xcookie_status'; + return this.cacheManager.set(cacheKey, 0, 365 * 24 * 3600); + } + async setStateXCookiesIsSillALive() { + const cacheKey = 'state_xcookie_status'; + return this.cacheManager.set(cacheKey, 1, 365 * 24 * 3600); + } + + async isXCookiesAlive() { + const cacheKey = 'state_xcookie_status'; + return 1 === Number(await this.cacheManager.get(cacheKey)); + } } diff --git a/src/x-poster/utils/x-headers.util.ts b/src/x-poster/utils/x-headers.util.ts index 8592cfa..02a1384 100644 --- a/src/x-poster/utils/x-headers.util.ts +++ b/src/x-poster/utils/x-headers.util.ts @@ -19,20 +19,6 @@ export function buildXHeaders(authToken: string, ct0: string) { }; } -export function buildXCookies() { - return [ - { - name: "ct0", - value: process.env.X_COOKIE_CT0, - }, - { - name: "auth_token", - value: process.env.X_COOKIE_AUTH_TOKEN, - }, - {name: "lang", value: "en"}, - ]; -} - export function getAccount() { return { id: process.env.X_USERNAME!, diff --git a/src/x-poster/x-browser.service.ts b/src/x-poster/x-browser.service.ts index aa46fd0..71eeaab 100644 --- a/src/x-poster/x-browser.service.ts +++ b/src/x-poster/x-browser.service.ts @@ -100,7 +100,7 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy { }); console.log('getOrCreateContext:6') - console.log(account.cookies); + // console.log(account.cookies); await ctx.addCookies( account.cookies.map((c) => ({ @@ -136,11 +136,40 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy { domain: c.domain || '.x.com', path: c.path || '/', })); - console.log('cookies:', cookies); + // console.log('cookies:', cookies); await ctx.addCookies(cookies); return ctx.newPage(); } + async verifyCookie() { + const page = await this.newPage(); + await page.goto('https://x.com/', { + waitUntil: 'domcontentloaded', + timeout: 30_000, + }); + await page.waitForTimeout(2000 + (Math.random() + Math.random()) * 3000); + await page.mouse.wheel(0, rand(300, 500)); + // Detect login/challenge screen + if (page.url().includes('/login') || page.url().includes('/flow')) { + this.logger.error('Cookies is die, please get news'); + return false; + // return { + // success: false, + // error: 'Redirected to login', + // needsRelogin: true, + // }; + } + const isLoggedIn = await page + .locator('[data-testid="SideNav_AccountSwitcher_Button"], [data-testid="AppTabBar_Home_Link"]') + .first() + .isVisible() + .catch(() => false); + + this.logger.log(`🔐 Session restore: ${isLoggedIn ? 'LOGGED IN' : 'GUEST (cookie có thể expired)'}`); + await page.close(); + return isLoggedIn; + } + async postTweet( account: BrowserAccount, text: string, @@ -158,16 +187,16 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy { // Intercept để lấy tweet id từ response let capturedTweetId: string | undefined; - page.on('response', async (resp) => { - if (resp.url().includes('/CreateTweet')) { - try { - const json = await resp.json(); - capturedTweetId = - json?.data?.create_tweet?.tweet_results?.result?.rest_id; - } catch { - } - } - }); + // page.on('response', async (resp) => { + // if (resp.url().includes('/CreateTweet')) { + // try { + // const json = await resp.json(); + // capturedTweetId = + // json?.data?.create_tweet?.tweet_results?.result?.rest_id; + // } catch { + // } + // } + // }); // await page.keyboard.press('Mở trang ...'); await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', diff --git a/src/x-poster/x-cookie.service.ts b/src/x-poster/x-cookie.service.ts index 9fe4256..0e2b485 100644 --- a/src/x-poster/x-cookie.service.ts +++ b/src/x-poster/x-cookie.service.ts @@ -200,7 +200,7 @@ export class XCookieService { * @param account * @param userHandle Optional: Nếu biết @username */ - async verifyCookie(account: XCookieAccount, userHandle?: string): Promise<{ screen_name: string; name: string }> { + async verifyCookie(account: XCookieAccount, userHandle?: string): Promise { try { let variables: GraphQLVariables; let queryId: string; @@ -234,10 +234,13 @@ export class XCookieService { const user = res?.data.data.user; // Hoặc res.data.data.result this.screenName = user.rest_id ? user.legacy.screen_name : user.screen_name; this.logger.log(`Verified: @${this.screenName}`); - return {screen_name: this.screenName!, name: ''}; + // return {screen_name: this.screenName!, name: ''}; + return true; } catch (error: any) { + console.log(error); this.logger.error(`Verify failed: ${error.response?.data?.errors?.[0]?.message || error.message}`); - throw error; + return false; + // throw error; } } } diff --git a/src/x-poster/x-poster.controller.ts b/src/x-poster/x-poster.controller.ts index 006f07e..3f2018d 100644 --- a/src/x-poster/x-poster.controller.ts +++ b/src/x-poster/x-poster.controller.ts @@ -97,62 +97,22 @@ export class XPosterController { async xreply( @Body() tweet: ReplyTweetDto, ) { - const account = { - accountId: process.env.X_USERNAME!, - cookies: [ - { - name: 'auth_token', - value: process.env.X_COOKIE_AUTH_TOKEN!, - }, - { - name: 'ct0', - value: process.env.X_COOKIE_CT0!, - }, - ], - proxy: '', - userAgent: '' - }; - return this.xBrowserService.postReply(account, tweet.tweetUrl, tweet.text) + + // return this.xBrowserService.postReply(account, tweet.tweetUrl, tweet.text) } @Post('tweet') async tweet( @Body() tweet: CreateTweetDto, ) { - const authToken = 'f5574950a0d98cf49ca2e574cc6a4139cc8a2d81'; - const ct0 = '75db96b40f4814925670722e3335840467b87c7eb403aab14fbe719297bf465347810f92dc2116c3d30f15153a332976c50d856983dfe19046d4f10413b3d1cd8bac6f7a99e5b03c6949bafdad0f01c0'; - // return this.service.postTweet( - // { - // account: { - // id: 'realflashkaze', - // browser: { - // accountId: 'realflashkaze', - // cookies: [ - // { - // name: "ct0", - // value: '75db96b40f4814925670722e3335840467b87c7eb403aab14fbe719297bf465347810f92dc2116c3d30f15153a332976c50d856983dfe19046d4f10413b3d1cd8bac6f7a99e5b03c6949bafdad0f01c0' - // }, - // { - // name: "auth_token", - // value: 'f5574950a0d98cf49ca2e574cc6a4139cc8a2d81' - // }, - // {name: "dnt", value: "1"}, - // {name: "lang", value: "en"}, - // {name: "twid", value: "u%3D2043937828644536320"}, - // ] - // } - // }, - // text: tweet.text, - // strategy: XStrategy.BROWSER_ONLY - // }); } @Get('verify') verify() { - const account = { - authToken: process.env.X_COOKIE_AUTH_TOKEN!, // auth_token cookie - ct0: process.env.X_COOKIE_CT0!, - } - return this.xCookieService.verifyCookie(account); + // const account = { + // authToken: process.env.X_COOKIE_AUTH_TOKEN!, // auth_token cookie + // ct0: process.env.X_COOKIE_CT0!, + // } + // return this.xCookieService.verifyCookie(account); } } diff --git a/src/x-poster/x-poster.router.service.ts b/src/x-poster/x-poster.router.service.ts index 615a201..5892777 100644 --- a/src/x-poster/x-poster.router.service.ts +++ b/src/x-poster/x-poster.router.service.ts @@ -6,6 +6,7 @@ import {XApiService} from "./x-api.service"; import {XCookieService} from "./x-cookie.service"; import {NotifyService} from "../notify.service"; import {getAccount} from "./utils/x-headers.util"; +import {XCacheService} from "../x-cache/x-cache.service"; export enum SUPPORT_SOCIAL_PROVIDERS { FB = 'fb', @@ -51,6 +52,7 @@ export class XPosterRouterService { private readonly cookieSvc: XCookieService, private readonly browserSvc: XBrowserService, private readonly notifyService: NotifyService, + private readonly xCacheService: XCacheService, ) { this.X_UNIFIED_ACCOUNT = getAccount(); @@ -58,8 +60,18 @@ export class XPosterRouterService { } - async verifyCookie(account: XCookieAccount): Promise { - return this.cookieSvc.verifyCookie(account, 'UserByScreenName') + async verifyCookie(): Promise { + // const isAlive = await this.cookieSvc.verifyCookie(); + const isAlive = await this.browserSvc.verifyCookie(); + if (!isAlive) { + await this.xCacheService.setStateXCookiesIsDie(); + await this.notifyService.sendUrgentMessageToTele('Cookie đã hết hạn vui lòng cập nhập để sử dụng.') + } + await this.xCacheService.setStateXCookiesIsSillALive(); + } + + async canUseXCookies(): Promise { + return this.xCacheService.isXCookiesAlive() } async postTweet(params: { @@ -71,8 +83,18 @@ export class XPosterRouterService { const chain = this.buildChain(strategy, account); const attempts: RouterResult['attempts'] = []; + const canUseCookie = this.canUseXCookies(); for (const method of chain) { this.logger.log(`[${account.id}] Trying via ${method}`); + + if (['cookie', 'browser'].includes(method)) { + if (!canUseCookie) { + await this.notifyService.sendUrgentMessageToTele('❌ Vui lòng cập nhập cookie để sử dụng '); + this.logger.error('Cookie đã hết hạn, vui lòng cập nhập'); + continue; + } + } + const result = await this.executeTweet(method, account, params.text); attempts.push({method, error: result.error}); @@ -100,7 +122,7 @@ export class XPosterRouterService { }; } - this.logger.log(`Dăng bài thất bại, thử phương pháp khác`); + this.logger.log(`Đăng bài thất bại, thử phương pháp khác`); } diff --git a/src/xbot-follow/xbot-follow.service.ts b/src/xbot-follow/xbot-follow.service.ts index 01ef04f..5cfce2b 100644 --- a/src/xbot-follow/xbot-follow.service.ts +++ b/src/xbot-follow/xbot-follow.service.ts @@ -41,6 +41,8 @@ export class XbotFollowService { return {username: target, success: false, alreadyFollowing: false, error: 'Account not found'}; } + // await p.scrollIntoViewIfNeeded(); + this.logger.log(`➡️ Nếu đã follow rồi -> nút sẽ là "Following" (unfollowButton)`); // Nếu đã follow rồi -> nút sẽ là "Following" (unfollowButton) if (await this.isVisible(p, 'button[data-testid$="-unfollow"]')) {