diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-18 07:52:22 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-18 07:52:22 +0000 |
| commit | a3b5cc9250978080a3ad269cf6c66d15de72355a (patch) | |
| tree | 47314aee7a09ffc21daf7f57316b836ff460235d /lib/knox-api/messenger/messenger.ts | |
| parent | 48a2255bfc45ffcfb0b39ffefdd57cbacf8b36df (diff) | |
| parent | 5cb225e9cd6b0ba2f52572a3afa0de6e5b2a2ece (diff) | |
Merge remote-tracking branch 'origin/approval' into dujinkim
Diffstat (limited to 'lib/knox-api/messenger/messenger.ts')
| -rw-r--r-- | lib/knox-api/messenger/messenger.ts | 925 |
1 files changed, 925 insertions, 0 deletions
diff --git a/lib/knox-api/messenger/messenger.ts b/lib/knox-api/messenger/messenger.ts new file mode 100644 index 00000000..58611621 --- /dev/null +++ b/lib/knox-api/messenger/messenger.ts @@ -0,0 +1,925 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use server" + +import { z } from 'zod'; + +// 환경 변수 타입 정의 +const MESSENGER_BASE_URL = process.env.MESSENGER_BASE_URL || 'https://api.messenger.com'; +const ACCESS_TOKEN = process.env.MESSENGER_ACCESS_TOKEN; +const DEVICE_ID = process.env.MESSENGER_DEVICE_ID; + +// 공통 헤더 생성 함수 +function createHeaders(additionalHeaders?: Record<string, string>) { + const headers: Record<string, string> = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${ACCESS_TOKEN}`, + 'x-device-id': DEVICE_ID!, + 'x-device-type': 'relation', + ...additionalHeaders + }; + + return headers; +} + +// 응답 타입 정의 +type ApiResponse<T> = { + success: boolean; + data?: T; + error?: string; + statusCode?: number; +}; + +// 에러 핸들링 함수 +function handleApiError(error: any, endpoint: string): ApiResponse<never> { + console.error(`Knox Messenger API Error (${endpoint}):`, error); + + return { + success: false, + error: error.message || `Failed to call ${endpoint}`, + statusCode: error.status || 500 + }; +} + +// ============================================================================= +// 연락처 관리 API +// ============================================================================= + +// 연락처 추가 스키마 +const addContactSchema = z.object({ + contactList: z.array(z.object({ + userID: z.number() + })) +}); + +// 연락처 추가 +export async function addContact(data: z.infer<typeof addContactSchema>): Promise<ApiResponse<any>> { + try { + const validated = addContactSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/contact/o1/user`, { + method: 'POST', + headers: createHeaders(), + body: JSON.stringify(validated) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to add contact', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'addContact'); + } +} + +// 연락처 삭제 스키마 +const deleteContactSchema = z.object({ + contactList: z.array(z.object({ + userID: z.number() + })) +}); + +// 연락처 삭제 +export async function deleteContact(data: z.infer<typeof deleteContactSchema>): Promise<ApiResponse<any>> { + try { + const validated = deleteContactSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/contact/o1/user/delete`, { + method: 'POST', + headers: createHeaders(), + body: JSON.stringify(validated) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to delete contact', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'deleteContact'); + } +} + +// 연락처 목록 조회 +export async function getContactList(): Promise<ApiResponse<any>> { + try { + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/contact/o1/list`, { + method: 'GET', + headers: createHeaders() + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get contact list', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getContactList'); + } +} + +// 사용자 검색 스키마 +const searchUserSchema = z.object({ + singleIdList: z.array(z.object({ + singleId: z.string() + })) +}); + +// 사용자 검색 +export async function searchUserByLoginId(data: z.infer<typeof searchUserSchema>): Promise<ApiResponse<any>> { + try { + const validated = searchUserSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/profile/o1/search/loginid`, { + method: 'POST', + headers: createHeaders(), + body: JSON.stringify(validated) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to search user', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'searchUserByLoginId'); + } +} + +// ============================================================================= +// 장치 관리 API +// ============================================================================= + +// 장치 등록 +export async function registerDevice(): Promise<ApiResponse<any>> { + try { + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/device/o1/reg`, { + method: 'GET', + headers: createHeaders() + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to register device', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'registerDevice'); + } +} + +// 장치 해지 스키마 +const deregisterDeviceSchema = z.object({ + deviceServerID: z.number() +}); + +// 장치 해지 +export async function deregisterDevice(data: z.infer<typeof deregisterDeviceSchema>): Promise<ApiResponse<any>> { + try { + const validated = deregisterDeviceSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/device/o1/delete`, { + method: 'POST', + headers: createHeaders(), + body: JSON.stringify(validated) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to deregister device', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'deregisterDevice'); + } +} + +// 장치 사용 정보 조회 스키마 +const getDeviceUseInfoSchema = z.object({ + singleIdList: z.array(z.object({ + singleId: z.string() + })) +}); + +// 장치 사용 정보 조회 +export async function getDeviceUseInfo(data: z.infer<typeof getDeviceUseInfoSchema>): Promise<ApiResponse<any>> { + try { + const validated = getDeviceUseInfoSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/contact/api/v1.0/device/o1/use/info`, { + method: 'POST', + headers: createHeaders(), + body: JSON.stringify(validated) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get device use info', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getDeviceUseInfo'); + } +} + +// ============================================================================= +// 파일 관리 API +// ============================================================================= + +// 파일 서버 시간 및 키 조회 스키마 +const getFileServerTimeSchema = z.object({ + word: z.string() +}); + +// 파일 서버 시간 및 키 조회 +export async function getFileServerTime(data: z.infer<typeof getFileServerTimeSchema>): Promise<ApiResponse<any>> { + try { + const validated = getFileServerTimeSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/file/api/v1.0/file/v1/getCurrentTime?word=${validated.word}`, { + method: 'GET', + headers: createHeaders({ + 'x-access-token': ACCESS_TOKEN! + }) + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get file server time', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getFileServerTime'); + } +} + +// 파일 업로드 스키마 +const uploadFileSchema = z.object({ + filename: z.string(), + fileData: z.instanceof(FormData).or(z.any()) +}); + +// 파일 업로드 +export async function uploadFile(data: z.infer<typeof uploadFileSchema>): Promise<ApiResponse<any>> { + try { + const validated = uploadFileSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/file/api/v1.0/file/v1s/file/${validated.filename}`, { + method: 'PUT', + headers: createHeaders({ + 'x-access-token': ACCESS_TOKEN!, + 'Content-Type': 'multipart/form-data' + }), + body: validated.fileData + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to upload file', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'uploadFile'); + } +} + +// 파일 다운로드 스키마 +const downloadFileSchema = z.object({ + fileId: z.string() +}); + +// 파일 다운로드 +export async function downloadFile(data: z.infer<typeof downloadFileSchema>): Promise<ApiResponse<any>> { + try { + const validated = downloadFileSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/file/api/v1.0/file/v1s/file/${validated.fileId}`, { + method: 'GET', + headers: createHeaders({ + 'x-access-token': ACCESS_TOKEN! + }) + }); + + if (!response.ok) { + return { + success: false, + error: 'Failed to download file', + statusCode: response.status + }; + } + + const blob = await response.blob(); + + return { + success: true, + data: blob + }; + } catch (error) { + return handleApiError(error, 'downloadFile'); + } +} + +// ============================================================================= +// 메시지 관리 API +// ============================================================================= + +// 메시지 서버 키 조회 +export async function getMessageServerKeys(): Promise<ApiResponse<any>> { + try { + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/msgctx/api/v1.0/key/getkeys`, { + method: 'GET', + headers: createHeaders() + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get message server keys', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getMessageServerKeys'); + } +} + +// 대화방 참여자 정보 조회 스키마 +const getChatMembersSchema = z.object({ + chatroomId: z.number() +}); + +// 대화방 참여자 정보 조회 +export async function getChatMembers(data: z.infer<typeof getChatMembersSchema>): Promise<ApiResponse<any>> { + try { + const validated = getChatMembersSchema.parse(data); + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/msgctx/api/v1.0/chat/activemember?chatroomId=${validated.chatroomId}`, { + method: 'GET', + headers: createHeaders() + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get chat members', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getChatMembers'); + } +} + +// 대화방 생성 스키마 +const createChatroomSchema = z.object({ + requestId: z.number(), + chatType: z.number(), // 0: 1:1, 1: GROUP, 2: BROADCAST GROUP, 5: BROADCAST SINGLE + receivers: z.array(z.number()), + chatroomTitle: z.string().optional() +}); + +// 대화방 생성 +export async function createChatroom(data: z.infer<typeof createChatroomSchema>): Promise<ApiResponse<any>> { + try { + const validated = createChatroomSchema.parse(data); + + // TODO: 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/createChatroomRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to create chatroom', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'createChatroom'); + } +} + +// 메시지 발송 스키마 +const sendMessageSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + chatMessageParams: z.array(z.object({ + msgId: z.number(), + msgType: z.number(), // 0: TEXT, 1: MEDIA, 7: RTF, 8: NCUSTOM + chatMsg: z.string(), + msgTtl: z.number().optional() + })) +}); + +// 메시지 발송 +export async function sendMessage(data: z.infer<typeof sendMessageSchema>): Promise<ApiResponse<any>> { + try { + const validated = sendMessageSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/chatRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to send message', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'sendMessage'); + } +} + +// 대화상대 초대 스키마 +const inviteUserSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + invitingMembers: z.array(z.number()) +}); + +// 대화상대 초대 +export async function inviteUser(data: z.infer<typeof inviteUserSchema>): Promise<ApiResponse<any>> { + try { + const validated = inviteUserSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/inviteRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to invite user', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'inviteUser'); + } +} + +// 대화상대 내보내기 스키마 +const removeUserSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + removingMembers: z.array(z.number()) +}); + +// 대화상대 내보내기 +export async function removeUser(data: z.infer<typeof removeUserSchema>): Promise<ApiResponse<any>> { + try { + const validated = removeUserSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/removeMemberRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to remove user', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'removeUser'); + } +} + +// 대화방 삭제 스키마 +const deleteChatroomSchema = z.object({ + requestId: z.number(), + chatroomId: z.number() +}); + +// 대화방 삭제 +export async function deleteChatroom(data: z.infer<typeof deleteChatroomSchema>): Promise<ApiResponse<any>> { + try { + const validated = deleteChatroomSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/destroyChatroomRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to delete chatroom', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'deleteChatroom'); + } +} + +// 메시지 발신취소 스키마 +const recallMessageSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + msgId: z.number() +}); + +// 메시지 발신취소 +export async function recallMessage(data: z.infer<typeof recallMessageSchema>): Promise<ApiResponse<any>> { + try { + const validated = recallMessageSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/recallMessageRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to recall message', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'recallMessage'); + } +} + +// 대화방명 변경 스키마 +const changeChatroomNameSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + chatroomTitle: z.string() +}); + +// 대화방명 변경 +export async function changeChatroomName(data: z.infer<typeof changeChatroomNameSchema>): Promise<ApiResponse<any>> { + try { + const validated = changeChatroomNameSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/changeChatroomMetaRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to change chatroom name', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'changeChatroomName'); + } +} + +// 방장 변경 스키마 +const changeOwnerSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + newOwnerUserId: z.number() +}); + +// 방장 변경 +export async function changeOwner(data: z.infer<typeof changeOwnerSchema>): Promise<ApiResponse<any>> { + try { + const validated = changeOwnerSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/changeOwnerRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to change owner', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'changeOwner'); + } +} + +// 대화방 나가기 스키마 +const leaveChatroomSchema = z.object({ + requestId: z.number(), + chatroomId: z.number() +}); + +// 대화방 나가기 +export async function leaveChatroom(data: z.infer<typeof leaveChatroomSchema>): Promise<ApiResponse<any>> { + try { + const validated = leaveChatroomSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/endChatRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to leave chatroom', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'leaveChatroom'); + } +} + +// 메시지 읽음 카운트 조회 스키마 +const getMessageReadCountSchema = z.object({ + requestId: z.number(), + chatroomId: z.number(), + msgId: z.number() +}); + +// 메시지 읽음 카운트 조회 +export async function getMessageReadCount(data: z.infer<typeof getMessageReadCountSchema>): Promise<ApiResponse<any>> { + try { + const validated = getMessageReadCountSchema.parse(data); + + // 실제 구현에서는 메시지 서버 키를 사용하여 암호화해야 함 + const encryptedBody = JSON.stringify(validated); // 임시로 평문 사용 + + const response = await fetch(`${MESSENGER_BASE_URL}/messenger/message/api/v1.0/message/messageReadCountRequest`, { + method: 'POST', + headers: createHeaders(), + body: encryptedBody + }); + + const result = await response.json(); + + if (!response.ok) { + return { + success: false, + error: result.message || 'Failed to get message read count', + statusCode: response.status + }; + } + + return { + success: true, + data: result + }; + } catch (error) { + return handleApiError(error, 'getMessageReadCount'); + } +} + +// ============================================================================= +// 유틸리티 함수 +// ============================================================================= + +// 현재 시간을 밀리초로 반환 (requestId 생성용) +export async function generateRequestId(): Promise<number> { + return Date.now(); +} + +// 메시지 ID 생성 (각 메시지마다 고유한 ID 필요) +export async function generateMessageId(): Promise<number> { + return Date.now() + Math.floor(Math.random() * 1000); +} + +// 헬퍼 함수 - 간단한 텍스트 메시지 발송 +export async function sendTextMessage(chatroomId: number, message: string): Promise<ApiResponse<any>> { + const requestId = await generateRequestId(); + const msgId = await generateMessageId(); + + return sendMessage({ + requestId, + chatroomId, + chatMessageParams: [{ + msgId, + msgType: 0, // TEXT + chatMsg: message, + msgTtl: 7200 // 2시간 + }] + }); +} + +// 헬퍼 함수 - 그룹 대화방 생성 +export async function createGroupChatroom(userIds: number[], title?: string): Promise<ApiResponse<any>> { + const requestId = await generateRequestId(); + + return createChatroom({ + requestId, + chatType: 2, // BROADCAST GROUP + receivers: userIds, + chatroomTitle: title + }); +} + +// 헬퍼 함수 - 1:1 대화방 생성 +export async function createPrivateChatroom(userId: number): Promise<ApiResponse<any>> { + const requestId = await generateRequestId(); + + return createChatroom({ + requestId, + chatType: 5, // BROADCAST SINGLE + receivers: [userId] + }); +} |
