@taskeren/bungie-api-ts 5.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +4 -0
- package/.gitattributes +2 -0
- package/.github/workflows/pr-build.yml +50 -0
- package/.github/workflows/publish.yml +48 -0
- package/.github/workflows/update.yml +55 -0
- package/.gitmodules +3 -0
- package/.nvmrc +1 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +8 -0
- package/.vscode/settings.json +7 -0
- package/LICENSE.md +21 -0
- package/README.md +97 -0
- package/api-src/.github/workflows/close-inactive.yml +24 -0
- package/api-src/CHANGELOG.md +271 -0
- package/api-src/CONTRIBUTING.md +19 -0
- package/api-src/LICENSE +28 -0
- package/api-src/README.md +378 -0
- package/api-src/openapi-2.json +44518 -0
- package/api-src/openapi.json +46467 -0
- package/build.sh +30 -0
- package/bungie-api-LICENSE +29 -0
- package/generated-src/app/api.ts +50 -0
- package/generated-src/app/index.ts +17 -0
- package/generated-src/app/interfaces.ts +119 -0
- package/generated-src/common.ts +985 -0
- package/generated-src/communitycontent/api.ts +36 -0
- package/generated-src/communitycontent/index.ts +16 -0
- package/generated-src/content/api.ts +142 -0
- package/generated-src/content/index.ts +17 -0
- package/generated-src/content/interfaces.ts +225 -0
- package/generated-src/core/api.ts +57 -0
- package/generated-src/core/index.ts +17 -0
- package/generated-src/core/interfaces.ts +151 -0
- package/generated-src/destiny2/api.ts +821 -0
- package/generated-src/destiny2/index.ts +17 -0
- package/generated-src/destiny2/interfaces.ts +15714 -0
- package/generated-src/destiny2/manifest.ts +341 -0
- package/generated-src/fireteam/api.ts +151 -0
- package/generated-src/fireteam/index.ts +17 -0
- package/generated-src/fireteam/interfaces.ts +183 -0
- package/generated-src/forum/api.ts +197 -0
- package/generated-src/forum/index.ts +17 -0
- package/generated-src/forum/interfaces.ts +171 -0
- package/generated-src/groupv2/api.ts +515 -0
- package/generated-src/groupv2/index.ts +17 -0
- package/generated-src/groupv2/interfaces.ts +773 -0
- package/generated-src/http.ts +52 -0
- package/generated-src/index.ts +37 -0
- package/generated-src/package.json.notyet +99 -0
- package/generated-src/platform.ts +39 -0
- package/generated-src/social/api.ts +115 -0
- package/generated-src/social/index.ts +17 -0
- package/generated-src/social/interfaces.ts +89 -0
- package/generated-src/tokens/api.ts +126 -0
- package/generated-src/tokens/index.ts +17 -0
- package/generated-src/tokens/interfaces.ts +102 -0
- package/generated-src/trending/api.ts +62 -0
- package/generated-src/trending/index.ts +17 -0
- package/generated-src/trending/interfaces.ts +188 -0
- package/generated-src/user/api.ts +130 -0
- package/generated-src/user/index.ts +17 -0
- package/generated-src/user/interfaces.ts +338 -0
- package/generator/generate-api.ts +254 -0
- package/generator/generate-common.ts +123 -0
- package/generator/generate-index.ts +45 -0
- package/generator/generate-interfaces.ts +173 -0
- package/generator/generate-manifest.ts +217 -0
- package/generator/generate-package-json.ts +45 -0
- package/generator/generate.ts +54 -0
- package/generator/http.ts +52 -0
- package/generator/missing-props.ts +11 -0
- package/generator/type-index.ts +184 -0
- package/generator/util.ts +131 -0
- package/lib/README.md +97 -0
- package/lib/app/api.d.ts +36 -0
- package/lib/app/api.js +15 -0
- package/lib/app/index.d.ts +16 -0
- package/lib/app/index.js +5 -0
- package/lib/app/interfaces.d.ts +108 -0
- package/lib/app/interfaces.js +17 -0
- package/lib/bungie-api-LICENSE +29 -0
- package/lib/common.d.ts +979 -0
- package/lib/common.js +923 -0
- package/lib/communitycontent/api.d.ts +31 -0
- package/lib/communitycontent/api.js +7 -0
- package/lib/communitycontent/index.d.ts +15 -0
- package/lib/communitycontent/index.js +4 -0
- package/lib/content/api.d.ts +111 -0
- package/lib/content/api.js +75 -0
- package/lib/content/index.d.ts +16 -0
- package/lib/content/index.js +5 -0
- package/lib/content/interfaces.d.ts +214 -0
- package/lib/content/interfaces.js +17 -0
- package/lib/core/api.d.ts +45 -0
- package/lib/core/api.js +18 -0
- package/lib/core/index.d.ts +16 -0
- package/lib/core/index.js +5 -0
- package/lib/core/interfaces.d.ts +144 -0
- package/lib/core/interfaces.js +10 -0
- package/lib/destiny2/api.d.ts +765 -0
- package/lib/destiny2/api.js +295 -0
- package/lib/destiny2/index.d.ts +16 -0
- package/lib/destiny2/index.js +5 -0
- package/lib/destiny2/interfaces.d.ts +15340 -0
- package/lib/destiny2/interfaces.js +826 -0
- package/lib/destiny2/manifest.d.ts +465 -0
- package/lib/destiny2/manifest.js +56 -0
- package/lib/fireteam/api.d.ts +131 -0
- package/lib/fireteam/api.js +50 -0
- package/lib/fireteam/index.d.ts +16 -0
- package/lib/fireteam/index.js +5 -0
- package/lib/fireteam/interfaces.d.ts +166 -0
- package/lib/fireteam/interfaces.js +33 -0
- package/lib/forum/api.d.ts +165 -0
- package/lib/forum/api.js +79 -0
- package/lib/forum/index.d.ts +16 -0
- package/lib/forum/index.js +5 -0
- package/lib/forum/interfaces.d.ts +147 -0
- package/lib/forum/interfaces.js +61 -0
- package/lib/groupv2/api.d.ts +484 -0
- package/lib/groupv2/api.js +154 -0
- package/lib/groupv2/index.d.ts +16 -0
- package/lib/groupv2/index.js +5 -0
- package/lib/groupv2/interfaces.d.ts +718 -0
- package/lib/groupv2/interfaces.js +100 -0
- package/lib/http.d.ts +18 -0
- package/lib/http.js +20 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.js +24 -0
- package/lib/package.json +99 -0
- package/lib/platform.d.ts +36 -0
- package/lib/platform.js +9 -0
- package/lib/social/api.d.ts +101 -0
- package/lib/social/api.js +26 -0
- package/lib/social/index.d.ts +16 -0
- package/lib/social/index.js +5 -0
- package/lib/social/interfaces.d.ts +75 -0
- package/lib/social/interfaces.js +22 -0
- package/lib/tokens/api.d.ts +120 -0
- package/lib/tokens/api.js +38 -0
- package/lib/tokens/index.d.ts +16 -0
- package/lib/tokens/index.js +5 -0
- package/lib/tokens/interfaces.d.ts +88 -0
- package/lib/tokens/interfaces.js +5 -0
- package/lib/trending/api.d.ts +53 -0
- package/lib/trending/api.js +11 -0
- package/lib/trending/index.d.ts +16 -0
- package/lib/trending/index.js +5 -0
- package/lib/trending/interfaces.d.ts +171 -0
- package/lib/trending/interfaces.js +14 -0
- package/lib/user/api.d.ts +117 -0
- package/lib/user/api.js +35 -0
- package/lib/user/index.d.ts +16 -0
- package/lib/user/index.js +5 -0
- package/lib/user/interfaces.d.ts +320 -0
- package/lib/user/interfaces.js +28 -0
- package/package.json +60 -0
- package/test-consumer.ts +5 -0
- package/tests/__snapshots__/config-builders.test.ts.snap +343 -0
- package/tests/config-builders.test.ts +145 -0
- package/tests/manifest-fetcher.test.ts +43 -0
- package/tests/test-manifest.js +69 -0
- package/tsconfig-package.json +14 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { DefInfo, getReferencedTypes, getRef } from './util.js';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import mkdirp from 'mkdirp';
|
|
6
|
+
import { OpenAPIObject, SchemaObject, ReferenceObject } from 'openapi3-ts';
|
|
7
|
+
|
|
8
|
+
export function generateHeader(doc: OpenAPIObject): string {
|
|
9
|
+
const { info } = doc;
|
|
10
|
+
return `/**
|
|
11
|
+
* ${info.title}
|
|
12
|
+
* ${info.description}
|
|
13
|
+
*
|
|
14
|
+
* OpenAPI spec version: ${info.version}
|
|
15
|
+
* Contact: ${info.contact!.email}
|
|
16
|
+
*
|
|
17
|
+
* NOTE: This class is auto generated by the bungie-api-ts code generator program.
|
|
18
|
+
* https://github.com/DestinyItemManager/bungie-api-ts
|
|
19
|
+
* Do not edit these files manually.
|
|
20
|
+
*/`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Add the name of a referenced type from `schema` to the list of symbols to
|
|
25
|
+
* import for a given imported module file.
|
|
26
|
+
*/
|
|
27
|
+
export function addImport(
|
|
28
|
+
doc: OpenAPIObject,
|
|
29
|
+
schema: SchemaObject | ReferenceObject,
|
|
30
|
+
componentByDef: Readonly<{ [def: string]: DefInfo }>,
|
|
31
|
+
importFiles: { [filename: string]: Set<string> } // mutated
|
|
32
|
+
) {
|
|
33
|
+
const typeRef = getReferencedTypes(schema);
|
|
34
|
+
if (typeRef && componentByDef[typeRef]) {
|
|
35
|
+
if (typeRef.includes('/responses/')) {
|
|
36
|
+
const component = getRef(doc, typeRef);
|
|
37
|
+
if (component) {
|
|
38
|
+
const property = component.properties!.Response;
|
|
39
|
+
if (property) {
|
|
40
|
+
(importFiles['common.ts'] ??= new Set()).add('ServerResponse');
|
|
41
|
+
addImport(doc, property, componentByDef, importFiles);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const filename = componentByDef[typeRef].filename;
|
|
48
|
+
(importFiles[filename] ??= new Set()).add(componentByDef[typeRef].interfaceName);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Given an output filename and a list of all symbols to be imported, generate
|
|
54
|
+
* the actual import lines.
|
|
55
|
+
*/
|
|
56
|
+
export function generateImports(
|
|
57
|
+
filename: string,
|
|
58
|
+
importFiles: Readonly<{ [filename: string]: Set<string> }>
|
|
59
|
+
): string {
|
|
60
|
+
return _.compact(
|
|
61
|
+
_.map(importFiles, (types, f) => {
|
|
62
|
+
const absImport = path.resolve('generated-src', f);
|
|
63
|
+
const absDest = path.resolve(filename);
|
|
64
|
+
if (absImport === absDest) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
let relativePath = path.relative(path.dirname(absDest), absImport).replace(/(\.d)?\.ts$/, '');
|
|
68
|
+
if (!relativePath.startsWith('.')) {
|
|
69
|
+
relativePath = './' + relativePath;
|
|
70
|
+
}
|
|
71
|
+
if (path.sep === '\\') relativePath = relativePath.replace(/\\/g, '/');
|
|
72
|
+
return `import {
|
|
73
|
+
${[...types].sort().join(',\n ')}
|
|
74
|
+
} from '${relativePath}.js';`;
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
.sort()
|
|
78
|
+
.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Print a JSDoc documentation comment with the given string as its contents.
|
|
83
|
+
*/
|
|
84
|
+
export function docComment(text: string) {
|
|
85
|
+
const lines = _.flatten(
|
|
86
|
+
text
|
|
87
|
+
.trim()
|
|
88
|
+
.split('\n')
|
|
89
|
+
.map((l) => l.replace(/(.{1,80}(?:\W|$))/g, '$1\n').split('\n'))
|
|
90
|
+
).map((s: string) => s.trim());
|
|
91
|
+
lines.pop();
|
|
92
|
+
|
|
93
|
+
if (lines.length === 1) {
|
|
94
|
+
return `/** ${lines} */`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `/**
|
|
98
|
+
${lines.map((line) => (line.length ? ' * ' + line : ' *')).join('\n')}
|
|
99
|
+
*/`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Indent the given text a certain number of levels.
|
|
104
|
+
*/
|
|
105
|
+
export function indent(text: string, indentLevel: number) {
|
|
106
|
+
const lines = text.split('\n');
|
|
107
|
+
return lines.map((line) => ' '.repeat(indentLevel) + line).join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Write a string contents out to a file.
|
|
112
|
+
*/
|
|
113
|
+
export async function writeOutFile(filename: string, contents: string) {
|
|
114
|
+
try {
|
|
115
|
+
await mkdirp(path.dirname(filename));
|
|
116
|
+
|
|
117
|
+
fs.writeFile(filename, contents, null, (error) => {
|
|
118
|
+
console.log(error ? error : `Done with ${filename}!`);
|
|
119
|
+
});
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error(e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OpenAPIObject } from 'openapi3-ts';
|
|
2
|
+
import { generateHeader, writeOutFile } from './generate-common.js';
|
|
3
|
+
import { DefInfo } from './util.js';
|
|
4
|
+
|
|
5
|
+
export function generateIndex(
|
|
6
|
+
tag: string,
|
|
7
|
+
doc: OpenAPIObject,
|
|
8
|
+
componentsByFile: {
|
|
9
|
+
[filename: string]: DefInfo[];
|
|
10
|
+
}
|
|
11
|
+
) {
|
|
12
|
+
const filename = `generated-src/${tag.toLowerCase()}/index.ts`;
|
|
13
|
+
|
|
14
|
+
let imports = `export * from '../common.js';${
|
|
15
|
+
tag !== 'Destiny2' ? "\nexport * from '../platform.js';" : ''
|
|
16
|
+
}
|
|
17
|
+
export type {HttpClientConfig, HttpClient} from '../http.js';
|
|
18
|
+
export * from './api.js';`;
|
|
19
|
+
|
|
20
|
+
if (componentsByFile[`${tag.toLowerCase()}/interfaces.ts`]) {
|
|
21
|
+
imports = `${imports}\nexport * from './interfaces.js';`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Destiny2 service has special manifest helpers
|
|
25
|
+
if (tag === 'Destiny2') {
|
|
26
|
+
imports = `${imports}\nexport * from './manifest.js';`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const definition = [generateHeader(doc), imports].join('\n\n') + '\n';
|
|
30
|
+
|
|
31
|
+
writeOutFile(filename, definition);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function generateSuperIndex(tags: string[], doc: OpenAPIObject) {
|
|
35
|
+
const filename = `generated-src/index.ts`;
|
|
36
|
+
|
|
37
|
+
const imports = tags
|
|
38
|
+
.map((tag) => `import * as ${tag}Import from './${tag.toLowerCase()}/index.js';`)
|
|
39
|
+
.join('\n');
|
|
40
|
+
const exportStatements = tags.map((tag) => `export const ${tag} = ${tag}Import;`).join('\n');
|
|
41
|
+
|
|
42
|
+
const definition = [generateHeader(doc), imports, exportStatements].join('\n\n') + '\n';
|
|
43
|
+
|
|
44
|
+
writeOutFile(filename, definition);
|
|
45
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { DefInfo, getRef, resolveSchemaType } from './util.js';
|
|
3
|
+
import { OpenAPIObject, SchemaObject } from 'openapi3-ts';
|
|
4
|
+
import {
|
|
5
|
+
generateHeader,
|
|
6
|
+
generateImports,
|
|
7
|
+
docComment,
|
|
8
|
+
indent,
|
|
9
|
+
addImport,
|
|
10
|
+
writeOutFile,
|
|
11
|
+
} from './generate-common.js';
|
|
12
|
+
import { missingPropsByInterfaceName } from './missing-props.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Some properties aren't marked as nullable in the openapi docs, but they are frequently null in practice and cause trouble.
|
|
16
|
+
* Adding a property to this list forces it to be emitted as nullable.
|
|
17
|
+
*/
|
|
18
|
+
const frequentlyNullProperties = ['itemCategoryHashes', 'itemValueVisibility'];
|
|
19
|
+
|
|
20
|
+
export function generateInterfaceDefinitions(
|
|
21
|
+
file: string,
|
|
22
|
+
components: DefInfo[],
|
|
23
|
+
doc: OpenAPIObject,
|
|
24
|
+
componentByDef: { [def: string]: DefInfo }
|
|
25
|
+
) {
|
|
26
|
+
const importFiles: { [filename: string]: Set<string> } = {};
|
|
27
|
+
|
|
28
|
+
const componentDefinitions = components.map((component) =>
|
|
29
|
+
generateComponentDefinition(component, doc, componentByDef, importFiles)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const filename = `generated-src/${file}`;
|
|
33
|
+
|
|
34
|
+
let specialDefinitions;
|
|
35
|
+
if (file === 'destiny2/interfaces.ts') {
|
|
36
|
+
specialDefinitions = generateSpecialDefinitions();
|
|
37
|
+
}
|
|
38
|
+
if (file === 'common.ts') {
|
|
39
|
+
specialDefinitions = generateServerResponseDefinitions();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const imports = generateImports(filename, importFiles);
|
|
43
|
+
|
|
44
|
+
const definition =
|
|
45
|
+
_.compact([generateHeader(doc), imports, specialDefinitions, ...componentDefinitions]).join(
|
|
46
|
+
'\n\n'
|
|
47
|
+
) + '\n';
|
|
48
|
+
|
|
49
|
+
writeOutFile(filename, definition);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function generateComponentDefinition(
|
|
53
|
+
defInfo: DefInfo,
|
|
54
|
+
doc: OpenAPIObject,
|
|
55
|
+
// mutated
|
|
56
|
+
componentByDef: { [def: string]: DefInfo },
|
|
57
|
+
// mutated
|
|
58
|
+
importFiles: { [filename: string]: Set<string> }
|
|
59
|
+
) {
|
|
60
|
+
const component = getRef(doc, defInfo.def);
|
|
61
|
+
if (!component) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (component.enum) {
|
|
66
|
+
return generateEnum(defInfo, component);
|
|
67
|
+
} else if (isSpecialType(defInfo.interfaceName)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
} else {
|
|
70
|
+
return generateInterfaceSchema(
|
|
71
|
+
defInfo.interfaceName,
|
|
72
|
+
component,
|
|
73
|
+
doc,
|
|
74
|
+
componentByDef,
|
|
75
|
+
importFiles
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generateEnum(defInfo: DefInfo, component: SchemaObject) {
|
|
81
|
+
const values = component['x-enum-values']
|
|
82
|
+
.map((value: SchemaObject) => {
|
|
83
|
+
const doc = value.description ? docComment(value.description) + '\n' : '';
|
|
84
|
+
return `${doc}${value.identifier} = ${value.numericValue}`;
|
|
85
|
+
})
|
|
86
|
+
.join(',\n');
|
|
87
|
+
|
|
88
|
+
const docs = component.description ? [component.description] : [];
|
|
89
|
+
if (component['x-enum-is-bitmask']) {
|
|
90
|
+
docs.push(
|
|
91
|
+
`This enum represents a set of flags - use bitwise operators to check which of these match your value.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const docString = docs.length ? docComment(docs.join('\n')) + '\n' : '';
|
|
96
|
+
|
|
97
|
+
// TODO: const enums are super efficient (they get inlined) but we may want to change this if we want to do things like
|
|
98
|
+
// print out the name of an enum case.
|
|
99
|
+
return `${docString}export const enum ${defInfo.interfaceName} {
|
|
100
|
+
${indent(values, 1)}
|
|
101
|
+
}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function generateInterfaceSchema(
|
|
105
|
+
interfaceName: string,
|
|
106
|
+
component: SchemaObject,
|
|
107
|
+
doc: OpenAPIObject,
|
|
108
|
+
componentByDef: { [def: string]: DefInfo },
|
|
109
|
+
importFiles: { [filename: string]: Set<string> }
|
|
110
|
+
) {
|
|
111
|
+
// for *missing* props, this acts conservatively and only inserts our overrides if the prop isn't provided by bungie at all.
|
|
112
|
+
// we could add *overrides* later but for now don't want to overwrite a prop if they get around to adding it.
|
|
113
|
+
const missingProps = missingPropsByInterfaceName[interfaceName] ?? {};
|
|
114
|
+
for (const k in component.properties) delete missingProps[k];
|
|
115
|
+
const properties = { ...component.properties, ...missingProps };
|
|
116
|
+
|
|
117
|
+
const parameterArgs = _.map(properties, (schema: SchemaObject, param) => {
|
|
118
|
+
const paramType = resolveSchemaType(schema, doc);
|
|
119
|
+
addImport(doc, schema, componentByDef, importFiles);
|
|
120
|
+
const docs = schema.description ? [schema.description] : [];
|
|
121
|
+
|
|
122
|
+
const crossMappedDef = schema['x-mapped-definition']?.$ref;
|
|
123
|
+
if (crossMappedDef) {
|
|
124
|
+
docs.push(`Mapped to ${componentByDef[crossMappedDef].interfaceName} in the manifest.`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (schema['x-enum-is-bitmask']) {
|
|
128
|
+
docs.push(
|
|
129
|
+
`This enum represents a set of flags - use bitwise operators to check which of these match your value.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const docString = docs.length ? docComment(docs.join('\n')) + '\n' : '';
|
|
133
|
+
|
|
134
|
+
const isNullable =
|
|
135
|
+
schema.nullable ||
|
|
136
|
+
frequentlyNullProperties.includes(param) ||
|
|
137
|
+
schema.description?.toLowerCase().includes('null');
|
|
138
|
+
|
|
139
|
+
return `${docString}readonly ${param}${isNullable ? '?' : ''}: ${paramType};`;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const docString = component.description ? docComment(component.description) + '\n' : '';
|
|
143
|
+
return `${docString}export interface ${interfaceName} {
|
|
144
|
+
${indent(parameterArgs.join('\n'), 1)}
|
|
145
|
+
}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isSpecialType(name: string) {
|
|
149
|
+
return name.includes('>');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function generateSpecialDefinitions() {
|
|
153
|
+
return `export interface SingleComponentResponse<T> {
|
|
154
|
+
readonly data?: T;
|
|
155
|
+
readonly privacy: ComponentPrivacySetting;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface DictionaryComponentResponse<T> {
|
|
159
|
+
readonly data?: { [key: string]: T };
|
|
160
|
+
readonly privacy: ComponentPrivacySetting;
|
|
161
|
+
}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function generateServerResponseDefinitions() {
|
|
165
|
+
return `export interface ServerResponse<T> {
|
|
166
|
+
readonly Response: T;
|
|
167
|
+
readonly ErrorCode: PlatformErrorCodes;
|
|
168
|
+
readonly ThrottleSeconds: number;
|
|
169
|
+
readonly ErrorStatus: string;
|
|
170
|
+
readonly Message: string;
|
|
171
|
+
readonly MessageData: { [key: string]: string };
|
|
172
|
+
}`;
|
|
173
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { DefInfo } from './util.js';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts';
|
|
3
|
+
import { writeOutFile } from './generate-common.js';
|
|
4
|
+
|
|
5
|
+
const httpClientType = `import { HttpClient, get } from '../http.js';`;
|
|
6
|
+
|
|
7
|
+
const manifestMetadataPromise = (async () => {
|
|
8
|
+
try {
|
|
9
|
+
let manifestMeta = await (fetch as any)(
|
|
10
|
+
'https://www.bungie.net/Platform/Destiny2/Manifest/'
|
|
11
|
+
).then((res: Response) => res.json());
|
|
12
|
+
return (manifestMeta as any).Response;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.log(e);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
})();
|
|
18
|
+
|
|
19
|
+
export async function generateManifestUtils(components: DefInfo[], doc: OpenAPIObject) {
|
|
20
|
+
const manifestStructure = await generateManifestDefinitions(components);
|
|
21
|
+
|
|
22
|
+
const filename = `generated-src/destiny2/manifest.ts`;
|
|
23
|
+
|
|
24
|
+
const definition =
|
|
25
|
+
[generateManifestHeader(doc), httpClientType, manifestStructure, manifestUtilDefinitions].join(
|
|
26
|
+
'\n\n'
|
|
27
|
+
) + '\n';
|
|
28
|
+
|
|
29
|
+
writeOutFile(filename, definition);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function generateManifestDefinitions(components: DefInfo[]) {
|
|
33
|
+
let manifestMetadata = await manifestMetadataPromise;
|
|
34
|
+
|
|
35
|
+
// defs we have documentation for. some stuff in manifest doesn't have interface definitions. idk why.
|
|
36
|
+
const documentedDefs = components.map((component) => component.interfaceName);
|
|
37
|
+
|
|
38
|
+
// exclude some tables from the definitionmanifest table because we don't have the format for them
|
|
39
|
+
const defsToInclude = Object.keys(manifestMetadata.jsonWorldComponentContentPaths.en).filter(
|
|
40
|
+
(tableName) => documentedDefs.includes(tableName)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const languageList = Object.keys(manifestMetadata.jsonWorldComponentContentPaths).sort();
|
|
44
|
+
|
|
45
|
+
return `import {${defsToInclude.map((c) => `\n ${c},`).join('')}
|
|
46
|
+
DestinyManifest
|
|
47
|
+
} from './interfaces.js';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* this describes a big object holding several tables of hash-keyed DestinyDefinitions.
|
|
51
|
+
* this is roughly what you get if you decode the gigantic, single-json manifest blob,
|
|
52
|
+
* but also just what we use here to dole out single-table, typed definitions
|
|
53
|
+
*/
|
|
54
|
+
export interface AllDestinyManifestComponents {
|
|
55
|
+
${defsToInclude
|
|
56
|
+
.map((manifestComponent) => ` ${manifestComponent}: { [key: number]: ${manifestComponent} };\n`)
|
|
57
|
+
.join('')}}
|
|
58
|
+
|
|
59
|
+
export type DestinyInventoryItemLiteDefinition = Omit<
|
|
60
|
+
DestinyInventoryItemDefinition,
|
|
61
|
+
| "iconWatermarkShelved" | "screenshot" | "flavorText" | "tooltipStyle"
|
|
62
|
+
| "action" | "stats" | "statsEnabledUnlockExpression" | "translationBlock"
|
|
63
|
+
| "preview" | "sourceData" | "objectives" | "metrics" | "plug"
|
|
64
|
+
| "gearset" | "sack" | "sockets" | "investmentStats" | "traitIds"
|
|
65
|
+
| "traitHashes" | "boundToRelease" | "hash" | "index" | "contentIdentifier"
|
|
66
|
+
| "redacted" | "blacklisted"
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* languages the manifest comes in, as their required keys to download them
|
|
71
|
+
*/
|
|
72
|
+
export const destinyManifestLanguages = [
|
|
73
|
+
${languageList.map((l) => ` '${l}',`).join('\n')}
|
|
74
|
+
] as const;
|
|
75
|
+
|
|
76
|
+
export type DestinyManifestLanguage = typeof destinyManifestLanguages[number];
|
|
77
|
+
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function generateManifestHeader(doc: OpenAPIObject): string {
|
|
82
|
+
const { info } = doc;
|
|
83
|
+
return `/**
|
|
84
|
+
* these helper functions and definitions are based off the structure of DestinyManifest
|
|
85
|
+
* in the bungie.net API spec, but are not explicity defined endpoints in the spec.
|
|
86
|
+
*
|
|
87
|
+
* they were last hand-checked for OpenAPI spec version 2.8.0,
|
|
88
|
+
* and have been automatically tested for the latest OpenAPI spec version ${info.version}.
|
|
89
|
+
* if there are typing issues with them, please let us know at the below repo.
|
|
90
|
+
*
|
|
91
|
+
* NOTE: This class is auto generated by the bungie-api-ts code generator program.
|
|
92
|
+
* https://github.com/DestinyItemManager/bungie-api-ts
|
|
93
|
+
* Do not edit these files manually.
|
|
94
|
+
*/`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const manifestUtilDefinitions = `
|
|
98
|
+
// thoughts:
|
|
99
|
+
// this relies on the assumption that the separate
|
|
100
|
+
// manifest pieces offered in jsonWorldComponentContentPaths,
|
|
101
|
+
// will all be present in the big manifest at jsonWorldContentPaths.
|
|
102
|
+
|
|
103
|
+
// this has been the case so far, but there aren't
|
|
104
|
+
// strict spec standards for how the manifest will be available
|
|
105
|
+
|
|
106
|
+
export type DestinyManifestComponentName = keyof AllDestinyManifestComponents;
|
|
107
|
+
|
|
108
|
+
export type DestinyManifestSlice<K extends Readonly<DestinyManifestComponentName[]>> = Pick<
|
|
109
|
+
AllDestinyManifestComponents,
|
|
110
|
+
K[number]
|
|
111
|
+
>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* given a STRING table name, returns that TYPE, so that you can write a function like:
|
|
115
|
+
* func<K extends DestinyManifestComponentName>(arg0:K):DestinyDefinitionFrom<K>{...}
|
|
116
|
+
* i.e.
|
|
117
|
+
* func('DestinyInventoryItemDefinition') will return type DestinyInventoryItemDefinition
|
|
118
|
+
*/
|
|
119
|
+
export type DestinyDefinitionFrom<
|
|
120
|
+
K extends DestinyManifestComponentName
|
|
121
|
+
> = AllDestinyManifestComponents[K][number];
|
|
122
|
+
|
|
123
|
+
export interface GetAllDestinyManifestComponentsParams {
|
|
124
|
+
destinyManifest: DestinyManifest;
|
|
125
|
+
language: DestinyManifestLanguage;
|
|
126
|
+
}
|
|
127
|
+
/** fetches the enormous combined JSON manifest file */
|
|
128
|
+
export function getAllDestinyManifestComponents(
|
|
129
|
+
http: HttpClient,
|
|
130
|
+
params: GetAllDestinyManifestComponentsParams
|
|
131
|
+
): Promise<AllDestinyManifestComponents> {
|
|
132
|
+
return get(http, 'https://www.bungie.net'+params.destinyManifest.jsonWorldContentPaths[params.language]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface GetDestinyManifestComponentParams<T extends DestinyManifestComponentName> {
|
|
136
|
+
destinyManifest: DestinyManifest;
|
|
137
|
+
tableName: T;
|
|
138
|
+
language: DestinyManifestLanguage;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* this fetches and returns a single table (Component) from the d2 manifest
|
|
142
|
+
* i.e. DestinyInventoryItemDefinition / DestinyObjectiveDefinition /
|
|
143
|
+
* DestinyVendorDefinition / DestinySeasonDefinition / etc.
|
|
144
|
+
*
|
|
145
|
+
* due to typescript limitations, the table name needs to be recognized by
|
|
146
|
+
* typescript as a const (not mutable between inception and going into the function),
|
|
147
|
+
* so that it considers it a table name and not just a string.
|
|
148
|
+
*
|
|
149
|
+
* this is easy with a string, since
|
|
150
|
+
*
|
|
151
|
+
* \`const x = 'thing';\` is type \`'thing'\`, not type \`string\`,
|
|
152
|
+
*
|
|
153
|
+
* but make sure it's not a \`let x =\` or a dynamically set string.
|
|
154
|
+
*/
|
|
155
|
+
export async function getDestinyManifestComponent<T extends DestinyManifestComponentName>(
|
|
156
|
+
http: HttpClient,
|
|
157
|
+
params: GetDestinyManifestComponentParams<T>
|
|
158
|
+
): Promise<AllDestinyManifestComponents[T]> {
|
|
159
|
+
const url = 'https://www.bungie.net' +
|
|
160
|
+
params.destinyManifest.jsonWorldComponentContentPaths[params.language][params.tableName];
|
|
161
|
+
try {
|
|
162
|
+
return await get(http, url);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
try {
|
|
165
|
+
return await get(http, \`\${url}?retry\`);
|
|
166
|
+
} catch {
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface GetDestinyManifestSliceParams<T extends DestinyManifestComponentName[]> {
|
|
173
|
+
destinyManifest: DestinyManifest;
|
|
174
|
+
tableNames: T;
|
|
175
|
+
language: DestinyManifestLanguage;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* this returns a similar structure to getAllDestinyManifestComponents (the big manifest json)
|
|
179
|
+
* but only specific components within. it bundles multiple single tables requests,
|
|
180
|
+
* into a single properly typed object with keys named after manifest components
|
|
181
|
+
*
|
|
182
|
+
* i.e. \`{ DestinyInventoryItemDefinition: etc...,
|
|
183
|
+
* DestinyObjectiveDefinition: etc... }\`
|
|
184
|
+
*
|
|
185
|
+
* due to typescript limitations, the array of tableNames needs to be recognized by
|
|
186
|
+
* typescript as readonly (not mutable between inception and going into the function),
|
|
187
|
+
* so that it considers them table names and not just strings.
|
|
188
|
+
*
|
|
189
|
+
* like \`['DestinyInventoryItemDefinition' as const]\`
|
|
190
|
+
*
|
|
191
|
+
* or maybe \`['DestinyInventoryItemDefinition'] as const\`
|
|
192
|
+
*
|
|
193
|
+
* or just feed in into the function hardcoded like
|
|
194
|
+
*
|
|
195
|
+
* \`function(['DestinyInventoryItemDefinition'])\`
|
|
196
|
+
*/
|
|
197
|
+
export async function getDestinyManifestSlice<T extends DestinyManifestComponentName[]>(
|
|
198
|
+
http: HttpClient,
|
|
199
|
+
params: GetDestinyManifestSliceParams<T>
|
|
200
|
+
): Promise<DestinyManifestSlice<T>> {
|
|
201
|
+
const downloadedTables = await Promise.all(
|
|
202
|
+
params.tableNames.map(async (tableName) => {
|
|
203
|
+
const tableContent = await getDestinyManifestComponent(http, {
|
|
204
|
+
destinyManifest: params.destinyManifest,
|
|
205
|
+
tableName,
|
|
206
|
+
language: params.language,
|
|
207
|
+
});
|
|
208
|
+
return { tableName, tableContent };
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
const manifestSlice = {} as AllDestinyManifestComponents;
|
|
212
|
+
for (const downloadedTable of downloadedTables) {
|
|
213
|
+
manifestSlice[downloadedTable.tableName] = downloadedTable.tableContent as any;
|
|
214
|
+
}
|
|
215
|
+
return manifestSlice as DestinyManifestSlice<T>;
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import packageJson from './package.json' with { type: 'json' };
|
|
2
|
+
import { writeOutFile } from './generate-common.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate a package.json file for the library, based on the package.json file at the
|
|
6
|
+
* root of the repo.
|
|
7
|
+
*/
|
|
8
|
+
export function generatePackageJson(modules: string[]) {
|
|
9
|
+
console.log('Generate package.json??');
|
|
10
|
+
const moduleExports: { [key: string]: any } = {
|
|
11
|
+
'.': {
|
|
12
|
+
default: './index.js',
|
|
13
|
+
types: './index.d.ts',
|
|
14
|
+
},
|
|
15
|
+
'./http': {
|
|
16
|
+
default: './http.js',
|
|
17
|
+
types: './http.d.ts',
|
|
18
|
+
},
|
|
19
|
+
'./package.json': './package.json',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
for (const module of modules) {
|
|
23
|
+
moduleExports[`./${module.toLowerCase()}`] = {
|
|
24
|
+
default: `./${module.toLowerCase()}/index.js`,
|
|
25
|
+
types: `./${module.toLowerCase()}/index.d.ts`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// clear out dependencies
|
|
30
|
+
const newPackageJson = {
|
|
31
|
+
...packageJson,
|
|
32
|
+
main: './index.js',
|
|
33
|
+
types: './index.d.ts',
|
|
34
|
+
module: './index.js',
|
|
35
|
+
exports: moduleExports,
|
|
36
|
+
scripts: {},
|
|
37
|
+
dependencies: {},
|
|
38
|
+
devDependencies: {},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
writeOutFile(
|
|
42
|
+
'generated-src/package.json.notyet',
|
|
43
|
+
JSON.stringify(newPackageJson, undefined, ' ')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a custom, hand-rolled generator for TS typings for the Bungie.net API. It's meant for use
|
|
3
|
+
* in DIM, but is free for anyone to use.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import _ from 'lodash';
|
|
8
|
+
import { OpenAPIObject, PathItemObject } from 'openapi3-ts';
|
|
9
|
+
import { generateIndex, generateSuperIndex } from './generate-index.js';
|
|
10
|
+
|
|
11
|
+
import { DefInfo } from './util.js';
|
|
12
|
+
import { computeTypeMaps } from './type-index.js';
|
|
13
|
+
import { generateInterfaceDefinitions } from './generate-interfaces.js';
|
|
14
|
+
import { generateManifestUtils } from './generate-manifest.js';
|
|
15
|
+
import { generateServiceDefinition } from './generate-api.js';
|
|
16
|
+
import { generatePackageJson } from './generate-package-json.js';
|
|
17
|
+
|
|
18
|
+
// allow some async operations
|
|
19
|
+
(async () => {
|
|
20
|
+
const doc = JSON.parse(fs.readFileSync('api-src/openapi.json').toString()) as OpenAPIObject;
|
|
21
|
+
|
|
22
|
+
// Pairs of [request path, path service description]
|
|
23
|
+
const pathPairs = _.toPairs(doc.paths) as [string, PathItemObject][];
|
|
24
|
+
|
|
25
|
+
// Grouped by "tag" which says which service (destiny, groups, forums, etc)
|
|
26
|
+
const pathPairsByTag = _.groupBy(pathPairs, ([path, desc]) => {
|
|
27
|
+
return (desc.get || desc.post)!.tags![0];
|
|
28
|
+
});
|
|
29
|
+
pathPairsByTag['Core'] = pathPairsByTag[''];
|
|
30
|
+
delete pathPairsByTag[''];
|
|
31
|
+
|
|
32
|
+
const { componentsByFile, componentByDef } = computeTypeMaps(pathPairsByTag, doc);
|
|
33
|
+
|
|
34
|
+
_.each(componentsByFile, (components: DefInfo[], file: string) => {
|
|
35
|
+
generateInterfaceDefinitions(file, components, doc, componentByDef);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await generateManifestUtils(componentsByFile['destiny2/interfaces.ts'], doc);
|
|
39
|
+
|
|
40
|
+
_.each(pathPairsByTag, (paths, tag) => {
|
|
41
|
+
generateServiceDefinition(tag, paths, doc, componentByDef);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
_.each(pathPairsByTag, (paths, tag) => {
|
|
45
|
+
generateIndex(tag, doc, componentsByFile);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
generateSuperIndex(Object.keys(pathPairsByTag), doc);
|
|
49
|
+
|
|
50
|
+
// read top package.json, remove dependencies, add exports block per service
|
|
51
|
+
generatePackageJson(Object.keys(pathPairsByTag));
|
|
52
|
+
|
|
53
|
+
// some way to mark "preview" stuff
|
|
54
|
+
})();
|