This commit is contained in:
NAME
2026-05-12 08:33:40 +00:00
parent d9766dc7f0
commit a3decd63de
10 changed files with 143 additions and 91 deletions
+11 -3
View File
@@ -1,15 +1,23 @@
import {Controller, Get} from '@nestjs/common'; import {Controller, Get, Post} from '@nestjs/common';
import {AppService} from './app.service'; import {AppService} from './app.service';
import {XCookieAccountDto} from "./x-poster/dto/x-cookie-account.dto";
import {XBrowserService} from "./x-poster/x-browser.service";
@Controller() @Controller()
export class AppController { export class AppController {
constructor( constructor(
private readonly appService: AppService, private readonly appService: AppService,
private readonly xBrowserService: XBrowserService,
) { ) {
} }
@Get() @Get()
getHello(): string { getHello() {
return this.appService.getHello(); return this.xBrowserService.verifyCookie();
}
@Post('/set-x-cookies')
setXCookies(dto: XCookieAccountDto) {
} }
} }
+20 -1
View File
@@ -19,7 +19,26 @@ export class NotifyService {
); );
} }
async sendMessageToTeleByChatId(chatId:number,message: string): Promise<any> {
async sendUrgentMessageToTele(message: string): Promise<any> {
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<any> {
const axios = require('axios'); const axios = require('axios');
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!; const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!;
+3
View File
@@ -22,6 +22,9 @@ export class SqsPosterWorker {
console.log(`🚀 Worker started for ${await this.sqs.getQueueName()}`); console.log(`🚀 Worker started for ${await this.sqs.getQueueName()}`);
await this.notifyService.sendMessageToTele(`🚀 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 = ''; let ReceiptHandle = '';
while (true) { while (true) {
try { try {
+20
View File
@@ -57,4 +57,24 @@ export class XCacheService {
async incrCountCollectNewsapi() { async incrCountCollectNewsapi() {
//this.cacheManager.c //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));
}
} }
-14
View File
@@ -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() { export function getAccount() {
return { return {
id: process.env.X_USERNAME!, id: process.env.X_USERNAME!,
+41 -12
View File
@@ -100,7 +100,7 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy {
}); });
console.log('getOrCreateContext:6') console.log('getOrCreateContext:6')
console.log(account.cookies); // console.log(account.cookies);
await ctx.addCookies( await ctx.addCookies(
account.cookies.map((c) => ({ account.cookies.map((c) => ({
@@ -136,11 +136,40 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy {
domain: c.domain || '.x.com', domain: c.domain || '.x.com',
path: c.path || '/', path: c.path || '/',
})); }));
console.log('cookies:', cookies); // console.log('cookies:', cookies);
await ctx.addCookies(cookies); await ctx.addCookies(cookies);
return ctx.newPage(); 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( async postTweet(
account: BrowserAccount, account: BrowserAccount,
text: string, text: string,
@@ -158,16 +187,16 @@ export class XBrowserService implements OnModuleInit, OnModuleDestroy {
// Intercept để lấy tweet id từ response // Intercept để lấy tweet id từ response
let capturedTweetId: string | undefined; let capturedTweetId: string | undefined;
page.on('response', async (resp) => { // page.on('response', async (resp) => {
if (resp.url().includes('/CreateTweet')) { // if (resp.url().includes('/CreateTweet')) {
try { // try {
const json = await resp.json(); // const json = await resp.json();
capturedTweetId = // capturedTweetId =
json?.data?.create_tweet?.tweet_results?.result?.rest_id; // json?.data?.create_tweet?.tweet_results?.result?.rest_id;
} catch { // } catch {
} // }
} // }
}); // });
// await page.keyboard.press('Mở trang ...'); // await page.keyboard.press('Mở trang ...');
await page.goto('https://x.com/home', { await page.goto('https://x.com/home', {
waitUntil: 'domcontentloaded', waitUntil: 'domcontentloaded',
+6 -3
View File
@@ -200,7 +200,7 @@ export class XCookieService {
* @param account * @param account
* @param userHandle Optional: Nếu biết @username * @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<boolean> {
try { try {
let variables: GraphQLVariables; let variables: GraphQLVariables;
let queryId: string; let queryId: string;
@@ -234,10 +234,13 @@ export class XCookieService {
const user = res?.data.data.user; // Hoặc res.data.data.result 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.screenName = user.rest_id ? user.legacy.screen_name : user.screen_name;
this.logger.log(`Verified: @${this.screenName}`); this.logger.log(`Verified: @${this.screenName}`);
return {screen_name: this.screenName!, name: ''}; // return {screen_name: this.screenName!, name: ''};
return true;
} catch (error: any) { } catch (error: any) {
console.log(error);
this.logger.error(`Verify failed: ${error.response?.data?.errors?.[0]?.message || error.message}`); this.logger.error(`Verify failed: ${error.response?.data?.errors?.[0]?.message || error.message}`);
throw error; return false;
// throw error;
} }
} }
} }
+7 -47
View File
@@ -97,62 +97,22 @@ export class XPosterController {
async xreply( async xreply(
@Body() tweet: ReplyTweetDto, @Body() tweet: ReplyTweetDto,
) { ) {
const account = {
accountId: process.env.X_USERNAME!, // return this.xBrowserService.postReply(account, tweet.tweetUrl, tweet.text)
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)
} }
@Post('tweet') @Post('tweet')
async tweet( async tweet(
@Body() tweet: CreateTweetDto, @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') @Get('verify')
verify() { verify() {
const account = { // const account = {
authToken: process.env.X_COOKIE_AUTH_TOKEN!, // auth_token cookie // authToken: process.env.X_COOKIE_AUTH_TOKEN!, // auth_token cookie
ct0: process.env.X_COOKIE_CT0!, // ct0: process.env.X_COOKIE_CT0!,
} // }
return this.xCookieService.verifyCookie(account); // return this.xCookieService.verifyCookie(account);
} }
} }
+25 -3
View File
@@ -6,6 +6,7 @@ import {XApiService} from "./x-api.service";
import {XCookieService} from "./x-cookie.service"; import {XCookieService} from "./x-cookie.service";
import {NotifyService} from "../notify.service"; import {NotifyService} from "../notify.service";
import {getAccount} from "./utils/x-headers.util"; import {getAccount} from "./utils/x-headers.util";
import {XCacheService} from "../x-cache/x-cache.service";
export enum SUPPORT_SOCIAL_PROVIDERS { export enum SUPPORT_SOCIAL_PROVIDERS {
FB = 'fb', FB = 'fb',
@@ -51,6 +52,7 @@ export class XPosterRouterService {
private readonly cookieSvc: XCookieService, private readonly cookieSvc: XCookieService,
private readonly browserSvc: XBrowserService, private readonly browserSvc: XBrowserService,
private readonly notifyService: NotifyService, private readonly notifyService: NotifyService,
private readonly xCacheService: XCacheService,
) { ) {
this.X_UNIFIED_ACCOUNT = getAccount(); this.X_UNIFIED_ACCOUNT = getAccount();
@@ -58,8 +60,18 @@ export class XPosterRouterService {
} }
async verifyCookie(account: XCookieAccount): Promise<any> { async verifyCookie(): Promise<any> {
return this.cookieSvc.verifyCookie(account, 'UserByScreenName') // 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<boolean> {
return this.xCacheService.isXCookiesAlive()
} }
async postTweet(params: { async postTweet(params: {
@@ -71,8 +83,18 @@ export class XPosterRouterService {
const chain = this.buildChain(strategy, account); const chain = this.buildChain(strategy, account);
const attempts: RouterResult['attempts'] = []; const attempts: RouterResult['attempts'] = [];
const canUseCookie = this.canUseXCookies();
for (const method of chain) { for (const method of chain) {
this.logger.log(`[${account.id}] Trying via ${method}`); 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); const result = await this.executeTweet(method, account, params.text);
attempts.push({method, error: result.error}); 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`);
} }
+2
View File
@@ -41,6 +41,8 @@ export class XbotFollowService {
return {username: target, success: false, alreadyFollowing: false, error: 'Account not found'}; 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)`); 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) // Nếu đã follow rồi -> nút sẽ là "Following" (unfollowButton)
if (await this.isVisible(p, 'button[data-testid$="-unfollow"]')) { if (await this.isVisible(p, 'button[data-testid$="-unfollow"]')) {