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 {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
@@ -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!;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]')) {
|
||||
|
||||
Reference in New Issue
Block a user