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 {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) {
}
}
+27 -8
View File
@@ -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<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 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()}`);
await this.notifyService.sendMessageToTele(`🚀 Worker started for ${await this.sqs.getQueueName()}`)
//check cookie
this.xRouterService.verifyCookie();
let ReceiptHandle = '';
while (true) {
try {
+21 -1
View File
@@ -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));
}
}
-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() {
return {
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(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',
+6 -3
View File
@@ -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<boolean> {
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;
}
}
}
+7 -47
View File
@@ -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);
}
}
+25 -3
View File
@@ -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<any> {
return this.cookieSvc.verifyCookie(account, 'UserByScreenName')
async verifyCookie(): Promise<any> {
// 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: {
@@ -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`);
}
+2
View File
@@ -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"]')) {