@miichom/lodestone 0.1.0 → 0.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.
package/README.md CHANGED
@@ -1,133 +1,133 @@
1
- # @miichom/lodestone
2
-
3
- [![npm](https://img.shields.io/npm/v/@miichom/lodestone.svg)](https://www.npmjs.com/package/@miichom/lodestone)
4
- ![node](https://img.shields.io/node/v/@miichom/lodestone)
5
-
6
- A **minimal, fully typed [Lodestone](https://na.finalfantasyxiv.com/lodestone/) client** for _[Final Fantasy XIV](https://www.finalfantasyxiv.com/)_, providing access to **all endpoints exposed by the Lodestone** through a consistent, schema-driven API.
7
-
8
- Designed for **server-side and worker runtimes**: [Node.js 20+](https://nodejs.org/), [Bun](https://bun.sh/), [Cloudflare Workers](https://developers.cloudflare.com/workers/), and [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
9
-
10
- ## Why?
11
-
12
- Most Lodestone scrapers rely on DOM emulation (e.g. [JSDOM](https://github.com/jsdom/jsdom), [cheerio](https://cheerio.js.org/)), which increases bundle size, slows cold starts, and often breaks in edge or serverless environments.
13
-
14
- `@miichom/lodestone` avoids DOM dependencies entirely, using a **predictable, schema-driven parsing model** that works consistently across modern runtimes.
15
-
16
- ## Features
17
-
18
- - 🪶 Lightweight and dependency-minimal
19
- - 🧭 Fully typed [TypeScript](https://www.typescriptlang.org/) API
20
- - ⚡ Edge- and serverless-friendly
21
- - 🧱 No DOM, no JSDOM, no cheerio
22
- - 🌐 Access to all Lodestone endpoints
23
- - 🔍 Search and lookup across supported resources
24
- - 🧩 Optional column-based data fetching
25
-
26
- ## Install
27
-
28
- ```bash
29
- npm install @miichom/lodestone
30
- ```
31
-
32
- ## Example usage
33
-
34
- ```ts
35
- import Lodestone from "@miichom/lodestone";
36
-
37
- const ls = new Lodestone();
38
-
39
- // fetch a character
40
- const character = await ls.character.get(12345678);
41
-
42
- // fetch specific columns
43
- const partial = await ls.character.get(12345678, {
44
- columns: ["mount"],
45
- });
46
-
47
- // search characters
48
- const results = await ls.character.find({
49
- q: "Y'shtola",
50
- worldname: "Twintania",
51
- });
52
- ```
53
-
54
- > Additional Lodestone endpoints follow the same API pattern and are exposed through their respective namespaces.
55
-
56
- ## Options
57
-
58
- The `Lodestone` constructor accepts a small set of configuration options.
59
- These apply globally to all endpoints (`character`, `cwls`, `freecompany`, `linkshell`, `pvpteam`).
60
-
61
- ```ts
62
- const ls = new Lodestone({
63
- locale: "eu",
64
- headers: {
65
- "user-agent": "my-xiv-tool/1.0",
66
- },
67
- });
68
- ```
69
-
70
- ### `locale?: "de" | "eu" | "fr" | "jp" | "na"`
71
-
72
- Selects which Lodestone region to target. Defaults to **`"na"`**.
73
-
74
- Each locale maps to its own Lodestone instance:
75
-
76
- - `na` → https://na.finalfantasyxiv.com/lodestone
77
- - `eu` → https://eu.finalfantasyxiv.com/lodestone
78
- - `jp` → https://jp.finalfantasyxiv.com/lodestone
79
- - `fr` → https://fr.finalfantasyxiv.com/lodestone
80
- - `de` → https://de.finalfantasyxiv.com/lodestone
81
-
82
- All requests made through the client automatically use the selected locale.
83
-
84
- ### `headers?: Record<string, string>`
85
-
86
- Optional request headers applied to every Lodestone request.
87
-
88
- - Header keys are normalized to lowercase.
89
- - A default User‑Agent is always prepended:
90
-
91
- ```
92
- curl/0.1.0 (+https://github.com/miichom/lodestone)
93
- ```
94
-
95
- If you provide your own `user-agent`, it is appended:
96
-
97
- ```ts
98
- headers: {
99
- "user-agent": "my-app/1.0",
100
- }
101
- // → curl/0.1.0 (+https://github.com/miichom/lodestone) my-app/1.0
102
- ```
103
-
104
- For more information on the `User-Agent` header, please see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent.
105
-
106
- > While optional, providing a custom User‑Agent is recommended for any automated or high‑volume usage.
107
-
108
- ### Column options (for `get` only)
109
-
110
- Some endpoints (notably **characters**) expose additional “column” pages on Lodestone.
111
- You can request them via the `columns` option:
112
-
113
- ```ts
114
- const profile = await ls.character.get(12345678, {
115
- columns: ["mount", "minion"],
116
- });
117
- ```
118
-
119
- Columns are fetched lazily and merged into the returned object.
120
-
121
- ## Attribution
122
-
123
- _Final Fantasy XIV_ and all related assets, including data accessed through the [Lodestone](https://na.finalfantasyxiv.com/lodestone/), are the intellectual property of &copy; [SQUARE ENIX CO., LTD.](https://www.square-enix.com/) All rights reserved.
124
-
125
- This project is not affiliated with or endorsed by Square Enix.
126
-
127
- # Contributing
128
-
129
- See [`CONTRIBUTING.md`](.github/CONTRIBUTING.md).
130
-
131
- # License
132
-
133
- See [`LICENSE.md`](./LICENSE.md).
1
+ # @miichom/lodestone
2
+
3
+ [![npm](https://img.shields.io/npm/v/@miichom/lodestone.svg)](https://www.npmjs.com/package/@miichom/lodestone)
4
+ ![node](https://img.shields.io/node/v/@miichom/lodestone)
5
+
6
+ A **minimal, fully typed [Lodestone](https://na.finalfantasyxiv.com/lodestone/) client** for _[Final Fantasy XIV](https://www.finalfantasyxiv.com/)_, providing access to **all endpoints exposed by the Lodestone** through a consistent, schema-driven API.
7
+
8
+ Designed for **server-side and worker runtimes**: [Node.js 20+](https://nodejs.org/), [Bun](https://bun.sh/), [Cloudflare Workers](https://developers.cloudflare.com/workers/), and [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
9
+
10
+ ## Why?
11
+
12
+ Most Lodestone scrapers rely on DOM emulation (e.g. [JSDOM](https://github.com/jsdom/jsdom), [cheerio](https://cheerio.js.org/)), which increases bundle size, slows cold starts, and often breaks in edge or serverless environments.
13
+
14
+ `@miichom/lodestone` avoids DOM dependencies entirely, using a **predictable, schema-driven parsing model** that works consistently across modern runtimes.
15
+
16
+ ## Features
17
+
18
+ - 🪶 Lightweight and dependency-minimal
19
+ - 🧭 Fully typed [TypeScript](https://www.typescriptlang.org/) API
20
+ - ⚡ Edge- and serverless-friendly
21
+ - 🧱 No DOM, no JSDOM, no cheerio
22
+ - 🌐 Access to all Lodestone endpoints
23
+ - 🔍 Search and lookup across supported resources
24
+ - 🧩 Optional column-based data fetching
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ npm install @miichom/lodestone
30
+ ```
31
+
32
+ ## Example usage
33
+
34
+ ```ts
35
+ import Lodestone from "@miichom/lodestone";
36
+
37
+ const ls = new Lodestone();
38
+
39
+ // fetch a character
40
+ const character = await ls.character.get(12345678);
41
+
42
+ // fetch specific columns
43
+ const partial = await ls.character.get(12345678, {
44
+ columns: ["mount"],
45
+ });
46
+
47
+ // search characters
48
+ const results = await ls.character.find({
49
+ q: "Y'shtola",
50
+ worldname: "Twintania",
51
+ });
52
+ ```
53
+
54
+ > Additional Lodestone endpoints follow the same API pattern and are exposed through their respective namespaces.
55
+
56
+ ## Options
57
+
58
+ The `Lodestone` constructor accepts a small set of configuration options.
59
+ These apply globally to all endpoints (`character`, `cwls`, `freecompany`, `linkshell`, `pvpteam`).
60
+
61
+ ```ts
62
+ const ls = new Lodestone({
63
+ locale: "eu",
64
+ headers: {
65
+ "user-agent": "my-xiv-tool/1.0",
66
+ },
67
+ });
68
+ ```
69
+
70
+ ### `locale?: "de" | "eu" | "fr" | "jp" | "na"`
71
+
72
+ Selects which Lodestone region to target. Defaults to **`"na"`**.
73
+
74
+ Each locale maps to its own Lodestone instance:
75
+
76
+ - `na` → https://na.finalfantasyxiv.com/lodestone
77
+ - `eu` → https://eu.finalfantasyxiv.com/lodestone
78
+ - `jp` → https://jp.finalfantasyxiv.com/lodestone
79
+ - `fr` → https://fr.finalfantasyxiv.com/lodestone
80
+ - `de` → https://de.finalfantasyxiv.com/lodestone
81
+
82
+ All requests made through the client automatically use the selected locale.
83
+
84
+ ### `headers?: Record<string, string>`
85
+
86
+ Optional request headers applied to every Lodestone request.
87
+
88
+ - Header keys are normalized to lowercase.
89
+ - A default User‑Agent is always prepended:
90
+
91
+ ```
92
+ curl/0.1.0 (+https://github.com/miichom/lodestone)
93
+ ```
94
+
95
+ If you provide your own `user-agent`, it is appended:
96
+
97
+ ```ts
98
+ headers: {
99
+ "user-agent": "my-app/1.0",
100
+ }
101
+ // → curl/0.1.0 (+https://github.com/miichom/lodestone) my-app/1.0
102
+ ```
103
+
104
+ For more information on the `User-Agent` header, please see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent.
105
+
106
+ > While optional, providing a custom User‑Agent is recommended for any automated or high‑volume usage.
107
+
108
+ ### Column options (for `get` only)
109
+
110
+ Some endpoints (notably **characters**) expose additional “column” pages on Lodestone.
111
+ You can request them via the `columns` option:
112
+
113
+ ```ts
114
+ const profile = await ls.character.get(12345678, {
115
+ columns: ["mount", "minion"],
116
+ });
117
+ ```
118
+
119
+ Columns are fetched lazily and merged into the returned object.
120
+
121
+ ## Attribution
122
+
123
+ _Final Fantasy XIV_ and all related assets, including data accessed through the [Lodestone](https://na.finalfantasyxiv.com/lodestone/), are the intellectual property of &copy; [SQUARE ENIX CO., LTD.](https://www.square-enix.com/) All rights reserved.
124
+
125
+ This project is not affiliated with or endorsed by Square Enix.
126
+
127
+ # Contributing
128
+
129
+ See [`CONTRIBUTING.md`](.github/CONTRIBUTING.md).
130
+
131
+ # License
132
+
133
+ See [`LICENSE.md`](./LICENSE.md).
package/dist/index.d.ts CHANGED
@@ -58,11 +58,22 @@ type InferSelectors<T extends Selectors> = {
58
58
  type InferSelector<T> = T extends {
59
59
  type: infer U;
60
60
  } ? U extends keyof Primitives ? Primitives[U] : U extends `${infer Base}[]` ? Base extends keyof Primitives ? Primitives[Base][] : Base extends "object" ? InferSelectors<T extends SelectorShape ? T["shape"] : never>[] : never : U extends "object" ? InferSelectors<T extends SelectorShape ? T["shape"] : never> : never : never;
61
- type InferItem<R extends Registry> = InferSelectors<R["item"]["fields"]>;
62
- type InferList<R extends Registry> = InferSelectors<R["list"]["fields"]>;
63
- type InferColumns<R extends Registry> = R["item"]["columns"] extends Selectors ? {
61
+ type InferFields<R extends Registry> = InferSelectors<R["item"]["fields"]>;
62
+ type InferColumns<R extends Registry> = R["item"] extends {
63
+ columns: Selectors;
64
+ } ? {
64
65
  [K in keyof R["item"]["columns"]]: InferSelector<R["item"]["columns"][K]>;
65
- } : never;
66
+ } : object;
67
+ type InferSelectedFields<R extends Registry, F extends Array<keyof InferFields<R>>> = {
68
+ [K in F[number]]: InferFields<R>[K];
69
+ };
70
+ type InferSelectedColumns<R extends Registry, C extends Array<keyof InferColumns<R>>> = {
71
+ [K in C[number]]: InferColumns<R>[K];
72
+ };
73
+ type InferItemFields<R extends Registry, F> = F extends undefined ? InferFields<R> : F extends Array<keyof InferFields<R>> ? InferSelectedFields<R, F> : never;
74
+ type InferItemColumns<R extends Registry, C> = C extends undefined ? InferColumns<R> : C extends Array<keyof InferColumns<R>> ? InferSelectedColumns<R, C> : never;
75
+ type InferItem<R extends Registry, F = undefined, C = undefined> = InferItemFields<R, F> & InferItemColumns<R, C>;
76
+ type InferList<R extends Registry> = InferSelectors<R["list"]["fields"]>;
66
77
  declare const character: {
67
78
  item: {
68
79
  columns: {
@@ -97,7 +108,7 @@ declare const character: {
97
108
  avatar: {
98
109
  attribute: string;
99
110
  selector: string;
100
- type: "string";
111
+ type: "url";
101
112
  };
102
113
  bio: {
103
114
  selector: string;
@@ -113,7 +124,7 @@ declare const character: {
113
124
  crest: {
114
125
  attribute: string;
115
126
  selector: string;
116
- type: "string[]";
127
+ type: "url[]";
117
128
  };
118
129
  id: {
119
130
  attribute: string;
@@ -156,14 +167,14 @@ declare const character: {
156
167
  portrait: {
157
168
  attribute: string;
158
169
  selector: string;
159
- type: "string";
170
+ type: "url";
160
171
  };
161
172
  pvp_team: {
162
173
  shape: {
163
174
  crest: {
164
175
  attribute: string;
165
176
  selector: string;
166
- type: "string[]";
177
+ type: "url[]";
167
178
  };
168
179
  id: {
169
180
  attribute: string;
@@ -194,7 +205,7 @@ declare const character: {
194
205
  avatar: {
195
206
  attribute: string;
196
207
  selector: string;
197
- type: "string";
208
+ type: "url";
198
209
  };
199
210
  data_center: {
200
211
  regex: RegExp;
@@ -277,7 +288,7 @@ declare const cwls: {
277
288
  formed: {
278
289
  regex: RegExp;
279
290
  selector: string;
280
- type: "string";
291
+ type: "date";
281
292
  };
282
293
  members: {
283
294
  regex: RegExp;
@@ -342,7 +353,7 @@ declare const freecompany: {
342
353
  crest: {
343
354
  attribute: string;
344
355
  selector: string;
345
- type: "string[]";
356
+ type: "url[]";
346
357
  };
347
358
  data_center: {
348
359
  regex: RegExp;
@@ -369,7 +380,7 @@ declare const freecompany: {
369
380
  formed: {
370
381
  regex: RegExp;
371
382
  selector: string;
372
- type: "string";
383
+ type: "date";
373
384
  };
374
385
  grand_company: {
375
386
  shape: {
@@ -438,7 +449,7 @@ declare const freecompany: {
438
449
  crest: {
439
450
  attribute: string;
440
451
  selector: string;
441
- type: "string[]";
452
+ type: "url[]";
442
453
  };
443
454
  data_center: {
444
455
  regex: RegExp;
@@ -448,7 +459,7 @@ declare const freecompany: {
448
459
  formed: {
449
460
  regex: RegExp;
450
461
  selector: string;
451
- type: "string";
462
+ type: "date";
452
463
  };
453
464
  grand_company: {
454
465
  shape: {
@@ -614,7 +625,7 @@ declare const pvpteam: {
614
625
  crest: {
615
626
  attribute: string;
616
627
  selector: string;
617
- type: "string[]";
628
+ type: "url[]";
618
629
  };
619
630
  data_center: {
620
631
  selector: string;
@@ -623,7 +634,7 @@ declare const pvpteam: {
623
634
  formed: {
624
635
  regex: RegExp;
625
636
  selector: string;
626
- type: "string";
637
+ type: "date";
627
638
  };
628
639
  name: {
629
640
  selector: string;
@@ -636,7 +647,7 @@ declare const pvpteam: {
636
647
  crest: {
637
648
  attribute: string;
638
649
  selector: string;
639
- type: "string[]";
650
+ type: "url[]";
640
651
  };
641
652
  data_center: {
642
653
  selector: string;
@@ -689,7 +700,7 @@ declare class Endpoint<R extends Registry> {
689
700
  protected readonly options?: EndpointOptions | undefined;
690
701
  /**
691
702
  * A generic Lodestone endpoint used to search and get items from Lodestone.
692
- * @param {T} registry The provided endpoint registry containing field selectors to obtain
703
+ * @param {R} registry The provided endpoint registry containing field selectors to obtain
693
704
  * @param {EndpointOptions<R>} [options] Default method options to use when fetching from Lodestone.
694
705
  * @since 0.1.0
695
706
  */
@@ -704,6 +715,7 @@ declare class Endpoint<R extends Registry> {
704
715
  private getRawValue;
705
716
  private applyRegex;
706
717
  private coerce;
718
+ private pickSelectors;
707
719
  private extract;
708
720
  /**
709
721
  * @param {InferQuery<R>} query The raw query parameters used by the Lodestone search.
@@ -711,16 +723,19 @@ declare class Endpoint<R extends Registry> {
711
723
  * @returns {Promise<InferList<R>[] | null>}
712
724
  * @since 0.1.0
713
725
  */
714
- find(query: InferQuery<R>, options?: EndpointOptions): Promise<InferList<R>[] | null>;
726
+ find<F extends Array<Extract<keyof InferFields<R>, string>> = []>(query: InferQuery<R>, options?: EndpointOptions & {
727
+ fields?: F;
728
+ }): Promise<InferList<R>[] | null>;
715
729
  /**
716
730
  * @param {NumberResolvable} id The unique identifier for the Lodestone item.
717
731
  * @param {EndpointOptions & { columns?: Array<keyof InferColumns<R>> }} [options] Optional method overrides.
718
- * @returns {Promise<(InferItem<R> & Partial<InferColumns<R>) | null>}
732
+ * @returns {Promise<(InferItem<R>) | null>}
719
733
  * @since 0.1.0
720
734
  */
721
- get(id: NumberResolvable, options?: EndpointOptions & {
722
- columns?: Array<keyof InferColumns<R>>;
723
- }): Promise<(InferItem<R> & Partial<InferColumns<R>>) | null>;
735
+ get<F extends Array<Extract<keyof InferFields<R>, string>> | undefined = undefined, C extends Array<Extract<keyof InferColumns<R>, string>> | undefined = undefined>(id: NumberResolvable, options?: EndpointOptions & {
736
+ fields?: F;
737
+ columns?: C;
738
+ }): Promise<InferItem<R, F, C> | null>;
724
739
  }
725
740
 
726
741
  declare class Lodestone {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var e=Object.defineProperty,t=(t,r,n)=>((t,r,n)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[r]=n)(t,"symbol"!=typeof r?r+"":r,n);import{parseHTML as r}from"linkedom/worker";var n=class extends Error{constructor(e){super(e),Error.captureStackTrace(this),this.name="LodestoneError"}},s=class{constructor(e,r){this.registry=e,this.options=r,t(this,"isMissing",e=>null==e),t(this,"isValid",(e,t)=>{switch(t){case"number":return"number"==typeof e||!Number.isNaN(Number(e));case"boolean":return"boolean"==typeof e;case"date":return e instanceof Date||!Number.isNaN(Date.parse(String(e)));case"url":try{return new URL(String(e)),!0}catch{return!1}default:return"string"==typeof e}}),t(this,"validate",e=>{const t=this.registry.list.query;for(const r of Object.keys(e))if(!(r in t))throw new n(`Unknown query parameter "${r}".`);for(const[r,s]of Object.entries(t)){const t=e[r];if(s.required&&this.isMissing(t))throw new n(`Missing required query parameter "${r}".`);if(!this.isMissing(t)){if(!this.isValid(t,s.type))throw new n(`Query parameter "${r}" must be type ${s.type}.`);if(s.pattern&&!s.pattern.test(String(t)))throw new n(`Query parameter "${r}" does not match required pattern.`)}}})}check(e){const{status:t}=e;if(200!==t){if(302===t)throw new n("Lodestone redirected the request.");if(503===t)throw new n("Lodestone is undergoing maintenance.");if(t>=500)throw new n(`Lodestone server error (${t}).`);if(t>=400)throw new n(`Request failed with status ${t}.`)}}async req(e,t){const{headers:r={},locale:n="na"}=t??this.options,s=Object.fromEntries(Object.entries(r).map(([e,t])=>[e.toLowerCase(),String(t)])),a="curl/0.1.0 (+https://github.com/miichom/lodestone)";return s["user-agent"]=s["user-agent"]?`${a} ${s["user-agent"]}`:a,fetch(`https://${n}.finalfantasyxiv.com/lodestone/${this.registry.path}/${e.replace(/^\/+/,"")}`,{headers:s,method:"GET",redirect:"follow"})}async fetchDocument(e,t){const n=await this.req(e,t);if(!n.ok||404===n.status)return null;this.check(n);const{document:s}=r(await n.text());return!s||s.querySelector(".parts__zero")?null:s}async fetchColumn(e,t,r){const n=this.registry.item.columns?.[t];if(!n)return;const s=await this.fetchDocument(`${e.toString()}/${t}`,r);if(!s)return;if(!("shape"in n))return this.extract(s,{value:n}).value;if("object"===n.type)return this.extract(s,n.shape);const a=n.shape.root.selector;return[...s.querySelectorAll(a)].map(e=>this.extract(e,n.shape))}getRawValue(e,t){return t.attribute?e.getAttribute(t.attribute)??"":e.textContent.trim()??""}applyRegex(e,t){if(!t.regex)return e;const r=e.match(t.regex);if(!r)return"";if(r.groups){const[e]=Object.keys(r.groups);return r.groups[e]??""}return r[1]??""}coerce(e,t){switch(t){case"boolean":return"1"===e||"true"===e;case"number":return Number(e);case"date":return new Date(e);case"url":return new URL(e);default:return e}}extract(e,t){const r={};for(const[n,s]of Object.entries(t)){if("shape"in s){if("object"===s.type)r[n]=this.extract(e,s.shape);else{const t=[...e.querySelectorAll(s.shape.root.selector)];r[n]=t.map(e=>this.extract(e,s.shape))}continue}const t=s.type.endsWith("[]"),a=t?s.type.slice(0,-2):s.type;if(t){const t=[...e.querySelectorAll(s.selector)];r[n]=t.map(e=>{const t=this.getRawValue(e,s),r=this.applyRegex(t,s);return this.coerce(r,a)});continue}const i=e.querySelector(s.selector);if(!i){r[n]=void 0;continue}const c=this.getRawValue(i,s),o=this.applyRegex(c,s);r[n]=this.coerce(o,a)}return r}async find(e,t={}){this.validate(e);const r=new URLSearchParams(Object.fromEntries(Object.entries(e).filter(([,e])=>null!=e).map(([e,t])=>[e,"boolean"==typeof t?t?"1":"0":String(t)]))).toString(),n=await this.fetchDocument(`?${r}`,Object.assign(this.options??{},t));if(!n)return null;return[...n.querySelectorAll("div.entry")].map(e=>this.extract(e,this.registry.list.fields)).filter(e=>null!==e.id||void 0!==e.id)}async get(e,t={}){const{columns:r,...n}=Object.assign(this.options??{},t),s=await this.fetchDocument(e.toString(),n);if(!s)return null;const a=this.extract(s,this.registry.item.fields);if(r&&this.registry.item.columns)for(const t of r){const r=await this.fetchColumn(e,String(t),n);void 0!==r&&(a[t]=r)}return a}},a={item:{columns:{achievement:{shape:{score:{selector:".achievement__point",type:"number"},total:{regex:/^(?<name>\d+)/,selector:".parts__total",type:"number"}},type:"object"},faceaccessory:{selector:".faceaccessory__sort__total > span:nth-child(1)",type:"number"},minion:{selector:".minion__sort__total > span:nth-child(1)",type:"number"},mount:{selector:".minion__sort__total > span:nth-child(1)",type:"number"}},fields:{avatar:{attribute:"src",selector:".frame__chara__face > img:nth-child(1)",type:"string"},bio:{selector:".character__selfintroduction",type:"string"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".frame__chara__world",type:"string"},free_company:{shape:{crest:{attribute:"src",selector:"div.character__freecompany__crest > div > img",type:"string[]"},id:{attribute:"href",regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:".character__freecompany__name > h4:nth-child(2) > a:nth-child(1)",type:"string"},name:{selector:".character__freecompany__name > h4:nth-child(2) > a:nth-child(1)",type:"string"}},type:"object"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"div.character-block:nth-child(4) > div:nth-child(2) > p:nth-child(2)",type:"string"},rank:{regex:/\/\s*(?<rank>.+)$/,selector:"div.character-block:nth-child(4) > div:nth-child(2) > p:nth-child(2)",type:"string"}},type:"object"},id:{attribute:"href",regex:/lodestone\/character\/(?<id>\d+)\//,selector:".frame__chara__link",type:"string"},name:{selector:"div.frame__chara__box:nth-child(2) > .frame__chara__name",type:"string"},portrait:{attribute:"src",selector:".js__image_popup > img:nth-child(1)",type:"string"},pvp_team:{shape:{crest:{attribute:"src",selector:".character__pvpteam__crest__image > img",type:"string[]"},id:{attribute:"href",regex:/lodestone\/pvpteam\/(?<id>[\da-z]+)\//,selector:".character__pvpteam__name > h4 > a",type:"string"},name:{selector:".character__pvpteam__name > h4 > a",type:"string"}},type:"object"},title:{selector:".frame__chara__title",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".frame__chara__world",type:"string"}}},list:{fields:{avatar:{attribute:"src",selector:".entry__chara__face > img",type:"string"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},grand_company:{shape:{name:{attribute:"data-tooltip",regex:/^(?<name>[^/]+)/,selector:".entry__chara_info > .js__tooltip",type:"string"},rank:{attribute:"data-tooltip",regex:/\/\s*(?<rank>.+)$/,selector:".entry__chara_info > .js__tooltip",type:"string"}},type:"object"},id:{attribute:"href",regex:/lodestone\/character\/(?<id>\d+)\//,selector:".entry__link",type:"string"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}},query:{blog_lang:{pattern:/^(?:ja|en|de|fr)$/,type:"string"},classjob:{pattern:/^(?:\d+|_job_(?:TANK|HEALER|MELEE|RANGED|CASTER|GATHERER|CRAFTER))$/,type:"string"},gcid:{pattern:/^[1-3]$/,type:"string"},order:{pattern:/^[18]?$/,type:"string"},q:{required:!0,type:"string"},race_tribe:{pattern:/^(?:race_\d+|tribe_\d+)$/,type:"string"},worldname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"character"},i={item:{fields:{data_center:{selector:".heading__cwls__dcname",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".heading__cwls__formed > script",type:"string"},members:{regex:/(?<total>\d+)/,selector:"div.cf-member-list > .parts__total",type:"number"},name:{regex:/\s*(?<name>.+)/,selector:".heading__linkshell__name",type:"string"}}},list:{fields:{data_center:{selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/crossworld_linkshell\/(?<id>.+)\//,selector:".entry__link--line",type:"string"},members:{selector:".entry__linkshell__member > div > span",type:"string"},name:{selector:".entry__name",type:"string"}},query:{cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},dcname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4])$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"}}},path:"crossworld_linkshell"},c={item:{fields:{crest:{attribute:"src",selector:"div.ldst__window:nth-child(1) > div:nth-child(2) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > img",type:"string[]"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:"p.entry__freecompany__gc:nth-child(2)",type:"string"},estate:{shape:{greeting:{selector:".freecompany__estate__greeting",type:"string"},name:{selector:".freecompany__estate__name",type:"string"},plot:{selector:".freecompany__estate__text",type:"string"}},type:"object"},formed:{regex:/ldst_strftime\((\d+),/,selector:"p.freecompany__text:nth-of-type(5) > script",type:"string"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"},rank:{regex:/\/\s*(?<rank>.+)$/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"}},type:"object"},id:{regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:"a.entry__freecompany",type:"string"},members:{selector:"p.freecompany__text:nth-of-type(6)",type:"number"},name:{selector:"p.entry__freecompany__name",type:"string"},rank:{selector:"p.freecompany__text:nth-of-type(7)",type:"number"},rankings:{shape:{monthly:{regex:/Monthly Rank:(?<rank>\d+)/,selector:".character__ranking__data tr:nth-child(2) > th:nth-child(1)",type:"number"},weekly:{regex:/Weekly Rank:(?<rank>\d+)/,selector:".character__ranking__data tr:nth-child(1) > th:nth-child(1)",type:"number"}},type:"object"},slogan:{selector:".freecompany__text__message",type:"string"},tag:{selector:".freecompany__text.freecompany__text__tag",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:"p.entry__freecompany__gc:nth-child(2)",type:"string"}}},list:{fields:{crest:{attribute:"src",selector:".entry__freecompany__crest__image > img",type:"string[]"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world:nth-child(3)",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".entry__freecompany__fc-day > script",type:"string"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"}},type:"object"},has_estate:{selector:".entry__freecompany__fc-housing",type:"boolean"},id:{attribute:"href",regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:".entry__block",type:"string"},members:{selector:".entry__freecompany__fc-member",type:"number"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world:nth-child(3)",type:"string"}},query:{activetime:{pattern:/^[1-3]$/,type:"string"},activities:{pattern:/^(?:-1|[0-8])$/,type:"string"},cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},gcid:{pattern:/^[1-3]$/,type:"string"},house:{pattern:/^[0-2]$/,type:"string"},join:{type:"boolean"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"},roles:{pattern:/^(?:-1|1[6-9]|20)$/,type:"string"},worldname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"freecompany"},o={item:{fields:{data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},members:{regex:/(?<total>\d+)/,selector:"div.cf-member-list > .parts__total",type:"number"},name:{regex:/\s*(?<name>.+)/,selector:".heading__linkshell__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}}},list:{fields:{data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/linkshell\/(?<id>.+)\//,selector:".entry__block",type:"string"},members:{regex:/(?<total>\d+)/,selector:".entry__linkshell__member > div > span",type:"number"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}},query:{cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"},worldname:{pattern:/^(?:_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"linkshell"},l={item:{fields:{crest:{attribute:"src",selector:".entry__pvpteam__crest__image > img",type:"string[]"},data_center:{selector:".entry__pvpteam__name--dc",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".entry__pvpteam__data--formed > script",type:"string"},name:{selector:".entry__pvpteam__name--team",type:"string"}}},list:{fields:{crest:{attribute:"src",selector:".entry__pvpteam__search__crest__image > img",type:"string[]"},data_center:{selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/pvpteam\/(?<id>.+)\//,selector:".entry__link",type:"string"},name:{selector:".entry__name",type:"string"}},query:{cf_public:{type:"boolean"},dcname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4])$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"}}},path:"pvpteam"},_=class{constructor(e={}){t(this,"character"),t(this,"cwls"),t(this,"freecompany"),t(this,"linkshell"),t(this,"pvpteam"),this.character=new s(a,e),this.cwls=new s(i,e),this.freecompany=new s(c,e),this.linkshell=new s(o,e),this.pvpteam=new s(l,e)}},p=_;export{_ as Lodestone,p as default};
1
+ var e=Object.defineProperty,t=(t,r,n)=>((t,r,n)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[r]=n)(t,"symbol"!=typeof r?r+"":r,n);import{parseHTML as r}from"linkedom/worker";var n=class extends Error{constructor(e){super(e),Error.captureStackTrace(this),this.name="LodestoneError"}},s=class{constructor(e,r){this.registry=e,this.options=r,t(this,"isMissing",e=>null==e),t(this,"isValid",(e,t)=>{switch(t){case"number":return"number"==typeof e||!Number.isNaN(Number(e));case"boolean":return"boolean"==typeof e;case"date":return e instanceof Date||!Number.isNaN(Date.parse(String(e)));case"url":try{return new URL(String(e)),!0}catch{return!1}default:return"string"==typeof e}}),t(this,"validate",e=>{const t=this.registry.list.query;for(const r of Object.keys(e))if(!(r in t))throw new n(`Unknown query parameter "${r}".`);for(const[r,s]of Object.entries(t)){const t=e[r];if(s.required&&this.isMissing(t))throw new n(`Missing required query parameter "${r}".`);if(!this.isMissing(t)){if(!this.isValid(t,s.type))throw new n(`Query parameter "${r}" must be type ${s.type}.`);if(s.pattern&&!s.pattern.test(String(t)))throw new n(`Query parameter "${r}" does not match required pattern.`)}}})}check(e){const{status:t}=e;if(200!==t){if(302===t)throw new n("Lodestone redirected the request.");if(503===t)throw new n("Lodestone is undergoing maintenance.");if(t>=500)throw new n(`Lodestone server error (${t}).`);if(t>=400)throw new n(`Request failed with status ${t}.`)}}async req(e,t){const{headers:r={},locale:n="na"}=t??this.options,s=Object.fromEntries(Object.entries(r).map(([e,t])=>[e.toLowerCase(),String(t)])),a="curl/0.1.0 (+https://github.com/miichom/lodestone)";return s["user-agent"]=s["user-agent"]?`${a} ${s["user-agent"]}`:a,fetch(`https://${n}.finalfantasyxiv.com/lodestone/${this.registry.path}/${e.replace(/^\/+/,"")}`,{headers:s,method:"GET",redirect:"follow"})}async fetchDocument(e,t){const n=await this.req(e,t);if(!n.ok||404===n.status)return null;this.check(n);const{document:s}=r(await n.text());return!s||s.querySelector(".parts__zero")?null:s}async fetchColumn(e,t,r){const n=this.registry.item.columns?.[t];if(!n)return;const s=await this.fetchDocument(`${e.toString()}/${t}`,r);if(!s)return;if(!("shape"in n))return this.extract(s,{value:n}).value;if("object"===n.type)return this.extract(s,n.shape);const a=n.shape.root.selector;return[...s.querySelectorAll(a)].map(e=>this.extract(e,n.shape))}getRawValue(e,t){return t.attribute?e.getAttribute(t.attribute)??"":e.textContent.trim()??""}applyRegex(e,t){if(!t.regex)return e;const r=e.match(t.regex);if(!r)return"";if(r.groups){const[e]=Object.keys(r.groups);return r.groups[e]??""}return r[1]??""}coerce(e,t){switch(t){case"boolean":return"1"===e||"true"===e;case"number":return Number(e);case"date":return new Date(e);case"url":return new URL(e);default:return e}}pickSelectors(e,t){const r={};for(const n of t)r[n]=e[n];return r}extract(e,t){const r={};for(const[n,s]of Object.entries(t)){if("shape"in s){if("object"===s.type)r[n]=this.extract(e,s.shape);else{const t=[...e.querySelectorAll(s.shape.root.selector)];r[n]=t.map(e=>this.extract(e,s.shape))}continue}const t=s.type.endsWith("[]"),a=t?s.type.slice(0,-2):s.type;if(t){const t=[...e.querySelectorAll(s.selector)].map(e=>{const t=this.getRawValue(e,s),r=this.applyRegex(t,s);return this.coerce(r,a)});r[n]=t.length>0?t:void 0;continue}const i=e.querySelector(s.selector);if(!i){r[n]=void 0;continue}const c=this.getRawValue(i,s),o=this.applyRegex(c,s);r[n]=this.coerce(o,a)}return r}async find(e,t={}){const{fields:r,...n}=t;this.validate(e);const s=new URLSearchParams(Object.fromEntries(Object.entries(e).filter(([,e])=>null!=e).map(([e,t])=>[e,"boolean"==typeof t?t?"1":"0":String(t)]))).toString(),a=await this.fetchDocument(`?${s}`,Object.assign(this.options??{},n));if(!a)return null;const i=r?.length?this.pickSelectors(this.registry.item.fields,r):this.registry.item.fields;return[...a.querySelectorAll("div.entry")].map(e=>this.extract(e,i)).filter(e=>null!==e.id||void 0!==e.id)}async get(e,t={}){const{columns:r,fields:n,...s}=t,a=await this.fetchDocument(e.toString(),Object.assign(this.options??{},s));if(!a)return null;const i=n?.length?this.pickSelectors(this.registry.item.fields,n):this.registry.item.fields,c=this.extract(a,i);if(r&&this.registry.item.columns)for(const t of r){const r=await this.fetchColumn(e,String(t),s);void 0!==r&&(c[t]=r)}return c}},a={item:{columns:{achievement:{shape:{score:{selector:".achievement__point",type:"number"},total:{regex:/^(?<name>\d+)/,selector:".parts__total",type:"number"}},type:"object"},faceaccessory:{selector:".faceaccessory__sort__total > span:nth-child(1)",type:"number"},minion:{selector:".minion__sort__total > span:nth-child(1)",type:"number"},mount:{selector:".minion__sort__total > span:nth-child(1)",type:"number"}},fields:{avatar:{attribute:"src",selector:".frame__chara__face > img:nth-child(1)",type:"url"},bio:{selector:".character__selfintroduction",type:"string"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".frame__chara__world",type:"string"},free_company:{shape:{crest:{attribute:"src",selector:"div.character__freecompany__crest > div > img",type:"url[]"},id:{attribute:"href",regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:".character__freecompany__name > h4:nth-child(2) > a:nth-child(1)",type:"string"},name:{selector:".character__freecompany__name > h4:nth-child(2) > a:nth-child(1)",type:"string"}},type:"object"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"div.character-block:nth-child(4) > div:nth-child(2) > p:nth-child(2)",type:"string"},rank:{regex:/\/\s*(?<rank>.+)$/,selector:"div.character-block:nth-child(4) > div:nth-child(2) > p:nth-child(2)",type:"string"}},type:"object"},id:{attribute:"href",regex:/lodestone\/character\/(?<id>\d+)\//,selector:".frame__chara__link",type:"string"},name:{selector:"div.frame__chara__box:nth-child(2) > .frame__chara__name",type:"string"},portrait:{attribute:"src",selector:".js__image_popup > img:nth-child(1)",type:"url"},pvp_team:{shape:{crest:{attribute:"src",selector:".character__pvpteam__crest__image > img",type:"url[]"},id:{attribute:"href",regex:/lodestone\/pvpteam\/(?<id>[\da-z]+)\//,selector:".character__pvpteam__name > h4 > a",type:"string"},name:{selector:".character__pvpteam__name > h4 > a",type:"string"}},type:"object"},title:{selector:".frame__chara__title",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".frame__chara__world",type:"string"}}},list:{fields:{avatar:{attribute:"src",selector:".entry__chara__face > img",type:"url"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},grand_company:{shape:{name:{attribute:"data-tooltip",regex:/^(?<name>[^/]+)/,selector:".entry__chara_info > .js__tooltip",type:"string"},rank:{attribute:"data-tooltip",regex:/\/\s*(?<rank>.+)$/,selector:".entry__chara_info > .js__tooltip",type:"string"}},type:"object"},id:{attribute:"href",regex:/lodestone\/character\/(?<id>\d+)\//,selector:".entry__link",type:"string"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}},query:{blog_lang:{pattern:/^(?:ja|en|de|fr)$/,type:"string"},classjob:{pattern:/^(?:\d+|_job_(?:TANK|HEALER|MELEE|RANGED|CASTER|GATHERER|CRAFTER))$/,type:"string"},gcid:{pattern:/^[1-3]$/,type:"string"},order:{pattern:/^[18]?$/,type:"string"},q:{required:!0,type:"string"},race_tribe:{pattern:/^(?:race_\d+|tribe_\d+)$/,type:"string"},worldname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"character"},i={item:{fields:{data_center:{selector:".heading__cwls__dcname",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".heading__cwls__formed > script",type:"date"},members:{regex:/(?<total>\d+)/,selector:"div.cf-member-list > .parts__total",type:"number"},name:{regex:/\s*(?<name>.+)/,selector:".heading__linkshell__name",type:"string"}}},list:{fields:{data_center:{selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/crossworld_linkshell\/(?<id>.+)\//,selector:".entry__link--line",type:"string"},members:{selector:".entry__linkshell__member > div > span",type:"string"},name:{selector:".entry__name",type:"string"}},query:{cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},dcname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4])$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"}}},path:"crossworld_linkshell"},c={item:{fields:{crest:{attribute:"src",selector:"div.ldst__window:nth-child(1) > div:nth-child(2) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > img",type:"url[]"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:"p.entry__freecompany__gc:nth-child(2)",type:"string"},estate:{shape:{greeting:{selector:".freecompany__estate__greeting",type:"string"},name:{selector:".freecompany__estate__name",type:"string"},plot:{selector:".freecompany__estate__text",type:"string"}},type:"object"},formed:{regex:/ldst_strftime\((\d+),/,selector:"p.freecompany__text:nth-of-type(5) > script",type:"date"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"},rank:{regex:/\/\s*(?<rank>.+)$/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"}},type:"object"},id:{regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:"a.entry__freecompany",type:"string"},members:{selector:"p.freecompany__text:nth-of-type(6)",type:"number"},name:{selector:"p.entry__freecompany__name",type:"string"},rank:{selector:"p.freecompany__text:nth-of-type(7)",type:"number"},rankings:{shape:{monthly:{regex:/Monthly Rank:(?<rank>\d+)/,selector:".character__ranking__data tr:nth-child(2) > th:nth-child(1)",type:"number"},weekly:{regex:/Weekly Rank:(?<rank>\d+)/,selector:".character__ranking__data tr:nth-child(1) > th:nth-child(1)",type:"number"}},type:"object"},slogan:{selector:".freecompany__text__message",type:"string"},tag:{selector:".freecompany__text.freecompany__text__tag",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:"p.entry__freecompany__gc:nth-child(2)",type:"string"}}},list:{fields:{crest:{attribute:"src",selector:".entry__freecompany__crest__image > img",type:"url[]"},data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world:nth-child(3)",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".entry__freecompany__fc-day > script",type:"date"},grand_company:{shape:{name:{regex:/^(?<name>[^/]+)/,selector:"p.entry__freecompany__gc:nth-child(1)",type:"string"}},type:"object"},has_estate:{selector:".entry__freecompany__fc-housing",type:"boolean"},id:{attribute:"href",regex:/lodestone\/freecompany\/(?<id>\d+)\//,selector:".entry__block",type:"string"},members:{selector:".entry__freecompany__fc-member",type:"number"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world:nth-child(3)",type:"string"}},query:{activetime:{pattern:/^[1-3]$/,type:"string"},activities:{pattern:/^(?:-1|[0-8])$/,type:"string"},cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},gcid:{pattern:/^[1-3]$/,type:"string"},house:{pattern:/^[0-2]$/,type:"string"},join:{type:"boolean"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"},roles:{pattern:/^(?:-1|1[6-9]|20)$/,type:"string"},worldname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"freecompany"},o={item:{fields:{data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},members:{regex:/(?<total>\d+)/,selector:"div.cf-member-list > .parts__total",type:"number"},name:{regex:/\s*(?<name>.+)/,selector:".heading__linkshell__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}}},list:{fields:{data_center:{regex:/\[(?<datacenter>\w+)]/,selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/linkshell\/(?<id>.+)\//,selector:".entry__block",type:"string"},members:{regex:/(?<total>\d+)/,selector:".entry__linkshell__member > div > span",type:"number"},name:{selector:".entry__name",type:"string"},world_name:{regex:/^(?<world>\w+)/,selector:".entry__world",type:"string"}},query:{cf_public:{type:"boolean"},character_count:{pattern:/^(?:\d+-\d+|\d+-)$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"},worldname:{pattern:/^(?:_region_[1-4]|[A-Za-z]+)$/,type:"string"}}},path:"linkshell"},l={item:{fields:{crest:{attribute:"src",selector:".entry__pvpteam__crest__image > img",type:"url[]"},data_center:{selector:".entry__pvpteam__name--dc",type:"string"},formed:{regex:/ldst_strftime\((\d+),/,selector:".entry__pvpteam__data--formed > script",type:"date"},name:{selector:".entry__pvpteam__name--team",type:"string"}}},list:{fields:{crest:{attribute:"src",selector:".entry__pvpteam__search__crest__image > img",type:"url[]"},data_center:{selector:".entry__world",type:"string"},id:{attribute:"href",regex:/lodestone\/pvpteam\/(?<id>.+)\//,selector:".entry__link",type:"string"},name:{selector:".entry__name",type:"string"}},query:{cf_public:{type:"boolean"},dcname:{pattern:/^(?:_dc_[A-Za-z]+|_region_[1-4])$/,type:"string"},order:{pattern:/^[16]?$/,type:"string"},q:{required:!0,type:"string"}}},path:"pvpteam"},_=class{constructor(e={}){t(this,"character"),t(this,"cwls"),t(this,"freecompany"),t(this,"linkshell"),t(this,"pvpteam"),this.character=new s(a,e),this.cwls=new s(i,e),this.freecompany=new s(c,e),this.linkshell=new s(o,e),this.pvpteam=new s(l,e)}},p=_;export{_ as Lodestone,p as default};
package/package.json CHANGED
@@ -1,63 +1,61 @@
1
- {
2
- "name": "@miichom/lodestone",
3
- "version": "0.1.0",
4
- "description": "Fast, worker-safe Lodestone parsing ✨",
5
- "keywords": [
6
- "lodestone",
7
- "ffxiv",
8
- "final fantasy xiv",
9
- "ffxiv data",
10
- "scraper",
11
- "dom parser",
12
- "html parser",
13
- "fast"
14
- ],
15
- "homepage": "https://github.com/miichom/lodestone#readme",
16
- "bugs": {
17
- "url": "https://github.com/miichom/lodestone/issues"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/miichom/lodestone.git"
22
- },
23
- "license": "MIT",
24
- "author": "miichom <hello@cammy.xyz> (https://cammy.xyz/)",
25
- "type": "module",
26
- "main": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
- "scripts": {
29
- "test": "vitest",
30
- "build": "tsup",
31
- "format": "prettier --write .",
32
- "lint": "eslint --ext .ts",
33
- "prepublishOnly": "pnpm run build"
34
- },
35
- "dependencies": {
36
- "linkedom": "^0.18.12"
37
- },
38
- "devDependencies": {
39
- "@eslint/js": "^9.39.2",
40
- "@tsconfig/node20": "^20.1.8",
41
- "@types/node": "^25.0.9",
42
- "@vitest/coverage-v8": "^4.0.18",
43
- "eslint": "^9.39.2",
44
- "eslint-config-prettier": "^10.1.8",
45
- "eslint-plugin-sort": "^4.0.0",
46
- "eslint-plugin-unicorn": "^62.0.0",
47
- "globals": "^17.0.0",
48
- "jiti": "^2.6.1",
49
- "prettier": "^3.8.0",
50
- "terser": "^5.46.0",
51
- "tsup": "^8.5.1",
52
- "typescript": "^5.9.3",
53
- "typescript-eslint": "^8.53.0",
54
- "vitest": "^4.0.17"
55
- },
56
- "files": [
57
- "dist"
58
- ],
59
- "engines": {
60
- "node": ">=20.0.0"
61
- },
62
- "packageManager": "pnpm@10.28.0"
63
- }
1
+ {
2
+ "name": "@miichom/lodestone",
3
+ "version": "0.1.1",
4
+ "description": "Fast, worker-safe Lodestone parsing ✨",
5
+ "keywords": [
6
+ "lodestone",
7
+ "ffxiv",
8
+ "final fantasy xiv",
9
+ "ffxiv data",
10
+ "scraper",
11
+ "dom parser",
12
+ "html parser",
13
+ "fast"
14
+ ],
15
+ "homepage": "https://github.com/miichom/lodestone#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/miichom/lodestone/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/miichom/lodestone.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "miichom <hello@cammy.xyz> (https://cammy.xyz/)",
25
+ "type": "module",
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "dependencies": {
29
+ "linkedom": "^0.18.12"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "^9.39.2",
33
+ "@tsconfig/node20": "^20.1.8",
34
+ "@types/node": "^25.0.9",
35
+ "@vitest/coverage-v8": "^4.0.18",
36
+ "eslint": "^9.39.2",
37
+ "eslint-config-prettier": "^10.1.8",
38
+ "eslint-plugin-sort": "^4.0.0",
39
+ "eslint-plugin-unicorn": "^62.0.0",
40
+ "globals": "^17.0.0",
41
+ "jiti": "^2.6.1",
42
+ "prettier": "^3.8.0",
43
+ "terser": "^5.46.0",
44
+ "tsup": "^8.5.1",
45
+ "typescript": "^5.9.3",
46
+ "typescript-eslint": "^8.53.0",
47
+ "vitest": "^4.0.17"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "engines": {
53
+ "node": ">=20.0.0"
54
+ },
55
+ "scripts": {
56
+ "test": "vitest",
57
+ "build": "tsup",
58
+ "format": "prettier --write .",
59
+ "lint": "eslint --ext .ts"
60
+ }
61
+ }