universalis-mcp-server 0.2.0 → 0.2.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 +15 -0
- package/data/materia.json +1 -1
- package/dist/index.js +2 -0
- package/dist/instructions.js +6 -0
- package/dist/tools/wiki.js +176 -0
- package/docs/wiki/index.json +153 -0
- package/docs/wiki/saddlebag/allagan-tools-inventory-analysis.md +94 -0
- package/docs/wiki/saddlebag/api-call-guide.md +173 -0
- package/docs/wiki/saddlebag/ffxiv-advanced-undercut-alert-options.md +123 -0
- package/docs/wiki/saddlebag/ffxiv-experimental-discount-price-sniper.md +7 -0
- package/docs/wiki/saddlebag/ffxiv-job-category-ids.md +13 -0
- package/docs/wiki/saddlebag/ffxiv-sale-alerts.md +48 -0
- package/docs/wiki/saddlebag/ffxiv-sale-leads.md +42 -0
- package/docs/wiki/saddlebag/how-to-trade-using-our-ffxiv-market-overview.md +102 -0
- package/docs/wiki/saddlebag/how-to-use-the-ffxiv-crafting-profit-simulator-craftsim.md +88 -0
- package/docs/wiki/saddlebag/item-categories-ids-and-list.md +217 -0
- package/docs/wiki/saddlebag/tldr-how-to-earn-gil-with-cross-server-trading.md +89 -0
- package/docs/wiki/universalis/api-app-overview.md +236 -0
- package/docs/wiki/universalis/how-to-help-update-the-data-on-universalis.md +21 -0
- package/docs/wiki/universalis/how-to-make-universalis-lists.md +24 -0
- package/docs/wiki/universalis/how-to-search-for-items-on-universalis-and-saddlebag-exchange.md +56 -0
- package/docs/wiki/universalis/how-to-setup-universalis-alerts.md +54 -0
- package/docs/wiki/universalis/how-to-use-universalis-favorites.md +17 -0
- package/docs/wiki/universalis/interactive-tutorials.md +3 -0
- package/docs/wiki/universalis/navigating-an-item-overview-on-universalis.md +72 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -43,6 +43,11 @@ All tools support `response_format` as `markdown` or `json`.
|
|
|
43
43
|
|
|
44
44
|
Best-deals is excluded (premium gated + inconsistent API validation).
|
|
45
45
|
|
|
46
|
+
### Guides (Wiki snapshots)
|
|
47
|
+
|
|
48
|
+
- `wiki_list_pages`: List curated wiki pages for Saddlebag and Universalis guides.
|
|
49
|
+
- `wiki_get_page`: Fetch a wiki page by slug for extra context.
|
|
50
|
+
|
|
46
51
|
### Reference data (Universalis)
|
|
47
52
|
|
|
48
53
|
- `universalis_list_worlds`: List worlds with pagination.
|
|
@@ -90,6 +95,12 @@ pnpm install
|
|
|
90
95
|
pnpm build
|
|
91
96
|
```
|
|
92
97
|
|
|
98
|
+
To refresh curated wiki snapshots:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm update-wiki-docs
|
|
102
|
+
```
|
|
103
|
+
|
|
93
104
|
## Run (stdio)
|
|
94
105
|
|
|
95
106
|
```bash
|
|
@@ -121,6 +132,8 @@ If you change code, re-run `pnpm build` and restart the MCP connection.
|
|
|
121
132
|
- `UNIVERSALIS_TIMEOUT_MS`: Request timeout for Universalis (default: 30000).
|
|
122
133
|
- `XIVAPI_TIMEOUT_MS`: Request timeout for XIVAPI (default: 30000).
|
|
123
134
|
- `SADDLEBAG_TIMEOUT_MS`: Request timeout for Saddlebag (default: 30000).
|
|
135
|
+
- `WIKI_FETCH_MODE`: `local` (default) for snapshot files or `live` to fetch from GitHub.
|
|
136
|
+
- `WIKI_REFRESH_TTL_MS`: Cache TTL for live wiki fetches (default: 3600000).
|
|
124
137
|
- `XIVAPI_LANGUAGE`: Default XIVAPI language (default: `en`).
|
|
125
138
|
- `XIVAPI_VERSION`: Default XIVAPI version (default: `latest`).
|
|
126
139
|
|
|
@@ -129,3 +142,5 @@ If you change code, re-run `pnpm build` and restart the MCP connection.
|
|
|
129
142
|
- Rate limits are enforced client-side for Universalis and XIVAPI.
|
|
130
143
|
- Tools support `response_format` as `markdown` or `json`.
|
|
131
144
|
- Some Saddlebag endpoints proxy Universalis data; avoid excessive polling.
|
|
145
|
+
- Wiki pages are curated snapshots stored in `docs/wiki`.
|
|
146
|
+
- Set `WIKI_FETCH_MODE=live` to refresh wiki pages at runtime using the allowlisted index.
|
package/data/materia.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { registerMarketTools } from "./tools/market.js";
|
|
|
9
9
|
import { registerReferenceTools } from "./tools/reference.js";
|
|
10
10
|
import { registerSaddlebagTools } from "./tools/saddlebag.js";
|
|
11
11
|
import { registerStatsTools } from "./tools/stats.js";
|
|
12
|
+
import { registerWikiTools } from "./tools/wiki.js";
|
|
12
13
|
import { registerWorkflowTools } from "./tools/workflows.js";
|
|
13
14
|
function toNumber(value) {
|
|
14
15
|
if (!value)
|
|
@@ -37,6 +38,7 @@ async function main() {
|
|
|
37
38
|
registerStatsTools(server, clients);
|
|
38
39
|
registerLookupTools(server, clients);
|
|
39
40
|
registerSaddlebagTools(server, clients);
|
|
41
|
+
registerWikiTools(server);
|
|
40
42
|
registerWorkflowTools(server, clients);
|
|
41
43
|
server.registerPrompt("universalis_usage_guide", {
|
|
42
44
|
title: "Universalis Usage Guide",
|
package/dist/instructions.js
CHANGED
|
@@ -41,6 +41,10 @@ Saddlebag tools (FFXIV):
|
|
|
41
41
|
- saddlebag_get_craftsim: crafting profitability scan (use max_results to limit payloads).
|
|
42
42
|
- saddlebag_get_blog_description: item description text lookup.
|
|
43
43
|
|
|
44
|
+
Wiki tools:
|
|
45
|
+
- wiki_list_pages: list curated Saddlebag/Universalis guide pages.
|
|
46
|
+
- wiki_get_page: fetch a wiki page by slug for extra context.
|
|
47
|
+
|
|
44
48
|
Notes:
|
|
45
49
|
- world_dc_region accepts a world name/ID, data center name, or region.
|
|
46
50
|
- response_format defaults to markdown; use json for structured processing.
|
|
@@ -53,4 +57,6 @@ Notes:
|
|
|
53
57
|
- Saddlebag best-deals is excluded (premium gated + inconsistent API validation).
|
|
54
58
|
- Some Saddlebag endpoints call Universalis directly; be mindful of rate limits.
|
|
55
59
|
- saddlebag_get_raw_stats supports item_ids = -1 for all items and can return very large payloads.
|
|
60
|
+
- Wiki pages are curated snapshots; refresh with scripts/update-wiki-docs.mjs as needed.
|
|
61
|
+
- Set WIKI_FETCH_MODE=live to fetch allowlisted wiki pages from GitHub (cached by WIKI_REFRESH_TTL_MS).
|
|
56
62
|
`;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { BaseOutputSchema, ResponseFormatSchema } from "../schemas/common.js";
|
|
6
|
+
import { buildToolResponse } from "../utils/format.js";
|
|
7
|
+
import { CHARACTER_LIMIT } from "../constants.js";
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const wikiRoot = path.resolve(__dirname, "..", "..", "docs", "wiki");
|
|
10
|
+
const wikiIndexPath = path.join(wikiRoot, "index.json");
|
|
11
|
+
const WIKI_FETCH_MODE = (process.env.WIKI_FETCH_MODE ?? "local").toLowerCase();
|
|
12
|
+
const WIKI_REFRESH_TTL_MS = Number.parseInt(process.env.WIKI_REFRESH_TTL_MS ?? "", 10);
|
|
13
|
+
const DEFAULT_WIKI_REFRESH_TTL_MS = 1000 * 60 * 60;
|
|
14
|
+
const wikiTtlMs = Number.isFinite(WIKI_REFRESH_TTL_MS)
|
|
15
|
+
? WIKI_REFRESH_TTL_MS
|
|
16
|
+
: DEFAULT_WIKI_REFRESH_TTL_MS;
|
|
17
|
+
let wikiIndexCache = null;
|
|
18
|
+
const wikiLiveCache = new Map();
|
|
19
|
+
function rawWikiUrl(repo, slug) {
|
|
20
|
+
return `https://raw.githubusercontent.com/wiki/${repo}/${slug}.md`;
|
|
21
|
+
}
|
|
22
|
+
async function loadWikiIndex() {
|
|
23
|
+
if (wikiIndexCache)
|
|
24
|
+
return wikiIndexCache;
|
|
25
|
+
const raw = await readFile(wikiIndexPath, "utf8");
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
wikiIndexCache = parsed;
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
function resolveSource(index, sourceId) {
|
|
31
|
+
return index.sources.find((source) => source.id === sourceId);
|
|
32
|
+
}
|
|
33
|
+
function formatSources(index, sourceId) {
|
|
34
|
+
const sources = sourceId
|
|
35
|
+
? index.sources.filter((source) => source.id === sourceId)
|
|
36
|
+
: index.sources;
|
|
37
|
+
return sources.map((source) => ({
|
|
38
|
+
id: source.id,
|
|
39
|
+
title: source.title,
|
|
40
|
+
base_url: source.base_url,
|
|
41
|
+
pages: source.pages.map((page) => ({
|
|
42
|
+
slug: page.slug,
|
|
43
|
+
title: page.title,
|
|
44
|
+
description: page.description,
|
|
45
|
+
url: page.url,
|
|
46
|
+
})),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
export function registerWikiTools(server) {
|
|
50
|
+
server.registerTool("wiki_list_pages", {
|
|
51
|
+
title: "List Wiki Pages",
|
|
52
|
+
description: "List curated wiki pages available for Saddlebag and Universalis guides.",
|
|
53
|
+
inputSchema: z
|
|
54
|
+
.object({
|
|
55
|
+
source: z.string().min(1).optional().describe("Wiki source ID (saddlebag or universalis)."),
|
|
56
|
+
response_format: ResponseFormatSchema,
|
|
57
|
+
})
|
|
58
|
+
.strict(),
|
|
59
|
+
outputSchema: BaseOutputSchema,
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: true,
|
|
62
|
+
destructiveHint: false,
|
|
63
|
+
idempotentHint: true,
|
|
64
|
+
openWorldHint: true,
|
|
65
|
+
},
|
|
66
|
+
}, async ({ source, response_format }) => {
|
|
67
|
+
const index = await loadWikiIndex();
|
|
68
|
+
const availableSources = index.sources.map((item) => item.id);
|
|
69
|
+
if (source && !availableSources.includes(source)) {
|
|
70
|
+
throw new Error(`Unknown wiki source '${source}'. Available: ${availableSources.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
const data = { sources: formatSources(index, source) };
|
|
73
|
+
const totalPages = data.sources.reduce((sum, item) => sum + item.pages.length, 0);
|
|
74
|
+
return buildToolResponse({
|
|
75
|
+
title: "Wiki Pages",
|
|
76
|
+
responseFormat: response_format,
|
|
77
|
+
data,
|
|
78
|
+
meta: { source: "wiki", total_pages: totalPages },
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
server.registerTool("wiki_get_page", {
|
|
82
|
+
title: "Get Wiki Page",
|
|
83
|
+
description: "Fetch a curated wiki page by slug.",
|
|
84
|
+
inputSchema: z
|
|
85
|
+
.object({
|
|
86
|
+
source: z.string().min(1).describe("Wiki source ID (saddlebag or universalis)."),
|
|
87
|
+
page: z.string().min(1).describe("Wiki page slug from wiki_list_pages."),
|
|
88
|
+
max_chars: z
|
|
89
|
+
.number()
|
|
90
|
+
.int()
|
|
91
|
+
.min(200)
|
|
92
|
+
.max(CHARACTER_LIMIT)
|
|
93
|
+
.default(20000)
|
|
94
|
+
.describe("Maximum characters to return (default: 20000)."),
|
|
95
|
+
response_format: ResponseFormatSchema,
|
|
96
|
+
})
|
|
97
|
+
.strict(),
|
|
98
|
+
outputSchema: BaseOutputSchema,
|
|
99
|
+
annotations: {
|
|
100
|
+
readOnlyHint: true,
|
|
101
|
+
destructiveHint: false,
|
|
102
|
+
idempotentHint: true,
|
|
103
|
+
openWorldHint: true,
|
|
104
|
+
},
|
|
105
|
+
}, async ({ source, page, max_chars, response_format }) => {
|
|
106
|
+
const index = await loadWikiIndex();
|
|
107
|
+
const resolvedSource = resolveSource(index, source);
|
|
108
|
+
if (!resolvedSource) {
|
|
109
|
+
const availableSources = index.sources.map((item) => item.id);
|
|
110
|
+
throw new Error(`Unknown wiki source '${source}'. Available: ${availableSources.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
const match = resolvedSource.pages.find((entry) => entry.slug === page);
|
|
113
|
+
if (!match) {
|
|
114
|
+
const availablePages = resolvedSource.pages.map((entry) => entry.slug).join(", ");
|
|
115
|
+
throw new Error(`Unknown wiki page '${page}'. Available: ${availablePages}`);
|
|
116
|
+
}
|
|
117
|
+
const filePath = path.join(wikiRoot, resolvedSource.id, match.file);
|
|
118
|
+
const cacheKey = `${resolvedSource.id}:${match.slug}`;
|
|
119
|
+
let content = "";
|
|
120
|
+
let fetchMode = "local";
|
|
121
|
+
let cacheHit = false;
|
|
122
|
+
if (WIKI_FETCH_MODE === "live") {
|
|
123
|
+
fetchMode = "live";
|
|
124
|
+
const cached = wikiLiveCache.get(cacheKey);
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (cached && now - cached.fetchedAt < wikiTtlMs) {
|
|
127
|
+
content = cached.content;
|
|
128
|
+
cacheHit = true;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
try {
|
|
132
|
+
const url = rawWikiUrl(resolvedSource.repo, match.slug);
|
|
133
|
+
const res = await fetch(url);
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
throw new Error(`Failed to fetch wiki page (${res.status})`);
|
|
136
|
+
}
|
|
137
|
+
content = await res.text();
|
|
138
|
+
wikiLiveCache.set(cacheKey, { content, fetchedAt: now });
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
fetchMode = "fallback_local";
|
|
142
|
+
content = await readFile(filePath, "utf8");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
content = await readFile(filePath, "utf8");
|
|
148
|
+
}
|
|
149
|
+
let outputContent = content;
|
|
150
|
+
let truncated = false;
|
|
151
|
+
if (outputContent.length > max_chars) {
|
|
152
|
+
truncated = true;
|
|
153
|
+
outputContent = `${outputContent.slice(0, max_chars)}\n\n... truncated ...`;
|
|
154
|
+
}
|
|
155
|
+
const data = {
|
|
156
|
+
source: resolvedSource.id,
|
|
157
|
+
slug: match.slug,
|
|
158
|
+
title: match.title,
|
|
159
|
+
url: match.url,
|
|
160
|
+
content: outputContent,
|
|
161
|
+
};
|
|
162
|
+
const summaryLines = truncated ? [`Truncated to ${max_chars} characters.`] : undefined;
|
|
163
|
+
return buildToolResponse({
|
|
164
|
+
title: "Wiki Page",
|
|
165
|
+
responseFormat: response_format,
|
|
166
|
+
data,
|
|
167
|
+
meta: {
|
|
168
|
+
source: "wiki",
|
|
169
|
+
truncated,
|
|
170
|
+
fetch_mode: fetchMode,
|
|
171
|
+
cache_hit: cacheHit,
|
|
172
|
+
},
|
|
173
|
+
summaryLines,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sources": [
|
|
3
|
+
{
|
|
4
|
+
"id": "saddlebag",
|
|
5
|
+
"title": "Saddlebag Exchange Wiki",
|
|
6
|
+
"repo": "ff14-advanced-market-search/saddlebag-with-pockets",
|
|
7
|
+
"base_url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki",
|
|
8
|
+
"pages": [
|
|
9
|
+
{
|
|
10
|
+
"slug": "Item-categories-ids-and-list",
|
|
11
|
+
"file": "item-categories-ids-and-list.md",
|
|
12
|
+
"title": "Item categories ids and list",
|
|
13
|
+
"description": "Category filter IDs used by Saddlebag tools.",
|
|
14
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Item-categories-ids-and-list"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"slug": "FFXIV-job-category-ids",
|
|
18
|
+
"file": "ffxiv-job-category-ids.md",
|
|
19
|
+
"title": "FFXIV job category ids",
|
|
20
|
+
"description": "Job IDs used by crafting and shopping list tools.",
|
|
21
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-job-category-ids"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"slug": "How-to-use-the-FFXIV-crafting-profit-simulator-(craftsim)",
|
|
25
|
+
"file": "how-to-use-the-ffxiv-crafting-profit-simulator-craftsim.md",
|
|
26
|
+
"title": "How to use the FFXIV crafting profit simulator",
|
|
27
|
+
"description": "Craftsim workflow and parameter guidance.",
|
|
28
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/How-to-use-the-FFXIV-crafting-profit-simulator-(craftsim)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"slug": "How-to-trade-using-our-FFXIV-Market-Overview",
|
|
32
|
+
"file": "how-to-trade-using-our-ffxiv-market-overview.md",
|
|
33
|
+
"title": "How to trade using the FFXIV market overview",
|
|
34
|
+
"description": "Marketshare overview workflow and filters.",
|
|
35
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/How-to-trade-using-our-FFXIV-Market-Overview"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"slug": "FFXIV-Advanced-Undercut-Alert-Options",
|
|
39
|
+
"file": "ffxiv-advanced-undercut-alert-options.md",
|
|
40
|
+
"title": "FFXIV advanced undercut alert options",
|
|
41
|
+
"description": "Advanced undercut alert parameters.",
|
|
42
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-Advanced-Undercut-Alert-Options"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"slug": "FFXIV-Sale-Alerts",
|
|
46
|
+
"file": "ffxiv-sale-alerts.md",
|
|
47
|
+
"title": "FFXIV sale alerts",
|
|
48
|
+
"description": "Sale alert inputs and usage.",
|
|
49
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-Sale-Alerts"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"slug": "Allagan-Tools-Inventory-Analysis",
|
|
53
|
+
"file": "allagan-tools-inventory-analysis.md",
|
|
54
|
+
"title": "Allagan tools inventory analysis",
|
|
55
|
+
"description": "Allagan Tools data import and alerts.",
|
|
56
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Allagan-Tools-Inventory-Analysis"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"slug": "FFXIV-Experimental-Discount-Price-Sniper",
|
|
60
|
+
"file": "ffxiv-experimental-discount-price-sniper.md",
|
|
61
|
+
"title": "FFXIV experimental discount price sniper",
|
|
62
|
+
"description": "Discount price sniper guide and thresholds.",
|
|
63
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-Experimental-Discount-Price-Sniper"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"slug": "FFXIV-Sale-Leads",
|
|
67
|
+
"file": "ffxiv-sale-leads.md",
|
|
68
|
+
"title": "FFXIV sale leads",
|
|
69
|
+
"description": "Sale leads ideas and examples.",
|
|
70
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-Sale-Leads"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"slug": "TLDR:-How-to-earn-gil-with-cross-server-trading",
|
|
74
|
+
"file": "tldr-how-to-earn-gil-with-cross-server-trading.md",
|
|
75
|
+
"title": "TLDR: How to earn gil with cross server trading",
|
|
76
|
+
"description": "Condensed workflow for cross-server trading.",
|
|
77
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/TLDR:-How-to-earn-gil-with-cross-server-trading"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"slug": "API-Call-Guide",
|
|
81
|
+
"file": "api-call-guide.md",
|
|
82
|
+
"title": "API call guide",
|
|
83
|
+
"description": "General API notes and examples.",
|
|
84
|
+
"url": "https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/API-Call-Guide"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "universalis",
|
|
90
|
+
"title": "Universalis Guides Wiki",
|
|
91
|
+
"repo": "Universalis-FFXIV/guides",
|
|
92
|
+
"base_url": "https://github.com/Universalis-FFXIV/guides/wiki",
|
|
93
|
+
"pages": [
|
|
94
|
+
{
|
|
95
|
+
"slug": "API-App-Overview",
|
|
96
|
+
"file": "api-app-overview.md",
|
|
97
|
+
"title": "API app overview",
|
|
98
|
+
"description": "Universalis API overview and usage context.",
|
|
99
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/API-App-Overview"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"slug": "How-to-search-for-items-on-Universalis-and-Saddlebag-Exchange",
|
|
103
|
+
"file": "how-to-search-for-items-on-universalis-and-saddlebag-exchange.md",
|
|
104
|
+
"title": "How to search for items on Universalis and Saddlebag Exchange",
|
|
105
|
+
"description": "Item search workflow notes.",
|
|
106
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/How-to-search-for-items-on-Universalis-and-Saddlebag-Exchange"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"slug": "How-to-make-Universalis-Lists",
|
|
110
|
+
"file": "how-to-make-universalis-lists.md",
|
|
111
|
+
"title": "How to make Universalis lists",
|
|
112
|
+
"description": "Universalis list creation guide.",
|
|
113
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/How-to-make-Universalis-Lists"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"slug": "How-to-setup-Universalis-Alerts",
|
|
117
|
+
"file": "how-to-setup-universalis-alerts.md",
|
|
118
|
+
"title": "How to setup Universalis alerts",
|
|
119
|
+
"description": "Alert setup and usage guidance.",
|
|
120
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/How-to-setup-Universalis-Alerts"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"slug": "Navigating-an-item-overview-on-Universalis",
|
|
124
|
+
"file": "navigating-an-item-overview-on-universalis.md",
|
|
125
|
+
"title": "Navigating an item overview on Universalis",
|
|
126
|
+
"description": "Item overview explanation and metrics.",
|
|
127
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/Navigating-an-item-overview-on-Universalis"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"slug": "How-to-use-Universalis-Favorites",
|
|
131
|
+
"file": "how-to-use-universalis-favorites.md",
|
|
132
|
+
"title": "How to use Universalis favorites",
|
|
133
|
+
"description": "Favorites workflow and recommendations.",
|
|
134
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/How-to-use-Universalis-Favorites"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"slug": "How-to-help-update-the-data-on-Universalis%3F",
|
|
138
|
+
"file": "how-to-help-update-the-data-on-universalis.md",
|
|
139
|
+
"title": "How to help update the data on Universalis",
|
|
140
|
+
"description": "Contributor guidance for data updates.",
|
|
141
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/How-to-help-update-the-data-on-Universalis%3F"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"slug": "Interactive-Tutorials",
|
|
145
|
+
"file": "interactive-tutorials.md",
|
|
146
|
+
"title": "Interactive tutorials",
|
|
147
|
+
"description": "Interactive tutorial index.",
|
|
148
|
+
"url": "https://github.com/Universalis-FFXIV/guides/wiki/Interactive-Tutorials"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Our Allagan Data tool will analyze your inventory and items you are selling on the marketboard.
|
|
2
|
+
|
|
3
|
+
https://saddlebagexchange.com/allagan-data
|
|
4
|
+
|
|
5
|
+
This tool will do the following:
|
|
6
|
+
|
|
7
|
+
1. Show you valuable items in your inventory you may have forgotten about
|
|
8
|
+
2. Create the json data for our sale alerts
|
|
9
|
+
3. Adds a 2nd method of generating json for undercut alerts (we still recommend [the original method](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Undercut-Alerts---Alpha-version) for more [advanced filtering](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/FFXIV-Advanced-Undercut-Alert-Options))
|
|
10
|
+
4. Shows you what items are out of date on universalis, that you should update before leaving the marketboard.
|
|
11
|
+
|
|
12
|
+
Quicklinks:
|
|
13
|
+
- [How to use the Allgan Data import](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Allagan-Tools-Inventory-Analysis#how-to-use-the-allgan-data-import)
|
|
14
|
+
- [How to use the in bags report](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Allagan-Tools-Inventory-Analysis#in-bags-report)
|
|
15
|
+
- [How to update items for "out of date data"](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Allagan-Tools-Inventory-Analysis/#out-of-date-data)
|
|
16
|
+
- [How to get json data for sale and undercut alerts](https://github.com/ff14-advanced-market-search/saddlebag-with-pockets/wiki/Allagan-Tools-Inventory-Analysis#sale-and-undercut-alert-json-data)
|
|
17
|
+
|
|
18
|
+
## How to use the Allgan Data import
|
|
19
|
+
|
|
20
|
+
1. Make sure you have Dalamund Plugins and Allagan tools installed and that you have the following columns enabled.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
Make sure source, location, quantity and type are enabled
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
2. On the top left menu click > Edit > Copy List Contents > JSON Format
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
3. Make sure your server is set in the website settings, go to https://saddlebagexchange.com/allagan-data, then paste in the json data from Allagan Tools and hit search:
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
4. Review the report fields
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## In Bags Report
|
|
44
|
+
|
|
45
|
+
The in bags report is very simple it shows you the most valuable items sitting in your inventory you may have forgotten about. These are very good items to list on the marketboard:
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Out of Date Data
|
|
50
|
+
|
|
51
|
+
When an item shows up in the `Out of date` lists for undercuts or sale alerts, it means that universalis does not see the data we need for our alerts. This could also mean that you are already undercut or have already sold these items.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
To update this you just need to go to the marketboard (while Dalamund Plugins is running) and view the listings for any items that show up in our lists:
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
To make this easier I recommend adding all items you are selling to your favorites list so you can click through them instead of pasting the names into your marketboard search:
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
After you do this and all items are up to date then the list will be empty and you are ready to use the sale alerts and undercut alerts:
|
|
64
|
+
|
|
65
|
+

|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Sale and Undercut alert json data
|
|
69
|
+
|
|
70
|
+
If you click the copy to clipboard buttons for the undercut or sale alerts you will get data similar to the following to be used in our discord with slash commands.
|
|
71
|
+
|
|
72
|
+
Sale alert (use with `/ff sale-register`):
|
|
73
|
+
```
|
|
74
|
+
{
|
|
75
|
+
"item_ids":[21826,20744,35562,2731,7148,3023,35984,29682,7986,39378,2642,6460,4311,35573,6459,2236,30813,39391],
|
|
76
|
+
"retainer_names":["my retainer","my other retainer","my other other retainer"],
|
|
77
|
+
"server":"Famfrit"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
Undercut alert (use with `/ff undercut`):
|
|
83
|
+
```
|
|
84
|
+
{
|
|
85
|
+
"retainer_names": ["my retainer","my other retainer","my other other retainer"],
|
|
86
|
+
"server": "Lich",
|
|
87
|
+
"add_ids": [],
|
|
88
|
+
"ignore_ids": [],
|
|
89
|
+
"hq_only": false,
|
|
90
|
+
"ignore_data_after_hours": 720,
|
|
91
|
+
"ignore_undercuts_with_quantity_over": 9999
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# swagger
|
|
2
|
+
|
|
3
|
+
[View the full api and definitions on our swaggerhub page](https://app.swaggerhub.com/apis/SaddlebagExchange/ffxiv/)
|
|
4
|
+
|
|
5
|
+
[converted with this tool](https://kevinswiber.github.io/postman2openapi/)
|
|
6
|
+
|
|
7
|
+
# curl examples
|
|
8
|
+
|
|
9
|
+
Using DNS record. Note `http` must be used with http://api.saddlebagexchange.com, https doesnt work because of some cloudflare stuff
|
|
10
|
+
```
|
|
11
|
+
$ curl -s -X POST \
|
|
12
|
+
http://api.saddlebagexchange.com/api/seller/ \
|
|
13
|
+
-H 'Accept: application/json' \
|
|
14
|
+
-H 'Content-Type: application/json' \
|
|
15
|
+
-d '{
|
|
16
|
+
"item_id": 4745,
|
|
17
|
+
"home_server": "Midgardsormr",
|
|
18
|
+
"retainer_name": "Kainin"
|
|
19
|
+
}' | jq .
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"seller_id": "590362b2eb930741fc65059060033d10e6415743f6e677ec822ba6b2074bf8d4"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
another to try
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
curl -s -X POST \
|
|
30
|
+
http://api.saddlebagexchange.com/api/scan/ \
|
|
31
|
+
-H 'Accept: application/json' \
|
|
32
|
+
-H 'Content-Type: application/json' \
|
|
33
|
+
-d '{
|
|
34
|
+
"preferred_roi": 99,
|
|
35
|
+
"min_profit_amount": 10000,
|
|
36
|
+
"min_desired_avg_ppu": 10000,
|
|
37
|
+
"min_stack_size": 1,
|
|
38
|
+
"hours_ago": 168,
|
|
39
|
+
"min_sales": 2,
|
|
40
|
+
"hq": false,
|
|
41
|
+
"home_server": "Yojimbo",
|
|
42
|
+
"filters": [0],
|
|
43
|
+
"region_wide": false,
|
|
44
|
+
"include_vendor": false,
|
|
45
|
+
"show_out_stock": true
|
|
46
|
+
}' | jq .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
curl example with ip
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
$ curl -s -X POST \
|
|
53
|
+
http://1.2.3.4/api/seller/ \
|
|
54
|
+
-H 'Accept: application/json' \
|
|
55
|
+
-H 'Content-Type: application/json' \
|
|
56
|
+
-d '{
|
|
57
|
+
"item_id": 4745,
|
|
58
|
+
"home_server": "Midgardsormr",
|
|
59
|
+
"retainer_name": "Kainin"
|
|
60
|
+
}' | jq .
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
"seller_id": "590362b2eb930741fc65059060033d10e6415743f6e677ec822ba6b2074bf8d4"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
# Postman examples
|
|
68
|
+
|
|
69
|
+
You can also run this with postman instead of curl
|
|
70
|
+
|
|
71
|
+
<img width="619" alt="image" src="https://user-images.githubusercontent.com/17516896/190919261-cb541bdc-70e3-4773-b2d5-94275bac3da0.png">
|
|
72
|
+
|
|
73
|
+
<img width="611" alt="image" src="https://user-images.githubusercontent.com/17516896/190919290-312a9998-1951-451d-b031-f26e87fd94d1.png">
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# POST Content
|
|
77
|
+
|
|
78
|
+
## FFXIV
|
|
79
|
+
|
|
80
|
+
### Scan
|
|
81
|
+
|
|
82
|
+
http://api.saddlebagexchange.com/api/scan
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"preferred_roi": 50,
|
|
87
|
+
"min_profit_amount": 10000,
|
|
88
|
+
"min_desired_avg_ppu": 10000,
|
|
89
|
+
"min_stack_size": 1,
|
|
90
|
+
"hours_ago": 24,
|
|
91
|
+
"min_sales": 4,
|
|
92
|
+
"hq": false,
|
|
93
|
+
"home_server": "Famfrit",
|
|
94
|
+
"filters": [0],
|
|
95
|
+
"region_wide": false,
|
|
96
|
+
"include_vendor": false,
|
|
97
|
+
"show_out_stock": true,
|
|
98
|
+
"universalis_list_uid": ""
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### History
|
|
103
|
+
http://api.saddlebagexchange.com/api/history
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"item_id": 36109,
|
|
107
|
+
"home_server": "Midgardsormr",
|
|
108
|
+
"initial_days": 7,
|
|
109
|
+
"end_days": 0,
|
|
110
|
+
"item_type": "all"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
### Listing
|
|
114
|
+
http://api.saddlebagexchange.com/api/listing
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"item_id": 36109,
|
|
118
|
+
"home_server": "Midgardsormr",
|
|
119
|
+
"initial_days": 30,
|
|
120
|
+
"end_days": 0
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### export
|
|
125
|
+
|
|
126
|
+
http://api.saddlebagexchange.com/api/export
|
|
127
|
+
|
|
128
|
+
api post body
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"home_server": "Famfrit",
|
|
133
|
+
"export_servers": ["Lamia","Seraph"],
|
|
134
|
+
"item_ids": [33275,4745],
|
|
135
|
+
"hq_only": false
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### ffxiv market share
|
|
140
|
+
|
|
141
|
+
http://api.saddlebagexchange.com/api/ffxivmarketshare/
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"server": "Famfrit",
|
|
146
|
+
"time_period": 24,
|
|
147
|
+
"sales_amount": 2,
|
|
148
|
+
"average_price": 10000,
|
|
149
|
+
"filters": [0]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Allagan Tools data parsing
|
|
154
|
+
|
|
155
|
+
http://api.saddlebagexchange.com/api/parseallagan
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"server": "Famfrit",
|
|
160
|
+
"allagan_json_data": $DATA_COPIED_FROM_ALLAGAN_TOOLS
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
### Self purchase
|
|
164
|
+
|
|
165
|
+
http://api.saddlebagexchange.com/api/selfpurchase
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"server": "Famfrit",
|
|
171
|
+
"player_name": "Aere Noctum"
|
|
172
|
+
}
|
|
173
|
+
```
|