algokit-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +137 -0
  2. package/dist/api/boj-scraper.d.ts +68 -0
  3. package/dist/api/boj-scraper.d.ts.map +1 -0
  4. package/dist/api/boj-scraper.js +197 -0
  5. package/dist/api/boj-scraper.js.map +1 -0
  6. package/dist/api/programmers-scraper.d.ts +70 -0
  7. package/dist/api/programmers-scraper.d.ts.map +1 -0
  8. package/dist/api/programmers-scraper.js +303 -0
  9. package/dist/api/programmers-scraper.js.map +1 -0
  10. package/dist/api/solvedac-client.d.ts +67 -0
  11. package/dist/api/solvedac-client.d.ts.map +1 -0
  12. package/dist/api/solvedac-client.js +220 -0
  13. package/dist/api/solvedac-client.js.map +1 -0
  14. package/dist/api/types.d.ts +125 -0
  15. package/dist/api/types.d.ts.map +1 -0
  16. package/dist/api/types.js +110 -0
  17. package/dist/api/types.js.map +1 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +594 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/prompts/hint-guide.d.ts +73 -0
  23. package/dist/prompts/hint-guide.d.ts.map +1 -0
  24. package/dist/prompts/hint-guide.js +174 -0
  25. package/dist/prompts/hint-guide.js.map +1 -0
  26. package/dist/prompts/programmers-hint-guide.d.ts +38 -0
  27. package/dist/prompts/programmers-hint-guide.d.ts.map +1 -0
  28. package/dist/prompts/programmers-hint-guide.js +134 -0
  29. package/dist/prompts/programmers-hint-guide.js.map +1 -0
  30. package/dist/services/code-analyzer.d.ts +46 -0
  31. package/dist/services/code-analyzer.d.ts.map +1 -0
  32. package/dist/services/code-analyzer.js +177 -0
  33. package/dist/services/code-analyzer.js.map +1 -0
  34. package/dist/services/hint-generator.d.ts +60 -0
  35. package/dist/services/hint-generator.d.ts.map +1 -0
  36. package/dist/services/hint-generator.js +222 -0
  37. package/dist/services/hint-generator.js.map +1 -0
  38. package/dist/services/problem-analyzer.d.ts +38 -0
  39. package/dist/services/problem-analyzer.d.ts.map +1 -0
  40. package/dist/services/problem-analyzer.js +114 -0
  41. package/dist/services/problem-analyzer.js.map +1 -0
  42. package/dist/services/programmers-problem-analyzer.d.ts +26 -0
  43. package/dist/services/programmers-problem-analyzer.d.ts.map +1 -0
  44. package/dist/services/programmers-problem-analyzer.js +64 -0
  45. package/dist/services/programmers-problem-analyzer.js.map +1 -0
  46. package/dist/services/programmers-review-template-generator.d.ts +27 -0
  47. package/dist/services/programmers-review-template-generator.d.ts.map +1 -0
  48. package/dist/services/programmers-review-template-generator.js +153 -0
  49. package/dist/services/programmers-review-template-generator.js.map +1 -0
  50. package/dist/services/review-generator.d.ts +51 -0
  51. package/dist/services/review-generator.d.ts.map +1 -0
  52. package/dist/services/review-generator.js +149 -0
  53. package/dist/services/review-generator.js.map +1 -0
  54. package/dist/services/review-template-generator.d.ts +27 -0
  55. package/dist/services/review-template-generator.d.ts.map +1 -0
  56. package/dist/services/review-template-generator.js +163 -0
  57. package/dist/services/review-template-generator.js.map +1 -0
  58. package/dist/templates/review-guideline.md +89 -0
  59. package/dist/tools/analyze-code-submission-boj.d.ts +67 -0
  60. package/dist/tools/analyze-code-submission-boj.d.ts.map +1 -0
  61. package/dist/tools/analyze-code-submission-boj.js +89 -0
  62. package/dist/tools/analyze-code-submission-boj.js.map +1 -0
  63. package/dist/tools/analyze-code-submission-programmers.d.ts +60 -0
  64. package/dist/tools/analyze-code-submission-programmers.d.ts.map +1 -0
  65. package/dist/tools/analyze-code-submission-programmers.js +83 -0
  66. package/dist/tools/analyze-code-submission-programmers.js.map +1 -0
  67. package/dist/tools/analyze-code-submission.d.ts +67 -0
  68. package/dist/tools/analyze-code-submission.d.ts.map +1 -0
  69. package/dist/tools/analyze-code-submission.js +89 -0
  70. package/dist/tools/analyze-code-submission.js.map +1 -0
  71. package/dist/tools/analyze-problem-boj.d.ts +48 -0
  72. package/dist/tools/analyze-problem-boj.d.ts.map +1 -0
  73. package/dist/tools/analyze-problem-boj.js +52 -0
  74. package/dist/tools/analyze-problem-boj.js.map +1 -0
  75. package/dist/tools/analyze-problem-programmers.d.ts +48 -0
  76. package/dist/tools/analyze-problem-programmers.d.ts.map +1 -0
  77. package/dist/tools/analyze-problem-programmers.js +53 -0
  78. package/dist/tools/analyze-problem-programmers.js.map +1 -0
  79. package/dist/tools/analyze-problem.d.ts +48 -0
  80. package/dist/tools/analyze-problem.d.ts.map +1 -0
  81. package/dist/tools/analyze-problem.js +52 -0
  82. package/dist/tools/analyze-problem.js.map +1 -0
  83. package/dist/tools/create-review.d.ts +47 -0
  84. package/dist/tools/create-review.d.ts.map +1 -0
  85. package/dist/tools/create-review.js +122 -0
  86. package/dist/tools/create-review.js.map +1 -0
  87. package/dist/tools/fetch-problem-content-boj.d.ts +49 -0
  88. package/dist/tools/fetch-problem-content-boj.d.ts.map +1 -0
  89. package/dist/tools/fetch-problem-content-boj.js +93 -0
  90. package/dist/tools/fetch-problem-content-boj.js.map +1 -0
  91. package/dist/tools/fetch-problem-content-programmers.d.ts +46 -0
  92. package/dist/tools/fetch-problem-content-programmers.d.ts.map +1 -0
  93. package/dist/tools/fetch-problem-content-programmers.js +74 -0
  94. package/dist/tools/fetch-problem-content-programmers.js.map +1 -0
  95. package/dist/tools/fetch-problem-content.d.ts +49 -0
  96. package/dist/tools/fetch-problem-content.d.ts.map +1 -0
  97. package/dist/tools/fetch-problem-content.js +93 -0
  98. package/dist/tools/fetch-problem-content.js.map +1 -0
  99. package/dist/tools/generate-hint-boj.d.ts +42 -0
  100. package/dist/tools/generate-hint-boj.d.ts.map +1 -0
  101. package/dist/tools/generate-hint-boj.js +80 -0
  102. package/dist/tools/generate-hint-boj.js.map +1 -0
  103. package/dist/tools/generate-hint-programmers.d.ts +42 -0
  104. package/dist/tools/generate-hint-programmers.d.ts.map +1 -0
  105. package/dist/tools/generate-hint-programmers.js +78 -0
  106. package/dist/tools/generate-hint-programmers.js.map +1 -0
  107. package/dist/tools/generate-hint.d.ts +42 -0
  108. package/dist/tools/generate-hint.d.ts.map +1 -0
  109. package/dist/tools/generate-hint.js +80 -0
  110. package/dist/tools/generate-hint.js.map +1 -0
  111. package/dist/tools/generate-review-template-boj.d.ts +48 -0
  112. package/dist/tools/generate-review-template-boj.d.ts.map +1 -0
  113. package/dist/tools/generate-review-template-boj.js +52 -0
  114. package/dist/tools/generate-review-template-boj.js.map +1 -0
  115. package/dist/tools/generate-review-template-programmers.d.ts +48 -0
  116. package/dist/tools/generate-review-template-programmers.d.ts.map +1 -0
  117. package/dist/tools/generate-review-template-programmers.js +53 -0
  118. package/dist/tools/generate-review-template-programmers.js.map +1 -0
  119. package/dist/tools/generate-review-template.d.ts +48 -0
  120. package/dist/tools/generate-review-template.d.ts.map +1 -0
  121. package/dist/tools/generate-review-template.js +52 -0
  122. package/dist/tools/generate-review-template.js.map +1 -0
  123. package/dist/tools/get-hint.d.ts +41 -0
  124. package/dist/tools/get-hint.d.ts.map +1 -0
  125. package/dist/tools/get-hint.js +108 -0
  126. package/dist/tools/get-hint.js.map +1 -0
  127. package/dist/tools/get-problem.d.ts +22 -0
  128. package/dist/tools/get-problem.d.ts.map +1 -0
  129. package/dist/tools/get-problem.js +89 -0
  130. package/dist/tools/get-problem.js.map +1 -0
  131. package/dist/tools/get-programmers-problem.d.ts +53 -0
  132. package/dist/tools/get-programmers-problem.d.ts.map +1 -0
  133. package/dist/tools/get-programmers-problem.js +161 -0
  134. package/dist/tools/get-programmers-problem.js.map +1 -0
  135. package/dist/tools/search-problems.d.ts +42 -0
  136. package/dist/tools/search-problems.d.ts.map +1 -0
  137. package/dist/tools/search-problems.js +123 -0
  138. package/dist/tools/search-problems.js.map +1 -0
  139. package/dist/tools/search-programmers-problems.d.ts +73 -0
  140. package/dist/tools/search-programmers-problems.d.ts.map +1 -0
  141. package/dist/tools/search-programmers-problems.js +171 -0
  142. package/dist/tools/search-programmers-problems.js.map +1 -0
  143. package/dist/tools/search-tags.d.ts +22 -0
  144. package/dist/tools/search-tags.d.ts.map +1 -0
  145. package/dist/tools/search-tags.js +70 -0
  146. package/dist/tools/search-tags.js.map +1 -0
  147. package/dist/types/analysis.d.ts +211 -0
  148. package/dist/types/analysis.d.ts.map +1 -0
  149. package/dist/types/analysis.js +11 -0
  150. package/dist/types/analysis.js.map +1 -0
  151. package/dist/types/problem-content.d.ts +110 -0
  152. package/dist/types/problem-content.d.ts.map +1 -0
  153. package/dist/types/problem-content.js +7 -0
  154. package/dist/types/problem-content.js.map +1 -0
  155. package/dist/types/programmers.d.ts +72 -0
  156. package/dist/types/programmers.d.ts.map +1 -0
  157. package/dist/types/programmers.js +5 -0
  158. package/dist/types/programmers.js.map +1 -0
  159. package/dist/utils/browser-pool.d.ts +93 -0
  160. package/dist/utils/browser-pool.d.ts.map +1 -0
  161. package/dist/utils/browser-pool.js +193 -0
  162. package/dist/utils/browser-pool.js.map +1 -0
  163. package/dist/utils/cache-stats.d.ts +60 -0
  164. package/dist/utils/cache-stats.d.ts.map +1 -0
  165. package/dist/utils/cache-stats.js +78 -0
  166. package/dist/utils/cache-stats.js.map +1 -0
  167. package/dist/utils/cache.d.ts +72 -0
  168. package/dist/utils/cache.d.ts.map +1 -0
  169. package/dist/utils/cache.js +99 -0
  170. package/dist/utils/cache.js.map +1 -0
  171. package/dist/utils/html-parser.d.ts +57 -0
  172. package/dist/utils/html-parser.d.ts.map +1 -0
  173. package/dist/utils/html-parser.js +383 -0
  174. package/dist/utils/html-parser.js.map +1 -0
  175. package/dist/utils/lru-cache.d.ts +124 -0
  176. package/dist/utils/lru-cache.d.ts.map +1 -0
  177. package/dist/utils/lru-cache.js +259 -0
  178. package/dist/utils/lru-cache.js.map +1 -0
  179. package/dist/utils/programmers-converter.d.ts +15 -0
  180. package/dist/utils/programmers-converter.d.ts.map +1 -0
  181. package/dist/utils/programmers-converter.js +41 -0
  182. package/dist/utils/programmers-converter.js.map +1 -0
  183. package/dist/utils/rate-limiter.d.ts +145 -0
  184. package/dist/utils/rate-limiter.d.ts.map +1 -0
  185. package/dist/utils/rate-limiter.js +244 -0
  186. package/dist/utils/rate-limiter.js.map +1 -0
  187. package/dist/utils/tier-converter.d.ts +108 -0
  188. package/dist/utils/tier-converter.d.ts.map +1 -0
  189. package/dist/utils/tier-converter.js +198 -0
  190. package/dist/utils/tier-converter.js.map +1 -0
  191. package/dist/utils/url-parser.d.ts +25 -0
  192. package/dist/utils/url-parser.d.ts.map +1 -0
  193. package/dist/utils/url-parser.js +45 -0
  194. package/dist/utils/url-parser.js.map +1 -0
  195. package/package.json +61 -0
@@ -0,0 +1,303 @@
1
+ import { BrowserPool } from '../utils/browser-pool.js';
2
+ import { RateLimiter } from '../utils/rate-limiter.js';
3
+ import { LRUCache } from '../utils/lru-cache.js';
4
+ import { parseProgrammersProblemContent } from '../utils/html-parser.js';
5
+ /**
6
+ * 프로그래머스 스크래핑 에러
7
+ */
8
+ export class ProgrammersScrapeError extends Error {
9
+ code;
10
+ originalError;
11
+ constructor(message, code, originalError) {
12
+ super(message);
13
+ this.code = code;
14
+ this.originalError = originalError;
15
+ this.name = 'ProgrammersScrapeError';
16
+ }
17
+ }
18
+ /**
19
+ * 프로그래머스 스크래퍼
20
+ */
21
+ export class ProgrammersScraper {
22
+ browserPool;
23
+ rateLimiter;
24
+ searchCache;
25
+ problemCache;
26
+ baseUrl = 'https://school.programmers.co.kr';
27
+ constructor() {
28
+ this.browserPool = BrowserPool.getInstance();
29
+ // 초당 1회 요청 (보수적)
30
+ this.rateLimiter = new RateLimiter({
31
+ capacity: 2,
32
+ refillRate: 1,
33
+ });
34
+ // 검색 캐시: 50개 항목, 30분 TTL
35
+ this.searchCache = new LRUCache(50, 30 * 60 * 1000);
36
+ // 문제 상세 캐시: 50개 항목, 30일 TTL
37
+ this.problemCache = new LRUCache(50, 30 * 24 * 60 * 60 * 1000);
38
+ }
39
+ /**
40
+ * 프로그래머스 문제 검색
41
+ *
42
+ * @param options 검색 옵션
43
+ * @returns 문제 목록
44
+ * @throws {ProgrammersScrapeError}
45
+ */
46
+ async searchProblems(options = {}) {
47
+ const { levels = [], order = 'recent', page = 1, limit, query, } = options;
48
+ // 캐시 키 생성
49
+ const cacheKey = JSON.stringify({ levels, order, page, query });
50
+ // 캐시 확인
51
+ const cached = this.searchCache.get(cacheKey);
52
+ if (cached !== undefined) {
53
+ console.log('[ProgrammersScraper] 캐시 히트: 검색 결과');
54
+ // limit 적용 후 반환
55
+ return limit && limit > 0 ? cached.slice(0, limit) : cached;
56
+ }
57
+ // Rate limiting (캐시 미스 시에만)
58
+ await this.rateLimiter.acquire();
59
+ let browser = null;
60
+ try {
61
+ // 1. BrowserPool에서 브라우저 획득
62
+ browser = await this.browserPool.acquire();
63
+ const browserPage = await browser.newPage();
64
+ // 2. 검색 URL 생성
65
+ const searchUrl = this.buildSearchUrl({
66
+ levels,
67
+ order,
68
+ page,
69
+ query,
70
+ });
71
+ console.log(`[ProgrammersScraper] 검색 URL: ${searchUrl}`);
72
+ // 3. 페이지 이동 및 로딩 대기
73
+ try {
74
+ await browserPage.goto(searchUrl, {
75
+ waitUntil: 'networkidle2',
76
+ timeout: 30000,
77
+ });
78
+ }
79
+ catch (error) {
80
+ throw new ProgrammersScrapeError(`페이지 로딩 실패: ${searchUrl}`, 'NAVIGATION_ERROR', error);
81
+ }
82
+ // 4. JavaScript 렌더링 대기
83
+ try {
84
+ await browserPage.waitForSelector('table tbody tr', {
85
+ timeout: 10000,
86
+ });
87
+ }
88
+ catch (error) {
89
+ // 빈 결과일 수 있으므로 스크린샷 저장 후 빈 배열 반환
90
+ await browserPage.screenshot({
91
+ path: 'programmers-search-empty.png',
92
+ });
93
+ const rowCount = await browserPage.$$eval('table tbody tr', (rows) => rows.length);
94
+ if (rowCount === 0) {
95
+ console.log('[ProgrammersScraper] 검색 결과 없음');
96
+ await browserPage.close();
97
+ return [];
98
+ }
99
+ throw new ProgrammersScrapeError('table tbody tr selector를 찾을 수 없습니다', 'SELECTOR_NOT_FOUND', error);
100
+ }
101
+ // 5. 문제 목록 추출
102
+ let problems;
103
+ try {
104
+ problems = await browserPage.$$eval('table tbody tr', (rows) => {
105
+ return rows.map((row) => {
106
+ const titleLink = row.querySelector('td.title a[href*="/lessons/"]');
107
+ const categoryEl = row.querySelector('td.title small.part-title');
108
+ const levelSpan = row.querySelector('td.level span[class*="level-"]');
109
+ const finishedEl = row.querySelector('td.finished-count');
110
+ const rateEl = row.querySelector('td.acceptance-rate');
111
+ const href = titleLink?.getAttribute('href') || '';
112
+ const problemId = href.match(/lessons\/(\d+)/)?.[1] || '';
113
+ const title = titleLink?.textContent?.trim() || '';
114
+ const category = categoryEl?.textContent?.trim() || '기타';
115
+ const levelClass = levelSpan?.className || '';
116
+ const level = parseInt(levelClass.match(/level-(\d+)/)?.[1] || '0');
117
+ const finishedText = finishedEl?.textContent?.trim() || '0명';
118
+ const finishedCount = parseInt(finishedText.replace(/,/g, '').replace('명', '') || '0');
119
+ const rateText = rateEl?.textContent?.trim() || '0%';
120
+ const acceptanceRate = parseInt(rateText.replace('%', '') || '0');
121
+ return {
122
+ problemId,
123
+ title,
124
+ level,
125
+ category,
126
+ url: `https://school.programmers.co.kr${href}`,
127
+ finishedCount,
128
+ acceptanceRate,
129
+ };
130
+ });
131
+ });
132
+ }
133
+ catch (error) {
134
+ throw new ProgrammersScrapeError('문제 목록 파싱 실패', 'PARSE_ERROR', error);
135
+ }
136
+ await browserPage.close();
137
+ // 6. 캐시에 저장 (limit 적용 전 전체 결과 저장)
138
+ this.searchCache.set(cacheKey, problems);
139
+ // 7. limit 적용
140
+ if (limit && limit > 0) {
141
+ problems = problems.slice(0, limit);
142
+ }
143
+ console.log(`[ProgrammersScraper] ${problems.length}개 문제 검색 완료`);
144
+ return problems;
145
+ }
146
+ finally {
147
+ // 7. 브라우저 반환 (필수!)
148
+ if (browser) {
149
+ await this.browserPool.release(browser);
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * 검색 URL 생성
155
+ */
156
+ buildSearchUrl(options) {
157
+ const params = new URLSearchParams();
158
+ params.set('order', options.order);
159
+ params.set('page', options.page.toString());
160
+ if (options.levels.length > 0) {
161
+ params.set('levels', options.levels.join(','));
162
+ }
163
+ if (options.query) {
164
+ params.set('query', options.query);
165
+ }
166
+ return `${this.baseUrl}/learn/challenges?${params.toString()}`;
167
+ }
168
+ /**
169
+ * 문제 상세 페이지 HTML 가져오기 (fetch 기반, BOJScraper 패턴)
170
+ *
171
+ * @param problemId 문제 ID
172
+ * @returns HTML 문자열
173
+ * @throws {ProgrammersScrapeError}
174
+ */
175
+ async fetchProblemPage(problemId) {
176
+ if (!problemId || !/^\d+$/.test(problemId)) {
177
+ throw new ProgrammersScrapeError(`유효하지 않은 문제 ID: ${problemId}`, 'PARSE_ERROR');
178
+ }
179
+ // Rate limiting
180
+ await this.rateLimiter.acquire();
181
+ const url = `${this.baseUrl}/learn/courses/30/lessons/${problemId}`;
182
+ let lastError;
183
+ // 재시도 로직 (최대 2회)
184
+ for (let attempt = 0; attempt <= 2; attempt++) {
185
+ try {
186
+ const html = await this._fetchWithTimeout(url);
187
+ return html;
188
+ }
189
+ catch (error) {
190
+ lastError = error;
191
+ // 404는 재시도 불필요
192
+ if (error instanceof ProgrammersScrapeError &&
193
+ error.code === 'NAVIGATION_ERROR') {
194
+ throw error;
195
+ }
196
+ // 마지막 시도가 아니면 재시도
197
+ if (attempt < 2) {
198
+ await this._delay(3000); // 3초 대기
199
+ continue;
200
+ }
201
+ }
202
+ }
203
+ // 모든 재시도 실패
204
+ throw new ProgrammersScrapeError(`문제 ${problemId}를 3번 시도했으나 실패했습니다.`, 'NAVIGATION_ERROR', lastError);
205
+ }
206
+ /**
207
+ * 타임아웃 적용된 HTTP 요청 (fetch 기반, BOJScraper 패턴)
208
+ */
209
+ async _fetchWithTimeout(url) {
210
+ const controller = new AbortController();
211
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
212
+ try {
213
+ const response = await fetch(url, {
214
+ signal: controller.signal,
215
+ headers: {
216
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
217
+ Accept: 'text/html',
218
+ 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
219
+ },
220
+ });
221
+ clearTimeout(timeoutId);
222
+ // 404 처리
223
+ if (response.status === 404) {
224
+ throw new ProgrammersScrapeError(`문제를 찾을 수 없습니다: ${url}`, 'NAVIGATION_ERROR');
225
+ }
226
+ // 기타 HTTP 에러
227
+ if (!response.ok) {
228
+ throw new ProgrammersScrapeError(`HTTP 에러 ${response.status}: ${response.statusText}`, 'NAVIGATION_ERROR');
229
+ }
230
+ const html = await response.text();
231
+ // HTML 검증
232
+ if (!html || html.length < 100) {
233
+ throw new ProgrammersScrapeError('빈 HTML 응답을 받았습니다.', 'PARSE_ERROR');
234
+ }
235
+ return html;
236
+ }
237
+ catch (error) {
238
+ clearTimeout(timeoutId);
239
+ // 이미 ProgrammersScrapeError면 그대로 throw
240
+ if (error instanceof ProgrammersScrapeError) {
241
+ throw error;
242
+ }
243
+ // AbortError는 타임아웃
244
+ if (error.name === 'AbortError') {
245
+ throw new ProgrammersScrapeError(`요청이 타임아웃되었습니다 (10000ms 초과)`, 'TIMEOUT', error);
246
+ }
247
+ // 기타 네트워크 에러
248
+ throw new ProgrammersScrapeError(`네트워크 요청 실패: ${error.message}`, 'NAVIGATION_ERROR', error);
249
+ }
250
+ }
251
+ /**
252
+ * 지연 함수 (재시도 간격)
253
+ */
254
+ _delay(ms) {
255
+ return new Promise((resolve) => setTimeout(resolve, ms));
256
+ }
257
+ /**
258
+ * 캐시 통계 조회
259
+ *
260
+ * @returns 검색 및 문제 캐시 통계
261
+ */
262
+ getCacheStats() {
263
+ return {
264
+ search: this.searchCache.getStats(),
265
+ problem: this.problemCache.getStats(),
266
+ };
267
+ }
268
+ /**
269
+ * 캐시 초기화
270
+ */
271
+ clearCache() {
272
+ this.searchCache.clear();
273
+ this.problemCache.clear();
274
+ }
275
+ /**
276
+ * 문제 상세 정보 조회 (fetch + cheerio 기반)
277
+ *
278
+ * @param problemId 문제 ID
279
+ * @returns 문제 상세 정보
280
+ * @throws {ProgrammersScrapeError}
281
+ */
282
+ async getProblem(problemId) {
283
+ // 캐시 확인
284
+ const cached = this.problemCache.get(problemId);
285
+ if (cached !== undefined) {
286
+ console.log(`[ProgrammersScraper] 캐시 히트: 문제 ${problemId}`);
287
+ return cached;
288
+ }
289
+ // 1. HTML 가져오기 (캐시 미스 시에만)
290
+ const html = await this.fetchProblemPage(problemId);
291
+ // 2. HTML 파싱
292
+ try {
293
+ const detail = parseProgrammersProblemContent(html, problemId);
294
+ // 3. 캐시에 저장
295
+ this.problemCache.set(problemId, detail);
296
+ return detail;
297
+ }
298
+ catch (error) {
299
+ throw new ProgrammersScrapeError(`HTML 파싱 실패: ${error.message}`, 'PARSE_ERROR', error);
300
+ }
301
+ }
302
+ }
303
+ //# sourceMappingURL=programmers-scraper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"programmers-scraper.js","sourceRoot":"","sources":["../../src/api/programmers-scraper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * solved.ac API 클라이언트
3
+ *
4
+ * solved.ac의 공개 API를 사용하여 백준 문제 데이터를 조회합니다.
5
+ * - Base URL: https://solved.ac/api/v3
6
+ * - 인증 불필요
7
+ * - Rate Limiting 적용
8
+ */
9
+ import { Problem, Tag, SearchResult, SearchParams } from './types.js';
10
+ /**
11
+ * solved.ac API 클라이언트
12
+ */
13
+ export declare class SolvedAcClient {
14
+ private cache;
15
+ /**
16
+ * HTTP GET 요청
17
+ */
18
+ private request;
19
+ /**
20
+ * 에러 응답 처리
21
+ */
22
+ private handleErrorResponse;
23
+ /**
24
+ * 지연 함수 (재시도용)
25
+ */
26
+ private delay;
27
+ /**
28
+ * 캐시에서 데이터 조회
29
+ */
30
+ private getCached;
31
+ /**
32
+ * 캐시에 데이터 저장
33
+ */
34
+ private setCache;
35
+ /**
36
+ * 캐시 초기화
37
+ */
38
+ clearCache(): void;
39
+ /**
40
+ * 캐시 통계 조회
41
+ */
42
+ getCacheStats(): import("../utils/lru-cache.js").CacheStats;
43
+ /**
44
+ * 문제 검색
45
+ *
46
+ * @param params - 검색 파라미터
47
+ * @returns 검색 결과 (문제 배열 및 메타데이터)
48
+ */
49
+ searchProblems(params?: SearchParams): Promise<SearchResult>;
50
+ /**
51
+ * 문제 상세 정보 조회
52
+ *
53
+ * @param problemId - 문제 ID
54
+ * @returns 문제 정보
55
+ * @throws {ProblemNotFoundError} 문제를 찾을 수 없는 경우
56
+ * @throws {InvalidInputError} 유효하지 않은 문제 ID
57
+ */
58
+ getProblem(problemId: number): Promise<Problem>;
59
+ /**
60
+ * 태그 검색
61
+ *
62
+ * @param query - 검색 키워드
63
+ * @returns 태그 배열
64
+ */
65
+ searchTags(query: string): Promise<Tag[]>;
66
+ }
67
+ //# sourceMappingURL=solvedac-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solvedac-client.d.ts","sourceRoot":"","sources":["../../src/api/solvedac-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,OAAO,EACP,GAAG,EACH,YAAY,EACZ,YAAY,EAOb,MAAM,YAAY,CAAC;AAQpB;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA+C;IAE5D;;OAEG;YACW,OAAO;IAgFrB;;OAEG;YACW,mBAAmB;IAyBjC;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,OAAO,CAAC,SAAS;IAKjB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB;;OAEG;IACI,aAAa;IAIpB;;;;;OAKG;IACU,cAAc,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IA6C7E;;;;;;;OAOG;IACU,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB5D;;;;;OAKG;IACU,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAQvD"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * solved.ac API 클라이언트
3
+ *
4
+ * solved.ac의 공개 API를 사용하여 백준 문제 데이터를 조회합니다.
5
+ * - Base URL: https://solved.ac/api/v3
6
+ * - 인증 불필요
7
+ * - Rate Limiting 적용
8
+ */
9
+ import { ProblemNotFoundError, SolvedAcAPIError, TimeoutError, NetworkError, RateLimitError, InvalidInputError, } from './types.js';
10
+ import { solvedAcLimiter } from '../utils/rate-limiter.js';
11
+ import { LRUCache } from '../utils/lru-cache.js';
12
+ const API_BASE_URL = 'https://solved.ac/api/v3';
13
+ const DEFAULT_TIMEOUT = 10000; // 10초
14
+ const MAX_RETRIES = 3;
15
+ /**
16
+ * solved.ac API 클라이언트
17
+ */
18
+ export class SolvedAcClient {
19
+ cache = new LRUCache(100, 3600000); // 용량 100, TTL 1시간
20
+ /**
21
+ * HTTP GET 요청
22
+ */
23
+ async request(endpoint, params = {}, retries = 0) {
24
+ const url = new URL(`${API_BASE_URL}${endpoint}`);
25
+ // 쿼리 파라미터 추가
26
+ Object.entries(params).forEach(([key, value]) => {
27
+ if (value !== undefined && value !== null && value !== '') {
28
+ url.searchParams.append(key, String(value));
29
+ }
30
+ });
31
+ const cacheKey = url.toString();
32
+ // 캐시 확인
33
+ const cached = this.getCached(cacheKey);
34
+ if (cached !== null) {
35
+ return cached; // 캐시 히트 시 Rate Limiter 우회
36
+ }
37
+ // Rate Limiting 적용
38
+ await solvedAcLimiter.acquire();
39
+ try {
40
+ const controller = new AbortController();
41
+ const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
42
+ const response = await fetch(url.toString(), {
43
+ signal: controller.signal,
44
+ headers: {
45
+ 'Accept': 'application/json',
46
+ 'User-Agent': 'AlgoKit/1.0',
47
+ },
48
+ });
49
+ clearTimeout(timeoutId);
50
+ // 에러 처리
51
+ if (!response.ok) {
52
+ await this.handleErrorResponse(response);
53
+ }
54
+ const data = await response.json();
55
+ // 캐시 저장
56
+ this.setCache(cacheKey, data);
57
+ return data;
58
+ }
59
+ catch (error) {
60
+ // Abort 에러 = 타임아웃
61
+ if (error instanceof Error && error.name === 'AbortError') {
62
+ throw new TimeoutError();
63
+ }
64
+ // 네트워크 에러
65
+ if (error instanceof TypeError) {
66
+ // Fetch API는 네트워크 실패 시 TypeError를 던짐
67
+ if (retries < MAX_RETRIES) {
68
+ await this.delay(1000 * (retries + 1)); // 지수 백오프
69
+ return this.request(endpoint, params, retries + 1);
70
+ }
71
+ throw new NetworkError('Network request failed', error);
72
+ }
73
+ // 이미 커스텀 에러인 경우 재던지기
74
+ if (error instanceof SolvedAcAPIError ||
75
+ error instanceof TimeoutError ||
76
+ error instanceof NetworkError) {
77
+ throw error;
78
+ }
79
+ // 알 수 없는 에러
80
+ throw new SolvedAcAPIError(500, 'Unknown error occurred', error);
81
+ }
82
+ }
83
+ /**
84
+ * 에러 응답 처리
85
+ */
86
+ async handleErrorResponse(response) {
87
+ const statusCode = response.status;
88
+ if (statusCode === 404) {
89
+ throw new ProblemNotFoundError(0); // problemId는 호출자에서 설정
90
+ }
91
+ if (statusCode === 429) {
92
+ const retryAfter = response.headers.get('Retry-After');
93
+ throw new RateLimitError(retryAfter ? parseInt(retryAfter, 10) : undefined);
94
+ }
95
+ let message = `API request failed with status ${statusCode}`;
96
+ try {
97
+ const errorBody = await response.json();
98
+ if (errorBody.message) {
99
+ message = errorBody.message;
100
+ }
101
+ }
102
+ catch {
103
+ // JSON 파싱 실패 시 기본 메시지 사용
104
+ }
105
+ throw new SolvedAcAPIError(statusCode, message);
106
+ }
107
+ /**
108
+ * 지연 함수 (재시도용)
109
+ */
110
+ delay(ms) {
111
+ return new Promise(resolve => setTimeout(resolve, ms));
112
+ }
113
+ /**
114
+ * 캐시에서 데이터 조회
115
+ */
116
+ getCached(key) {
117
+ const data = this.cache.get(key);
118
+ return data !== undefined ? data : null;
119
+ }
120
+ /**
121
+ * 캐시에 데이터 저장
122
+ */
123
+ setCache(key, data) {
124
+ this.cache.set(key, data);
125
+ }
126
+ /**
127
+ * 캐시 초기화
128
+ */
129
+ clearCache() {
130
+ this.cache.clear();
131
+ }
132
+ /**
133
+ * 캐시 통계 조회
134
+ */
135
+ getCacheStats() {
136
+ return this.cache.getStats();
137
+ }
138
+ /**
139
+ * 문제 검색
140
+ *
141
+ * @param params - 검색 파라미터
142
+ * @returns 검색 결과 (문제 배열 및 메타데이터)
143
+ */
144
+ async searchProblems(params = {}) {
145
+ // 입력 검증
146
+ if (params.level_min !== undefined && (params.level_min < 1 || params.level_min > 30)) {
147
+ throw new InvalidInputError('level_min must be between 1 and 30');
148
+ }
149
+ if (params.level_max !== undefined && (params.level_max < 1 || params.level_max > 30)) {
150
+ throw new InvalidInputError('level_max must be between 1 and 30');
151
+ }
152
+ if (params.page !== undefined && params.page < 1) {
153
+ throw new InvalidInputError('page must be positive');
154
+ }
155
+ const queryParams = {};
156
+ // query 문자열 구성 (level, tag 포함)
157
+ const queryParts = [];
158
+ if (params.query) {
159
+ queryParts.push(params.query);
160
+ }
161
+ // level 필터 (solved.ac는 "tier:g3..g1" 같은 형식 사용)
162
+ if (params.level_min !== undefined || params.level_max !== undefined) {
163
+ const min = params.level_min || 1;
164
+ const max = params.level_max || 30;
165
+ queryParts.push(`*${min}..${max}`);
166
+ }
167
+ // tags 필터 (단일 문자열 또는 배열 지원)
168
+ if (params.tags) {
169
+ const tagsArray = Array.isArray(params.tags) ? params.tags : [params.tags];
170
+ tagsArray.forEach(tag => queryParts.push(`#${tag}`));
171
+ }
172
+ if (queryParts.length > 0) {
173
+ queryParams.query = queryParts.join(' ');
174
+ }
175
+ if (params.sort)
176
+ queryParams.sort = params.sort;
177
+ if (params.direction)
178
+ queryParams.direction = params.direction;
179
+ if (params.page)
180
+ queryParams.page = params.page;
181
+ return this.request('/search/problem', queryParams);
182
+ }
183
+ /**
184
+ * 문제 상세 정보 조회
185
+ *
186
+ * @param problemId - 문제 ID
187
+ * @returns 문제 정보
188
+ * @throws {ProblemNotFoundError} 문제를 찾을 수 없는 경우
189
+ * @throws {InvalidInputError} 유효하지 않은 문제 ID
190
+ */
191
+ async getProblem(problemId) {
192
+ if (!Number.isInteger(problemId) || problemId <= 0) {
193
+ throw new InvalidInputError('problemId must be a positive integer');
194
+ }
195
+ try {
196
+ return await this.request(`/problem/show`, { problemId });
197
+ }
198
+ catch (error) {
199
+ if (error instanceof ProblemNotFoundError) {
200
+ // problemId를 정확하게 설정
201
+ throw new ProblemNotFoundError(problemId);
202
+ }
203
+ throw error;
204
+ }
205
+ }
206
+ /**
207
+ * 태그 검색
208
+ *
209
+ * @param query - 검색 키워드
210
+ * @returns 태그 배열
211
+ */
212
+ async searchTags(query) {
213
+ if (typeof query !== 'string' || query.trim() === '') {
214
+ throw new InvalidInputError('query must be a non-empty string');
215
+ }
216
+ const result = await this.request('/search/tag', { query });
217
+ return result.items || [];
218
+ }
219
+ }
220
+ //# sourceMappingURL=solvedac-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solvedac-client.js","sourceRoot":"","sources":["../../src/api/solvedac-client.ts"],"names":[],"mappings":"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"}