Files
ranking/app.ts
2025-11-12 02:58:36 +09:00

158 lines
4.4 KiB
TypeScript

import prisma from "./src/lib/prisma";
import dayjs from "dayjs";
import { RankingService } from "./src/services/ranking.service";
import { NaverRealEstate } from "./src/services/naver.service";
import { realestateTypes, realtorIds } from "./config";
import {
TelegramService,
telegramUsers,
type TelegramUser,
} from "./src/services/telegram.service";
import type { RealEstateArticle } from "./src/generated/prisma";
import * as XLSX from "xlsx";
const telegramService = new TelegramService(
"233460568:AAHWgRQo5IgcWR0uXdsiMEzNnsmIqjOgk24",
false
);
async function main() {
await updateRanking();
// await sendTelegram();
}
async function sendTelegram() {
for (let telegramUser of telegramUsers) {
const articles = await prisma.realEstateArticle.findMany({
where: {
isActive: true,
realtorId: telegramUser.realtorId,
...(telegramUser.site !== "ALL" && { cpNm: telegramUser.site }),
...(telegramUser.site === "부동산포스" && {
brokerPhone: telegramUser.phone,
}),
},
});
const excelFilePath = await createExcelFile(telegramUser, articles);
await telegramService.sendDocument(
telegramUser.chatId,
excelFilePath,
`네이버 부동산 매물 목록 (${dayjs().format("YYYY-MM-DD")})`
);
}
}
async function createExcelFile(
telegramUser: TelegramUser,
articles: RealEstateArticle[]
): Promise<string> {
// 데이터를 배열로 변환
const data = articles.map((item) => {
const getOwnerType = () => {
if (["MOBL", "NDOC1", "OWNER"].includes(item.verificationType || "")) {
if (
["VL", "APT", "OPST", "DDDGG"].includes(item.realEstateType || "")
) {
return "집주인";
} else {
return "소유자";
}
}
if (["SITE", "S_VR"].includes(item.verificationType || "")) {
return "현장";
}
return "";
};
const getPrice = () => {
return item.prcInfo;
};
return {
매물번호: item.articleNumber,
: `https://fin.land.naver.com/articles/${item.articleNumber}`,
소유자구분: getOwnerType(),
매물형태: item.articleName || "",
매매구분: item.tradTpNm || "",
: `${item.city || ""} ${item.division || ""} ${item.sector || ""}`,
상세주소: item.detailAddress || "",
층수: item.floorInfo || "",
가격: getPrice(),
매물특징: item.articleDescription || "",
광고사: item.cpNm || "",
확인일자: item.articleConfirmDate
? dayjs(item.articleConfirmDate).format("YYYY-MM-DD")
: "",
순위: item.ranking || 9999,
};
});
// 워크시트 생성
const worksheet = XLSX.utils.json_to_sheet(data);
// 바로가기 컬럼에 하이퍼링크 추가 (B열, 2번째 컬럼)
articles.forEach((item, index) => {
const cellAddress = `B${index + 2}`; // 헤더가 1행이므로 데이터는 2행부터
const url = `https://fin.land.naver.com/articles/${item.articleNumber}`;
worksheet[cellAddress] = {
t: "s", // string type
v: "열기", // 표시되는 텍스트
l: { Target: url }, // hyperlink
};
});
// 컬럼 너비 설정
worksheet["!cols"] = [
{ wch: 12 }, // 매물번호
{ wch: 10 }, // 바로가기
{ wch: 10 }, // 소유자구분
{ wch: 12 }, // 매물형태
{ wch: 10 }, // 매매구분
{ wch: 30 }, // 주소
{ wch: 40 }, // 상세주소
{ wch: 8 }, // 층수
{ wch: 14 }, // 가격
{ wch: 30 }, // 매물특징
{ wch: 14 }, // 광고사
{ wch: 10 }, // 확인일자
{ wch: 6 }, // 순위
];
// 워크북 생성
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "매물목록");
// 파일 저장
const filePath = `./xlsx/${dayjs().format("YYYY-MM-DD_HH-mm")}_순위_${
telegramUser.site
}_${telegramUser.realtorId}.xlsx`;
XLSX.writeFile(workbook, filePath);
return filePath;
}
async function updateRanking() {
const checkDate = dayjs().toDate();
for (let realtorId of realtorIds) {
const articles = await prisma.realEstateArticle.findMany({
where: {
isActive: true,
realtorId: realtorId,
},
});
const rankingService = new RankingService(
new NaverRealEstate({
realtorId: realtorId,
})
);
await rankingService.updateRanking(articles, checkDate);
}
}
main();