update detech new message and auto sync conversations
This commit is contained in:
parent
b6d6a6dd79
commit
9dfa87e2c0
|
|
@ -1,3 +0,0 @@
|
|||
VITE_API_URL=https://notable-recently-seagull.ngrok-free.app/api/v1/
|
||||
VITE_WS_URL=wss://notable-recently-seagull.ngrok-free.app
|
||||
VITE_API_TYPE_URL=MyCoolApp
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -12,7 +12,9 @@ port.onMessage.addListener((msg: IMsg<any>) => {
|
|||
// Set height input chat tager cho nó ra dễ click
|
||||
contentService.fixedHeightChatInput();
|
||||
|
||||
if (msg.type !== "socket") return;
|
||||
if (msg.type !== "socket") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.event) {
|
||||
case EVENTS.GET_CONVERSATIONS: {
|
||||
|
|
@ -36,3 +38,9 @@ port.onMessage.addListener((msg: IMsg<any>) => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// DETECH NEW MESSAGE (INTERVAl)
|
||||
contentService.detectNewMessage();
|
||||
|
||||
// SYNC CONVERSASIONS (INTERVAL)
|
||||
contentService.startSyncConversations();
|
||||
|
|
|
|||
|
|
@ -1,6 +1 @@
|
|||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { TeamsChatService } from "./teams-chat.service";
|
|||
import { EVENTS } from "@/lib/event";
|
||||
import { typeingService } from "./typing.service";
|
||||
import { delay } from "@/features/app";
|
||||
import { messageApi } from "@/api/message-api.service";
|
||||
|
||||
export class ContentService {
|
||||
service: TeamsChatService;
|
||||
|
|
@ -283,7 +284,7 @@ export class ContentService {
|
|||
});
|
||||
}
|
||||
|
||||
async getConversations(_: IMsg<any>) {
|
||||
async getConversations(_?: IMsg<any>) {
|
||||
queue.add(async () => {
|
||||
console.log("[Queue] Handling GET_CONVERSATIONS");
|
||||
|
||||
|
|
@ -381,7 +382,7 @@ export class ContentService {
|
|||
await this._waitToloadMessages();
|
||||
}
|
||||
|
||||
this._clickIfExists(this.service.elTags.close_reply_btn);
|
||||
await this._clickIfExists(this.service.elTags.close_reply_btn);
|
||||
|
||||
await this._rightClickMessage(time);
|
||||
|
||||
|
|
@ -443,4 +444,53 @@ export class ContentService {
|
|||
|
||||
tryFind();
|
||||
}
|
||||
|
||||
async detectNewMessage(interval = 2000) {
|
||||
console.log("[Monitor] Starting...");
|
||||
// this.initialHistories = this.extractAllMessages();
|
||||
// this.lastMessage = this.initialHistories.pop();
|
||||
|
||||
// await messageApi.sendBulkMessages(this.initialHistories);
|
||||
setInterval(async () => {
|
||||
const aria_value = document
|
||||
.querySelector(
|
||||
'[aria-labelledby^="cn-normal-notification-main-content-"]'
|
||||
)
|
||||
?.getAttribute("aria-labelledby");
|
||||
|
||||
if (!aria_value) {
|
||||
console.log("No new message...");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const room_id = aria_value
|
||||
.split(" ")[0]
|
||||
.replaceAll("cn-normal-notification-main-content-", "");
|
||||
|
||||
if (!room_id) return;
|
||||
|
||||
console.log({ room_id, aria_value });
|
||||
|
||||
queue.add(async () => {
|
||||
console.log("[Queue] Handling SYNC NEW MESSAGE");
|
||||
|
||||
this._clickToConversation(room_id);
|
||||
|
||||
await delay(2000);
|
||||
|
||||
const allMessages = this.service.extractAllMessages();
|
||||
|
||||
const lastMessage = allMessages.at(-1);
|
||||
|
||||
if (!lastMessage) return;
|
||||
|
||||
await messageApi.sendSingleMessage(lastMessage);
|
||||
});
|
||||
}, interval);
|
||||
}
|
||||
|
||||
startSyncConversations() {
|
||||
setInterval(() => this.getConversations(), 20000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,14 @@ export class TeamsChatService {
|
|||
"/html/body/div[1]/div/div/div/div[5]/div[1]/div[1]/div[2]/div[1]/div[1]/div",
|
||||
conatainer_conversations:
|
||||
"/html/body/div[1]/div/div/div/div[5]/div[1]/div[1]/div[2]/div[1]/div[1]/div/div[1]",
|
||||
container_chat:
|
||||
// "/html/body/div[1]/div/div/div/div[6]/div[4]/div/div[1]/div/div[1]/div/div/div/div[1]/div/div/div[4]",
|
||||
'[data-testid="message-wrapper"]',
|
||||
container_chat: '[data-testid="message-wrapper"]',
|
||||
|
||||
root_id: '[aria-selected="true"] [id^="chat-list-item"]',
|
||||
room_name: '[data-tid="chat-title"]',
|
||||
close_reply_btn:
|
||||
'[data-track-action-scenario="messageQuotedReplyDismissed"]',
|
||||
reply_btn: '[aria-label="Reply"]',
|
||||
chat_input:
|
||||
// "/html/body/div[1]/div/div/div/div[6]/div[4]/div/div[1]/div/div[3]/div/div[3]/div/div[2]/div/div[2]/div[1]/div",
|
||||
'[placeholder="Type a message"]',
|
||||
chat_input: '[placeholder="Type a message"]',
|
||||
};
|
||||
|
||||
public getCurrentRoomInfo(): { room_id?: string; room_name?: string } {
|
||||
|
|
@ -160,7 +156,7 @@ export class TeamsChatService {
|
|||
this.initialHistories = this.extractAllMessages();
|
||||
this.lastMessage = this.initialHistories.pop();
|
||||
|
||||
await messageApi.sendBulkMessages(this.initialHistories);
|
||||
// await messageApi.sendBulkMessages(this.initialHistories);
|
||||
setInterval(async () => await this.detectNewMessages(), interval);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@keyv/redis": "^5.0.0",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/class-transformer": "^0.4.0",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
|
|
@ -2428,6 +2429,17 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/axios": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz",
|
||||
"integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"axios": "^1.3.1",
|
||||
"rxjs": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cache-manager": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.0.1.tgz",
|
||||
|
|
@ -4862,7 +4874,6 @@
|
|||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
|
|
@ -4911,6 +4922,18 @@
|
|||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
||||
|
|
@ -5725,7 +5748,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
|
|
@ -6094,7 +6116,6 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
|
|
@ -6420,7 +6441,6 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
|
|
@ -7257,6 +7277,27 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
|
|
@ -7321,7 +7362,6 @@
|
|||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
|
|
@ -7348,7 +7388,6 @@
|
|||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
|
|
@ -7358,7 +7397,6 @@
|
|||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
|
|
@ -10625,6 +10663,13 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"dependencies": {
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@keyv/redis": "^5.0.0",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/class-transformer": "^0.4.0",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { IsString, IsNumber, IsOptional } from 'class-validator';
|
||||
import { Expose } from '@nestjs/class-transformer';
|
||||
import { Optional } from '@nestjs/common';
|
||||
|
||||
export class CreateMessageDto {
|
||||
@IsOptional()
|
||||
|
|
@ -16,6 +17,11 @@ export class CreateMessageDto {
|
|||
@Expose()
|
||||
time: number;
|
||||
|
||||
@IsNumber()
|
||||
@Expose()
|
||||
@Optional()
|
||||
date_time: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Expose()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ export class MessagesController {
|
|||
return this.service.sendMessage(data);
|
||||
}
|
||||
|
||||
@Post('')
|
||||
save(@Body() data: CreateMessageDto) {
|
||||
return this.service.create(data);
|
||||
}
|
||||
|
||||
@Post('reply-message')
|
||||
replyMessage(@Body() data: ReplyMessageDto) {
|
||||
return this.service.replyMessage(data);
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ import { MessagesController } from './messages.controller';
|
|||
import { MessagesGateway } from './messages.gateway';
|
||||
import { MessagesService } from './messages.service';
|
||||
import { MessagesListener } from './messages.listener';
|
||||
import { ZulipService } from './zulip.service';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Message, Conversation])],
|
||||
imports: [TypeOrmModule.forFeature([Message, Conversation]), HttpModule],
|
||||
providers: [
|
||||
MessagesService,
|
||||
MessagesGateway,
|
||||
MessagesEventService,
|
||||
MessagesListener,
|
||||
ZulipService,
|
||||
],
|
||||
controllers: [MessagesController],
|
||||
exports: [MessagesGateway, MessagesService, MessagesEventService],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { Conversation } from '@/entities/conversation.entity';
|
|||
import { Message } from '@/entities/message.entity';
|
||||
import AppResponse from '@/system/filters/response/app-response';
|
||||
import { SystemLang } from '@/system/lang/system.lang';
|
||||
import { HttpStatus, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { paginate, PaginateQuery } from 'nestjs-paginate';
|
||||
import { Repository } from 'typeorm';
|
||||
|
|
@ -10,6 +15,7 @@ import { CreateMessageDto } from './dtos/create-message.dto';
|
|||
import { ReplyMessageDto } from './dtos/reply-message.dto';
|
||||
import { SendMessageDto } from './dtos/send-message.dto';
|
||||
import { MessagesEventService } from './messages-event.service';
|
||||
import { ZulipService } from './zulip.service';
|
||||
@Injectable()
|
||||
export class MessagesService {
|
||||
constructor(
|
||||
|
|
@ -17,7 +23,8 @@ export class MessagesService {
|
|||
readonly repo: Repository<Message>,
|
||||
@InjectRepository(Conversation)
|
||||
readonly conversationRepo: Repository<Conversation>,
|
||||
private event: MessagesEventService,
|
||||
private readonly event: MessagesEventService,
|
||||
private readonly zulupService: ZulipService,
|
||||
) {}
|
||||
|
||||
async index(query: PaginateQuery) {
|
||||
|
|
@ -48,16 +55,45 @@ export class MessagesService {
|
|||
});
|
||||
|
||||
if (existing) {
|
||||
return existing; // hoặc throw error nếu muốn
|
||||
return existing;
|
||||
}
|
||||
|
||||
const conversation = await this.conversationRepo.findOne({
|
||||
where: { id: dto.room_id },
|
||||
});
|
||||
|
||||
if (!conversation)
|
||||
throw new BadRequestException(
|
||||
AppResponse.toResponse(null, {
|
||||
message: SystemLang.getText('messages', 'not_found'),
|
||||
}),
|
||||
);
|
||||
|
||||
const entity = this.repo.create({
|
||||
...dto,
|
||||
time,
|
||||
time_raw: dto.time,
|
||||
});
|
||||
|
||||
return this.repo.save(entity);
|
||||
const result = await this.repo.save(entity);
|
||||
|
||||
if (result) {
|
||||
if (
|
||||
!conversation.name.includes(process.env.GROUP_PREFIX) ||
|
||||
conversation.type !== 'group'
|
||||
)
|
||||
return;
|
||||
|
||||
if (!conversation) return;
|
||||
|
||||
await this.zulupService.sendMessageToTopic(
|
||||
process.env.ZULIP_STREAMS_NAME,
|
||||
conversation.name,
|
||||
result.message,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async bulkCreate(dtos: CreateMessageDto[]): Promise<Message[]> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class ZulipService {
|
||||
private readonly logger = new Logger(ZulipService.name);
|
||||
|
||||
constructor(private readonly httpService: HttpService) {}
|
||||
|
||||
async sendMessageToTopic(stream: string, topic: string, content: string) {
|
||||
const url = process.env.ZULIP_API_URL;
|
||||
const botEmail = process.env.ZULIP_BOT_EMAIL;
|
||||
const apiKey = process.env.ZULIP_API_KEY;
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(
|
||||
url,
|
||||
new URLSearchParams({
|
||||
type: 'stream',
|
||||
to: stream,
|
||||
topic: topic,
|
||||
content: content,
|
||||
}).toString(),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
auth: {
|
||||
username: botEmail,
|
||||
password: apiKey,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(`Message sent to stream "${stream}" topic "${topic}"`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'Failed to send message to Zulip',
|
||||
error?.response?.data || error.message,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue