@piyoraik/ffxiv-lodestone-character-lookup 1.0.5 → 1.0.7

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,10 +1,10 @@
1
- # @piyoraik/ffxiv-lodestone-character-lookup
1
+ # ffxiv-lodestone-character-lookup
2
2
 
3
3
  FFXIV の Lodestone を使って、以下を行うための共通ライブラリです。
4
4
 
5
5
  - `キャラクター名` と `サーバー名` から検索パラメータを作る
6
6
  - キャラクター検索の先頭ヒットから「キャラクターURL(/lodestone/character/.../)」を取得する
7
- - キャラクターの「アチーブメント(カテゴリ4)」ページから、高難度(絶/零式)の達成状況を判定する
7
+ - Lodestone からアチーブメントの達成状況と達成要件を取得する
8
8
 
9
9
  ## 動作環境
10
10
 
@@ -21,10 +21,12 @@ yarn add @piyoraik/ffxiv-lodestone-character-lookup
21
21
  ```ts
22
22
  import {
23
23
  buildLodestoneSearchUrl,
24
- fetchTopCharacterUrl,
25
- buildAchievementCategoryUrl,
26
- fetchAchievementCategoryHtml,
27
- parseUltimateClearsFromAchievementHtml
24
+ fetchTopCharacterResult,
25
+ fetchAchievementDetailsByCategory,
26
+ getAllAchievementDefinitions,
27
+ getAchievementCategoryDefinitions,
28
+ getAchievementDefinitions,
29
+ getAchievementCategoryId
28
30
  } from "@piyoraik/ffxiv-lodestone-character-lookup";
29
31
 
30
32
  const creator = { name: "Hoge Fuga", world: "World" };
@@ -32,55 +34,109 @@ const creator = { name: "Hoge Fuga", world: "World" };
32
34
  // 1) Lodestoneの検索URLを作る
33
35
  const searchUrl = buildLodestoneSearchUrl(creator);
34
36
 
35
- // 2) 検索の先頭ヒットからキャラクターURLを取る
36
- const characterUrl = await fetchTopCharacterUrl(searchUrl);
37
- if (!characterUrl) throw new Error("キャラクターが見つかりませんでした");
38
-
39
- // 3) アチーブメントページ(カテゴリ4)のURLを作る
40
- const achievementUrl = buildAchievementCategoryUrl(characterUrl);
41
- if (!achievementUrl) throw new Error("アチーブURLの生成に失敗しました");
42
-
43
- // 4) HTMLを取得して達成状況を判定
44
- const html = await fetchAchievementCategoryHtml(achievementUrl);
45
- const result = parseUltimateClearsFromAchievementHtml(html);
46
- console.log(result);
37
+ // 2) 検索結果の状態を取得する
38
+ const searchResult = await fetchTopCharacterResult(searchUrl);
39
+ if (searchResult.status !== "ok") {
40
+ throw new Error(`キャラクターが取得できません: ${searchResult.status}`);
41
+ }
42
+ const characterUrl = searchResult.characterUrl;
43
+
44
+ // 3) カテゴリ指定で取得する
45
+ const raids = await fetchAchievementDetailsByCategory(characterUrl, "raids");
46
+ console.log(raids);
47
+ const fieldOps = await fetchAchievementDetailsByCategory(characterUrl, "field_ops");
48
+ console.log(fieldOps);
49
+ const dungeons = await fetchAchievementDetailsByCategory(characterUrl, "dungeons");
50
+ console.log(dungeons);
51
+
52
+ // 定義をまとめて取得する場合
53
+ const categories = getAchievementCategoryDefinitions();
54
+ const allAchievements = getAllAchievementDefinitions();
55
+ const raidsDefinitions = getAchievementDefinitions("raids");
56
+ const raidsCategoryId = getAchievementCategoryId("raids");
57
+ console.log(categories.length, allAchievements.length, raidsDefinitions.length, raidsCategoryId);
58
+
59
+ /*
60
+ {
61
+ status: "ok",
62
+ lodestone: "https://jp.finalfantasyxiv.com/lodestone/character/12345/",
63
+ achievements: {
64
+ raids: [
65
+ { name: "絶バハムートを狩りし者", date: "2014/06/10", requirement: "絶バハムート討滅戦で、バハムート・プライムを討伐する" },
66
+ { name: "万魔殿の辺獄を完全制覇せし者:ランク1", date: null, requirement: "万魔殿パンデモニウム零式:辺獄編を攻略する" }
67
+ ]
68
+ }
69
+ }
70
+ */
47
71
  ```
48
72
 
49
- ## 公開API(関数の挙動)
50
-
51
- ## API一覧(概要)
73
+ ## 関数一覧
52
74
 
53
75
  | 関数 | 返り値 | 用途 |
54
76
  |---|---|---|
55
77
  | `buildLodestoneSearchUrl(info)` | `string` | キャラクター検索URLを生成 |
56
- | `fetchTopCharacterUrl(searchUrl)` | `Promise<string \| undefined>` | 検索結果の先頭ヒットのキャラURLを取得 |
57
- | `buildAchievementCategoryUrl(characterUrl)` | `string \| undefined` | カテゴリ4(レイド)のアチーブURLを生成 |
58
- | `fetchAchievementCategoryHtml(url)` | `Promise<string>` | アチーブHTMLを取得 |
59
- | `parseUltimateClearsFromAchievementHtml(html)` | `{ status, clears }` | 高難度(絶/零式)達成状況を抽出 |
60
- | `getHighEndAchievements()` | `HighEndAchievementDefinition[]` | 同梱定義(正式名/略称/種別)を取得 |
61
- | `getHighEndAchievementShortMap()` | `Map<string, string>` | `正式名 -> 略称` |
62
- | `getHighEndAchievementGroupMap()` | `Map<string, "ultimate" \| "savage">` | `正式名 -> 種別` |
63
-
64
- ## 対応アチーブメント(高難度)
78
+ | `fetchTopCharacterResult(searchUrl)` | `Promise<{ status, characterUrl?, reason? }>` | 検索結果の状態を取得(非公開は `private`) |
79
+ | `fetchAchievementDetailsByCategory(characterUrl, category)` | `Promise<{ status, lodestone, achievements, reason? }>` | カテゴリ指定で達成状況/達成要件を取得 |
80
+ | `getAchievementDefinitions(category)` | `AchievementDefinition[]` | カテゴリ指定で定義を取得 |
81
+ | `getAllAchievementDefinitions()` | `AchievementDefinition[]` | 全カテゴリの定義を取得 |
82
+ | `getAchievementCategoryDefinitions()` | `AchievementCategoryDefinition[]` | カテゴリ一覧(ID/定義)を取得 |
83
+ | `getAchievementCategoryId(category)` | `number \| undefined` | カテゴリ名からカテゴリIDを取得 |
65
84
 
66
- このライブラリが達成判定に対応しているアチーブメントの一覧です(`getHighEndAchievements()` の内容と同じ)。
85
+ ## 対応カテゴリ一覧
67
86
 
68
- | 種別 | 略称 | 正式名 |
87
+ | category | 説明 | categoryId |
69
88
  |---|---|---|
70
- | | 絶バハ | 絶バハムートを狩りし者 |
71
- | | 絶テマ | 絶アルテマウェポンを破壊せし者 |
72
- | | 絶アレキ | 絶アレキサンダーを破壊せし者 |
73
- | 絶 | 絶竜詩 | 絶竜詩戦争を平定せし者 |
74
- | 絶 | 絶オメガ | 絶オメガ検証戦を完遂せし者 |
75
- | 絶 | 絶エデン | 絶もうひとつの未来を見届けし者 |
76
- | 零式 | 【パンデモ】辺獄 | 万魔殿の辺獄を完全制覇せし者:ランク1 |
77
- | 零式 | 【パンデモ】煉獄 | 万魔殿の煉獄を完全制覇せし者:ランク1 |
78
- | 零式 | 【パンデモ】天獄 | 万魔殿の天獄を完全制覇せし者:ランク1 |
79
- | 零式 | 【アルカディア】ライトヘビー | アルカディアのライトヘビー級を制覇せし者:ランク1 |
80
- | 零式 | 【アルカディア】クルーザー | アルカディアのクルーザー級を完全制覇せし者:ランク1 |
81
- | 零式 | 【アルカディア】ヘビー | アルカディアのヘビー級を完全制覇せし者:ランク1 |
89
+ | `raids` | レイド(高難度) | `4` |
90
+ | `field_ops` | 特殊フィールド探索 | `71` |
91
+ | `dungeons` | ダンジョン | `2` |
92
+
93
+ ## 対応アチーブメント(カテゴリ別)
94
+
95
+ このライブラリが達成判定に対応しているアチーブメントの一覧です。
96
+ アップデートごとにベストエフォートで更新し、必要であればIssueやプルリクを頂ければ優先的に対応します。
97
+
98
+ ### レイド(高難度)
99
+ | 正式名 | 達成要件 |
100
+ |---|---|
101
+ | 絶バハムートを狩りし者 | 絶バハムート討滅戦で、バハムート・プライムを討伐する |
102
+ | 絶アルテマウェポンを破壊せし者 | 絶アルテマウェポン破壊作戦で、アルテマウェポンを討伐する |
103
+ | 絶アレキサンダーを破壊せし者 | 絶アレキサンダー討滅戦で、パーフェクト・アレキサンダーを討伐する |
104
+ | 絶竜詩戦争を平定せし者 | 絶竜詩戦争を平定する |
105
+ | 絶オメガ検証戦を完遂せし者 | オメガの求める検証を完遂する |
106
+ | 絶もうひとつの未来を見届けし者 | 光と闇の巫女の運命を集約させる |
107
+ | 万魔殿の辺獄を完全制覇せし者:ランク1 | 万魔殿パンデモニウム零式:辺獄編を攻略する |
108
+ | 万魔殿の煉獄を完全制覇せし者:ランク1 | 万魔殿パンデモニウム零式:煉獄編を攻略する |
109
+ | 万魔殿の天獄を完全制覇せし者:ランク1 | 万魔殿パンデモニウム零式:天獄編を攻略する |
110
+ | アルカディアのライトヘビー級を制覇せし者:ランク1 | 至天の座アルカディア:ライトヘビー級を攻略し、クエスト「次世代魔女」をコンプリートする |
111
+ | アルカディアのクルーザー級を完全制覇せし者:ランク1 | 至天の座アルカディア零式:クルーザー級を攻略する |
112
+ | アルカディアのヘビー級を完全制覇せし者:ランク1 | 至天の座アルカディア零式:ヘビー級を攻略する |
113
+
114
+ ### 特殊フィールド探索
115
+ | 正式名 | 達成要件 |
116
+ |---|---|
117
+ | バルデシオンアーセナルの覇者:ランク1 | バルデシオンアーセナルを初めて攻略する |
118
+ | ボズヤの彗星 | 堅守彗星章、勇猛彗星章、救命彗星章を、各10個入手する |
119
+ | グンヒルド・ディルーブラムを完全制覇せし者:ランク1 | グンヒルド・ディルーブラム零式を攻略する |
120
+ | 力の塔を制覇せし者:ランク1 | 蜃気楼の島 クレセントアイルにて、フォークタワー:力の塔を初めて攻略する |
121
+
122
+ ### ダンジョン
123
+ | 正式名 | 達成要件 |
124
+ |---|---|
125
+ | 語り継がれし冒険譚:ランク3 | ダンジョン、討伐・討滅戦を計10,000回攻略する |
126
+ | 孤独なる挑戦者:ランク3 | 死者の宮殿にソロでB1から突入しB200まで攻略する |
127
+ | 埋もれた財宝:ランク7 | 埋もれた財宝を30,000個発見した |
128
+ | ここ掘れワンワン:ランク2 | 埋もれた財宝を、魔土器:財宝感知もしくは魔科学器:財宝感知を使わずに100個発見した |
129
+ | 孤高なる挑戦者:ランク2 | アメノミハシラにソロで1層から突入し100層まで攻略する |
130
+ | 崇高なる挑戦者:ランク2 | オルト・エウレカにソロでB1から突入しB100まで攻略する |
131
+ | 至高なる挑戦者:ランク2 | ピルグリム・トラバースにソロで第1巡礼路から突入し第100巡礼路まで攻略する |
132
+ | 死せる巡礼路の果てへ | 詩想エミネントグリーフ討滅戦を供物を最大限捧げた状態で攻略する |
133
+ | 異聞シラディハ水道を完全制覇せし者 | 異聞シラディハ水道 零式(アナザーダンジョン)を攻略する |
134
+ | 異聞六根山を完全制覇せし者 | 異聞六根山 零式(アナザーダンジョン)を攻略する |
135
+ | 異聞アロアロ島を完全制覇せし者 | 異聞アロアロ島 零式(アナザーダンジョン)を攻略する |
136
+ | 異聞奇譚の勇傑 | アチーブメント「異聞シラディハ水道を完全制覇せし者」「異聞六根山を完全制覇せし者」「異聞アロアロ島を完全制覇せし者」をすべて達成する |
82
137
 
83
138
  ## 注意
84
139
 
85
140
  - Lodestone検索は「先頭ヒット」を採用します(同名が複数いる場合、意図と違うキャラに当たる可能性があります)
86
141
  - HTML構造(class名など)が変わるとパースが壊れます(その場合はバージョン更新で追随します)
142
+ - アチーブメントの達成要件は Lodestone から引用しています
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": 1,
3
+ "category": "dungeons",
4
+ "categoryId": 2,
5
+ "achievements": [
6
+ { "name": "語り継がれし冒険譚:ランク3" },
7
+ { "name": "孤独なる挑戦者:ランク3" },
8
+ { "name": "埋もれた財宝:ランク7" },
9
+ { "name": "ここ掘れワンワン:ランク2" },
10
+ { "name": "孤高なる挑戦者:ランク2" },
11
+ { "name": "崇高なる挑戦者:ランク2" },
12
+ { "name": "至高なる挑戦者:ランク2" },
13
+ { "name": "死せる巡礼路の果てへ" },
14
+ { "name": "異聞シラディハ水道を完全制覇せし者" },
15
+ { "name": "異聞六根山を完全制覇せし者" },
16
+ { "name": "異聞アロアロ島を完全制覇せし者" },
17
+ { "name": "異聞奇譚の勇傑" }
18
+ ]
19
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "category": "field_ops",
4
+ "categoryId": 71,
5
+ "achievements": [
6
+ { "name": "バルデシオンアーセナルの覇者:ランク1" },
7
+ { "name": "ボズヤの彗星" },
8
+ { "name": "グンヒルド・ディルーブラムを完全制覇せし者:ランク1" },
9
+ { "name": "力の塔を制覇せし者:ランク1" }
10
+ ]
11
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": 1,
3
+ "category": "raids",
4
+ "categoryId": 4,
5
+ "achievements": [
6
+ { "name": "絶バハムートを狩りし者" },
7
+ { "name": "絶アルテマウェポンを破壊せし者" },
8
+ { "name": "絶アレキサンダーを破壊せし者" },
9
+ { "name": "絶竜詩戦争を平定せし者" },
10
+ { "name": "絶オメガ検証戦を完遂せし者" },
11
+ { "name": "絶もうひとつの未来を見届けし者" },
12
+ { "name": "万魔殿の辺獄を完全制覇せし者:ランク1" },
13
+ { "name": "万魔殿の煉獄を完全制覇せし者:ランク1" },
14
+ { "name": "万魔殿の天獄を完全制覇せし者:ランク1" },
15
+ { "name": "アルカディアのライトヘビー級を制覇せし者:ランク1" },
16
+ { "name": "アルカディアのクルーザー級を完全制覇せし者:ランク1" },
17
+ { "name": "アルカディアのヘビー級を完全制覇せし者:ランク1" }
18
+ ]
19
+ }
@@ -2,6 +2,18 @@ export type CreatorInfo = {
2
2
  name: string;
3
3
  world: string;
4
4
  };
5
+ export type CharacterSearchStatus = "ok" | "private" | "ng";
6
+ export type CharacterSearchReason = "search_hidden" | "empty_html" | "structure_mismatch" | "http_error";
7
+ export type CharacterSearchResult = {
8
+ status: "ok";
9
+ characterUrl: string;
10
+ } | {
11
+ status: "private";
12
+ reason: CharacterSearchReason;
13
+ } | {
14
+ status: "ng";
15
+ reason: CharacterSearchReason;
16
+ };
5
17
  /**
6
18
  * Lodestone のキャラクター検索URLを生成します。
7
19
  * 例:
@@ -9,11 +21,7 @@ export type CreatorInfo = {
9
21
  */
10
22
  export declare function buildLodestoneSearchUrl(info: CreatorInfo): string;
11
23
  /**
12
- * キャラクター検索結果HTMLから、先頭に表示されるキャラクターURLを取得します。
13
- */
14
- export declare function parseTopCharacterUrlFromSearchHtml(html: string): string | undefined;
15
- /**
16
- * Lodestone のキャラクター検索を行い、先頭にヒットしたキャラクターURLを返します。
24
+ * Lodestone のキャラクター検索を行い、検索結果の状態を返します。
17
25
  */
18
- export declare function fetchTopCharacterUrl(searchUrl: string): Promise<string | undefined>;
26
+ export declare function fetchTopCharacterResult(searchUrl: string): Promise<CharacterSearchResult>;
19
27
  //# sourceMappingURL=lodestone.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lodestone.d.ts","sourceRoot":"","sources":["../src/lodestone.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAsBjE;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKnF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAYzF"}
1
+ {"version":3,"file":"lodestone.d.ts","sourceRoot":"","sources":["../src/lodestone.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;AAE5D,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG,YAAY,GAAG,oBAAoB,GAAG,YAAY,CAAC;AAEzG,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,GACpD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAsBjE;AAoBD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAgB/F"}
package/dist/lodestone.js CHANGED
@@ -37,8 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.buildLodestoneSearchUrl = buildLodestoneSearchUrl;
40
- exports.parseTopCharacterUrlFromSearchHtml = parseTopCharacterUrlFromSearchHtml;
41
- exports.fetchTopCharacterUrl = fetchTopCharacterUrl;
40
+ exports.fetchTopCharacterResult = fetchTopCharacterResult;
42
41
  const axios_1 = __importDefault(require("axios"));
43
42
  const cheerio = __importStar(require("cheerio"));
44
43
  const LODESTONE_BASE_URL = "https://jp.finalfantasyxiv.com";
@@ -69,26 +68,37 @@ function buildLodestoneSearchUrl(info) {
69
68
  /**
70
69
  * キャラクター検索結果HTMLから、先頭に表示されるキャラクターURLを取得します。
71
70
  */
72
- function parseTopCharacterUrlFromSearchHtml(html) {
73
- const $ = cheerio.load(html);
71
+ function parseTopCharacterResultFromSearchHtml(html) {
72
+ const raw = html.trim();
73
+ if (!raw)
74
+ return { status: "ng", reason: "empty_html" };
75
+ const $ = cheerio.load(raw);
74
76
  const href = $("a.entry__link").first().attr("href");
75
- if (!href)
76
- return undefined;
77
- return new URL(href, LODESTONE_BASE_URL).toString();
77
+ if (href)
78
+ return { status: "ok", characterUrl: new URL(href, LODESTONE_BASE_URL).toString() };
79
+ if ($("li.entry").length === 0) {
80
+ return { status: "private", reason: "search_hidden" };
81
+ }
82
+ return { status: "ng", reason: "structure_mismatch" };
78
83
  }
79
84
  /**
80
- * Lodestone のキャラクター検索を行い、先頭にヒットしたキャラクターURLを返します。
85
+ * Lodestone のキャラクター検索を行い、検索結果の状態を返します。
81
86
  */
82
- async function fetchTopCharacterUrl(searchUrl) {
83
- const response = await axios_1.default.get(searchUrl, {
84
- responseType: "text",
85
- headers: {
86
- Accept: "text/html,application/xhtml+xml",
87
- "Accept-Language": "ja,en;q=0.8",
88
- "User-Agent": "ffxiv-lodestone-character-lookup/0.1 (+https://jp.finalfantasyxiv.com)"
89
- },
90
- timeout: 30_000
91
- });
92
- return parseTopCharacterUrlFromSearchHtml(response.data);
87
+ async function fetchTopCharacterResult(searchUrl) {
88
+ try {
89
+ const response = await axios_1.default.get(searchUrl, {
90
+ responseType: "text",
91
+ headers: {
92
+ Accept: "text/html,application/xhtml+xml",
93
+ "Accept-Language": "ja,en;q=0.8",
94
+ "User-Agent": "ffxiv-lodestone-character-lookup/0.1 (+https://jp.finalfantasyxiv.com)"
95
+ },
96
+ timeout: 30_000
97
+ });
98
+ return parseTopCharacterResultFromSearchHtml(response.data);
99
+ }
100
+ catch {
101
+ return { status: "ng", reason: "http_error" };
102
+ }
93
103
  }
94
104
  //# sourceMappingURL=lodestone.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lodestone.js","sourceRoot":"","sources":["../src/lodestone.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,0DAsBC;AAKD,gFAKC;AAKD,oDAYC;AAhED,kDAA0B;AAC1B,iDAAmC;AAEnC,MAAM,kBAAkB,GAAG,gCAAgC,CAAC;AAO5D;;;;GAIG;AACH,SAAgB,uBAAuB,CAAC,IAAiB;IACvD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,uBAAuB,EAAE,kBAAkB,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC;IAEhC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAE7B,qCAAqC;IACrC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE3B,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEjC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxB,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,SAAgB,kCAAkC,CAAC,IAAY;IAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IAC1D,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAS,SAAS,EAAE;QAClD,YAAY,EAAE,MAAM;QACpB,OAAO,EAAE;YACP,MAAM,EAAE,iCAAiC;YACzC,iBAAiB,EAAE,aAAa;YAChC,YAAY,EAAE,wEAAwE;SACvF;QACD,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IAEH,OAAO,kCAAkC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC"}
1
+ {"version":3,"file":"lodestone.js","sourceRoot":"","sources":["../src/lodestone.ts"],"names":[],"mappings":"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"}
@@ -1,76 +1,48 @@
1
1
  /**
2
- * 高難度(絶/零式)アチーブの正式名と表示用略称の定義(SSOT)。
2
+ * アチーブメントの正式名定義(SSOT)。
3
3
  *
4
- * 定義は `high_end_achievements_ja.json` を同梱して読み取ります。
4
+ * 定義は `data/achievements/*.json` を同梱して読み取ります。
5
5
  */
6
- export type HighEndAchievementGroup = "ultimate" | "savage";
7
- export type HighEndAchievementDefinition = {
6
+ export type AchievementDefinition = {
8
7
  name: string;
9
- short: string;
10
- group: HighEndAchievementGroup;
8
+ };
9
+ export type AchievementCategoryDefinition = {
10
+ version: number;
11
+ category: string;
12
+ categoryId: number;
13
+ achievements: AchievementDefinition[];
11
14
  };
12
15
  /**
13
- * 高難度アチーブ定義を取得します。
14
- */
15
- export declare function getHighEndAchievements(): HighEndAchievementDefinition[];
16
- export type HighEndAchievementName = string;
17
- /**
18
- * 互換用: 旧型名(`UltimateAchievementName`)。
19
- */
20
- export type UltimateAchievementName = HighEndAchievementName;
21
- /**
22
- * 互換用: 旧API名(`getUltimateAchievements`)。
23
- */
24
- export declare function getUltimateAchievements(): HighEndAchievementDefinition[];
25
- /**
26
- * 正式名 → 略称のルックアップを返します。
27
- */
28
- export declare function getHighEndAchievementShortMap(): Map<HighEndAchievementName, string>;
29
- /**
30
- * 互換用: 旧API名(`getUltimateAchievementShortMap`)。
31
- */
32
- export declare function getUltimateAchievementShortMap(): Map<HighEndAchievementName, string>;
33
- /**
34
- * 正式名 → 種別(絶/零式)のルックアップを返します。
35
- */
36
- export declare function getHighEndAchievementGroupMap(): Map<HighEndAchievementName, HighEndAchievementGroup>;
37
- /**
38
- * 互換用: 旧API名(`getUltimateAchievementGroupMap`)。
16
+ * カテゴリ指定でアチーブ定義を取得します。
39
17
  */
40
- export declare function getUltimateAchievementGroupMap(): Map<HighEndAchievementName, HighEndAchievementGroup>;
18
+ export declare function getAchievementDefinitions(category: string): AchievementDefinition[];
41
19
  /**
42
- * キャラクターURL(例: `https://.../lodestone/character/12345/`)から characterId を抽出します。
20
+ * 全カテゴリのアチーブ定義を取得します。
43
21
  */
44
- export declare function parseCharacterIdFromUrl(characterUrl: string): string | undefined;
22
+ export declare function getAllAchievementDefinitions(): AchievementDefinition[];
45
23
  /**
46
- * Lodestone のアチーブメント一覧URL(カテゴリ指定)を生成します。
24
+ * カテゴリ定義一覧を取得します。
47
25
  */
48
- export declare function buildHighEndAchievementCategoryUrl(characterUrl: string): string | undefined;
26
+ export declare function getAchievementCategoryDefinitions(): AchievementCategoryDefinition[];
49
27
  /**
50
- * 互換用: 旧API名(`buildAchievementCategoryUrl`)。
28
+ * カテゴリ名から、LodestoneのカテゴリIDを取得します。
51
29
  */
52
- export declare function buildAchievementCategoryUrl(characterUrl: string): string | undefined;
53
- export type HighEndAchievementParseResult = {
54
- status: "ok" | "private_or_unavailable";
55
- clears: string[];
30
+ export declare function getAchievementCategoryId(category: string): number | undefined;
31
+ export type HighEndAchievementStatus = "ok" | "private" | "ng";
32
+ export type HighEndAchievementStatusReason = "achievements_private" | "unknown_category" | "http_403" | "http_error" | "empty_html" | "structure_mismatch" | "invalid_character_url";
33
+ export type AchievementEntry = {
34
+ name: string;
35
+ date: string | null;
36
+ requirement?: string | null;
37
+ };
38
+ export type HighEndAchievementDetailResult = {
39
+ status: HighEndAchievementStatus;
40
+ reason?: HighEndAchievementStatusReason;
41
+ lodestone: string;
42
+ achievements: Record<string, AchievementEntry[]>;
56
43
  };
57
44
  /**
58
- * 互換用: 旧型名(`UltimateAchievementParseResult`)。
59
- */
60
- export type UltimateAchievementParseResult = HighEndAchievementParseResult;
61
- /**
62
- * アチーブメント一覧HTMLから、指定した高難度(絶/零式)アチーブの達成状況を判定します。
63
- *
64
- * 判定方法:
65
- * - 対象の `<li class="entry">` 内に `time.entry__activity__time` が存在するか(=日付が入る)
66
- */
67
- export declare function parseHighEndClearsFromAchievementHtml(html: string): HighEndAchievementParseResult;
68
- /**
69
- * 互換用: 旧API名(`parseUltimateClearsFromAchievementHtml`)。
70
- */
71
- export declare function parseUltimateClearsFromAchievementHtml(html: string): HighEndAchievementParseResult;
72
- /**
73
- * Lodestone のアチーブメント一覧ページを取得します。
45
+ * キャラクターURLとカテゴリを指定して、アチーブを取得して返します。
74
46
  */
75
- export declare function fetchAchievementCategoryHtml(url: string): Promise<string>;
47
+ export declare function fetchAchievementDetailsByCategory(characterUrl: string, category: string): Promise<HighEndAchievementDetailResult>;
76
48
  //# sourceMappingURL=lodestoneAchievements.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lodestoneAchievements.d.ts","sourceRoot":"","sources":["../src/lodestoneAchievements.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE5D,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,uBAAuB,CAAC;CAChC,CAAC;AAoBF;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,4BAA4B,EAAE,CAEvE;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,4BAA4B,EAAE,CAExE;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAMnF;AAED;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAEpF;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,GAAG,CAAC,sBAAsB,EAAE,uBAAuB,CAAC,CAMpG;AAED;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,GAAG,CAAC,sBAAsB,EAAE,uBAAuB,CAAC,CAErG;AAQD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhF;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO3F;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEpF;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,IAAI,GAAG,wBAAwB,CAAC;IACxC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,6BAA6B,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAqBjG;AAED;;GAEG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAElG;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW/E"}
1
+ {"version":3,"file":"lodestoneAchievements.d.ts","sourceRoot":"","sources":["../src/lodestoneAchievements.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,EAAE,CAAC;CACvC,CAAC;AAyBF;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAEnF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,IAAI,qBAAqB,EAAE,CAMtE;AAED;;GAEG;AACH,wBAAgB,iCAAiC,IAAI,6BAA6B,EAAE,CAEnF;AAkBD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE7E;AAgBD,MAAM,MAAM,wBAAwB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;AAE/D,MAAM,MAAM,8BAA8B,GACtC,sBAAsB,GACtB,kBAAkB,GAClB,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,oBAAoB,GACpB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;CAClD,CAAC;AAmHF;;GAEG;AACH,wBAAsB,iCAAiC,CACrD,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,8BAA8B,CAAC,CAkEzC"}
@@ -36,90 +36,68 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getHighEndAchievements = getHighEndAchievements;
40
- exports.getUltimateAchievements = getUltimateAchievements;
41
- exports.getHighEndAchievementShortMap = getHighEndAchievementShortMap;
42
- exports.getUltimateAchievementShortMap = getUltimateAchievementShortMap;
43
- exports.getHighEndAchievementGroupMap = getHighEndAchievementGroupMap;
44
- exports.getUltimateAchievementGroupMap = getUltimateAchievementGroupMap;
45
- exports.parseCharacterIdFromUrl = parseCharacterIdFromUrl;
46
- exports.buildHighEndAchievementCategoryUrl = buildHighEndAchievementCategoryUrl;
47
- exports.buildAchievementCategoryUrl = buildAchievementCategoryUrl;
48
- exports.parseHighEndClearsFromAchievementHtml = parseHighEndClearsFromAchievementHtml;
49
- exports.parseUltimateClearsFromAchievementHtml = parseUltimateClearsFromAchievementHtml;
50
- exports.fetchAchievementCategoryHtml = fetchAchievementCategoryHtml;
39
+ exports.getAchievementDefinitions = getAchievementDefinitions;
40
+ exports.getAllAchievementDefinitions = getAllAchievementDefinitions;
41
+ exports.getAchievementCategoryDefinitions = getAchievementCategoryDefinitions;
42
+ exports.getAchievementCategoryId = getAchievementCategoryId;
43
+ exports.fetchAchievementDetailsByCategory = fetchAchievementDetailsByCategory;
51
44
  const axios_1 = __importDefault(require("axios"));
52
45
  const cheerio = __importStar(require("cheerio"));
53
46
  const node_fs_1 = require("node:fs");
54
47
  const node_path_1 = require("node:path");
55
48
  const LODESTONE_BASE_URL = "https://jp.finalfantasyxiv.com";
56
- const HIGH_END_CATEGORY_ID = 4;
57
- const DEFINITIONS_FILE = (0, node_path_1.resolve)(__dirname, "data", "high_end_achievements_ja.json");
58
- let cachedDefinitions;
59
- let cachedShortMap;
60
- let cachedGroupMap;
61
- let cachedNameSet;
62
- function loadDefinitions() {
63
- if (cachedDefinitions)
64
- return cachedDefinitions;
65
- const rawText = (0, node_fs_1.readFileSync)(DEFINITIONS_FILE, "utf8");
66
- const raw = JSON.parse(rawText);
67
- cachedDefinitions = raw.achievements ?? [];
68
- return cachedDefinitions;
69
- }
70
- /**
71
- * 高難度アチーブ定義を取得します。
72
- */
73
- function getHighEndAchievements() {
74
- return loadDefinitions();
75
- }
76
- /**
77
- * 互換用: 旧API名(`getUltimateAchievements`)。
78
- */
79
- function getUltimateAchievements() {
80
- return getHighEndAchievements();
81
- }
82
- /**
83
- * 正式名 → 略称のルックアップを返します。
84
- */
85
- function getHighEndAchievementShortMap() {
86
- if (cachedShortMap)
87
- return cachedShortMap;
49
+ const DEFINITIONS_DIR = (0, node_path_1.resolve)(__dirname, "data", "achievements");
50
+ let cachedCategoryDefinitions;
51
+ const cachedNameSets = new Map();
52
+ function loadCategoryDefinitions() {
53
+ if (cachedCategoryDefinitions)
54
+ return cachedCategoryDefinitions;
55
+ const files = (0, node_fs_1.readdirSync)(DEFINITIONS_DIR).filter((file) => file.endsWith(".json"));
88
56
  const map = new Map();
89
- for (const a of loadDefinitions())
90
- map.set(a.name, a.short);
91
- cachedShortMap = map;
57
+ for (const file of files) {
58
+ const rawText = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(DEFINITIONS_DIR, file), "utf8");
59
+ const raw = JSON.parse(rawText);
60
+ if (!raw.category || !Array.isArray(raw.achievements))
61
+ continue;
62
+ map.set(raw.category, raw);
63
+ }
64
+ cachedCategoryDefinitions = map;
92
65
  return map;
93
66
  }
67
+ function loadDefinitions(category) {
68
+ const raw = loadCategoryDefinitions().get(category);
69
+ const definitions = raw?.achievements ?? [];
70
+ return definitions;
71
+ }
94
72
  /**
95
- * 互換用: 旧API名(`getUltimateAchievementShortMap`)。
73
+ * カテゴリ指定でアチーブ定義を取得します。
96
74
  */
97
- function getUltimateAchievementShortMap() {
98
- return getHighEndAchievementShortMap();
75
+ function getAchievementDefinitions(category) {
76
+ return loadDefinitions(category);
99
77
  }
100
78
  /**
101
- * 正式名 → 種別(絶/零式)のルックアップを返します。
79
+ * 全カテゴリのアチーブ定義を取得します。
102
80
  */
103
- function getHighEndAchievementGroupMap() {
104
- if (cachedGroupMap)
105
- return cachedGroupMap;
106
- const map = new Map();
107
- for (const a of loadDefinitions())
108
- map.set(a.name, a.group);
109
- cachedGroupMap = map;
110
- return map;
81
+ function getAllAchievementDefinitions() {
82
+ const all = [];
83
+ for (const category of loadCategoryDefinitions().keys()) {
84
+ all.push(...loadDefinitions(category));
85
+ }
86
+ return all;
111
87
  }
112
88
  /**
113
- * 互換用: 旧API名(`getUltimateAchievementGroupMap`)。
89
+ * カテゴリ定義一覧を取得します。
114
90
  */
115
- function getUltimateAchievementGroupMap() {
116
- return getHighEndAchievementGroupMap();
91
+ function getAchievementCategoryDefinitions() {
92
+ return Array.from(loadCategoryDefinitions().values());
117
93
  }
118
- function getHighEndAchievementNameSet() {
119
- if (cachedNameSet)
120
- return cachedNameSet;
121
- cachedNameSet = new Set(loadDefinitions().map((a) => a.name));
122
- return cachedNameSet;
94
+ function getAchievementNameSet(category) {
95
+ const cached = cachedNameSets.get(category);
96
+ if (cached)
97
+ return cached;
98
+ const nameSet = new Set(loadDefinitions(category).map((a) => a.name));
99
+ cachedNameSets.set(category, nameSet);
100
+ return nameSet;
123
101
  }
124
102
  /**
125
103
  * キャラクターURL(例: `https://.../lodestone/character/12345/`)から characterId を抽出します。
@@ -129,51 +107,193 @@ function parseCharacterIdFromUrl(characterUrl) {
129
107
  return match?.[1];
130
108
  }
131
109
  /**
132
- * Lodestone のアチーブメント一覧URL(カテゴリ指定)を生成します。
110
+ * カテゴリ名から、LodestoneのカテゴリIDを取得します。
133
111
  */
134
- function buildHighEndAchievementCategoryUrl(characterUrl) {
135
- const characterId = parseCharacterIdFromUrl(characterUrl);
136
- if (!characterId)
137
- return undefined;
138
- return new URL(`/lodestone/character/${characterId}/achievement/category/${HIGH_END_CATEGORY_ID}/#anchor_achievement`, LODESTONE_BASE_URL).toString();
112
+ function getAchievementCategoryId(category) {
113
+ return loadCategoryDefinitions().get(category)?.categoryId;
139
114
  }
140
115
  /**
141
- * 互換用: 旧API名(`buildAchievementCategoryUrl`)。
116
+ * カテゴリ名を指定して、Lodestone のアチーブメント一覧URLを生成します。
142
117
  */
143
- function buildAchievementCategoryUrl(characterUrl) {
144
- return buildHighEndAchievementCategoryUrl(characterUrl);
118
+ function buildAchievementCategoryUrlByCategory(characterUrl, category) {
119
+ const categoryId = getAchievementCategoryId(category);
120
+ if (!categoryId)
121
+ return undefined;
122
+ const characterId = parseCharacterIdFromUrl(characterUrl);
123
+ if (!characterId)
124
+ return undefined;
125
+ return new URL(`/lodestone/character/${characterId}/achievement/category/${categoryId}/#anchor_achievement`, LODESTONE_BASE_URL).toString();
145
126
  }
146
127
  /**
147
- * アチーブメント一覧HTMLから、指定した高難度(絶/零式)アチーブの達成状況を判定します。
128
+ * アチーブメント一覧HTMLから、指定カテゴリのアチーブ達成状況を抽出します。
148
129
  *
149
- * 判定方法:
150
- * - 対象の `<li class="entry">` 内に `time.entry__activity__time` が存在するか(=日付が入る)
130
+ * - `date` が取得できない場合は `null`
131
+ * - 対象アチーブが1件も見つからない場合は `private`
151
132
  */
152
- function parseHighEndClearsFromAchievementHtml(html) {
133
+ function parseAchievementDetailsFromHtml(html, characterUrl, category) {
153
134
  const $ = cheerio.load(html);
154
- const targetSet = getHighEndAchievementNameSet();
155
- const clears = new Set();
135
+ const targetSet = getAchievementNameSet(category);
136
+ const entries = [];
156
137
  let foundAny = false;
138
+ const totalEntries = $("li.entry").length;
139
+ const bodyText = $("body").text();
140
+ const isPrivate = bodyText.includes("非公開") || bodyText.includes("プライバシー") || bodyText.includes("公開されていません");
141
+ if (!html.trim()) {
142
+ return {
143
+ status: "ng",
144
+ reason: "empty_html",
145
+ lodestone: characterUrl,
146
+ achievements: { [category]: [] }
147
+ };
148
+ }
149
+ const parseDate = (raw) => {
150
+ const text = raw.trim();
151
+ if (!text)
152
+ return null;
153
+ const match = text.match(/ldst_strftime\((\d+),\s*'YMD'\)/);
154
+ if (!match)
155
+ return text;
156
+ const seconds = Number(match[1]);
157
+ if (!Number.isFinite(seconds))
158
+ return null;
159
+ const date = new Date(seconds * 1000);
160
+ const y = date.getFullYear();
161
+ const m = String(date.getMonth() + 1).padStart(2, "0");
162
+ const d = String(date.getDate()).padStart(2, "0");
163
+ return `${y}/${m}/${d}`;
164
+ };
157
165
  $("li.entry").each((_, el) => {
158
166
  const entry = $(el);
159
167
  const name = entry.find("p.entry__activity__txt").first().text().trim();
160
168
  if (!targetSet.has(name))
161
169
  return;
162
170
  foundAny = true;
163
- const hasDate = entry.find("time.entry__activity__time").length > 0;
164
- if (hasDate)
165
- clears.add(name);
171
+ const rawDateText = entry.find("time.entry__activity__time").first().text();
172
+ const dateText = parseDate(rawDateText);
173
+ entries.push({ name, date: dateText });
166
174
  });
175
+ if (totalEntries === 0) {
176
+ return {
177
+ status: isPrivate ? "private" : "ng",
178
+ reason: isPrivate ? "achievements_private" : "structure_mismatch",
179
+ lodestone: characterUrl,
180
+ achievements: { [category]: [] }
181
+ };
182
+ }
183
+ if (!foundAny) {
184
+ return {
185
+ status: "ng",
186
+ reason: "structure_mismatch",
187
+ lodestone: characterUrl,
188
+ achievements: { [category]: [] }
189
+ };
190
+ }
167
191
  return {
168
- status: foundAny ? "ok" : "private_or_unavailable",
169
- clears: Array.from(clears)
192
+ status: "ok",
193
+ lodestone: characterUrl,
194
+ achievements: {
195
+ [category]: entries
196
+ }
170
197
  };
171
198
  }
199
+ function extractAchievementDetailLinks(html) {
200
+ const $ = cheerio.load(html);
201
+ const map = new Map();
202
+ $("li.entry").each((_, el) => {
203
+ const entry = $(el);
204
+ const name = entry.find("p.entry__activity__txt").first().text().trim();
205
+ if (!name || map.has(name))
206
+ return;
207
+ const href = entry
208
+ .find("a")
209
+ .map((_, a) => $(a).attr("href"))
210
+ .get()
211
+ .find((h) => h && h.includes("/achievement/detail/"));
212
+ if (!href)
213
+ return;
214
+ const link = href.startsWith("http") ? href : `${LODESTONE_BASE_URL}${href}`;
215
+ map.set(name, link.split("#")[0]);
216
+ });
217
+ return map;
218
+ }
219
+ function parseAchievementRequirementFromDetailHtml(html) {
220
+ const $ = cheerio.load(html);
221
+ const text = $("p.achievement__base--text").first().text().trim();
222
+ return text || null;
223
+ }
224
+ async function fetchAchievementRequirement(detailUrl) {
225
+ const html = await fetchAchievementCategoryHtml(detailUrl);
226
+ return parseAchievementRequirementFromDetailHtml(html);
227
+ }
172
228
  /**
173
- * 互換用: 旧API名(`parseUltimateClearsFromAchievementHtml`)。
229
+ * キャラクターURLとカテゴリを指定して、アチーブを取得して返します。
174
230
  */
175
- function parseUltimateClearsFromAchievementHtml(html) {
176
- return parseHighEndClearsFromAchievementHtml(html);
231
+ async function fetchAchievementDetailsByCategory(characterUrl, category) {
232
+ const categoryId = getAchievementCategoryId(category);
233
+ if (!categoryId) {
234
+ return {
235
+ status: "ng",
236
+ reason: "unknown_category",
237
+ lodestone: characterUrl,
238
+ achievements: { [category]: [] }
239
+ };
240
+ }
241
+ const achievementUrl = buildAchievementCategoryUrlByCategory(characterUrl, category);
242
+ if (!achievementUrl) {
243
+ return {
244
+ status: "ng",
245
+ reason: "invalid_character_url",
246
+ lodestone: characterUrl,
247
+ achievements: { [category]: [] }
248
+ };
249
+ }
250
+ try {
251
+ const html = await fetchAchievementCategoryHtml(achievementUrl);
252
+ const parsed = parseAchievementDetailsFromHtml(html, characterUrl, category);
253
+ if (parsed.status !== "ok")
254
+ return parsed;
255
+ const detailLinks = extractAchievementDetailLinks(html);
256
+ const entries = parsed.achievements[category] ?? [];
257
+ let cursor = 0;
258
+ const concurrency = 5;
259
+ async function worker() {
260
+ while (cursor < entries.length) {
261
+ const index = cursor++;
262
+ const entry = entries[index];
263
+ if (!entry || entry.requirement !== undefined)
264
+ continue;
265
+ const link = detailLinks.get(entry.name);
266
+ if (!link) {
267
+ entry.requirement = null;
268
+ continue;
269
+ }
270
+ try {
271
+ entry.requirement = await fetchAchievementRequirement(link);
272
+ }
273
+ catch {
274
+ entry.requirement = null;
275
+ }
276
+ }
277
+ }
278
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
279
+ return parsed;
280
+ }
281
+ catch (error) {
282
+ if (axios_1.default.isAxiosError(error) && error.response?.status === 403) {
283
+ return {
284
+ status: "private",
285
+ reason: "http_403",
286
+ lodestone: characterUrl,
287
+ achievements: { [category]: [] }
288
+ };
289
+ }
290
+ return {
291
+ status: "ng",
292
+ reason: "http_error",
293
+ lodestone: characterUrl,
294
+ achievements: { [category]: [] }
295
+ };
296
+ }
177
297
  }
178
298
  /**
179
299
  * Lodestone のアチーブメント一覧ページを取得します。
@@ -1 +1 @@
1
- {"version":3,"file":"lodestoneAchievements.js","sourceRoot":"","sources":["../src/lodestoneAchievements.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,wDAEC;AAYD,0DAEC;AAKD,sEAMC;AAKD,wEAEC;AAKD,sEAMC;AAKD,wEAEC;AAWD,0DAGC;AAKD,gFAOC;AAKD,kEAEC;AAkBD,sFAqBC;AAKD,wFAEC;AAKD,oEAWC;AA9LD,kDAA0B;AAC1B,iDAAmC;AACnC,qCAAuC;AACvC,yCAAoC;AAEpC,MAAM,kBAAkB,GAAG,gCAAgC,CAAC;AAC5D,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,gBAAgB,GAAG,IAAA,mBAAO,EAAC,SAAS,EAAE,MAAM,EAAE,+BAA+B,CAAC,CAAC;AAoBrF,IAAI,iBAA6D,CAAC;AAClE,IAAI,cAA+C,CAAC;AACpD,IAAI,cAAgE,CAAC;AACrE,IAAI,aAAsC,CAAC;AAE3C,SAAS,eAAe;IACtB,IAAI,iBAAiB;QAAE,OAAO,iBAAiB,CAAC;IAChD,MAAM,OAAO,GAAG,IAAA,sBAAY,EAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA0B,CAAC;IACzD,iBAAiB,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IAC3C,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,SAAgB,sBAAsB;IACpC,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC;AASD;;GAEG;AACH,SAAgB,uBAAuB;IACrC,OAAO,sBAAsB,EAAE,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,SAAgB,6BAA6B;IAC3C,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5D,cAAc,GAAG,GAAG,CAAC;IACrB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,SAAgB,8BAA8B;IAC5C,OAAO,6BAA6B,EAAE,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,SAAgB,6BAA6B;IAC3C,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5D,cAAc,GAAG,GAAG,CAAC;IACrB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,SAAgB,8BAA8B;IAC5C,OAAO,6BAA6B,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,4BAA4B;IACnC,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC;IACxC,aAAa,GAAG,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,SAAgB,uBAAuB,CAAC,YAAoB;IAC1D,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACpE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,SAAgB,kCAAkC,CAAC,YAAoB;IACrE,MAAM,WAAW,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAC1D,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,OAAO,IAAI,GAAG,CACZ,wBAAwB,WAAW,yBAAyB,oBAAoB,sBAAsB,EACtG,kBAAkB,CACnB,CAAC,QAAQ,EAAE,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAgB,2BAA2B,CAAC,YAAoB;IAC9D,OAAO,kCAAkC,CAAC,YAAY,CAAC,CAAC;AAC1D,CAAC;AAYD;;;;;GAKG;AACH,SAAgB,qCAAqC,CAAC,IAAY;IAChE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,4BAA4B,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACxE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAEjC,QAAQ,GAAG,IAAI,CAAC;QAChB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACpE,IAAI,OAAO;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB;QAClD,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;KAC3B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAgB,sCAAsC,CAAC,IAAY;IACjE,OAAO,qCAAqC,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,4BAA4B,CAAC,GAAW;IAC5D,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAS,GAAG,EAAE;QAC5C,YAAY,EAAE,MAAM;QACpB,OAAO,EAAE;YACP,MAAM,EAAE,iCAAiC;YACzC,iBAAiB,EAAE,aAAa;YAChC,YAAY,EAAE,wEAAwE;SACvF;QACD,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"lodestoneAchievements.js","sourceRoot":"","sources":["../src/lodestoneAchievements.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,8DAEC;AAKD,oEAMC;AAKD,8EAEC;AAqBD,4DAEC;AA4JD,8EAqEC;AA9TD,kDAA0B;AAC1B,iDAAmC;AACnC,qCAAoD;AACpD,yCAAoC;AAEpC,MAAM,kBAAkB,GAAG,gCAAgC,CAAC;AAC5D,MAAM,eAAe,GAAG,IAAA,mBAAO,EAAC,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AAkBnE,IAAI,yBAAiF,CAAC;AACtF,MAAM,cAAc,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEtD,SAAS,uBAAuB;IAC9B,IAAI,yBAAyB;QAAE,OAAO,yBAAyB,CAAC;IAChE,MAAM,KAAK,GAAG,IAAA,qBAAW,EAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACpF,MAAM,GAAG,GAAG,IAAI,GAAG,EAAyC,CAAC;IAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAA,sBAAY,EAAC,IAAA,mBAAO,EAAC,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkC,CAAC;QACjE,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;YAAE,SAAS;QAChE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC;IACD,yBAAyB,GAAG,GAAG,CAAC;IAChC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,GAAG,GAAG,uBAAuB,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,GAAG,EAAE,YAAY,IAAI,EAAE,CAAC;IAC5C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAgB,yBAAyB,CAAC,QAAgB;IACxD,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAgB,4BAA4B;IAC1C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,QAAQ,IAAI,uBAAuB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACxD,GAAG,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,SAAgB,iCAAiC;IAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACtC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,uBAAuB,CAAC,YAAoB;IACnD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACpE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,SAAgB,wBAAwB,CAAC,QAAgB;IACvD,OAAO,uBAAuB,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,SAAS,qCAAqC,CAAC,YAAoB,EAAE,QAAgB;IACnF,MAAM,UAAU,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,WAAW,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAC1D,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,OAAO,IAAI,GAAG,CACZ,wBAAwB,WAAW,yBAAyB,UAAU,sBAAsB,EAC5F,kBAAkB,CACnB,CAAC,QAAQ,EAAE,CAAC;AACf,CAAC;AA0BD;;;;;GAKG;AACH,SAAS,+BAA+B,CACtC,IAAY,EACZ,YAAoB,EACpB,QAAgB;IAEhB,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,YAAY,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC;IAC1C,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,SAAS,GACb,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAE5F,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,YAAY;YACpB,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAW,EAAiB,EAAE;QAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEF,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACxE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAEjC,QAAQ,GAAG,IAAI,CAAC;QAChB,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;QAC5E,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;YACpC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,oBAAoB;YACjE,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,oBAAoB;YAC5B,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,SAAS,EAAE,YAAY;QACvB,YAAY,EAAE;YACZ,CAAC,QAAQ,CAAC,EAAE,OAAO;SACpB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,6BAA6B,CAAC,IAAY;IACjD,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEtC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACxE,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACnC,MAAM,IAAI,GAAG,KAAK;aACf,IAAI,CAAC,GAAG,CAAC;aACT,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;aAChC,GAAG,EAAE;aACL,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,kBAAkB,GAAG,IAAI,EAAE,CAAC;QAC7E,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,yCAAyC,CAAC,IAAY;IAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;IAClE,OAAO,IAAI,IAAI,IAAI,CAAC;AACtB,CAAC;AAED,KAAK,UAAU,2BAA2B,CAAC,SAAiB;IAC1D,MAAM,IAAI,GAAG,MAAM,4BAA4B,CAAC,SAAS,CAAC,CAAC;IAC3D,OAAO,yCAAyC,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,iCAAiC,CACrD,YAAoB,EACpB,QAAgB;IAEhB,MAAM,UAAU,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,kBAAkB;YAC1B,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,GAAG,qCAAqC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACrF,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,uBAAuB;YAC/B,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,4BAA4B,CAAC,cAAc,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,+BAA+B,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;QAE1C,MAAM,WAAW,GAAG,6BAA6B,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,MAAM,WAAW,GAAG,CAAC,CAAC;QAEtB,KAAK,UAAU,MAAM;YACnB,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC7B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;oBAAE,SAAS;gBACxD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACzC,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;oBACzB,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC;oBACH,KAAK,CAAC,WAAW,GAAG,MAAM,2BAA2B,CAAC,IAAI,CAAC,CAAC;gBAC9D,CAAC;gBAAC,MAAM,CAAC;oBACP,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,eAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;YAChE,OAAO;gBACL,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE,YAAY;gBACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;aACjC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,YAAY;YACpB,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE;SACjC,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,4BAA4B,CAAC,GAAW;IACrD,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAS,GAAG,EAAE;QAC5C,YAAY,EAAE,MAAM;QACpB,OAAO,EAAE;YACP,MAAM,EAAE,iCAAiC;YACzC,iBAAiB,EAAE,aAAa;YAChC,YAAY,EAAE,wEAAwE;SACvF;QACD,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piyoraik/ffxiv-lodestone-character-lookup",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "FFXIV Lodestone character lookup (search → character URL) and high-end achievement checks",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -28,7 +28,8 @@
28
28
  "scripts": {
29
29
  "build": "tsc -p tsconfig.json && node scripts/copy-data.cjs",
30
30
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
31
- "prepublishOnly": "npm run build"
31
+ "prepublishOnly": "npm run build",
32
+ "test": "npm run build && node --test test/achievements.test.js && node test/integration-runner.js"
32
33
  },
33
34
  "dependencies": {
34
35
  "axios": "^1.7.9",