summary refs log tree commit diff
path: root/scripts
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-10-16 09:39:21 +0200
committerRory& <root@rory.gay>2025-10-16 09:39:21 +0200
commit157cd64a665873d3a36905c0b9ea1705f8d5891d (patch)
tree36c4b07f25b7e57f6905479cff0490f421847e25 /scripts
parentImplement applications/@me (diff)
downloadserver-ts-dev/openapi-improvements.tar.xz
Diffstat (limited to 'scripts')
-rw-r--r--scripts/fetch_discord_openapi.js91
-rw-r--r--scripts/openapi.js85
-rw-r--r--scripts/openapi_missing.js69
-rw-r--r--scripts/schema.js19
4 files changed, 222 insertions, 42 deletions
diff --git a/scripts/fetch_discord_openapi.js b/scripts/fetch_discord_openapi.js
new file mode 100644

index 000000000..62cf8986b --- /dev/null +++ b/scripts/fetch_discord_openapi.js
@@ -0,0 +1,91 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +require("module-alias/register"); +const path = require("path"); +const fs = require("fs"); +require("../dist/util/util/extensions"); + +const openapiPathOut = path.join(__dirname, "..", "dist", "openapi_discord.json"); + +async function main() { + console.log("Generating OpenAPI Specification..."); + + const routesRes = await fetch( + "https://raw.githubusercontent.com/discord/discord-api-spec/refs/heads/main/specs/openapi_preview.json", + { + headers: { + Accept: "application/json", + }, + }, + ); + const dOpenApi = await routesRes.json(); + for (const pathName in dOpenApi.paths) { + console.log("Processing path:", pathName); + const path = dOpenApi.paths[pathName]; + for (const methodName in path) { + if (methodName === 'parameters') continue; + console.log(" Processing method:", methodName); + const method = path[methodName]; + if (method.operationId) + // delete dOpenApi.paths[path][methodName].operationId; + delete method.operationId; // we don't care about these + + for (const respCode in method.responses) { + console.log(" Processing response:", respCode); + const resp = method.responses[respCode]; + if(resp.description) + delete resp.description; // we don't care about these + + } + + console.log(" Method security:", JSON.stringify(method.security)); + // if(method.security?.length >= 1 && method.security[0]?.["BotToken"] !== undefined) { + // console.log(" Converting BotToken security to bearer"); + // // BotToken -> bearer + // method.security[0]["bearer"] = []; + // delete method.security[0]["BotToken"]; + // } + for (const securityObj of method.security||[]) { + if(securityObj["BotToken"] !== undefined) { + console.log(" Converting BotToken security to bearer"); + // BotToken -> bearer + securityObj["bearer"] = []; + delete securityObj["BotToken"]; + } + } + } + } + + + + fs.writeFileSync( + openapiPathOut, + JSON.stringify(dOpenApi, null, 2) + ); + console.log("Wrote OpenAPI specification to", openapiPathOut); + console.log( + "Specification contains", + Object.keys(dOpenApi.paths).length, + "paths and", + Object.keys(dOpenApi.components.schemas).length, + "schemas.", + ); +} + +main(); diff --git a/scripts/openapi.js b/scripts/openapi.js
index 6d4d63667..33e8c9f96 100644 --- a/scripts/openapi.js +++ b/scripts/openapi.js
@@ -20,9 +20,7 @@ require("module-alias/register"); const getRouteDescriptions = require("./util/getRouteDescriptions"); const path = require("path"); const fs = require("fs"); -const { - NO_AUTHORIZATION_ROUTES, -} = require("../dist/api/middlewares/Authentication"); +const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication"); require("../dist/util/util/extensions"); const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); @@ -39,7 +37,7 @@ let specification = { name: "AGPLV3", url: "https://www.gnu.org/licenses/agpl-3.0.en.html", }, - version: "1.0.0", + version: "9", }, externalDocs: { description: "Spacebar Docs", @@ -47,8 +45,8 @@ let specification = { }, servers: [ { - url: "https://old.server.spacebar.chat/api/", description: "Official Spacebar Instance", + url: "https://old.server.spacebar.chat/api/v9", }, ], components: { @@ -88,8 +86,7 @@ function combineSchemas(schemas) { continue; } specification.components = specification.components || {}; - specification.components.schemas = - specification.components.schemas || {}; + specification.components.schemas = specification.components.schemas || {}; specification.components.schemas[key] = definitions[key]; delete definitions[key].additionalProperties; delete definitions[key].$schema; @@ -134,8 +131,7 @@ function apiRoutes(missingRoutes) { if ( !NO_AUTHORIZATION_ROUTES.some((x) => { - if (typeof x === "string") - return (method.toUpperCase() + " " + path).startsWith(x); + if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x); return x.test(method.toUpperCase() + " " + path); }) ) { @@ -162,10 +158,10 @@ function apiRoutes(missingRoutes) { if (route.responses) { obj.responses = {}; - for (const [k, v] of Object.entries(route.responses)) { + for (const [statusCode, v] of Object.entries(route.responses)) { if (v.body) - obj.responses[k] = { - description: obj?.responses?.[k]?.description || "", + obj.responses[statusCode] = { + description: obj?.responses?.[statusCode]?.description, content: { "application/json": { schema: { @@ -175,12 +171,23 @@ function apiRoutes(missingRoutes) { }, }; else - obj.responses[k] = { - description: - obj?.responses?.[k]?.description || - "No description available", + obj.responses[statusCode] = { + description: obj?.responses?.[statusCode]?.description || "No description available", }; } + + if (route.ratelimitBucket) { + obj["x-ratelimit-bucket"] = route.ratelimitBucket; + obj.responses["429"] = {}; + if (obj.responses["200"]) { + obj.responses["200"].headers ??= {}; + for (const key in ["Limit", "Remaining", "Reset", "Reset-After", "Bucket"]) { + obj.responses["200"].headers[`X-RateLimit-${key}`] = { + $ref: `#/components/headers/X-RateLimit-${key}`, + }; + } + } + } } else { obj.responses = { default: { @@ -224,45 +231,39 @@ function apiRoutes(missingRoutes) { ]; } - specification.paths[path] = Object.assign( - specification.paths[path] || {}, - { - [method]: obj, - }, - ); + specification.paths[path] = Object.assign(specification.paths[path] || {}, { + [method]: obj, + }); }); + + // order top level + const topLevelOrder = ["openapi", "info", "externalDocs", "servers", "paths", "tags", "components"]; + specification = Object.fromEntries( + topLevelOrder + .map((x) => [x, specification[x]]) + .filter((x) => x[1] !== undefined) + .concat(Object.entries(specification).filter((x) => !topLevelOrder.includes(x[0]))), + ); + // order paths alphabetically + specification.paths = Object.fromEntries(Object.entries(specification.paths).sort((a, b) => a[0].localeCompare(b[0]))); } async function main() { console.log("Generating OpenAPI Specification..."); - const routesRes = await fetch( - "https://github.com/spacebarchat/missing-routes/raw/main/missing.json", - { - headers: { - Accept: "application/json", - }, + const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", { + headers: { + Accept: "application/json", }, - ); + }); const missingRoutes = await routesRes.json(); combineSchemas(schemas); apiRoutes(missingRoutes); - fs.writeFileSync( - openapiPath, - JSON.stringify(specification, null, 4) - .replaceAll("#/definitions", "#/components/schemas") - .replaceAll("bigint", "number"), - ); + fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")); console.log("Wrote OpenAPI specification to", openapiPath); - console.log( - "Specification contains", - Object.keys(specification.paths).length, - "paths and", - Object.keys(specification.components.schemas).length, - "schemas.", - ); + console.log("Specification contains", Object.keys(specification.paths).length, "paths and", Object.keys(specification.components.schemas).length, "schemas."); } main(); diff --git a/scripts/openapi_missing.js b/scripts/openapi_missing.js new file mode 100644
index 000000000..e5cb81d1d --- /dev/null +++ b/scripts/openapi_missing.js
@@ -0,0 +1,69 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +require("module-alias/register"); +const path = require("path"); +const fs = require("fs"); +require("../dist/util/util/extensions"); + +const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); +const openapiPathOut = path.join(__dirname, "..", "dist", "openapi_missing.json"); +const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); +const sbOpenApi = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); + +async function main() { + console.log("Generating OpenAPI Specification..."); + + const routesRes = await fetch( + "https://raw.githubusercontent.com/discord/discord-api-spec/refs/heads/main/specs/openapi_preview.json", + { + headers: { + Accept: "application/json", + }, + }, + ); + const dOpenApi = await routesRes.json(); + for (const path in dOpenApi.paths) { + if (!sbOpenApi.paths[path]) { + console.log("Missing path:", path); + continue; + } + + for (const method in dOpenApi.paths[path]) { + if(sbOpenApi.paths?.[path]?.[method]) delete sbOpenApi.paths[path][method]; + } + if(Object.keys(sbOpenApi.paths?.[path]||{}).length === 0) delete sbOpenApi.paths[path]; + } + + + + fs.writeFileSync( + openapiPathOut, + JSON.stringify(dOpenApi, null, 4) + ); + console.log("Wrote OpenAPI specification to", openapiPathOut); + console.log( + "Specification contains", + Object.keys(specification.paths).length, + "paths and", + Object.keys(specification.components.schemas).length, + "schemas.", + ); +} + +main(); diff --git a/scripts/schema.js b/scripts/schema.js
index 772058316..983eb697a 100644 --- a/scripts/schema.js +++ b/scripts/schema.js
@@ -46,6 +46,7 @@ const settings = { const ExcludeAndWarn = [ /^Record/, /^Partial/, + /^import\(/, ] const Excluded = [ "DefaultSchema", @@ -149,6 +150,24 @@ function main() { deleteOneOfKindUndefinedRecursive(definitions, "$"); + // cant do this here: + // let hasUnreferencedSchemas = true; + // while (hasUnreferencedSchemas) { + // hasUnreferencedSchemas = false; + // console.log(`Checking for unreferenced schemas in ${definitions.length}..."`); + // let fullMap = JSON.stringify(definitions); + // for (const [name, schema] of Object.entries(definitions)) { + // const schemaStr = JSON.stringify(schema); + // const refRe = new RegExp(`"\\$ref":"#/definitions/${name}"`, "g"); + // const count = (fullMap.match(refRe) || []).length; + // if (count <= 1) { + // console.log("Deleting unreferenced schema", name); + // delete definitions[name]; + // hasUnreferencedSchemas = true; + // } + // } + // } + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); console.log("Successfully wrote", Object.keys(definitions).length, "schemas to", schemaPath); }