update view message
This commit is contained in:
parent
ebd81e11a2
commit
14747053d2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -44,3 +44,11 @@ contentService.startSyncConversations();
|
|||
|
||||
// AUTO SYNC MESAGE PREFIX (INTERNAL)
|
||||
contentService.autoSyncConversationPrefixMessages();
|
||||
|
||||
// setTimeout(() => {
|
||||
// const a = new TeamsChatService();
|
||||
|
||||
// const message = a.extractAllMessages();
|
||||
|
||||
// console.log({ message });
|
||||
// }, 10000);
|
||||
|
|
|
|||
|
|
@ -53,53 +53,137 @@ export class TeamsChatService {
|
|||
return { room_id: roomId, room_name: roomName };
|
||||
}
|
||||
|
||||
private _getMessageByEl(el: HTMLElement | null): string | string[] {
|
||||
if (!el) return "";
|
||||
_getFileLinks(el: HTMLElement) {
|
||||
const parentId = `attachments-${(el as any)?.date_time}`;
|
||||
|
||||
const parent = document.getElementById(parentId);
|
||||
|
||||
if (!parent) return [] as { name: string; url: string }[];
|
||||
|
||||
// Lấy tất cả con trực tiếp
|
||||
const children = Array.from(parent.children);
|
||||
|
||||
// Lấy title, lọc link và trả về {name, url}
|
||||
const results = children.flatMap((child) => {
|
||||
const title = child.getAttribute("title");
|
||||
if (!title) return [];
|
||||
|
||||
// Giả sử format: "tên file \r\n link"
|
||||
const lines = title
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
const name = lines[0] || null;
|
||||
const urlMatch = lines.find((line) => /^https?:\/\//.test(line));
|
||||
if (!urlMatch) return [];
|
||||
|
||||
return [{ name, url: urlMatch }];
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// private _getMessageByEl(el: HTMLElement | null): string | string[] {
|
||||
// if (!el) return "";
|
||||
|
||||
// // Lấy text ban đầu và loại bỏ khoảng trắng dư
|
||||
// let message = el.innerText?.trim() || "";
|
||||
|
||||
// // Lấy danh sách ảnh (nếu có)
|
||||
// const sharedImages = this._getImageFormEl(el) || [];
|
||||
|
||||
// if (sharedImages.length > 0) {
|
||||
// const arrMessage = sharedImages.map(
|
||||
// (img) => img.getAttribute("src") || ""
|
||||
// );
|
||||
|
||||
// if (message.length > 0) {
|
||||
// // Kiểm tra overlay
|
||||
// const overlay = document
|
||||
// .getElementById(`message-body-${(el as any)?.date_time}`)
|
||||
// ?.querySelector('[data-tid="overlay-count-text"]');
|
||||
|
||||
// // Nếu overlay text === message gốc → chỉ trả về ảnh
|
||||
// if (overlay && (overlay as HTMLElement).innerText === message) {
|
||||
// return arrMessage;
|
||||
// }
|
||||
|
||||
// // Nếu không, thêm message vào cuối
|
||||
// arrMessage.push(message);
|
||||
// }
|
||||
|
||||
// return arrMessage;
|
||||
// }
|
||||
|
||||
// // Tìm tất cả emoji trong element
|
||||
// const emojiImgs = Array.from(
|
||||
// el.querySelectorAll("img[itemtype]")
|
||||
// ) as HTMLImageElement[];
|
||||
|
||||
// const emojiAlts = emojiImgs
|
||||
// .map((img) => img.getAttribute("alt") || "")
|
||||
// .filter(Boolean);
|
||||
|
||||
// // Nếu có emoji thì nối vào message
|
||||
// if (emojiAlts.length > 0) {
|
||||
// message = `${message}${emojiAlts.join("")}`;
|
||||
// }
|
||||
|
||||
// return message.trim();
|
||||
// }
|
||||
|
||||
private _getMessageByEl(el: HTMLElement | null): string[] {
|
||||
if (!el) return [];
|
||||
|
||||
const messages: string[] = [];
|
||||
|
||||
// Lấy text ban đầu và loại bỏ khoảng trắng dư
|
||||
let message = el.innerText?.trim() || "";
|
||||
const messageText = el.innerText?.trim() || "";
|
||||
|
||||
// Lấy danh sách ảnh (nếu có)
|
||||
const sharedImages = this._getImageFormEl(el) || [];
|
||||
|
||||
if (sharedImages.length > 0) {
|
||||
const arrMessage = sharedImages.map(
|
||||
(img) => img.getAttribute("src") || ""
|
||||
);
|
||||
const imageSrcs = sharedImages
|
||||
.map((img) => img.getAttribute("src"))
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
if (message.length > 0) {
|
||||
// Kiểm tra overlay
|
||||
const overlay = document
|
||||
.getElementById(`message-body-${(el as any)?.date_time}`)
|
||||
?.querySelector('[data-tid="overlay-count-text"]');
|
||||
if (imageSrcs.length > 0) {
|
||||
// Kiểm tra overlay
|
||||
const overlay = document
|
||||
.getElementById(`message-body-${(el as any)?.date_time}`)
|
||||
?.querySelector('[data-tid="overlay-count-text"]');
|
||||
|
||||
// Nếu overlay text === message gốc → chỉ trả về ảnh
|
||||
if (overlay && (overlay as HTMLElement).innerText === message) {
|
||||
return arrMessage;
|
||||
}
|
||||
|
||||
// Nếu không, thêm message vào cuối
|
||||
arrMessage.push(message);
|
||||
if (overlay && (overlay as HTMLElement).innerText === messageText) {
|
||||
messages.push(...imageSrcs);
|
||||
} else {
|
||||
messages.push(...imageSrcs);
|
||||
if (messageText) messages.push(messageText);
|
||||
}
|
||||
|
||||
return arrMessage;
|
||||
} else if (messageText) {
|
||||
messages.push(messageText);
|
||||
}
|
||||
|
||||
// Tìm tất cả emoji trong element
|
||||
const fileLinks = this._getFileLinks(el) || [];
|
||||
|
||||
if (fileLinks.length) {
|
||||
fileLinks.forEach((link) => {
|
||||
messages.push(JSON.stringify({ ...link, type: "file" }));
|
||||
});
|
||||
}
|
||||
|
||||
// Lấy emoji
|
||||
const emojiImgs = Array.from(
|
||||
el.querySelectorAll("img[itemtype]")
|
||||
) as HTMLImageElement[];
|
||||
|
||||
const emojiAlts = emojiImgs
|
||||
.map((img) => img.getAttribute("alt") || "")
|
||||
.filter(Boolean);
|
||||
|
||||
// Nếu có emoji thì nối vào message
|
||||
if (emojiAlts.length > 0) {
|
||||
message = `${message}${emojiAlts.join("")}`;
|
||||
messages.push(emojiAlts.join(""));
|
||||
}
|
||||
|
||||
return message.trim();
|
||||
return messages;
|
||||
}
|
||||
|
||||
public parseMessageElement(el: Element, isMine = false): IMessage | null {
|
||||
|
|
@ -156,11 +240,11 @@ export class TeamsChatService {
|
|||
return [...myMessages, ...otherMessages].sort((a, b) => a.time - b.time);
|
||||
}
|
||||
|
||||
private handleNewMessage(message: IMessage) {
|
||||
// Gửi tới một server AI để phản hồi (nếu cần)
|
||||
// fetch('https://127.0.0.1:8443/reply', { ... })
|
||||
console.log("%c[New incoming message]", "color: #007acc;", message);
|
||||
}
|
||||
// private handleNewMessage(message: IMessage) {
|
||||
// // Gửi tới một server AI để phản hồi (nếu cần)
|
||||
// // fetch('https://127.0.0.1:8443/reply', { ... })
|
||||
// console.log("%c[New incoming message]", "color: #007acc;", message);
|
||||
// }
|
||||
|
||||
public async detectNewMessages() {
|
||||
const allMessages = this.extractAllMessages();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,7 @@
|
|||
"dependencies": {
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@keyv/redis": "^5.0.0",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/class-transformer": "^0.4.0",
|
||||
|
|
@ -56,6 +57,7 @@
|
|||
"multer": "^2.0.2",
|
||||
"mysql2": "^3.14.3",
|
||||
"nestjs-paginate": "^12.5.0",
|
||||
"nodemailer": "^7.0.6",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"socket.io": "^4.8.1"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||
import { MessagesModule } from './modules/messages/messages.module';
|
||||
import { AppConfigsModule } from './modules/app-configs/app-configs.module';
|
||||
import { ConversationsModule } from './modules/conversations/conversations.module';
|
||||
import { MailerModule } from './modules/mailer/mailer.module';
|
||||
|
||||
@Module({
|
||||
imports: [MessagesModule, AppConfigsModule, ConversationsModule],
|
||||
imports: [MessagesModule, AppConfigsModule, ConversationsModule, MailerModule],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import { MailerModule as MLM } from '@nestjs-modules/mailer';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MailerService } from './mailer.service';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MLM.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
|
||||
useFactory: async (config: ConfigService) => ({
|
||||
transport: {
|
||||
host: config.get<string>('MAIL_HOST'),
|
||||
port: config.get<string>('MAIL_PORT'),
|
||||
// secure: true, // true nếu port là 465
|
||||
auth: {
|
||||
user: config.get<string>('MAIL_USERNAME'),
|
||||
pass: config.get<string>('MAIL_PASSWORD'),
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
from: `"Bids" <${config.get<string>('MAIL_FROM')}>`,
|
||||
},
|
||||
}),
|
||||
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
providers: [MailerService],
|
||||
exports: [MailerService],
|
||||
})
|
||||
export class MailerModule {}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { MailerService as NestMailerService } from '@nestjs-modules/mailer';
|
||||
|
||||
@Injectable()
|
||||
export class MailerService {
|
||||
constructor(private readonly mailerService: NestMailerService) {}
|
||||
|
||||
/**
|
||||
* Lấy danh sách email từ biến môi trường MAILS
|
||||
*/
|
||||
private getMailList(): string[] {
|
||||
const mailList = (process.env.MAILS || '')
|
||||
.split(',')
|
||||
.map((email) => email.trim())
|
||||
.filter(Boolean); // loại bỏ email rỗng
|
||||
|
||||
if (mailList.length === 0) {
|
||||
throw new Error('No valid email addresses found in MAILS env');
|
||||
}
|
||||
|
||||
return mailList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gửi email đến tất cả người nhận trong MAILS env
|
||||
*/
|
||||
async sendMail(subject: string, htmlContent: string) {
|
||||
const mailList = this.getMailList(); // lấy danh sách email từ hàm
|
||||
|
||||
await this.mailerService.sendMail({
|
||||
to: mailList,
|
||||
subject,
|
||||
html: htmlContent,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -6,13 +6,14 @@ import { SendLogDto } from './dtos/send-log.dto';
|
|||
import { ZulipService } from './zulip.service';
|
||||
import AppResponse from '@/system/filters/response/app-response';
|
||||
import { SystemLang } from '@/system/lang/system.lang';
|
||||
import { MailerService } from '../mailer/mailer.service';
|
||||
@Injectable()
|
||||
export class LogsService {
|
||||
constructor(
|
||||
@InjectRepository(Log)
|
||||
readonly repo: Repository<Log>,
|
||||
|
||||
private readonly zulipService: ZulipService,
|
||||
private readonly malerService: MailerService,
|
||||
) {}
|
||||
|
||||
async saveLog(data: SendLogDto) {
|
||||
|
|
@ -28,11 +29,17 @@ export class LogsService {
|
|||
}),
|
||||
);
|
||||
|
||||
await this.zulipService.sendMessageToTopic(
|
||||
process.env.ZULIP_STREAMS_NAME,
|
||||
process.env.ZULIP_TOPPIC_LOG_NAME,
|
||||
`[${result.type.toUpperCase()}] - ${result.message}`,
|
||||
);
|
||||
// 2. Build nội dung email
|
||||
const subject = `[${result.type.toUpperCase()}] - New Log Message`;
|
||||
const content = `
|
||||
<h3>Log Notification</h3>
|
||||
<p><b>Type:</b> ${result.type.toUpperCase()}</p>
|
||||
<p><b>Message:</b> ${result.message}</p>
|
||||
<p><b>Created At:</b> ${new Date().toLocaleString()}</p>
|
||||
`;
|
||||
|
||||
// 3. Gửi email
|
||||
await this.malerService.sendMail(subject, content);
|
||||
|
||||
return AppResponse.toResponse(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,14 @@ import { ZulipService } from './zulip.service';
|
|||
import { HttpModule } from '@nestjs/axios';
|
||||
import { Log } from '@/entities/log.entity';
|
||||
import { LogsService } from './logs.service';
|
||||
import { MailerModule } from '../mailer/mailer.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Message, Conversation, Log]), HttpModule],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Message, Conversation, Log]),
|
||||
HttpModule,
|
||||
MailerModule,
|
||||
],
|
||||
providers: [
|
||||
MessagesService,
|
||||
MessagesGateway,
|
||||
|
|
|
|||
|
|
@ -190,32 +190,83 @@ export class MessagesService {
|
|||
}
|
||||
|
||||
// 6. Build message để gửi lên Zulip
|
||||
// const buildZulipMessageContent = (
|
||||
// msgs: string[],
|
||||
// result: Message,
|
||||
// ): string => {
|
||||
// const imageUris: string[] = [];
|
||||
// const textMessages: string[] = [];
|
||||
|
||||
// for (const msg of msgs) {
|
||||
// // Nếu là link `/user_uploads/...` thì render ảnh
|
||||
// if (/\/user_uploads\//.test(msg)) {
|
||||
// imageUris.push(`[image](${msg.replace(/^\/api\/v1/, '')})`);
|
||||
// } else {
|
||||
// textMessages.push(formatTextIfValid(msg));
|
||||
// }
|
||||
// }
|
||||
|
||||
// let finalContent = `** :rocket: ${result.name} sent - ${formatTimeAU(result.time_raw)}:**\n`;
|
||||
|
||||
// // Nếu có text → thêm block code
|
||||
// if (textMessages.length > 0) {
|
||||
// finalContent += `\`\`\`\n${textMessages.join('\n')}\n\`\`\`\n`;
|
||||
// }
|
||||
|
||||
// // Nếu có ảnh → thêm danh sách ảnh ở cuối
|
||||
// if (imageUris.length > 0) {
|
||||
// finalContent += imageUris.join('\n');
|
||||
// }
|
||||
|
||||
// return finalContent.trim();
|
||||
// };
|
||||
|
||||
const buildZulipMessageContent = (
|
||||
msgs: string[],
|
||||
result: Message,
|
||||
): string => {
|
||||
const imageUris: string[] = [];
|
||||
const textMessages: string[] = [];
|
||||
const fileLinks: string[] = []; // Chứa danh sách file dạng [name](url)
|
||||
|
||||
for (const msg of msgs) {
|
||||
// Nếu là link `/user_uploads/...` thì render ảnh
|
||||
// 1. Nếu là link ảnh upload → hiển thị dạng image
|
||||
if (/\/user_uploads\//.test(msg)) {
|
||||
imageUris.push(`[image](${msg.replace(/^\/api\/v1/, '')})`);
|
||||
} else {
|
||||
textMessages.push(formatTextIfValid(msg));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Nếu là JSON và có type === 'file'
|
||||
try {
|
||||
const parsed = JSON.parse(msg);
|
||||
if (parsed?.type === 'file' && parsed.url && parsed.name) {
|
||||
fileLinks.push(`[${parsed.name}](${parsed.url})`);
|
||||
continue;
|
||||
}
|
||||
} catch {
|
||||
// Không phải JSON → bỏ qua, xử lý như text
|
||||
}
|
||||
|
||||
// 3. Còn lại là text thường
|
||||
textMessages.push(formatTextIfValid(msg));
|
||||
}
|
||||
|
||||
// ==== Build nội dung cuối ====
|
||||
let finalContent = `** :rocket: ${result.name} sent - ${formatTimeAU(result.time_raw)}:**\n`;
|
||||
|
||||
// Nếu có text → thêm block code
|
||||
// Text chính → code block
|
||||
if (textMessages.length > 0) {
|
||||
finalContent += `\`\`\`\n${textMessages.join('\n')}\n\`\`\`\n`;
|
||||
}
|
||||
|
||||
// Nếu có ảnh → thêm danh sách ảnh ở cuối
|
||||
// Ảnh → dưới text
|
||||
if (imageUris.length > 0) {
|
||||
finalContent += imageUris.join('\n');
|
||||
finalContent += `${imageUris.join('\n')}\n`;
|
||||
}
|
||||
|
||||
// File → phần cuối cùng
|
||||
if (fileLinks.length > 0) {
|
||||
finalContent += `\n**Files:**\n${fileLinks.join('\n')}`;
|
||||
}
|
||||
|
||||
return finalContent.trim();
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Loading…
Reference in New Issue