@book000/twitterts 0.27.0 → 0.28.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.
Files changed (29) hide show
  1. package/dist/generate-types/custom-types-generator.d.ts +53 -0
  2. package/dist/generate-types/custom-types-generator.d.ts.map +1 -0
  3. package/dist/generate-types/custom-types-generator.js +394 -0
  4. package/dist/generate-types/custom-types-generator.js.map +1 -0
  5. package/dist/generate-types/endpoint-type-generator.d.ts +66 -0
  6. package/dist/generate-types/endpoint-type-generator.d.ts.map +1 -0
  7. package/dist/generate-types/endpoint-type-generator.js +187 -0
  8. package/dist/generate-types/endpoint-type-generator.js.map +1 -0
  9. package/dist/generate-types/types-generator.d.ts +49 -0
  10. package/dist/generate-types/types-generator.d.ts.map +1 -0
  11. package/dist/generate-types/types-generator.js +89 -0
  12. package/dist/generate-types/types-generator.js.map +1 -0
  13. package/dist/generate-types/utils.d.ts +95 -0
  14. package/dist/generate-types/utils.d.ts.map +1 -0
  15. package/dist/generate-types/utils.js +160 -0
  16. package/dist/generate-types/utils.js.map +1 -0
  17. package/dist/generate-types.js +41 -741
  18. package/dist/generate-types.js.map +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/models/responses/custom/custom-user-legacy-object.d.ts +55 -0
  24. package/dist/models/responses/custom/custom-user-legacy-object.d.ts.map +1 -0
  25. package/dist/models/responses/custom/custom-user-legacy-object.js +4 -0
  26. package/dist/models/responses/custom/custom-user-legacy-object.js.map +1 -0
  27. package/dist/tsconfig.build.tsbuildinfo +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
@@ -1,755 +1,55 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_fs_1 = __importDefault(require("node:fs"));
7
- const json_schema_to_typescript_1 = require("json-schema-to-typescript");
8
3
  const node_utils_1 = require("@book000/node-utils");
9
- const genson_js_1 = require("genson-js");
10
- const node_path_1 = require("node:path");
11
- /**
12
- * json-schema-to-typescript のコンパイルオプションを作成・取得する
13
- *
14
- * @param tsDocument コンパイル結果の先頭に追加する tsdoc
15
- * @returns コンパイルオプション
16
- */
17
- function getCompileOptions(tsDocument) {
18
- const compileOptions = {
19
- bannerComment: '/* eslint-disable @typescript-eslint/ban-types */',
20
- additionalProperties: false,
21
- enableConstEnums: true,
22
- strictIndexSignatures: true,
23
- style: {
24
- semi: false,
25
- singleQuote: true,
26
- },
27
- unknownAny: true,
28
- };
29
- if (tsDocument) {
30
- compileOptions.bannerComment = `${compileOptions.bannerComment}\n\n/** ${tsDocument} */`;
31
- }
32
- return compileOptions;
33
- }
34
- /**
35
- * ユーティリティ
36
- */
37
- const Utils = {
38
- /**
39
- * レスポンスの型定義名を取得する
40
- *
41
- * @param rawType リクエストの種別(graphql または rest)
42
- * @param rawName レスポンスの名前
43
- * @param rawMethod レスポンスの HTTP メソッド
44
- * @param rawStatus レスポンスのステータスコード
45
- * @returns 型定義名
46
- */
47
- getName(rawType, rawName, rawMethod, rawStatus) {
48
- const type = rawType.toLocaleLowerCase() === 'graphql'
49
- ? 'GraphQL'
50
- : rawType.toLocaleLowerCase() === 'rest'
51
- ? 'REST'
52
- : null;
53
- if (!type) {
54
- throw new Error(`Invalid type: ${rawType}`);
55
- }
56
- const method = this.toCamelCase(rawMethod);
57
- const name = this.capitalize(rawName);
58
- const status = rawStatus === null ? '' : rawStatus.startsWith('2') ? 'Success' : 'Error';
59
- return `${type}${method}${name}${status}Response`;
60
- },
61
- /**
62
- * キャメルケースに変換する
63
- *
64
- * @param string 変換する文字列
65
- * @returns キャメルケース変換後の文字列
66
- */
67
- toCamelCase(string) {
68
- return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
69
- },
70
- /**
71
- * 先頭文字を大文字に変換する
72
- *
73
- * @param string 変換する文字列
74
- * @returns 変換後の文字列
75
- */
76
- capitalize(string) {
77
- return string.charAt(0).toUpperCase() + string.slice(1);
78
- },
79
- /**
80
- * ファイル名を取得する
81
- *
82
- * @param rawType リクエストの種別(graphql または rest)
83
- * @param rawName レスポンスの名前
84
- * @param rawMethod レスポンスの HTTP メソッド
85
- * @param rawStatus レスポンスのステータスコード
86
- * @returns ファイル名
87
- */
88
- getFilename(rawType, rawName, rawMethod, rawStatus) {
89
- const type = rawType.toLowerCase();
90
- const method = rawMethod.toLowerCase();
91
- const name = rawName.replaceAll(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
92
- const status = rawStatus.startsWith('2') ? '-success' : '-error';
93
- return `${type}/${method}/${name}${status}`;
94
- },
95
- };
96
- /**
97
- * 型定義生成クラス
98
- */
99
- class TwitterTypesGenerator {
100
- /**
101
- * @param options 型定義生成クラスのオプション
102
- */
103
- constructor(options) {
104
- this.options = options;
105
- }
106
- /**
107
- * ディレクトリ内にあるディレクトリ群を取得する
108
- *
109
- * @param baseDirectories ディレクトリを取得するディレクトリパス
110
- * @returns ディレクトリ群
111
- */
112
- getDirectories(baseDirectories = []) {
113
- const baseDirectory = (0, node_path_1.join)(this.options.debugOutputDirectory, ...baseDirectories);
114
- return node_fs_1.default
115
- .readdirSync(baseDirectory)
116
- .filter((directory) => !['.', '..'].includes(directory) &&
117
- node_fs_1.default.statSync(`${baseDirectory}/${directory}`).isDirectory());
118
- }
119
- /**
120
- * ディレクトリ内にある JSON ファイル群を取得する
121
- *
122
- * @param baseDirectories ファイルを取得するディレクトリパス
123
- * @returns JSON ファイル群
124
- */
125
- getJSONFiles(baseDirectories = []) {
126
- const baseDirectory = (0, node_path_1.join)(this.options.debugOutputDirectory, ...baseDirectories);
127
- return node_fs_1.default
128
- .readdirSync(baseDirectory)
129
- .filter((file) => !['.', '..'].includes(file) &&
130
- node_fs_1.default.statSync((0, node_path_1.join)(baseDirectory, file)).isFile() &&
131
- file.endsWith('.json'))
132
- .map((file) => (0, node_path_1.join)(baseDirectory, file));
133
- }
134
- /**
135
- * レスポンスデバッグ出力 JSON ファイルを元に、エンドポイントごとの情報をまとめて取得する
136
- *
137
- * @returns エンドポイントごとの情報
138
- */
139
- get() {
140
- const results = [];
141
- for (const type of this.getDirectories()) {
142
- for (const name of this.getDirectories([type])) {
143
- for (const method of this.getDirectories([type, name])) {
144
- for (const statusCode of this.getDirectories([type, name, method])) {
145
- results.push({
146
- type,
147
- name,
148
- method,
149
- statusCode,
150
- paths: this.getJSONFiles([type, name, method, statusCode]),
151
- });
152
- }
153
- }
154
- }
155
- }
156
- return results;
157
- }
158
- /**
159
- * エンドポイントの型定義を生成する
160
- *
161
- * @param options 単一の型定義生成オプション
162
- * @param result エンドポイントごとのレスポンス情報
163
- */
164
- async generateType(options, result) {
165
- const logger = node_utils_1.Logger.configure('TwitterGenerateTypes.generateType');
166
- if (result.paths.length === 0) {
167
- return;
168
- }
169
- logger.info(`🔍 Generating: ${options.name}`);
170
- let schema;
171
- for (const path of result.paths) {
172
- const data = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
173
- const fileSchema = (0, genson_js_1.createSchema)(data);
174
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
175
- }
176
- if (!schema) {
177
- throw new Error('No schema found');
178
- }
179
- node_fs_1.default.mkdirSync((0, node_path_1.dirname)(options.path.schema), { recursive: true });
180
- node_fs_1.default.writeFileSync(options.path.schema, JSON.stringify(schema, null, 2));
181
- node_fs_1.default.mkdirSync((0, node_path_1.dirname)(options.path.types), { recursive: true });
182
- const types = await (0, json_schema_to_typescript_1.compile)(schema, options.name, getCompileOptions(options.tsDocument));
183
- node_fs_1.default.writeFileSync(options.path.types, types);
184
- logger.info(`📝 Successful: ${options.name} (from ${result.paths.length} files)`);
185
- }
186
- /**
187
- * 保存されたデバッグレスポンスを元に、全てのエンドポイントの型定義を生成する。
188
- * カスタム型定義の生成(CustomTypeGenerator)や、エンドポイントのまとめ型定義も生成(EndPointTypeGenerator)する。
189
- *
190
- * @param options 型定義生成オプション
191
- */
192
- async generateTypes(options) {
193
- const results = this.get();
194
- for (const result of results) {
195
- const name = Utils.getName(result.type, result.name, result.method, result.statusCode);
196
- const filename = Utils.getFilename(result.type, result.name, result.method, result.statusCode);
197
- const schemaPath = `${options.directory.schema}/${filename}.json`;
198
- const typesPath = `${options.directory.types}/${filename}.ts`;
199
- const type = result.type === 'graphql'
200
- ? 'GraphQL'
201
- : result.type === 'rest'
202
- ? 'REST'
203
- : null;
204
- await this.generateType({
205
- path: {
206
- schema: schemaPath,
207
- types: typesPath,
208
- },
209
- name,
210
- tsDocument: `${type} ${result.method} ${result.name} ${result.statusCode.startsWith('2') ? '成功' : '失敗'}レスポンスモデル`,
211
- }, result);
212
- }
213
- await new CustomTypeGenerator(results, options.directory.schema, options.directory.types).generate();
214
- new EndPointTypeGenerator(results, options.directory.types).generate();
215
- }
216
- /**
217
- * デバッグレスポンスを元に、型定義を生成するメイン関数
218
- */
219
- static async main() {
4
+ const types_generator_1 = require("./generate-types/types-generator");
5
+ const custom_types_generator_1 = require("./generate-types/custom-types-generator");
6
+ const endpoint_type_generator_1 = require("./generate-types/endpoint-type-generator");
7
+ const utils_1 = require("./generate-types/utils");
8
+ class GenerateTypes {
9
+ calculateTime(name, runner) {
10
+ const logger = node_utils_1.Logger.configure('GenerateTypes:calculateTime');
11
+ const startTime = Date.now();
12
+ const result = runner();
13
+ const endTime = Date.now();
14
+ const time = endTime - startTime;
15
+ logger.info(`🕐 ${name}: ${time}ms`);
16
+ return result;
17
+ }
18
+ async awaitCalculateTime(name, runner) {
19
+ const logger = node_utils_1.Logger.configure('GenerateTypes:awaitCalculateTime');
20
+ const startTime = Date.now();
21
+ const result = await runner();
22
+ const endTime = Date.now();
23
+ const time = endTime - startTime;
24
+ logger.info(`🕐 ${name}: ${time}ms`);
25
+ return result;
26
+ }
27
+ async run() {
28
+ const logger = node_utils_1.Logger.configure('GenerateTypes:run');
220
29
  const debugOutputDirectory = process.env.DEBUG_OUTPUT_DIRECTORY || './data/responses';
221
30
  const schemaDirectory = process.env.SCHEMA_DIRECTORY || './data/schema';
222
31
  const typesDirectory = process.env.TYPES_DIRECTORY || './src/models/responses';
223
- const tgt = new TwitterTypesGenerator({
224
- debugOutputDirectory,
225
- });
226
- await tgt.generateTypes({
227
- directory: {
228
- schema: schemaDirectory,
229
- types: typesDirectory,
230
- },
231
- });
232
- }
233
- }
234
- /**
235
- * カスタム型定義を生成するクラス
236
- */
237
- class CustomTypeGenerator {
238
- /**
239
- * @param results エンドポイントごとのレスポンス情報
240
- * @param schemaDirectory スキーマ保存ディレクトリ
241
- * @param typesDirectory 型定義保存ディレクトリ
242
- */
243
- constructor(results, schemaDirectory, typesDirectory) {
244
- this.results = results;
245
- this.schemaDirectory = schemaDirectory;
246
- this.typesDirectory = typesDirectory;
247
- }
248
- /**
249
- * 検索タイムラインツイートモデル(CustomSearchTimelineEntry)のカスタム型定義を生成する
250
- */
251
- async runGraphQLSearchTimeline() {
252
- const results = this.results.filter((result) => result.type === 'graphql' &&
253
- result.name === 'SearchTimeline' &&
254
- result.method === 'GET' &&
255
- result.statusCode === '200');
256
- if (results.length === 0) {
257
- return;
258
- }
259
- const paths = results.flatMap((result) => result.paths);
260
- let schema;
261
- for (const path of paths) {
262
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
263
- const entries = response.data.search_by_raw_query.search_timeline.timeline.instructions
264
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
265
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
266
- const fileSchema = (0, genson_js_1.createCompoundSchema)(entries);
267
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
268
- }
269
- if (!schema) {
270
- return;
271
- }
272
- await this.generateTypeFromSchema(schema, 'CustomSearchTimelineEntry', '検索タイムラインツイートモデル');
273
- }
274
- /**
275
- * ユーザーツイートモデル(CustomUserTweetEntry)のカスタム型定義を生成する
276
- */
277
- async runGraphQLUserTweets() {
278
- const results = this.results.filter((result) => result.type === 'graphql' &&
279
- result.name === 'UserTweets' &&
280
- result.method === 'GET' &&
281
- result.statusCode === '200');
282
- if (results.length === 0) {
283
- return;
284
- }
285
- const paths = results.flatMap((result) => result.paths);
286
- let schema;
287
- for (const path of paths) {
288
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
289
- const entries = response.data.user.result.timeline_v2.timeline.instructions
290
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
291
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
292
- const fileSchema = (0, genson_js_1.createCompoundSchema)(entries);
293
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
294
- }
295
- if (!schema) {
296
- return;
297
- }
298
- await this.generateTypeFromSchema(schema, 'CustomUserTweetEntry', 'ユーザーツイートモデル');
299
- }
300
- /**
301
- * ユーザーいいねツイートモデル(CustomUserLikeTweetEntry)のカスタム型定義を生成する
302
- */
303
- async runGraphQLUserLikeTweets() {
304
- const results = this.results.filter((result) => result.type === 'graphql' &&
305
- result.name === 'Likes' &&
306
- result.method === 'GET' &&
307
- result.statusCode === '200');
308
- if (results.length === 0) {
309
- return;
310
- }
311
- const paths = results.flatMap((result) => result.paths);
312
- let schema;
313
- for (const path of paths) {
314
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
315
- const entries = response.data.user.result.timeline_v2.timeline.instructions
316
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
317
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
318
- const fileSchema = (0, genson_js_1.createCompoundSchema)(entries);
319
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
320
- }
321
- if (!schema) {
322
- return;
323
- }
324
- await this.generateTypeFromSchema(schema, 'CustomUserLikeTweetEntry', 'ユーザーいいねツイートエントリモデル');
325
- }
326
- async runGraphQLTimelineInstruction() {
327
- const homeTimelineResults = this.results.filter((result) => result.type === 'graphql' &&
328
- result.name === 'HomeTimeline' &&
329
- result.method === 'GET' &&
330
- result.statusCode === '200');
331
- const homeLatestTimelineResults = this.results.filter((result) => result.type === 'graphql' &&
332
- result.name === 'HomeLatestTimeline' &&
333
- result.method === 'GET' &&
334
- result.statusCode === '200');
335
- if (homeTimelineResults.length === 0 &&
336
- homeLatestTimelineResults.length === 0) {
337
- return;
338
- }
339
- const hometimelinePaths = homeTimelineResults.flatMap((result) => result.paths);
340
- const homeLatestTimelinePaths = homeLatestTimelineResults.flatMap((result) => result.paths);
341
- let schema;
342
- for (const path of hometimelinePaths) {
343
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
344
- const entries = response.data.home.home_timeline_urt.instructions
345
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
346
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
347
- const fileSchema = (0, genson_js_1.createCompoundSchema)(entries);
348
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
349
- }
350
- for (const path of homeLatestTimelinePaths) {
351
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
352
- const instructions = response.data.home.home_timeline_urt.instructions
353
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
354
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
355
- const fileSchema = (0, genson_js_1.createCompoundSchema)(instructions);
356
- schema = schema ? (0, genson_js_1.mergeSchemas)([schema, fileSchema]) : fileSchema;
357
- }
358
- if (!schema) {
359
- return;
360
- }
361
- await this.generateTypeFromSchema(schema, 'CustomTimelineTweetEntry', 'ホームタイムラインツイートエントリモデル');
362
- }
363
- // --- twitter-d 変換用オブジェクト
364
- /**
365
- * レスポンスツイートオブジェクト(CustomTweetObject)のカスタム型定義を生成する
366
- */
367
- async runTweetObject() {
368
- // 各レスポンスからツイートオブジェクトを抽出
369
- const schemas = [
370
- // HomeTimeline
371
- this.results
372
- .filter((result) => result.type === 'graphql' &&
373
- result.name === 'HomeTimeline' &&
374
- result.method === 'GET' &&
375
- result.statusCode === '200')
376
- .flatMap((result) => result.paths)
377
- .flatMap((path) => {
378
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
379
- return response.data.home.home_timeline_urt.instructions
380
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
381
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
382
- })
383
- .map((entry) => {
384
- return entry.content.itemContent
385
- .tweet_results.result;
386
- })
387
- .filter((entry) => !!entry)
388
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
389
- // HomeLatestTimeline
390
- this.results
391
- .filter((result) => result.type === 'graphql' &&
392
- result.name === 'HomeLatestTimeline' &&
393
- result.method === 'GET' &&
394
- result.statusCode === '200')
395
- .flatMap((result) => result.paths)
396
- .flatMap((path) => {
397
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
398
- return response.data.home.home_timeline_urt.instructions
399
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
400
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
401
- })
402
- .map((entry) => {
403
- return entry.content.itemContent
404
- .tweet_results.result;
405
- })
406
- .filter((entry) => !!entry)
407
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
408
- // SearchTimeline
409
- this.results
410
- .filter((result) => result.type === 'graphql' &&
411
- result.name === 'SearchTimeline' &&
412
- result.method === 'GET' &&
413
- result.statusCode === '200')
414
- .flatMap((result) => result.paths)
415
- .flatMap((path) => {
416
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
417
- return response.data.search_by_raw_query.search_timeline.timeline.instructions
418
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
419
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
420
- })
421
- .map((entry) => {
422
- return entry.content.itemContent
423
- .tweet_results.result;
424
- })
425
- .filter((entry) => !!entry)
426
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
427
- // UserTweets
428
- this.results
429
- .filter((result) => result.type === 'graphql' &&
430
- result.name === 'UserTweets' &&
431
- result.method === 'GET' &&
432
- result.statusCode === '200')
433
- .flatMap((result) => result.paths)
434
- .flatMap((path) => {
435
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
436
- return response.data.user.result.timeline_v2.timeline.instructions
437
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
438
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
439
- })
440
- .map((entry) => {
441
- return entry.content.itemContent
442
- .tweet_results.result;
443
- })
444
- .filter((entry) => !!entry)
445
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
446
- // Likes
447
- this.results
448
- .filter((result) => result.type === 'graphql' &&
449
- result.name === 'Likes' &&
450
- result.method === 'GET' &&
451
- result.statusCode === '200')
452
- .flatMap((result) => result.paths)
453
- .flatMap((path) => {
454
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
455
- return response.data.user.result.timeline_v2.timeline.instructions
456
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
457
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
458
- })
459
- .map((entry) => {
460
- return entry.content.itemContent
461
- .tweet_results.result;
462
- })
463
- .filter((entry) => !!entry)
464
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
465
- ].flat();
466
- await this.generateTypeFromSchema((0, genson_js_1.mergeSchemas)(schemas), 'CustomTweetObject', 'レスポンスツイートオブジェクト');
467
- }
468
- /**
469
- * レスポンスツイートレガシーオブジェクト(CustomTweetLegacyObject)のカスタム型定義を生成する
470
- */
471
- async runTweetLegacyObject() {
472
- // 各レスポンスからレガシーツイートオブジェクトを抽出
473
- const schemas = [
474
- // SearchTimeline
475
- this.results
476
- .filter((result) => result.type === 'graphql' &&
477
- result.name === 'SearchTimeline' &&
478
- result.method === 'GET' &&
479
- result.statusCode === '200')
480
- .flatMap((result) => result.paths)
481
- .flatMap((path) => {
482
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
483
- return response.data.search_by_raw_query.search_timeline.timeline.instructions
484
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
485
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
486
- })
487
- .map((entry) => {
488
- return entry.content.itemContent
489
- .tweet_results.result.legacy;
490
- })
491
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
492
- // UserTweets
493
- this.results
494
- .filter((result) => result.type === 'graphql' &&
495
- result.name === 'UserTweets' &&
496
- result.method === 'GET' &&
497
- result.statusCode === '200')
498
- .flatMap((result) => result.paths)
499
- .flatMap((path) => {
500
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
501
- return response.data.user.result.timeline_v2.timeline.instructions
502
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
503
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
504
- })
505
- .filter((entry) => entry.content.itemContent.tweet_results
506
- .result.legacy !== undefined)
507
- .map((entry) => {
508
- return entry.content.itemContent
509
- .tweet_results.result.legacy;
510
- })
511
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
512
- this.results
513
- .filter((result) => result.type === 'graphql' &&
514
- result.name === 'UserTweets' &&
515
- result.method === 'GET' &&
516
- result.statusCode === '200')
517
- .flatMap((result) => result.paths)
518
- .flatMap((path) => {
519
- const response = JSON.parse(node_fs_1.default.readFileSync(path, 'utf8'));
520
- return response.data.user.result.timeline_v2.timeline.instructions
521
- .filter((instruction) => instruction.type === 'TimelineAddEntries' && instruction.entries)
522
- .flatMap((instruction) => instruction.entries?.filter((entry) => entry.entryId.startsWith('tweet-')));
523
- })
524
- .filter((entry) => entry.content.itemContent.tweet_results
525
- .result.tweet?.legacy !== undefined)
526
- .map((entry) => {
527
- return entry.content.itemContent
528
- .tweet_results.result.tweet?.legacy;
529
- })
530
- .map((entry) => (0, genson_js_1.createSchema)(entry)),
531
- ].flat();
532
- await this.generateTypeFromSchema((0, genson_js_1.mergeSchemas)(schemas), 'CustomTweetLegacyObject', 'レスポンスツイートレガシーオブジェクト');
533
- }
534
- /**
535
- * カスタム型定義を、スキーマを元に生成する
536
- *
537
- * @param schema スキーマ
538
- * @param name 型名
539
- * @param tsDocument 型定義の tsdoc(1 行で記述)
540
- */
541
- async generateTypeFromSchema(schema, name, tsDocument) {
542
- const logger = node_utils_1.Logger.configure('CustomTypeGenerator.generateTypeFromSchema');
543
- if (!schema) {
544
- throw new Error('No schema found');
545
- }
546
- const kebabName = name.replaceAll(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
547
- const schemaPath = (0, node_path_1.join)(this.schemaDirectory, 'custom', `${kebabName}.json`);
548
- const typesPath = (0, node_path_1.join)(this.typesDirectory, 'custom', `${kebabName}.ts`);
549
- node_fs_1.default.mkdirSync((0, node_path_1.dirname)(schemaPath), { recursive: true });
550
- node_fs_1.default.writeFileSync(schemaPath, JSON.stringify(schema, null, 2));
551
- node_fs_1.default.mkdirSync((0, node_path_1.dirname)(typesPath), { recursive: true });
552
- const types = await (0, json_schema_to_typescript_1.compile)(schema, name, getCompileOptions(tsDocument));
553
- node_fs_1.default.writeFileSync(typesPath, types);
554
- logger.info(`📝 ${name}`);
555
- }
556
- /**
557
- * カスタム型定義を生成する
558
- */
559
- async generate() {
560
- await this.runGraphQLSearchTimeline();
561
- await this.runGraphQLUserTweets();
562
- await this.runGraphQLUserLikeTweets();
563
- await this.runGraphQLTimelineInstruction();
564
- await this.runTweetObject();
565
- // twitter-d 変換用オブジェクト
566
- await this.runTweetLegacyObject();
567
- }
568
- }
569
- /**
570
- * エンドポイントのまとめ型定義(src/models/responses/endpoints.ts)を生成するクラス
571
- */
572
- class EndPointTypeGenerator {
573
- /**
574
- * @param results エンドポイントごとのレスポンス情報
575
- * @param typesDirectory 型定義の出力先ディレクトリ
576
- */
577
- constructor(results, typesDirectory) {
578
- this.results = results.filter((result) => result.paths.some((path) => path.endsWith('.json')));
579
- this.typesDirectory = typesDirectory;
580
- }
581
- /**
582
- * TypeScript インポート文群を生成する
583
- *
584
- * @returns インポート文群
585
- */
586
- generateImport() {
587
- // import { GraphQLGetUserTweetsResponse } from './graphql/get/user-tweets'
588
- return this.results
589
- .map((result) => {
590
- const name = Utils.getName(result.type, result.name, result.method, result.statusCode);
591
- const filename = Utils.getFilename(result.type, result.name, result.method, result.statusCode);
592
- return `import { ${name} } from './${filename}'`;
593
- })
594
- .filter((value, index, self) => self.indexOf(value) === index)
595
- .join('\n');
596
- }
597
- /**
598
- * メソッド名の配列を取得する
599
- *
600
- * @param type エンドポイントの種類
601
- * @returns メソッド名の配列
602
- */
603
- getMethods(type) {
604
- return this.results
605
- .filter((result) => result.type === type.toLowerCase())
606
- .map((result) => result.method)
607
- .filter((value, index, self) => self.indexOf(value) === index);
608
- }
609
- /**
610
- * エンドポイント名群(<TYPE><METHOD>Endpoint)の定義を生成する
611
- *
612
- * @param type エンドポイントの種類
613
- * @param method メソッド名
614
- * @returns エンドポイント名群の定義
615
- */
616
- generateEndPointType(type, method) {
617
- const head = `export type ${type}${method}Endpoint =`;
618
- const types = this.results
619
- .filter((result) => result.type === type.toLowerCase() && result.method === method)
620
- .filter((value, index, self) => self.findIndex((v) => v.name === value.name) === index)
621
- .map((result) => {
622
- return ` | '${result.name}'`;
623
- });
624
- if (types.length === 0) {
625
- return null;
626
- }
627
- return `${head}\n${types.join('\n')}`;
628
- }
629
- /**
630
- * エンドポイント名を元に、成功・失敗のレスポンス型定義をまとめる型定義(<TYPE><METHOD>Response)を生成する。
631
- *
632
- * @param type エンドポイントの種類
633
- * @param method メソッド名
634
- */
635
- generateResponseMergeType(type, method) {
636
- const names = this.results
637
- .filter((result) => result.type === type.toLowerCase())
638
- .map((result) => result.name)
639
- .filter((value, index, self) => self.indexOf(value) === index);
640
- const types = names.map((name) => {
641
- const successType = Utils.getName(type, name, method, '200');
642
- const errorType = Utils.getName(type, name, method, '400');
643
- const responseType = Utils.getName(type, name, method, null);
644
- const isExistsSuccess = this.results.some((result) => result.type === type.toLowerCase() &&
645
- result.name === name &&
646
- result.method === method &&
647
- result.statusCode.startsWith('2'));
648
- const isExistsError = this.results.some((result) => result.type === type.toLowerCase() &&
649
- result.name === name &&
650
- result.method === method &&
651
- !result.statusCode.startsWith('2'));
652
- const tsDocument = `/** ${type} ${name} ${method} レスポンスモデル */`;
653
- if (isExistsSuccess && isExistsError) {
654
- return `${tsDocument}\nexport type ${responseType} = ${successType} | ${errorType}`;
655
- }
656
- if (isExistsSuccess) {
657
- return `${tsDocument}\nexport type ${responseType} = ${successType}`;
658
- }
659
- if (isExistsError) {
660
- return `${tsDocument}\nexport type ${responseType} = ${errorType}`;
661
- }
662
- return '';
663
- });
664
- return types.filter((type) => type !== '').join('\n');
665
- }
666
- /**
667
- * エンドポイント名を元に、レスポンス型定義を紐づけるような型定義(<TYPE><METHOD>EndPointResponseType)を生成する。
668
- *
669
- * @param type エンドポイントの種類
670
- * @param method メソッド名
671
- * @returns レスポンス型定義を紐づけるような型定義
672
- */
673
- generateResponseType(type, method) {
674
- const head = `export type ${type}${method}EndPointResponseType<T extends ${type}${method}Endpoint> =`;
675
- const types = this.results
676
- .filter((result) => result.type === type.toLowerCase() && result.method === method)
677
- .filter((value, index, self) => self.findIndex((v) => v.name === value.name) === index)
678
- .map((result) => {
679
- const name = Utils.getName(result.type, result.name, result.method, null);
680
- return ` T extends '${result.name}' ? ${name} :`;
681
- });
682
- return `${head}\n${types.join('\n')}\n never`;
683
- }
684
- /**
685
- * エンドポイントの種類と HTTP メソッドを元に、「レスポンス型定義を紐づけるような型定義」を生成する。
686
- *
687
- * @param types エンドポイントの種類の配列
688
- * @returns レスポンス型定義を紐づけるような型定義
689
- */
690
- generateEndpointResponseType(types) {
691
- const head = 'export type EndPointResponseType<M extends HttpMethod, T extends RequestType, N extends GraphQLEndpoint | RESTEndpoint> = ';
692
- const results = [];
693
- for (const type of types) {
694
- const methods = this.getMethods(type);
695
- if (methods.length === 0) {
696
- continue;
697
- }
698
- if (types.at(0) !== type) {
699
- results.push(':');
700
- }
701
- results.push(`T extends '${type.toUpperCase()}' ?`);
702
- for (const method of methods) {
703
- // 最後以外は : をつける
704
- if (methods.at(0) !== method) {
705
- results.push(':');
706
- }
707
- results.push(`M extends '${method}' ?`, ` N extends ${type}${method}Endpoint ?`, ` ${type}${method}EndPointResponseType<N> :`, ' never');
708
- }
709
- results.push(' : never');
32
+ const isParallel = process.env.IS_PARALLEL === 'true';
33
+ try {
34
+ // msで計測
35
+ const results = this.calculateTime('GetEndPointResponses', () => utils_1.Utils.getEndPointResponses(debugOutputDirectory));
36
+ await this.awaitCalculateTime('TwitterTypesGenerator', () => new types_generator_1.TwitterTypesGenerator(results).generateTypes({
37
+ directory: {
38
+ schema: schemaDirectory,
39
+ types: typesDirectory,
40
+ },
41
+ parallel: isParallel,
42
+ }));
43
+ await this.awaitCalculateTime('CustomTypesGenerator', async () => await new custom_types_generator_1.CustomTypesGenerator(results, schemaDirectory, typesDirectory).generate(isParallel));
44
+ this.calculateTime('EndPointTypeGenerator', () => new endpoint_type_generator_1.EndPointTypeGenerator(results, typesDirectory).generate());
710
45
  }
711
- return `${head}\n${results.join('\n')}\n: never`;
712
- }
713
- /**
714
- * エンドポイントのまとめ型定義(src/models/responses/endpoints.ts)を生成する
715
- */
716
- generate() {
717
- const logger = node_utils_1.Logger.configure('EndPointTypeGenerator.generate');
718
- const types = ['GraphQL', 'REST'];
719
- const data = [];
720
- const imports = this.generateImport();
721
- data.push(imports, "import { HttpMethod, RequestType } from '../../scraper'");
722
- for (const type of types) {
723
- const unionTypes = [];
724
- const methods = this.getMethods(type);
725
- for (const method of methods) {
726
- const mergeTypes = this.generateResponseMergeType(type, method);
727
- const endpointType = this.generateEndPointType(type, method);
728
- const responseType = this.generateResponseType(type, method);
729
- if (!endpointType || !responseType) {
730
- continue;
731
- }
732
- data.push(mergeTypes, endpointType, responseType);
733
- unionTypes.push(` | ${type}${method}Endpoint`);
734
- }
735
- if (unionTypes.length === 0) {
736
- unionTypes.push(' | never');
737
- }
738
- data.push(`export type ${type}Endpoint =\n${unionTypes.join('\n')}\n`);
46
+ catch (error) {
47
+ logger.error('An error occurred while generating types', error);
739
48
  }
740
- data.push(this.generateEndpointResponseType(types));
741
- node_fs_1.default.writeFileSync(`${this.typesDirectory}/endpoints.ts`, data.map((d) => d.trim()).join('\n\n'));
742
- logger.info(`📝 ${this.typesDirectory}/endpoints.ts`);
743
49
  }
744
50
  }
745
51
  ;
746
52
  (async () => {
747
- const logger = node_utils_1.Logger.configure('generate-types');
748
- try {
749
- await TwitterTypesGenerator.main();
750
- }
751
- catch (error) {
752
- logger.error('An error occurred while generating types', error);
753
- }
53
+ new GenerateTypes().run();
754
54
  })();
755
55
  //# sourceMappingURL=generate-types.js.map