dnf-api 1.0.7 → 1.1.1

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 (57) hide show
  1. package/dist/api/auction.d.ts +24 -0
  2. package/dist/api/characters.d.ts +32 -0
  3. package/dist/api/characters.equip.d.ts +29 -0
  4. package/dist/api/characters.skill.d.ts +29 -0
  5. package/dist/api/index.d.ts +9 -0
  6. package/dist/api/items.d.ts +21 -0
  7. package/dist/api/multi.d.ts +7 -0
  8. package/dist/api/server.d.ts +2 -0
  9. package/dist/api/setitems.d.ts +15 -0
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.js +19 -19
  12. package/dist/model/auction.d.ts +47 -0
  13. package/dist/model/character.d.ts +91 -0
  14. package/dist/model/index.d.ts +39 -0
  15. package/dist/model/item.d.ts +103 -0
  16. package/dist/model/setitem.d.ts +32 -0
  17. package/dist/src/api/auction.d.ts +24 -24
  18. package/dist/src/api/characters.d.ts +32 -32
  19. package/dist/src/api/characters.equip.d.ts +29 -29
  20. package/dist/src/api/characters.skill.d.ts +29 -29
  21. package/dist/src/api/index.d.ts +9 -9
  22. package/dist/src/api/items.d.ts +21 -21
  23. package/dist/src/api/multi.d.ts +7 -7
  24. package/dist/src/api/server.d.ts +2 -2
  25. package/dist/src/api/setitems.d.ts +15 -15
  26. package/dist/src/index.d.ts +14 -14
  27. package/dist/src/model/auction.d.ts +47 -47
  28. package/dist/src/model/character.d.ts +91 -91
  29. package/dist/src/model/index.d.ts +39 -39
  30. package/dist/src/model/item.d.ts +103 -103
  31. package/dist/src/model/setitem.d.ts +32 -32
  32. package/dist/src/util/config.d.ts +11 -11
  33. package/dist/src/util/index.d.ts +5 -5
  34. package/dist/src/util/params.d.ts +76 -76
  35. package/dist/src/util/query.d.ts +14 -14
  36. package/dist/src/util/static.d.ts +50 -50
  37. package/dist/util/config.d.ts +21 -0
  38. package/dist/util/index.d.ts +7 -0
  39. package/dist/util/params.d.ts +80 -0
  40. package/dist/util/query.d.ts +39 -0
  41. package/dist/util/queue.d.ts +31 -0
  42. package/dist/util/request-helper.d.ts +9 -0
  43. package/dist/util/static.d.ts +50 -0
  44. package/package.json +42 -39
  45. package/src/api/auction.ts +2 -2
  46. package/src/api/characters.equip.ts +4 -4
  47. package/src/api/characters.skill.ts +4 -4
  48. package/src/api/characters.ts +6 -6
  49. package/src/model/setitem.ts +3 -3
  50. package/src/util/config.ts +27 -4
  51. package/src/util/index.ts +13 -2
  52. package/src/util/params.ts +22 -9
  53. package/src/util/query.ts +126 -74
  54. package/src/util/queue.ts +104 -0
  55. package/src/util/request-helper.ts +19 -0
  56. package/bun.lockb +0 -0
  57. package/dist/test.d.ts +0 -1
@@ -13,7 +13,7 @@ export const style = (serverId: staticUtil.server, characterId: string) => {
13
13
  "characters",
14
14
  characterId,
15
15
  "skill",
16
- "style"
16
+ "style",
17
17
  )}`,
18
18
  };
19
19
  return query.Request(opt);
@@ -35,7 +35,7 @@ export const equipment = (serverId: staticUtil.server, characterId: string) => {
35
35
  "skill",
36
36
  "buff",
37
37
  "equip",
38
- "equipment"
38
+ "equipment",
39
39
  )}`,
40
40
  };
41
41
  return query.Request(opt);
@@ -57,7 +57,7 @@ export const avatar = (serverId: staticUtil.server, characterId: string) => {
57
57
  "skill",
58
58
  "buff",
59
59
  "equip",
60
- "avatar"
60
+ "avatar",
61
61
  )}`,
62
62
  };
63
63
  return query.Request(opt);
@@ -79,7 +79,7 @@ export const creature = (serverId: staticUtil.server, characterId: string) => {
79
79
  "skill",
80
80
  "buff",
81
81
  "equip",
82
- "creature"
82
+ "creature",
83
83
  )}`,
84
84
  };
85
85
  return query.Request(opt);
@@ -11,7 +11,7 @@ import { type params, query, staticUtil } from "../util";
11
11
  export const characterName = (
12
12
  serverId: staticUtil.server,
13
13
  characterName: string,
14
- params: params.ICharParams = {}
14
+ params: params.ICharParams = {},
15
15
  ) => {
16
16
  // if (params === undefined) params = {};
17
17
  params.characterName = characterName;
@@ -30,14 +30,14 @@ export const characterName = (
30
30
  */
31
31
  export const characterId = (
32
32
  serverId: staticUtil.server,
33
- characterId: string
33
+ characterId: string,
34
34
  ) => {
35
35
  const opt = {
36
36
  base: query.UriBuilder(
37
37
  staticUtil.baseUri.Servers,
38
38
  serverId,
39
39
  "characters",
40
- characterId
40
+ characterId,
41
41
  ),
42
42
  };
43
43
  return query.Request<model.char.IInfo>(opt);
@@ -53,7 +53,7 @@ export const characterId = (
53
53
  export const timeline = (
54
54
  serverId: staticUtil.server,
55
55
  characterId: string,
56
- params: params.ITimeLine = {}
56
+ params: params.ITimeLine = {},
57
57
  ) => {
58
58
  const opt = {
59
59
  base: query.UriBuilder(
@@ -61,7 +61,7 @@ export const timeline = (
61
61
  serverId,
62
62
  "characters",
63
63
  characterId,
64
- "timeline"
64
+ "timeline",
65
65
  ),
66
66
  params: {
67
67
  ...params,
@@ -84,7 +84,7 @@ export const status = (serverId: staticUtil.server, characterId: string) => {
84
84
  serverId,
85
85
  "characters",
86
86
  characterId,
87
- "status"
87
+ "status",
88
88
  ),
89
89
  };
90
90
  return query.Request<model.char.ICharacterStatus>(opt);
@@ -15,7 +15,7 @@ export interface IDetail {
15
15
  itemId: string;
16
16
  itemName: string;
17
17
  itemRarity: string;
18
- }
18
+ },
19
19
  ];
20
20
  setItemOption: [
21
21
  {
@@ -26,8 +26,8 @@ export interface IDetail {
26
26
  {
27
27
  name: string;
28
28
  value: number;
29
- }
29
+ },
30
30
  ];
31
- }
31
+ },
32
32
  ];
33
33
  }
@@ -1,14 +1,17 @@
1
+ // 설정 인터페이스
1
2
  export interface IConfig {
2
- key: string | undefined;
3
+ key: string;
3
4
  hideOnErrorApiKey: boolean;
4
5
  hideKeyText: string;
5
6
  timeout: number;
6
7
  returnJSON: boolean;
7
8
  responseHeader: boolean;
8
9
  showURL: boolean;
10
+ maxRequestsPerSecond: number;
9
11
  }
10
- // biome-ignore lint/style/useConst: <explanation>
11
- let defaultConfig: IConfig = {
12
+
13
+ // 기본 설정값
14
+ const defaultConfig: IConfig = {
12
15
  key: "",
13
16
  hideOnErrorApiKey: true,
14
17
  hideKeyText: "{HIDE_KEY}",
@@ -16,5 +19,25 @@ let defaultConfig: IConfig = {
16
19
  returnJSON: false,
17
20
  responseHeader: false,
18
21
  showURL: false,
22
+ maxRequestsPerSecond: 1000,
19
23
  };
20
- export default defaultConfig;
24
+
25
+ // 현재 설정 (복사본)
26
+ export const config: IConfig = { ...defaultConfig };
27
+
28
+ /**
29
+ * 설정을 업데이트합니다.
30
+ * @param newConfig 업데이트할 설정 값
31
+ */
32
+ export function setConfig(newConfig: Partial<IConfig>): void {
33
+ Object.assign(config, newConfig);
34
+ }
35
+
36
+ /**
37
+ * 설정을 기본값으로 초기화합니다.
38
+ */
39
+ export function resetConfig(): void {
40
+ Object.assign(config, defaultConfig);
41
+ }
42
+
43
+ export default config;
package/src/util/index.ts CHANGED
@@ -1,6 +1,17 @@
1
- import config from "./config";
1
+ import config, { setConfig, resetConfig } from "./config";
2
2
  import * as params from "./params";
3
3
  import query from "./query";
4
+ import { requestQueue } from "./queue";
5
+ import { createRequest } from "./request-helper";
4
6
  import * as staticUtil from "./static";
5
7
 
6
- export { config, query, staticUtil, params };
8
+ export {
9
+ config,
10
+ setConfig,
11
+ resetConfig,
12
+ query,
13
+ staticUtil,
14
+ params,
15
+ createRequest,
16
+ requestQueue,
17
+ };
@@ -1,9 +1,19 @@
1
1
  import type * as staticUtil from "./static";
2
- export interface QueryOptions<T = any> {
2
+
3
+ // 기본 파라미터 타입
4
+ export type ParamValue = string | number | boolean | undefined;
5
+
6
+ // 인덱스 시그니처가 있는 기본 인터페이스
7
+ export interface BaseParams {
8
+ [key: string]: ParamValue | ParamValue[] | object | undefined;
9
+ }
10
+
11
+ export interface QueryOptions<T = BaseParams> {
3
12
  base: string;
4
13
  params?: T;
5
14
  }
6
- export interface ICharParams {
15
+
16
+ export interface ICharParams extends BaseParams {
7
17
  characterName?: string;
8
18
  jobId?: string;
9
19
  jobGrowId?: string;
@@ -12,7 +22,7 @@ export interface ICharParams {
12
22
  limit?: number;
13
23
  }
14
24
 
15
- export interface ITimeLine {
25
+ export interface ITimeLine extends BaseParams {
16
26
  serverId?: staticUtil.server;
17
27
  characterId?: string;
18
28
  startDate?: Date;
@@ -22,7 +32,7 @@ export interface ITimeLine {
22
32
  next?: string;
23
33
  }
24
34
 
25
- export interface IAuction {
35
+ export interface IAuction extends BaseParams {
26
36
  limit?: number;
27
37
  sort?: IAuctionSort;
28
38
  itemId?: string;
@@ -31,11 +41,13 @@ export interface IAuction {
31
41
  wordShort?: boolean;
32
42
  q?: IAuctionQuery;
33
43
  }
44
+
34
45
  export interface IAuctionSort {
35
46
  unitPrice?: staticUtil.sort;
36
47
  reinforce?: staticUtil.sort;
37
48
  auctionNo?: staticUtil.sort;
38
49
  }
50
+
39
51
  export interface IAuctionQuery {
40
52
  minLevel?: number;
41
53
  maxLevel?: number;
@@ -49,7 +61,7 @@ export interface IAuctionQuery {
49
61
  maxFame?: number;
50
62
  }
51
63
 
52
- export interface IActionSoldOption {
64
+ export interface IActionSoldOption extends BaseParams {
53
65
  limit?: number;
54
66
  wordType?: staticUtil.auctionWordType;
55
67
  wordShort?: boolean;
@@ -58,25 +70,26 @@ export interface IActionSoldOption {
58
70
  sort?: IAuctionSort;
59
71
  }
60
72
 
61
- export interface IItem {
73
+ export interface IItem extends BaseParams {
62
74
  limit?: number;
63
75
  itemName?: string;
64
76
  hashtag?: string[];
65
77
  wordType?: staticUtil.auctionWordType;
66
78
  q?: IItemQuery;
67
79
  }
80
+
68
81
  export interface IItemQuery {
69
82
  minLevel?: number;
70
83
  maxLevel?: number;
71
84
  rarity?: staticUtil.rarity;
72
- // trade?: boolean;
73
85
  }
74
86
 
75
- export interface ISetItem {
87
+ export interface ISetItem extends BaseParams {
76
88
  setItemName?: string;
77
89
  limit?: number;
78
90
  wordType?: staticUtil.auctionWordType;
79
91
  }
80
- export interface ISkill {
92
+
93
+ export interface ISkill extends BaseParams {
81
94
  jobGrowId: string;
82
95
  }
package/src/util/query.ts CHANGED
@@ -1,95 +1,147 @@
1
1
  import consola from "consola";
2
- import querystring from "query-string";
3
- import { request } from "undici";
4
2
 
5
3
  import type * as model from "../model";
6
4
  import * as Util from "./";
5
+ import type { BaseParams } from "./params";
6
+ import { requestQueue } from "./queue";
7
7
 
8
- const apiUrl = new URL("https://api.neople.co.kr");
9
- // const client = new Client("https://api.neople.co.kr", {
10
- // connectTimeout: Util.Config.timeout,
11
- // // allowH2: true,
12
- // });
13
-
14
- const sender = async <T>(path: string, method: "GET" | "POST", query: any) => {
15
- apiUrl.pathname = path;
16
- apiUrl.search = querystring.stringify(query);
17
- const res = await request<model.IDnfResponse<T>>(apiUrl.href, {
18
- method,
19
- });
20
- return res;
8
+ // 요청 옵션 인터페이스
9
+ export interface RequestOptions {
10
+ base: string;
11
+ params?: BaseParams;
12
+ url?: string;
13
+ }
14
+
15
+ const API_BASE_URL = "https://api.neople.co.kr";
16
+
17
+ // 파라미터를 URLSearchParams로 변환 (중첩 객체는 JSON으로 직렬화)
18
+ const toSearchParams = (params: BaseParams): URLSearchParams => {
19
+ const entries: [string, string][] = [];
20
+ for (const [key, value] of Object.entries(params)) {
21
+ if (value === undefined) continue;
22
+ if (typeof value === "object" && !Array.isArray(value)) {
23
+ entries.push([key, JSON.stringify(value)]);
24
+ } else if (Array.isArray(value)) {
25
+ entries.push([key, value.join(",")]);
26
+ } else {
27
+ entries.push([key, String(value)]);
28
+ }
29
+ }
30
+ return new URLSearchParams(entries);
31
+ };
32
+
33
+ // HTTP 요청 전송 함수
34
+ const sender = async <T>(
35
+ path: string,
36
+ method: "GET" | "POST",
37
+ query: BaseParams
38
+ ): Promise<{
39
+ ok: boolean;
40
+ status: number;
41
+ data: T | model.IDnfResponse<T>;
42
+ }> => {
43
+ const url = new URL(path, API_BASE_URL);
44
+ url.search = toSearchParams(query).toString();
45
+
46
+ const res = await fetch(url.toString(), { method });
47
+ const data = (await res.json()) as T | model.IDnfResponse<T>;
48
+
49
+ return {
50
+ ok: res.ok,
51
+ status: res.status,
52
+ data,
53
+ };
21
54
  };
22
55
 
56
+ // URL에서 API 키 숨김 처리
23
57
  const showUrl = (url: string): string => {
24
58
  if (Util.config.key) {
25
59
  return url?.replace(Util.config.key, Util.config.hideKeyText);
26
- } else {
27
- return url;
28
60
  }
61
+ return url;
29
62
  };
30
63
 
31
- // biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
32
- export default class Request {
33
- public static UriBuilder(...args: any[]): string {
34
- return args.join("/");
64
+ /**
65
+ * URI 경로를 빌드합니다.
66
+ * @param args 경로 세그먼트들
67
+ * @returns 결합된 URI 경로
68
+ */
69
+ export function UriBuilder(...args: (string | number)[]): string {
70
+ return args.join("/");
71
+ }
72
+
73
+ /**
74
+ * 쿼리 문자열을 빌드합니다.
75
+ * @param query 쿼리 배열
76
+ * @returns 쿼리 문자열
77
+ */
78
+ export function QueryBuilder(query: (string | number)[]): string {
79
+ const qString: string[] = [];
80
+ for (const key in query) {
81
+ qString.push(`${key}:${query[key]},`);
35
82
  }
36
- public static QueryBuilder(query: string[] | number[]): string {
37
- const qString: string[] = [];
38
- for (const key in query) {
39
- qString.push(`${key}:${query[key]},`);
40
- }
41
- return qString.join(",");
83
+ return qString.join(",");
84
+ }
85
+
86
+ /**
87
+ * 던전앤파이터 API 서버에 요청을 보내는 함수입니다.
88
+ * @param opt 요청 옵션
89
+ * @param method HTTP 메서드
90
+ * @returns API 응답
91
+ */
92
+ export async function Request<T>(
93
+ opt: RequestOptions = { base: "" },
94
+ method: "GET" | "POST" = "GET"
95
+ ): Promise<model.IDnfResponse<T>> {
96
+ if (!Util.config.key) {
97
+ throw new Error(
98
+ "API key is required. Set config.key before making requests."
99
+ );
42
100
  }
43
101
 
44
- /**
45
- * 던전앤파이터 API 서버에 응답을 요청하는 함수 입니다.
46
- * 해당 함수를 직접 호출 하는것을 권장하지 않습니다.
47
- *
48
- * @param {object} opt (요청을 보낼 Parameter값)
49
- * @returns
50
- */
51
- public static async Request<T>(
52
- opt: any = {},
53
- method: "GET" | "POST" = "GET"
54
- ): Promise<model.IDnfResponse<T>> {
55
- if (!Util.config.key || Util.config.key === "") {
56
- consola.error("Please change to your api key. ");
57
- }
102
+ const params: BaseParams = { ...opt.params };
58
103
 
59
- if (opt.params === undefined) opt.params = {};
60
- if (opt.params.q) opt.params.q = Request.QueryBuilder(opt.params.q);
61
- if (opt.params.sort)
62
- opt.params.sort = Request.QueryBuilder(opt.params.sort);
63
-
64
- opt.params.apikey = Util.config.key;
65
-
66
- if (Util.config.showURL)
67
- consola.log(
68
- "request url:",
69
- showUrl(`${opt.base}?${querystring.stringify(opt.params)}`)
70
- );
71
-
72
- const res = await sender<T>(opt.base, method, opt.params);
73
- if (res.statusCode !== 200) {
74
- const resBody = (await res.body.json()) as model.IDnfResponse<T>;
75
- const error: model.IDnfErrorResponse = {
76
- url: showUrl(opt.url ?? ""),
77
- status: res.statusCode || 0,
78
- statusText: "",
79
- code: resBody.error?.code || "",
80
- message: resBody.error?.message || "",
81
- };
82
- return { error };
83
- } else {
84
- const resBody = (await res.body.json()) as T;
85
- return {
86
- data: resBody,
87
- };
88
- }
104
+ if (params.q && Array.isArray(params.q)) {
105
+ params.q = QueryBuilder(params.q as (string | number)[]);
89
106
  }
107
+ if (params.sort && Array.isArray(params.sort)) {
108
+ params.sort = QueryBuilder(params.sort as (string | number)[]);
109
+ }
110
+
111
+ params.apikey = Util.config.key;
90
112
 
91
- public static makeItemQuery(query: any) {
92
- // return JSON.stringify(query).replace(/\"|\{|\}/gi, "");
93
- return encodeURI(query);
113
+ if (Util.config.showURL) {
114
+ consola.log(
115
+ "request url:",
116
+ showUrl(`${opt.base}?${toSearchParams(params)}`)
117
+ );
94
118
  }
119
+
120
+ const res = await requestQueue.add(() => sender<T>(opt.base, method, params));
121
+
122
+ if (!res.ok) {
123
+ const resBody = res.data as model.IDnfResponse<T>;
124
+ const error: model.IDnfErrorResponse = {
125
+ url: showUrl(opt.url ?? ""),
126
+ status: res.status || 0,
127
+ statusText: "",
128
+ code: resBody.error?.code || "",
129
+ message: resBody.error?.message || "",
130
+ };
131
+ return { error };
132
+ }
133
+
134
+ return { data: res.data as T };
95
135
  }
136
+
137
+ /**
138
+ * 아이템 쿼리를 생성합니다.
139
+ * @param query 쿼리 문자열
140
+ * @returns 인코딩된 쿼리
141
+ */
142
+ export function makeItemQuery(query: string): string {
143
+ return encodeURI(query);
144
+ }
145
+
146
+ // 기존 호환성을 위한 default export
147
+ export default { UriBuilder, QueryBuilder, Request, makeItemQuery };
@@ -0,0 +1,104 @@
1
+ import config from "./config";
2
+
3
+ /**
4
+ * Rate limiting 요청 큐
5
+ * 1초당 최대 요청 수를 제한합니다.
6
+ */
7
+
8
+ interface QueueItem<T> {
9
+ task: () => Promise<T>;
10
+ resolve: (value: T) => void;
11
+ reject: (error: Error) => void;
12
+ }
13
+
14
+ export class RequestQueue {
15
+ private queue: QueueItem<unknown>[] = [];
16
+ private processing = false;
17
+ private requestCount = 0;
18
+ private lastResetTime = Date.now();
19
+
20
+ /**
21
+ * 큐에 요청을 추가합니다.
22
+ * @param task 실행할 비동기 작업
23
+ * @returns 작업 결과 Promise
24
+ */
25
+ async add<T>(task: () => Promise<T>): Promise<T> {
26
+ return new Promise<T>((resolve, reject) => {
27
+ this.queue.push({
28
+ task,
29
+ resolve: resolve as (value: unknown) => void,
30
+ reject,
31
+ });
32
+ this.processQueue();
33
+ });
34
+ }
35
+
36
+ /**
37
+ * 큐를 처리합니다.
38
+ */
39
+ private async processQueue(): Promise<void> {
40
+ if (this.processing) return;
41
+ this.processing = true;
42
+
43
+ while (this.queue.length > 0) {
44
+ // 1초마다 카운터 리셋
45
+ const now = Date.now();
46
+ if (now - this.lastResetTime >= 1000) {
47
+ this.requestCount = 0;
48
+ this.lastResetTime = now;
49
+ }
50
+
51
+ // 제한에 도달하면 대기 (config에서 실시간으로 가져옴)
52
+ if (this.requestCount >= config.maxRequestsPerSecond) {
53
+ const waitTime = 1000 - (now - this.lastResetTime);
54
+ if (waitTime > 0) {
55
+ await this.sleep(waitTime);
56
+ }
57
+ this.requestCount = 0;
58
+ this.lastResetTime = Date.now();
59
+ }
60
+
61
+ const item = this.queue.shift();
62
+ if (!item) break;
63
+
64
+ this.requestCount++;
65
+
66
+ try {
67
+ const result = await item.task();
68
+ item.resolve(result);
69
+ } catch (error) {
70
+ item.reject(error as Error);
71
+ }
72
+ }
73
+
74
+ this.processing = false;
75
+ }
76
+
77
+ private sleep(ms: number): Promise<void> {
78
+ return new Promise((resolve) => setTimeout(resolve, ms));
79
+ }
80
+
81
+ /**
82
+ * 현재 큐 상태를 반환합니다.
83
+ */
84
+ get status() {
85
+ return {
86
+ queueLength: this.queue.length,
87
+ requestCount: this.requestCount,
88
+ maxRequestsPerSecond: config.maxRequestsPerSecond,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * 큐를 초기화합니다.
94
+ */
95
+ clear(): void {
96
+ this.queue = [];
97
+ this.requestCount = 0;
98
+ }
99
+ }
100
+
101
+ // 기본 큐 인스턴스 (제한 수치는 config를 따름)
102
+ export const requestQueue = new RequestQueue();
103
+
104
+ export default requestQueue;
@@ -0,0 +1,19 @@
1
+ import type * as model from "../model";
2
+ import type { BaseParams } from "./params";
3
+ import { Request, UriBuilder } from "./query";
4
+
5
+ /**
6
+ * API 요청을 생성하는 헬퍼 함수
7
+ * @param baseParts URI 경로 세그먼트들
8
+ * @param params 요청 파라미터
9
+ * @returns API 응답 Promise
10
+ */
11
+ export function createRequest<T>(
12
+ baseParts: (string | number)[],
13
+ params?: BaseParams,
14
+ ): Promise<model.IDnfResponse<T>> {
15
+ return Request<T>({
16
+ base: UriBuilder(...baseParts),
17
+ params,
18
+ });
19
+ }
package/bun.lockb DELETED
Binary file
package/dist/test.d.ts DELETED
@@ -1 +0,0 @@
1
- import "dotenv/config";