D
This commit is contained in:
+11
-3
@@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,25 @@ export class NotifyService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
async sendMessageToTeleByChatId(chatId: number, message: string): Promise<any> {
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"]')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user