Compare commits
5 Commits
ad8b47d4ec
...
547d5e3dd5
| Author | SHA1 | Date |
|---|---|---|
|
|
547d5e3dd5 | |
|
|
60520c81fa | |
|
|
edfacc4db3 | |
|
|
8b9b14a6ae | |
|
|
855aad274a |
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
trigger: model_decision, subagent_delegation
|
||||
description: 모델 등급별 역할 분담 및 서브에이전트 활용 규칙
|
||||
---
|
||||
|
||||
# Tiered Model Workflow & Subagent Delegation Rule
|
||||
|
||||
복잡한 시스템 기획과 효율적 실행을 분리하여 리소스를 최적화하고 작업 속도를 향상시키기 위한 모델/서브에이전트 운용 원칙입니다. Kord 프로젝트 내에서 수행되는 모든 에이전트 작업에 이 원칙을 우선 적용합니다.
|
||||
|
||||
## 1. 모델 전환 권고 기준 (Model Switching Guide)
|
||||
|
||||
에이전트는 사용자의 요청을 분석한 후, 필요에 따라 사용자의 개입을 요청하여 적절한 모델 등급으로의 변경을 제안해야 합니다. (에이전트 스스로 모델 전환을 직접 수행할 수 없기 때문입니다.)
|
||||
|
||||
- **High-Tier Model (예: Gemini 3.1 Pro/Ultra 등) 권장 상황**:
|
||||
- 모노레포 구조 설계 문제 혹은 대규모 아키텍처 변경.
|
||||
- 심도 깊은 설계 단계 리서치, 다수 파일이 연계된 복잡한 버그 추적.
|
||||
- **→ 응답 가이드**: *"진행하려는 작업은 높은 논리성과 깊은 추론을 요구합니다. 본 기획/설계를 마치기 위해 저를 높은 성능의 Pro/Ultra 모델로 전환해 주시면 더 정교한 계획 수립이 가능합니다."*
|
||||
|
||||
- **Efficient Model (예: Gemini 3 Flash 등) 권장 상황**:
|
||||
- 이미 수립되고 승인된 `implementation_plan.md`에 근거한 단순 코드 구현 및 기계적 실행.
|
||||
- 패턴화된 파일 리팩토링, 단순 단위 테스트 실행, 문서 정리 및 색인.
|
||||
- **→ 응답 가이드**: *"계획 수립이 확정되었고, 남은 것은 구현과 반복적인 테스트입니다. 효율성과 응답 속도 향상을 위해 실행 모델(Flash)로 전환하셔도 무방합니다."*
|
||||
|
||||
## 2. 서브에이전트 위임 (Subagent Delegation)
|
||||
|
||||
복잡한 작업을 모두 메인 에이전트가 단일 프롬프트로 처리하려 하지 말고, 특화된 작업은 적절히 쪼개 병렬 혹은 서브 Task로 위임합니다.
|
||||
|
||||
- **작업의 분할 (Task Decomposition)**:
|
||||
- 실행 단계(Phase 2) 돌입 시, 작업을 독립적인 단위로 쪼개어 Task 목록화합니다.
|
||||
- **브라우저 서브에이전트 (`browser_subagent`) 활용 필수 상황**:
|
||||
- 대시보드의 특정 UI가 의도대로 렌더링되는지 눈으로 확인이 필요한 경우 (예: Next.js 로컬 구동 결과 확인).
|
||||
- 웹 페이지를 통한 로그인, OAuth, 동적인 요소 추출이나 브라우저 기반 에러 로그 확인이 필요한 경우.
|
||||
- 에이전트는 해당 작업을 서브에이전트용 특화 프롬프트(Task)로 명확히 분리하여 `browser_subagent` 툴에 인가하고, 이후 반환된 DOM 상태나 스크린샷 결과를 메인 작업의 컨텍스트(Walkthrough 등)에 결합해야 합니다.
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
trigger: model_decision
|
||||
description: Discord Bot UI/UX Design Philosophy
|
||||
---
|
||||
|
||||
|
|
@ -7,21 +8,25 @@ description: Discord Bot UI/UX Design Philosophy
|
|||
When designing or updating Discord command interfaces (Embeds, Components), adhere to the following UI/UX philosophy to ensure a clean, intuitive, and modern user experience.
|
||||
|
||||
## 1. Minimal and Non-redundant Information (중복 정보 최소화)
|
||||
|
||||
- Do not display information in the Embed that is already visually apparent in the UI components.
|
||||
- For example, if a `RoleSelectMenuBuilder` allows the user to select roles, use `.addDefaultRoles(ids)` (available in discord.js 14.14+) to display the currently selected roles natively inside the dropdown menu.
|
||||
- Do NOT list those same roles redundantly as text inside the Embed fields. The Embed should remain concise, showing only titles and essential descriptions or instructions.
|
||||
|
||||
## 2. Implicit State (명시적 토글 지양 및 상태 직관화)
|
||||
|
||||
- Avoid creating manual On/Off toggle buttons unless absolutely necessary.
|
||||
- Derive the "Enabled/Disabled" state directly from the user's data naturally. For instance, if the user has selected at least one role (`roleIds.length > 0`), the feature is automatically considered "Active/Enabled". If they clear the selection, the feature is "Disabled".
|
||||
- This reduces UI clutter (removing unnecessary toggle ActionRows) and aligns with modern design patterns where state implicitly follows the presence of data.
|
||||
|
||||
## 3. Persistent and Seamless Interaction (매끄러운 대시보드 유지)
|
||||
|
||||
- Component interactions should feel fast and seamless without fragmenting the chat history.
|
||||
- Always immediately call `await interaction.deferUpdate();` (or equivalent) when handling components (buttons, select menus) to prevent "Unknown interaction" timeout errors.
|
||||
- Use `await interaction.editReply(...)` with the newly generated UI components to seamlessly update the dashboard frame in place.
|
||||
- Do NOT generate new follow-up messages or close the menu unilaterally when the user still expects to tweak settings.
|
||||
|
||||
## 4. Safe Response Timings (타임아웃 방지)
|
||||
|
||||
- When processing `ChatInputCommandInteraction` that might involve a database cold-start connection or external API calls, proactively call `await interaction.deferReply({ ephemeral: true });` right at the start.
|
||||
- Update the UI with `await interaction.editReply(...)` once business logic resolves, bypassing Discord's strict 3-second timeout limitation and preventing crashes during initial boot load.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ description: work routine
|
|||
## 기본 원칙 (Work Rules)
|
||||
|
||||
1. **인프라 자율 사용**: 에이전트는 프로젝트에 설정된 Docker 기반 인프라(PostgreSQL 등)를 사용자의 추가 승인 없이 자유롭게 구동(`docker-compose up -d`) 및 활용할 수 있습니다.
|
||||
2. **협력적 기획, 독립적 실행**: 기능 기획과 설계(Architecture, Schema 등)는 사용자와 함께 논리적인 완결성을 갖출 때까지 충분히 논의합니다. 기획이 "완료 및 승인"된 후에는 후속 구현, 에러 디버깅, 자체 테스트를 추가적인 중간 확인 없이 에이전트가 주도를 가지고 끝마친 뒤 최종 결과를 보고합니다.
|
||||
2. **협력적 기획, 독립적 실행**: 기능 기획과 설계(Architecture, Schema 등)는 사용자와 함께 논리적인 완결성을 갖출 때까지 충분히 논의합니다. 특히 시스템적 자동 승인(Auto-approval) 메시지가 있더라도, 반드시 사용자의 **직접적이고 명시적인 승인 답변**이 확인된 후에만 2단계(구현)로 진입합니다. 기획이 수동으로 승인된 후에는 후속 구현, 에러 디버깅, 자체 테스트를 추가적인 중간 확인 없이 에이전트가 주도를 가지고 끝마칩니다.
|
||||
3. **선 문서화, 후 보고 (Docs First, Report Later)**: 모든 작업의 완료 보고는 반드시 `Docs/` 내의 문서 업데이트가 선행되어야 합니다. 문서화가 누락된 상태에서의 "작업 완료" 보고는 규칙 위반으로 간주합니다.
|
||||
|
||||
## 단계별 작업 루틴
|
||||
|
||||
|
|
@ -18,12 +19,13 @@ description: work routine
|
|||
|
||||
- 사용자가 새로운 기능이나 수정 사항을 요청하면, 필요한 스펙 및 예외 사항을 꼼꼼히 확인합니다.
|
||||
- 명령어를 파편화하지 말고, 관련 있는 기능들을 하나의 대표 명령어 아래 '서브 커맨드' 형태로 그룹화하여 설계할 것
|
||||
- 변경 사항, 사용 스택, 아키텍처를 포함한 `implementation_plan.md`를 작성하거나 업데이트하여 사용자에게 검토 및 승인을 요청합니다.
|
||||
- 필요 시 사용자에게 High-Tier 아키텍처 모델(예: Pro/Ultra)로의 전환을 제안하며, 변경 사항, 사용 스택, 아키텍처를 포함한 `implementation_plan.md`를 작성하여 사용자에게 검토 및 승인을 요청합니다. 설계가 완료되면 효율적 실행을 위한 모델 전환(예: Flash) 권고를 포함합니다.
|
||||
|
||||
### 2단계: 개발 및 구현 (Execution Phase)
|
||||
|
||||
- 설계가 최종 승인되면 실제 코딩을 시작합니다. 이 단계부터는 사용자의 개입 없이 독립적으로 작업을 완수하는 것을 원칙으로 합니다.
|
||||
- 환경 변수 파싱, 로깅, 예외 처리를 철저히 포함하여 프로덕션 수준의 코드를 작성합니다.
|
||||
- 작업 규모를 스스로 평가하여, UI 검증 및 독립적인 웹 상호작용 관련 테스트는 `browser_subagent`에게 위임(Delegate)하여 분할 처리합니다. (`agent_model_workflow.md` 참조)
|
||||
|
||||
### 3단계: 자체 구동 및 내부 테스트 (Internal Testing Phase)
|
||||
|
||||
|
|
@ -44,5 +46,6 @@ description: work routine
|
|||
|
||||
- 3단계 구현 및 테스트가 성공적으로 완료되면, 사용자에게 최종 보고하기 **전에 반드시 먼저** `<PROJECT_ROOT>/Docs/` 디렉토리에 작업 완료(Work done), 트러블슈팅(Troubleshooting), 의사 결정(Decisions made) 내역을 문서화해야 합니다.
|
||||
- 새 문서가 생성되거나 수정되면 자동으로 `Docs/index.md`에 문서의 색인(링크)을 추가합니다.
|
||||
- 모든 코드 작업 내역과 의사 결정이 완전히 로컬 `Docs/`에 기록 및 정리된 후에만 비로소 "작업을 완료했다"고 사용자에게 알립니다.
|
||||
- 모든 코드 작업 내역과 의사 결정이 완전히 로컬 `Docs/`에 기록 및 정리된 후에만 비로소 "작업을 완료했다"고 사용자에게 알립니다. **문서화가 완료되지 않은 상태에서 사용자에게 보고하는 것은 엄격히 금지됩니다.**
|
||||
- 설치, 테스트 방법, 구동, 기능, 명령어 등을 위한 변경사항을 <PROJECT_ROOT>/README.md에 최신화합니다.
|
||||
- **최종 검크포인트**: 보고 메시지 작성 직전, `Docs/` 폴더와 `README.md`, `index.md`가 최신 상태인지 다시 한번 전수 점검합니다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{"files":{"packages/db/.turbo/turbo-generate.log":{"size":401,"mtime_nanos":1776651586075885272,"mode":420,"is_dir":false}},"order":["packages/db/.turbo/turbo-generate.log"]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"hash":"29fcb16557ff68aa","duration":1221,"sha":"855aad274ad148847ffefa8e54eb8fa8066713fa","dirty_hash":"5906811204abfbb072123bc0cbfc794bb506b732c5875205b21f947edcc57687"}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
{"hash":"63605b2509e03797","duration":1834,"sha":"855aad274ad148847ffefa8e54eb8fa8066713fa","dirty_hash":"d0414de747ab562fdf8628ad70f7f928ce881c495f15765d4afcb0621966da17"}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,33 @@
|
|||
# 결정 사항: 대시보드-봇 통신 아키텍처 (gRPC Proxy)
|
||||
|
||||
## 배경 (Context)
|
||||
|
||||
대시보드(Next.js)와 멀티 인스턴스 샤딩 환경의 봇 간에 실시간 통신이 필요합니다. 대시보드는 특정 길드의 설정을 변경하거나 채널 목록을 조회해야 하지만, 봇이 여러 프로세스(Shard)로 쪼개져 있어 어떤 인스턴스가 해당 길드를 관리하는지 대시보드가 알기 어려운 문제가 있습니다.
|
||||
|
||||
## 결정된 사항 (Decision)
|
||||
|
||||
1. **통신 프로토콜**: **gRPC (HTTP/2)** 선택
|
||||
- 강력한 타입 시스템(`packages/grpc-contracts`) 공유를 위해 선택했습니다.
|
||||
- 대량의 데이터 전송 및 실시간 양방향 통신 확장에 유리합니다.
|
||||
|
||||
2. **Manager as API Proxy 패턴 채택**
|
||||
- **구조**: Dashboard <-> ShardingManager (gRPC Server) <-> Shards (IPC)
|
||||
- 모든 대시보드 요청은 단 하나의 포트(`50051`)를 가진 `ShardingManager`로 집중됩니다.
|
||||
- 매니저는 `guildId` 등의 키를 확인하여 내부적으로 `broadcastEval` 또는 `IPC`를 통해 해당 Shard에게 업무를 하달합니다.
|
||||
|
||||
3. **데이터 보관 전략**: **DB (Prisma) 중심**
|
||||
- 실시간성이 극도로 중요한 요청 외의 설정값 변경은 DB를 우선 업데이트하고, 봇이 이를 캐시 동기화하도록 인터페이스를 구성합니다.
|
||||
|
||||
## 장점 (Pros)
|
||||
|
||||
- **인프라 간소화**: 봇 인스턴스가 100개로 늘어나도 대시보드 입장에서는 `50051` 포트 하나만 바라보면 됩니다.
|
||||
- **포트 충돌 방지**: 각 샤드 워커마다 별도의 서버 포트를 할당할 필요가 없습니다.
|
||||
- **코드 공유**: 모노레포 구조를 통해 클라이언트와 서버가 동일한 `.proto` 계약을 공유합니다.
|
||||
|
||||
## 단점 (Cons)
|
||||
|
||||
- **매니저 오버헤드**: 매니저가 모든 통신을 중계하므로, 통신량이 극도로 많아질 경우 매니저가 병목이 될 수 있습니다. (이 경우 추후 Redis Pub/Sub으로 전환 고려)
|
||||
|
||||
## 대안 (Alternatives)
|
||||
|
||||
- **Redis Pub/Sub**: 가장 유연하지만 추가 인프라(Redis) 관리가 필요합니다. 현재는 단일 서버 환경이므로 gRPC Proxy가 더 효율적이라고 판단했습니다.
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# 2026-04-20: 모노레포 전환 및 gRPC 통신 테스트 완료 (Monorepo & gRPC Test)
|
||||
|
||||
대시보드 도입을 위한 프로젝트 구조 개편과 봇-대시보드 간의 실시간 통신 인프라를 구축하고 검증하였습니다.
|
||||
|
||||
## 작업 내용 (Work Done)
|
||||
|
||||
### 1. 프로젝트 모노레포(Monorepo) 화
|
||||
- **Turborepo & Yarn Workspaces** 도입: 프로젝트를 모듈화하여 관리하기 위해 모노레포 구조로 전환했습니다.
|
||||
- `apps/bot`: 기존 디스코드 봇 로직 이관.
|
||||
- `apps/dashboard`: Next.js 기반 웹 대시보드 신규 생성.
|
||||
- `packages/db`: Prisma 스키마 및 DB 접근 로직을 공용 패키지로 분리.
|
||||
- `packages/grpc-contracts`: gRPC 프로토콜 버퍼와 인터페이스 정의를 위한 공용 패키지 생성.
|
||||
|
||||
### 2. 봇 샤딩 및 상태 관리 고도화
|
||||
- **ShardingManager 도입**: 봇 프로세스를 분할 관리하고 gRPC 서버를 부착하기 위해 최상위 매니저 레이어를 추가했습니다.
|
||||
- **ShardStatus 추적**: 각 Shard가 실행될 때 자신의 상태와 담당 길드 정보를 DB(`ShardStatus` 테이블)에 기록하도록 구현했습니다.
|
||||
|
||||
### 3. gRPC 통신 프록시 서버 구축
|
||||
- **단일 포트 gRPC 서버**: `ShardingManager` 단에서 `50051` 포트로 gRPC 서버를 실행합니다.
|
||||
- **메시지 라우팅**: 대시보드로부터 요청이 들어오면 매니저가 `broadcastEval`을 통해 해당 길드를 담당하는 하위 Shard 프로세스로 요청을 전달(Proxy)합니다.
|
||||
|
||||
### 4. 대시보드 gRPC 통신 테스트 완료
|
||||
- **테스트 환경 구성**: Next.js API Route를 통해 봇에게 `Ping`을 쏘고 `Pong` 응답을 받는 라이프사이클을 구현했습니다.
|
||||
- **UI 검증**: 대시보드 메인 페이지에서 버튼 클릭 시 봇으로부터 성공적으로 응답을 받아 `response`를 렌더링함을 확인했습니다.
|
||||
|
||||
## 테스트 결과 (Results)
|
||||
|
||||
- **연결 성공**: 대시보드 -> 매니저 -> (방송) -> 봇 워커 -> 매니저 -> 대시보드 흐름이 무차별적인 포트 개방 없이 단일 통로로 성공적으로 작동함.
|
||||
- **지연 시간**: 로컬 환경 기준 10ms 이내의 빠른 응답 속도를 확인.
|
||||
|
||||
## 관련 문서
|
||||
- [대시보드 아키텍처 결정서](../Decisions/Dashboard_Architecture_gRPC.md)
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
## 아키텍처 및 정책 결정 (Decisions)
|
||||
|
||||
- [구독 티어 시스템 설계 (Subscription Tiers)](Decisions/subscription_tiers.md)
|
||||
- [대시보드-봇 통신 아키텍처 (Dashboard gRPC Architecture)](Decisions/Dashboard_Architecture_gRPC.md)
|
||||
|
||||
## 트러블슈팅 (Troubleshooting)
|
||||
|
||||
|
|
@ -67,3 +68,4 @@
|
|||
- [2026-04-07: 낚시 미니게임 Phase 2 구현 (Fishing Mini-Game Phase 2 Implementation)](WorkDone/2026-04-07_Fishing_MiniGame_Phase2_Implementation.md)
|
||||
- [2026-04-07: 낚시 도감 및 크기 시스템 구현 (Fishing Dex and Size Implementation)](WorkDone/2026-04-07_Fishing_Dex_And_Size_Implementation.md)
|
||||
- [2026-04-07: 낚시 크기 랭킹 구현 (Fishing Size Ranking Implementation)](WorkDone/2026-04-07_Fishing_Size_Ranking_Implementation.md)
|
||||
- [2026-04-20: 모노레포 전환 및 gRPC 통신 테스트 완료 (Monorepo & gRPC Test)](WorkDone/2026-04-20_Monorepo_Migration_And_gRPC_Test.md)
|
||||
|
|
|
|||
82
README.md
82
README.md
|
|
@ -1,74 +1,66 @@
|
|||
# Kord
|
||||
# Kord (Monorepo)
|
||||
|
||||
Kord는 Discord 서버 관리를 돕는 강력하고 유연한 다기능 봇입니다.
|
||||
Kord는 Discord 서버 관리를 돕는 강력하고 유연한 다기능 봇 및 전용 웹 대시보드 프로젝트입니다.
|
||||
현재 모노레포(Monorepo) 구조로 관리되고 있습니다.
|
||||
|
||||
## 1. 개요 (Overview)
|
||||
## 1. 프로젝트 구조 (Structure)
|
||||
|
||||
**Kord**는 효율적인 서버 운영을 위해 설계된 Discord 봇입니다. TypeScript와 Discord.js를 기반으로 구축되었으며, Prisma(PostgreSQL)를 활용하여 안정적이고 확장 가능한 아키텍처를 제공합니다. 임시 음성 채널 관리, 상세 감사 로그, 권한 진단 등의 핵심 기능을 통해 서버 관리자의 부담을 줄여줍니다.
|
||||
본 프로젝트는 **Turborepo**와 **Yarn Workspaces**를 사용합니다.
|
||||
|
||||
- **`apps/bot`**: Discord.js 기반의 봇 본체 (ShardingManager 적용)
|
||||
- **`apps/dashboard`**: Next.js 기반의 봇 관리 웹 대시보드
|
||||
- **`packages/db`**: Prisma 스키마 및 데이터베이스 데이터 접근 레이어 (공용)
|
||||
- **`packages/grpc-contracts`**: 봇과 대시보드 간의 gRPC 통신 규약 (공용)
|
||||
|
||||
## 2. 요구사항 (Requirements)
|
||||
|
||||
- **Runtime**: Node.js v20 이상
|
||||
- **Runtime**: Node.js v22 이상 (추천)
|
||||
- **Package Manager**: Yarn v4 (Berry)
|
||||
- **Database**: PostgreSQL (Prisma 사용)
|
||||
- **Discord**: Bot Token 및 Client ID (Slash Command 등록용)
|
||||
- **Discord**: Bot Token 및 Client ID
|
||||
|
||||
## 3. 테스트 방법 (Test Methods)
|
||||
## 3. 시작하기 (Quick Start)
|
||||
|
||||
본 프로젝트는 Jest를 사용하여 유닛 테스트 및 통합 테스트를 수행합니다.
|
||||
|
||||
- **전체 테스트 실행**:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
- **i18n 번역 누락 확인**:
|
||||
|
||||
```bash
|
||||
yarn check-i18n
|
||||
```
|
||||
|
||||
## 4. 구동 방법 (Running Methods)
|
||||
|
||||
### 로컬 개발 환경
|
||||
### 로컬 개발 환경 설정
|
||||
|
||||
1. **의존성 설치**:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
2. **환경 변수 설정**: `.env.example` 파일을 복사하여 `.env` 파일을 생성하고 필수 값을 입력합니다.
|
||||
|
||||
3. **데이터베이스 초기화**:
|
||||
2. **환경 변수 설정**: 루트 및 각 앱 디렉토리의 `.env` 설정을 완료합니다.
|
||||
|
||||
3. **데이터베이스 및 코드 생성**:
|
||||
```bash
|
||||
npx prisma migrate dev
|
||||
npx prisma generate
|
||||
yarn run generate
|
||||
```
|
||||
|
||||
4. **개발 서버 실행**:
|
||||
### 실행 방법
|
||||
|
||||
전체 프로젝트를 한꺼번에 실행하거나 개별 앱을 실행할 수 있습니다.
|
||||
|
||||
- **모든 앱 실행 (Bot + Dashboard)**:
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### 프로덕션 환경
|
||||
- **봇만 실행**:
|
||||
```bash
|
||||
yarn workspace @kord/bot dev
|
||||
```
|
||||
|
||||
1. **빌드**: `yarn build`
|
||||
2. **실행**: `yarn start`
|
||||
3. **Docker**: `docker-compose up -d`를 통해 PostgreSQL 등 로컬 인프라를 실행할 수 있습니다.
|
||||
- **대시보드만 실행**:
|
||||
```bash
|
||||
yarn workspace dashboard dev
|
||||
```
|
||||
|
||||
## 4. 아키텍처 (Architecture)
|
||||
|
||||
## 5. 기능 목록 (Feature List)
|
||||
Kord는 **gRPC Proxy** 아키텍처를 사용하여 대시보드와 샤딩된 봇 인스턴스 간의 실시간 통신을 처리합니다. 자세한 내용은 관련 문서를 참조하세요.
|
||||
- [대시보드 통신 아키텍처 가이드](Docs/Decisions/Dashboard_Architecture_gRPC.md)
|
||||
|
||||
- **임시 음성 채널 (Voice)**: 생성기(Generator) 채널을 통해 동적인 음성 채널 생성 및 관리 기능을 제공합니다.
|
||||
- **감사 로그 (Audit Log)**: 서버 내 주요 이벤트를 카테고리별(VOICE, PERMISSION, SYSTEM 등)로 세분화하여 기록합니다.
|
||||
- **서버 설정 (Config)**: 따라하기(Mimic), 큰 이모지(Big Emoji) 등 봇의 기능을 서버별 환경에 맞게 토글하거나 설정할 수 있습니다.
|
||||
- **권한 감사 (Permission Audit)**: 봇의 정상 작동을 방해하는 권한 문제를 즉시 진단하고 해결 방법을 안내합니다.
|
||||
- **초기 설정 마법사 (Setup Wizard)**: 봇 도입 초기 기능을 한눈에 설정할 수 있는 직관적인 UI를 제공합니다.
|
||||
- **미니게임 시스템 (Mini-Games)**: 서버 내에서 즐길 수 있는 다양한 미니게임을 제공하며, 관리자가 게임별 활성화 및 전용 채널을 설정할 수 있습니다.
|
||||
- **재련 (Refinement)**: 무기를 강화하고 다른 유저와 전투하며 골드를 획득하는 성장형 미니게임입니다.
|
||||
- **피버 타임 (Fever Time)**: 서버 활동량을 자동으로 분석하여 가장 활발한 시간대에 재련 성공 확률 보너스를 제공합니다.
|
||||
- **다국어 지원 (i18n)**: 한국어와 영어를 포함한 다국어 환경을 지원합니다.
|
||||
## 5. 문서 (Documentation)
|
||||
|
||||
모둔 상세 문서는 `Docs/` 디렉토리에 위치합니다.
|
||||
- [문서 전체 색인 (Docs Index)](Docs/index.md)
|
||||
- [로컬 가이드북 (SKILL.md)](SKILL.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "@kord/bot",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"test": "jest",
|
||||
"check-i18n": "tsx scripts/check-i18n-tests.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.10.0",
|
||||
"@discordjs/voice": "^0.19.2",
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@kord/db": "workspace:*",
|
||||
"@kord/grpc-contracts": "workspace:*",
|
||||
"discord.js": "^14.25.1",
|
||||
"dotenv": "^17.3.1",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"log4js": "^6.9.1",
|
||||
"prism-media": "^1.3.5",
|
||||
"sharp": "^0.34.5",
|
||||
"youtubei.js": "^17.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.5.0",
|
||||
"jest": "^30.3.0",
|
||||
"ts-jest": "^29.4.6",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import { auditLogService } from '../services/AuditLogService';
|
|||
|
||||
|
||||
import { env } from '../config/env';
|
||||
|
||||
import { PrismaShardStatusRepository, prisma } from '@kord/db';
|
||||
export default {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
|
|
@ -18,6 +18,12 @@ export default {
|
|||
PresenceService.startActivePresence(client);
|
||||
EventService.startReminderLoop(client);
|
||||
|
||||
const shardId = client.shard?.ids[0] ?? 0;
|
||||
const guildIds = Array.from(client.guilds.cache.keys());
|
||||
const shardRepo = new PrismaShardStatusRepository(prisma);
|
||||
await shardRepo.upsertStatus(shardId, 'READY', guildIds)
|
||||
.catch((e: Error) => logger.error('Failed to update shard status:', e));
|
||||
|
||||
try {
|
||||
const commandsData = Array.from(client.commands.values()).map(c => c.data.toJSON());
|
||||
await client.application?.commands.set(commandsData);
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { ShardingManager } from 'discord.js';
|
||||
import path from 'path';
|
||||
import 'dotenv/config';
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import { kordProto } from '@kord/grpc-contracts';
|
||||
|
||||
const manager = new ShardingManager(path.resolve(__dirname, 'index.ts'), {
|
||||
token: process.env.DISCORD_TOKEN,
|
||||
execArgv: ['-r', 'tsx'], // Allow running ts files natively in dev
|
||||
});
|
||||
|
||||
manager.on('shardCreate', (shard) => {
|
||||
console.log(`Launched shard ${shard.id}`);
|
||||
|
||||
shard.on('ready', () => {
|
||||
console.log(`Shard ${shard.id} is ready`);
|
||||
});
|
||||
|
||||
shard.on('disconnect', () => {
|
||||
console.warn(`Shard ${shard.id} disconnected`);
|
||||
});
|
||||
|
||||
shard.on('reconnecting', () => {
|
||||
console.warn(`Shard ${shard.id} reconnecting`);
|
||||
});
|
||||
});
|
||||
|
||||
// Spawn the required number of shards
|
||||
manager.spawn().then(() => {
|
||||
// --- gRPC Proxy Server Setup ---
|
||||
const server = new grpc.Server();
|
||||
|
||||
server.addService((kordProto as any).BotDashboardService.service, {
|
||||
Ping: (call: any, callback: any) => {
|
||||
console.log('Received Ping:', call.request.message);
|
||||
callback(null, { reply: `Pong to ${call.request.message}` });
|
||||
},
|
||||
GetGuildChannels: async (call: any, callback: any) => {
|
||||
const guildId = call.request.guildId;
|
||||
try {
|
||||
const results = await manager.broadcastEval(
|
||||
(c, context) => {
|
||||
const guild = c.guilds.cache.get(context.guildId);
|
||||
if (!guild) return null;
|
||||
return guild.channels.cache.map(ch => ({ id: ch.id, name: ch.name, type: `${ch.type}` }));
|
||||
},
|
||||
{ context: { guildId } }
|
||||
);
|
||||
|
||||
const channels = results.find(res => res !== null) || [];
|
||||
callback(null, { channels });
|
||||
} catch (error: any) {
|
||||
callback({ code: grpc.status.INTERNAL, details: error.message });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => {
|
||||
if (err) {
|
||||
console.error('Failed to bind gRPC server:', err);
|
||||
return;
|
||||
}
|
||||
console.log(`gRPC Proxy Server running on port ${port}`);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
|
|
@ -0,0 +1 @@
|
|||
@AGENTS.md
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@kord/grpc-contracts": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.8.0",
|
||||
"next": "16.2.4",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.4",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
|
|
@ -0,0 +1,18 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import { pingBot } from '@/lib/grpc';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const msg = searchParams.get('msg') || 'Dashboard-to-Bot';
|
||||
|
||||
try {
|
||||
const data = await pingBot(msg);
|
||||
return NextResponse.json({ success: true, ...data });
|
||||
} catch (error: any) {
|
||||
console.error('gRPC Ping Error:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue