fix spam message

This commit is contained in:
Admin 2025-05-14 10:03:53 +07:00
parent fe4d22f361
commit 4d2c39c053
13 changed files with 247 additions and 26 deletions

View File

@ -118,10 +118,6 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!isIBid(data)) {
console.log(data);
}
return (
<>
<Box
@ -200,7 +196,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
</Box>
<Box className="flex items-center gap-4">
<Button
rightSection={<IconImageInPicture size={14}/>}
rightSection={<IconImageInPicture size={14} />}
size="xs"
color="green"
onClick={open}

View File

@ -1 +1 @@
{"createdAt":1747107717780}
{"createdAt":1747191706164}

View File

@ -17,6 +17,7 @@
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/platform-socket.io": "^11.0.11",
"@nestjs/schedule": "^6.0.0",
"@nestjs/throttler": "^6.4.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.0.11",
@ -51,7 +52,7 @@
"@types/jest": "^29.5.2",
"@types/lodash": "^4.17.16",
"@types/multer": "^1.4.12",
"@types/node": "^20.3.1",
"@types/node": "^20.17.46",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
@ -2458,6 +2459,19 @@
"rxjs": "^7.1.0"
}
},
"node_modules/@nestjs/schedule": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz",
"integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==",
"license": "MIT",
"dependencies": {
"cron": "4.3.0"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/schematics": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz",
@ -3007,6 +3021,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/luxon": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
"integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"license": "MIT"
},
"node_modules/@types/methods": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@ -3032,9 +3052,9 @@
}
},
"node_modules/@types/node": {
"version": "20.17.24",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz",
"integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==",
"version": "20.17.46",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.46.tgz",
"integrity": "sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@ -4875,6 +4895,19 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/cron": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.0.tgz",
"integrity": "sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.6.0",
"luxon": "~3.6.0"
},
"engines": {
"node": ">=18.x"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -8335,6 +8368,15 @@
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/luxon": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",

View File

@ -33,6 +33,7 @@
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/platform-socket.io": "^11.0.11",
"@nestjs/schedule": "^6.0.0",
"@nestjs/throttler": "^6.4.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.0.11",
@ -67,7 +68,7 @@
"@types/jest": "^29.5.2",
"@types/lodash": "^4.17.16",
"@types/multer": "^1.4.12",
"@types/node": "^20.3.1",
"@types/node": "^20.17.46",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
@ -11,6 +12,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
wildcard: true,
global: true,
}),
ScheduleModule.forRoot()
],
})
export class AppConfigsModule {}

View File

@ -27,6 +27,9 @@ import { SendMessageHistoriesService } from './services/send-message-histories.s
import { WebBidsService } from './services/web-bids.service';
import { DashboardService } from './services/dashboard.service';
import { AdminDashboardController } from './controllers/admin/admin-dashboard.controller';
import { TasksService } from './services/tasks.servise';
import { ConfigsService } from './services/configs.service';
import { Config } from './entities/configs.entity';
@Module({
imports: [
@ -36,6 +39,7 @@ import { AdminDashboardController } from './controllers/admin/admin-dashboard.co
OutBidLog,
WebBid,
SendMessageHistory,
Config
]),
// AuthModule,
AdminsModule,
@ -64,6 +68,8 @@ import { AdminDashboardController } from './controllers/admin/admin-dashboard.co
SendMessageHistoriesService,
ImapService,
DashboardService,
TasksService,
ConfigsService
],
exports: [BotTelegramApi, SendMessageHistoriesService, BidsService],
})

View File

@ -0,0 +1,17 @@
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { Timestamp } from './timestamp';
@Entity('configs')
export class Config extends Timestamp {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ unique: true })
key_name: string;
@Column({ nullable: true, default: true })
value: string | null;
@Column()
type: 'string' | 'number';
}

View File

@ -1,8 +1,9 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { Timestamp } from './timestamp';
import { Bid } from './bid.entity';
@Entity('send_message_histories')
@Unique(['max_price', 'type', 'reserve_price'])
export class SendMessageHistory extends Timestamp {
@PrimaryGeneratedColumn('increment')
id: number;
@ -17,4 +18,10 @@ export class SendMessageHistory extends Timestamp {
onDelete: 'CASCADE',
})
bid: Bid;
@Column({ default: 0, nullable: true })
max_price: number;
@Column({ default: 0, nullable: true })
reserve_price: number;
}

View File

@ -19,7 +19,7 @@ import { Column } from 'nestjs-paginate/lib/helper';
import { join } from 'path';
import AppResponse from 'src/response/app-response';
import { extractModelId, isTimeReached, subtractMinutes } from 'src/ultils';
import { In, Repository } from 'typeorm';
import { In, IsNull, Not, Repository } from 'typeorm';
import { ClientUpdateBidDto } from '../dto/bid/client-update-bid.dto';
import { CreateBidDto } from '../dto/bid/create-bid.dto';
import { UpdateBidDto } from '../dto/bid/update-bid.dto';
@ -57,7 +57,7 @@ export class BidsService {
lot_id: true,
close_time: true,
name: [FilterOperator.ILIKE],
status: true
status: true,
};
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
@ -524,4 +524,29 @@ export class BidsService {
return AppResponse.toResponse(true);
}
async getNextBid(): Promise<Bid | null> {
const all = await this.bidsRepo.find({
where: { status: 'biding', close_time: Not(IsNull()) },
relations: { web_bid: true },
});
const now = Date.now();
let nextBid = null;
let minDiff = Infinity;
for (const bid of all) {
const time = Date.parse(bid.close_time);
if (!isNaN(time) && time >= now) {
const diff = time - now;
if (diff < minDiff) {
minDiff = diff;
nextBid = bid;
}
}
}
return nextBid;
}
}

View File

@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Config } from '../entities/configs.entity';
@Injectable()
export class ConfigsService {
public static CONFIG_KEYS = {
REFRESH_TOOL_TIME: 'REFRESH_TOOL_TIME',
};
constructor(
@InjectRepository(Config)
readonly configRepo: Repository<Config>,
private eventEmitter: EventEmitter2,
) {}
async getConfig(key_name: keyof typeof ConfigsService.CONFIG_KEYS) {
return await this.configRepo.findOne({ where: { key_name } }) || null;
}
async setConfig(key_name: keyof typeof ConfigsService.CONFIG_KEYS, value: string) {
return await this.configRepo.upsert(
{ key_name, value },
['key_name']
);
}
}

View File

@ -0,0 +1,68 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { IsNull, Not } from 'typeorm';
import { BidsService } from './bids.service';
import { isTimeReached, subtractMinutes, subtractSeconds } from '@/ultils';
import { ConfigsService } from './configs.service';
import { DashboardService } from './dashboard.service';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
constructor(
private readonly bidsService: BidsService,
private readonly configsService: ConfigsService,
private readonly dashboadService: DashboardService,
) {}
@Cron(CronExpression.EVERY_MINUTE)
async handleCron() {
const nextBid = await this.bidsService.getNextBid();
if (!nextBid) return;
const timeReset = subtractMinutes(nextBid.close_time, 20);
const timeToTracking = subtractSeconds(
nextBid.close_time,
nextBid.web_bid.early_tracking_seconds,
);
if (!isTimeReached(timeReset) || isTimeReached(timeToTracking)) {
console.log('Reset not allowed at this time');
return;
}
const lastestResetToolTime =
await this.configsService.getConfig('REFRESH_TOOL_TIME');
if (lastestResetToolTime?.value) {
const lastReset = Date.parse(lastestResetToolTime.value);
const now = Date.now();
const diffInMs = now - lastReset;
const diffInHours = diffInMs / (1000 * 60 * 60);
const minimumHours = 2;
if (diffInHours < minimumHours) {
console.log(`Last reset was less than ${minimumHours} hours ago`);
return;
}
}
console.log('Proceeding to reset tool for next bid:', nextBid);
await this.dashboadService.resetTool();
const time = new Date().toUTCString()
await this.configsService.setConfig(
'REFRESH_TOOL_TIME',
time,
);
console.log('Reset successfully at: ' + time);
}
}

View File

@ -109,18 +109,40 @@ export class NotificationService {
send_to: JSON.stringify(sendToData),
});
await this.sendMessageRepo.save({
bid: { id: bid.id },
message: notification.message,
type: bid.status,
});
try {
const prevAnyMessage = await this.sendMessageRepo.findOne({
where: {
bid: { id: bid.id },
message: notification.message,
type: bid.status,
max_price: bid.max_price,
reserve_price: bid.reserve_price
},
});
this.eventEmitter.emit(NAME_EVENTS.BID_STATUS, {
bid: {
...bid,
status: 'out-bid',
},
notification,
});
if (prevAnyMessage) return;
await this.sendMessageRepo.save({
bid: { id: bid.id },
message: notification.message,
type: bid.status,
max_price: bid.max_price,
reserve_price: bid.reserve_price
});
this.eventEmitter.emit(NAME_EVENTS.BID_STATUS, {
bid: {
...bid,
status: 'out-bid',
},
notification,
});
} catch (error) {
console.log(
'%csrc/modules/notification/notification.service.ts:131 Error',
'color: #007acc;',
Error,
);
}
}
}

View File

@ -25,6 +25,12 @@ export function subtractMinutes(timeStr: string, minutes: number) {
return date.toISOString(); // Trả về dạng chuẩn ISO
}
export function subtractSeconds(time: string, seconds: number) {
const date = new Date(time);
date.setSeconds(date.getSeconds() - seconds);
return date.toUTCString();
}
export function isTimeReached(targetTime: string) {
const targetDate = new Date(targetTime);
const now = new Date();