From 9cb178aea40198bb417c857624bc526e40fab66a Mon Sep 17 00:00:00 2001 From: creations Date: Tue, 15 Apr 2025 17:33:24 -0400 Subject: [PATCH] move to biomejs, add workflow move auth to header check readme --- .forgejo/workflows/biomejs.yml | 24 +++++ .vscode/settings.json | 5 +- README.md | 23 ++-- biome.json | 35 ++++++ config/booru.ts | 4 - config/environment.ts | 45 +------- eslint.config.js | 132 ----------------------- package.json | 26 ++--- src/helpers/char.ts | 80 +++++++++----- src/helpers/logger.ts | 26 ++--- src/index.ts | 6 +- src/routes/[booru]/autocomplete/[tag].ts | 54 ++++++++-- src/routes/[booru]/id/[id].ts | 59 ++++++++-- src/routes/[booru]/random.ts | 77 +++++++++---- src/routes/[booru]/search.ts | 64 ++++++++--- src/server.ts | 8 +- tsconfig.json | 38 ++----- types/config.d.ts | 2 - 18 files changed, 368 insertions(+), 340 deletions(-) create mode 100644 .forgejo/workflows/biomejs.yml create mode 100644 biome.json delete mode 100644 eslint.config.js diff --git a/.forgejo/workflows/biomejs.yml b/.forgejo/workflows/biomejs.yml new file mode 100644 index 0000000..15c990c --- /dev/null +++ b/.forgejo/workflows/biomejs.yml @@ -0,0 +1,24 @@ +name: Code quality checks + +on: + push: + pull_request: + +jobs: + biome: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Bun + run: | + curl -fsSL https://bun.sh/install | bash + export BUN_INSTALL="$HOME/.bun" + echo "$BUN_INSTALL/bin" >> $GITHUB_PATH + + - name: Install Dependencies + run: bun install + + - name: Run Biome with verbose output + run: bunx biome ci . --verbose diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b43f08..d64fcbc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "cSpell.words": [ - "Booru" - ] + "cSpell.words": ["Booru"], + "explorer.excludeGitIgnore": false } diff --git a/README.md b/README.md index b25d515..c50b89e 100644 --- a/README.md +++ b/README.md @@ -48,21 +48,20 @@ --- > **Note** -> To use the **e621 API**, you must update the following environment variables in your `.env` file: +> To use the **e621 Booru route**, include the following **headers** in your request: > -> ```env -> E621_USER_AGENT=YourApplication/1.0 (by username on e621) -> E621_USERNAME=your-username -> E621_API_KEY=your-apikey +> ```http +> e621UserAgent: YourApplication/1.0 (by username on e621) +> e621Username: your-username +> e621ApiKey: your-apikey > ``` -> Replace `your-username` and `your-apikey` with your e621 account credentials. Update the `User-Agent` string to include your application name, version, and a contact method (e.g., your e621 username) to comply with e621's API guidelines. +> Replace `your-username` and `your-apikey` with your e621 account credentials. Update the `e621UserAgent` string to include your application name, version, and a contact method (e.g., your e621 username) to comply with e621's API guidelines. > > -> To use the **Gelbooru API**, you must also update the following: +> To use the **Gelbooru Booru route**, include these **headers** in your request: > -> ```env -> GELBOORU_API_KEY=your-apikey -> GELBOORU_USER_ID=your-user-id +> ```http +> gelbooruApiKey: your-apikey +> gelbooruUserId: your-user-id > ``` -> You can find these credentials in your [Gelbooru account settings](https://gelbooru.com/index.php?page=account&s=options). -> These are required for authenticated API requests and higher rate limits. +> You can find these credentials in your [Gelbooru account settings](https://gelbooru.com/index.php?page=account&s=options). These are required for authenticated API requests and higher rate limits. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..921a7a5 --- /dev/null +++ b/biome.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": true, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineEnding": "lf" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "indentStyle": "tab", + "lineEnding": "lf", + "jsxQuoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/config/booru.ts b/config/booru.ts index 442325c..11198e4 100644 --- a/config/booru.ts +++ b/config/booru.ts @@ -1,7 +1,5 @@ // cSpell:disable -import { gelBooruAUTH, getE621Auth } from "@config/environment"; - const booruDefaults: IBooruDefaults = { search: "index.php?page=dapi&s=post&q=index&json=1", random: "s", @@ -68,7 +66,6 @@ export const booruConfig: IBooruConfigMap = { random: "defaultRandom", id: ["posts/", ".json"], }, - auth: getE621Auth(), }, "gelbooru.com": { enabled: true, @@ -77,6 +74,5 @@ export const booruConfig: IBooruConfigMap = { endpoint: "gelbooru.com", autocomplete: "gelbooru.com/index.php?page=autocomplete&term=", functions: booruDefaults, - auth: gelBooruAUTH(), }, }; diff --git a/config/environment.ts b/config/environment.ts index 7de045a..da15e14 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -1,47 +1,6 @@ -import { logger } from "@/helpers/logger"; - export const environment: Environment = { - port: parseInt(process.env.PORT || "6600", 10), + port: Number.parseInt(process.env.PORT || "6600", 10), host: process.env.HOST || "0.0.0.0", development: - process.env.NODE_ENV === "development" || - process.argv.includes("--dev"), + process.env.NODE_ENV === "development" || process.argv.includes("--dev"), }; - -if ( - !process.env.E621_USER_AGENT || - !process.env.E621_USERNAME || - !process.env.E621_API_KEY -) { - logger.error("Missing e621 credentials in .env file"); -} else { - if ( - process.env.E621_USERNAME === "username" || - process.env.E621_API_KEY === "apikey" - ) { - logger.error("Please update your e621 credentials in the .env file"); - } -} - -export function getE621Auth(): Record { - const e621UserAgent: string | undefined = process.env.E621_USER_AGENT; - const e621Username: string | undefined = process.env.E621_USERNAME; - const e621ApiKey: string | undefined = process.env.E621_API_KEY; - - return { - "User-Agent": e621UserAgent || "", - Authorization: - "Basic " + btoa(`${e621Username || ""}:${e621ApiKey || ""}`), - }; -} - -if (!process.env.GELBOORU_API_KEY || !process.env.GELBOORU_USER_ID) { - logger.error("Missing Gelbooru credentials in .env file"); -} - -export function gelBooruAUTH(): Record { - return { - apiKey: process.env.GELBOORU_API_KEY || "", - userId: process.env.GELBOORU_USER_ID || "", - }; -} diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index d43df76..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,132 +0,0 @@ -import pluginJs from "@eslint/js"; -import tseslintPlugin from "@typescript-eslint/eslint-plugin"; -import tsParser from "@typescript-eslint/parser"; -import prettier from "eslint-plugin-prettier"; -import promisePlugin from "eslint-plugin-promise"; -import simpleImportSort from "eslint-plugin-simple-import-sort"; -import unicorn from "eslint-plugin-unicorn"; -import unusedImports from "eslint-plugin-unused-imports"; -import globals from "globals"; - -/** @type {import('eslint').Linter.FlatConfig[]} */ -export default [ - { - files: ["**/*.{js,mjs,cjs}"], - languageOptions: { - globals: globals.node, - }, - ...pluginJs.configs.recommended, - plugins: { - "simple-import-sort": simpleImportSort, - "unused-imports": unusedImports, - promise: promisePlugin, - prettier: prettier, - unicorn: unicorn, - }, - rules: { - "eol-last": ["error", "always"], - "no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }], - "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", - "unused-imports/no-unused-imports": "error", - "unused-imports/no-unused-vars": [ - "warn", - { - vars: "all", - varsIgnorePattern: "^_", - args: "after-used", - argsIgnorePattern: "^_", - }, - ], - "promise/always-return": "error", - "promise/no-return-wrap": "error", - "promise/param-names": "error", - "promise/catch-or-return": "error", - "promise/no-nesting": "warn", - "promise/no-promise-in-callback": "warn", - "promise/no-callback-in-promise": "warn", - "prettier/prettier": [ - "error", - { - useTabs: true, - tabWidth: 4, - }, - ], - indent: ["error", "tab", { SwitchCase: 1 }], - "unicorn/filename-case": [ - "error", - { - case: "camelCase", - }, - ], - }, - }, - { - files: ["**/*.{ts,tsx}"], - languageOptions: { - parser: tsParser, - globals: globals.node, - }, - plugins: { - "@typescript-eslint": tseslintPlugin, - "simple-import-sort": simpleImportSort, - "unused-imports": unusedImports, - promise: promisePlugin, - prettier: prettier, - unicorn: unicorn, - }, - rules: { - ...tseslintPlugin.configs.recommended.rules, - quotes: ["error", "double"], - "eol-last": ["error", "always"], - "no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }], - "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", - "unused-imports/no-unused-imports": "error", - "unused-imports/no-unused-vars": [ - "warn", - { - vars: "all", - varsIgnorePattern: "^_", - args: "after-used", - argsIgnorePattern: "^_", - }, - ], - "promise/always-return": "error", - "promise/no-return-wrap": "error", - "promise/param-names": "error", - "promise/catch-or-return": "error", - "promise/no-nesting": "warn", - "promise/no-promise-in-callback": "warn", - "promise/no-callback-in-promise": "warn", - "prettier/prettier": [ - "error", - { - useTabs: true, - tabWidth: 4, - }, - ], - indent: ["error", "tab", { SwitchCase: 1 }], - "unicorn/filename-case": [ - "error", - { - case: "camelCase", - }, - ], - "@typescript-eslint/explicit-function-return-type": ["error"], - "@typescript-eslint/explicit-module-boundary-types": ["error"], - "@typescript-eslint/typedef": [ - "error", - { - arrowParameter: true, - variableDeclaration: true, - propertyDeclaration: true, - memberVariableDeclaration: true, - parameter: true, - }, - ], - }, - }, -]; diff --git a/package.json b/package.json index f60e383..3f5f4af 100644 --- a/package.json +++ b/package.json @@ -2,28 +2,20 @@ "name": "booru-api", "module": "src/index.ts", "devDependencies": { - "@eslint/js": "^9.17.0", - "@types/bun": "^1.1.14", - "@typescript-eslint/eslint-plugin": "^8.18.1", - "@typescript-eslint/parser": "^8.18.1", - "eslint": "^9.17.0", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-promise": "^7.2.1", - "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unicorn": "^56.0.1", - "eslint-plugin-unused-imports": "^4.1.4", - "globals": "^15.14.0", - "prettier": "^3.4.2" + "@biomejs/biome": "^1.9.4", + "@eslint/js": "^9.24.0", + "@types/bun": "^1.2.9", + "globals": "^16.0.0" }, "peerDependencies": { - "typescript": "^5.7.2" + "typescript": "^5.8.3" }, "scripts": { "start": "bun run src/index.ts", - "dev": "bun run --watch src/index.ts --dev", - "lint": "eslint", - "lint:fix": "bun lint --fix", - "cleanup": "rm -rf logs node_modules bun.lockb" + "dev": "bun run --hot src/index.ts --dev", + "lint": "bunx biome check", + "lint:fix": "bunx biome check --fix", + "cleanup": "rm -rf logs node_modules bun.lock" }, "type": "module" } diff --git a/src/helpers/char.ts b/src/helpers/char.ts index 1948fcd..0313a37 100644 --- a/src/helpers/char.ts +++ b/src/helpers/char.ts @@ -4,15 +4,15 @@ import { booruConfig } from "@config/booru"; export function timestampToReadable(timestamp?: number): string { const date: Date = - timestamp && !isNaN(timestamp) ? new Date(timestamp) : new Date(); - if (isNaN(date.getTime())) return "Invalid Date"; + timestamp && !Number.isNaN(timestamp) ? new Date(timestamp) : new Date(); + if (Number.isNaN(date.getTime())) return "Invalid Date"; return date.toISOString().replace("T", " ").replace("Z", ""); } export function tagsToExpectedFormat( tags: string[] | string | Record, - minus: boolean = false, - onlyMinus: boolean = false, + minus = false, + onlyMinus = false, ): string { const delimiter: string = minus ? (onlyMinus ? "-" : "+-") : "+"; @@ -76,7 +76,7 @@ export function determineBooru( export function postExpectedFormat( booru: IBooruConfig, posts: BooruPost[] | BooruPost, - tag_format: string = "string", + tag_format = "string", ): { posts: BooruPost[] } | null { if (!posts) return null; @@ -90,40 +90,64 @@ export function postExpectedFormat( ...post, file_url: post.file.url ?? null, post_url: - post.post_url ?? - `https://${booru.endpoint}/posts/${post.id}`, + post.post_url ?? `https://${booru.endpoint}/posts/${post.id}`, tags: tag_format === "unformatted" ? post.tags : Object.values(post.tags || {}) - .flat() - .join(" "), + .flat() + .join(" "), }; }), }; } const fixedDomain: string = booru.endpoint.replace(/^api\./, ""); - const formattedPosts: BooruPost[] = normalizedPosts.map( - (post: BooruPost) => { - const postUrl: string = - post.post_url ?? - `https://${fixedDomain}/index.php?page=post&s=view&id=${post.id}`; - const imageExtension: string = - post.image?.substring(post.image.lastIndexOf(".") + 1) ?? ""; - const fileUrl: string | null = - post.file_url ?? - (post.directory && post.hash && imageExtension - ? `https://${booru.endpoint}/images/${post.directory}/${post.hash}.${imageExtension}` - : null); + const formattedPosts: BooruPost[] = normalizedPosts.map((post: BooruPost) => { + const postUrl: string = + post.post_url ?? + `https://${fixedDomain}/index.php?page=post&s=view&id=${post.id}`; + const imageExtension: string = + post.image?.substring(post.image.lastIndexOf(".") + 1) ?? ""; + const fileUrl: string | null = + post.file_url ?? + (post.directory && post.hash && imageExtension + ? `https://${booru.endpoint}/images/${post.directory}/${post.hash}.${imageExtension}` + : null); - return { - ...post, - file_url: fileUrl, - post_url: postUrl, - }; - }, - ); + return { + ...post, + file_url: fileUrl, + post_url: postUrl, + }; + }); return { posts: formattedPosts }; } + +export function getE621Auth(headers: Headers): Record | null { + const userAgent = headers.get("e621UserAgent") ?? ""; + const username = headers.get("e621Username"); + const apiKey = headers.get("e621ApiKey"); + + if (!userAgent || !username || !apiKey) return null; + + return { + "User-Agent": userAgent, + Authorization: `Basic ${btoa(`${username}:${apiKey}`)}`, + }; +} + +export function getGelBooruAuth( + headers: Headers, +): Record | null { + const apiKey = headers.get("gelbooruApiKey"); + const userId = headers.get("gelbooruUserId"); + + if (!apiKey || !userId) return null; + + return { + apiKey, + userId, + }; +} diff --git a/src/helpers/logger.ts b/src/helpers/logger.ts index 4f67130..fa39adf 100644 --- a/src/helpers/logger.ts +++ b/src/helpers/logger.ts @@ -1,14 +1,14 @@ -import { environment } from "@config/environment"; -import type { Stats } from "fs"; +import type { Stats } from "node:fs"; import { + type WriteStream, createWriteStream, existsSync, mkdirSync, statSync, - WriteStream, -} from "fs"; -import { EOL } from "os"; -import { basename, join } from "path"; +} from "node:fs"; +import { EOL } from "node:os"; +import { basename, join } from "node:path"; +import { environment } from "@config/environment"; import { timestampToReadable } from "./char"; @@ -38,7 +38,7 @@ class Logger { mkdirSync(logDir, { recursive: true }); } - let addSeparator: boolean = false; + let addSeparator = false; if (existsSync(logFile)) { const fileStats: Stats = statSync(logFile); @@ -67,9 +67,9 @@ class Logger { private extractFileName(stack: string): string { const stackLines: string[] = stack.split("\n"); - let callerFile: string = ""; + let callerFile = ""; - for (let i: number = 2; i < stackLines.length; i++) { + for (let i = 2; i < stackLines.length; i++) { const line: string = stackLines[i].trim(); if (line && !line.includes("Logger.") && line.includes("(")) { callerFile = line.split("(")[1]?.split(")")[0] || ""; @@ -92,7 +92,7 @@ class Logger { return { filename, timestamp: readableTimestamp }; } - public info(message: string | string[], breakLine: boolean = false): void { + public info(message: string | string[], breakLine = false): void { const stack: string = new Error().stack || ""; const { filename, timestamp } = this.getCallerInfo(stack); @@ -111,7 +111,7 @@ class Logger { this.writeConsoleMessageColored(logMessageParts, breakLine); } - public warn(message: string | string[], breakLine: boolean = false): void { + public warn(message: string | string[], breakLine = false): void { const stack: string = new Error().stack || ""; const { filename, timestamp } = this.getCallerInfo(stack); @@ -132,7 +132,7 @@ class Logger { public error( message: string | string[] | Error | Error[], - breakLine: boolean = false, + breakLine = false, ): void { const stack: string = new Error().stack || ""; const { filename, timestamp } = this.getCallerInfo(stack); @@ -159,7 +159,7 @@ class Logger { private writeConsoleMessageColored( logMessageParts: ILogMessageParts, - breakLine: boolean = false, + breakLine = false, ): void { const logMessage: string = Object.keys(logMessageParts) .map((key: string) => { diff --git a/src/index.ts b/src/index.ts index e187c31..decf139 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,7 @@ import { logger } from "@helpers/logger"; import { serverHandler } from "./server"; async function main(): Promise { - try { - serverHandler.initialize(); - } catch (error) { - throw error; - } + serverHandler.initialize(); } main().catch((error: Error) => { diff --git a/src/routes/[booru]/autocomplete/[tag].ts b/src/routes/[booru]/autocomplete/[tag].ts index 3d5ff3c..e93ce13 100644 --- a/src/routes/[booru]/autocomplete/[tag].ts +++ b/src/routes/[booru]/autocomplete/[tag].ts @@ -1,7 +1,7 @@ -import { determineBooru } from "@helpers/char"; +import { determineBooru, getE621Auth, getGelBooruAuth } from "@helpers/char"; import { fetch } from "bun"; -import { logger } from "@/helpers/logger"; +import { logger } from "@helpers/logger"; const routeDef: RouteDef = { method: "GET", @@ -10,7 +10,7 @@ const routeDef: RouteDef = { }; async function handler( - _request: Request, + request: Request, _server: BunServer, _requestBody: unknown, query: Query, @@ -47,6 +47,22 @@ async function handler( const booruConfig: IBooruConfig | null = determineBooru(booru); const isE621: boolean = booruConfig?.name === "e621.net"; const isGelbooru: boolean = booruConfig?.name === "gelbooru.com"; + const gelbooruAuth: Record | null = getGelBooruAuth( + request.headers, + ); + + if (isGelbooru && !gelbooruAuth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing Gelbooru authentication headers", + }, + { + status: 401, + }, + ); + } if (!booruConfig) { return Response.json( @@ -106,15 +122,37 @@ async function handler( ); } - let url: string = `https://${booruConfig.autocomplete}${editedTag}`; + let url = `https://${booruConfig.autocomplete}${editedTag}`; - if (isGelbooru) { - url += `&api_key=${booruConfig.auth?.api_key}&user_id=${booruConfig.auth?.user_id}`; + if (isGelbooru && gelbooruAuth) { + url += `?api_key=${gelbooruAuth.api_key}&user_id=${gelbooruAuth.user_id}`; } try { - const headers: IBooruConfig["auth"] | undefined = - booruConfig.auth && isE621 ? booruConfig.auth : undefined; + let headers: Record | undefined; + + if (isE621) { + const e621Auth: Record | null = getE621Auth( + request.headers, + ); + + if (!e621Auth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing E621 authentication headers", + }, + { + status: 401, + }, + ); + } + + headers = { + ...e621Auth, + }; + } const response: Response = await fetch(url, { headers, diff --git a/src/routes/[booru]/id/[id].ts b/src/routes/[booru]/id/[id].ts index 89083d9..4e9da6e 100644 --- a/src/routes/[booru]/id/[id].ts +++ b/src/routes/[booru]/id/[id].ts @@ -1,7 +1,12 @@ -import { determineBooru, postExpectedFormat } from "@helpers/char"; +import { + determineBooru, + getE621Auth, + getGelBooruAuth, + postExpectedFormat, +} from "@helpers/char"; import { fetch } from "bun"; -import { logger } from "@/helpers/logger"; +import { logger } from "@helpers/logger"; const routeDef: RouteDef = { method: "GET", @@ -10,7 +15,7 @@ const routeDef: RouteDef = { }; async function handler( - _request: Request, + request: Request, _server: BunServer, _requestBody: unknown, query: Query, @@ -37,6 +42,22 @@ async function handler( const booruConfig: IBooruConfig | null = determineBooru(booru); const isE621: boolean = booruConfig?.name === "e621.net"; const isGelbooru: boolean = booruConfig?.name === "gelbooru.com"; + const gelbooruAuth: Record | null = getGelBooruAuth( + request.headers, + ); + + if (isGelbooru && !gelbooruAuth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing Gelbooru authentication headers", + }, + { + status: 401, + }, + ); + } if (!booruConfig) { return Response.json( @@ -65,10 +86,10 @@ async function handler( } const funcString: string | [string, string] = booruConfig.functions.id; - let url: string = `https://${booruConfig.endpoint}/${booruConfig.functions.id}${id}`; + let url = `https://${booruConfig.endpoint}/${booruConfig.functions.id}${id}`; - if (isGelbooru) { - url += `&api_key=${booruConfig.auth?.api_key}&user_id=${booruConfig.auth?.user_id}`; + if (isGelbooru && gelbooruAuth) { + url += `?api_key=${gelbooruAuth.api_key}&user_id=${gelbooruAuth.user_id}`; } if (Array.isArray(funcString)) { @@ -78,8 +99,30 @@ async function handler( } try { - const headers: IBooruConfig["auth"] | undefined = - booruConfig.auth && isE621 ? booruConfig.auth : undefined; + let headers: Record | undefined; + + if (isE621) { + const e621Auth: Record | null = getE621Auth( + request.headers, + ); + + if (!e621Auth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing E621 authentication headers", + }, + { + status: 401, + }, + ); + } + + headers = { + ...e621Auth, + }; + } const response: Response = await fetch(url, { headers, diff --git a/src/routes/[booru]/random.ts b/src/routes/[booru]/random.ts index e596159..3b1ebe8 100644 --- a/src/routes/[booru]/random.ts +++ b/src/routes/[booru]/random.ts @@ -1,5 +1,7 @@ import { determineBooru, + getE621Auth, + getGelBooruAuth, minPosts, postExpectedFormat, shufflePosts, @@ -7,7 +9,7 @@ import { } from "@helpers/char"; import { fetch } from "bun"; -import { logger } from "@/helpers/logger"; +import { logger } from "@helpers/logger"; const routeDef: RouteDef = { method: "POST", @@ -17,7 +19,7 @@ const routeDef: RouteDef = { }; async function handler( - _request: Request, + request: Request, _server: BunServer, requestBody: unknown, query: Query, @@ -121,6 +123,22 @@ async function handler( const isE621: boolean = booruConfig.name === "e621.net"; const isGelbooru: boolean = booruConfig.name === "gelbooru.com"; + const gelbooruAuth: Record | null = getGelBooruAuth( + request.headers, + ); + + if (isGelbooru && !gelbooruAuth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing Gelbooru authentication headers", + }, + { + status: 401, + }, + ); + } const formattedTags: string = tags ? tagsToExpectedFormat(tags) : ""; const formattedExcludeTags: string = excludeTags @@ -130,9 +148,11 @@ async function handler( const tagsString: () => string = (): string => { if (formattedTags && formattedExcludeTags) { return `tags=${formattedTags}+-${formattedExcludeTags}`; - } else if (formattedTags) { + } + if (formattedTags) { return `tags=${formattedTags}`; - } else if (formattedExcludeTags) { + } + if (formattedExcludeTags) { return `tags=-${formattedExcludeTags}`; } @@ -164,12 +184,12 @@ async function handler( parts.push("&"); } - if (isGelbooru) { + if (isGelbooru && gelbooruAuth) { parts.push("api_key"); - parts.push(booruConfig.auth?.apiKey || ""); + parts.push(gelbooruAuth.apiKey); parts.push("&"); parts.push("user_id"); - parts.push(booruConfig.auth?.userId || ""); + parts.push(gelbooruAuth.userId); parts.push("&"); } @@ -178,7 +198,6 @@ async function handler( .join("&"); parts.push(queryParams); - console.log("URL", parts.join("")); return parts.join(""); }; @@ -186,15 +205,37 @@ async function handler( maxPage: 12, maxTries: 6, }; - let state: { tries: number; page: number } = { tries: 0, page: 16 }; + const state: { tries: number; page: number } = { tries: 0, page: 16 }; while (state.tries < config.maxTries) { const url: string = getUrl(pageString(state.page), resultsString); try { - const headers: IBooruConfig["auth"] | undefined = booruConfig.auth - ? booruConfig.auth - : undefined; + let headers: Record | undefined; + + if (isE621) { + const e621Auth: Record | null = getE621Auth( + request.headers, + ); + + if (!e621Auth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing E621 authentication headers", + }, + { + status: 401, + }, + ); + } + + headers = { + ...e621Auth, + }; + } + const response: Response = await fetch(url, { headers, }); @@ -204,9 +245,7 @@ async function handler( { success: false, code: response.status || 500, - error: - response.statusText || - `Could not reach ${booruConfig.name}`, + error: response.statusText || `Could not reach ${booruConfig.name}`, }, { status: response.status || 500, @@ -246,8 +285,11 @@ async function handler( if (posts.length === 0) continue; - let expectedData: { posts: BooruPost[] } | null = - postExpectedFormat(booruConfig, posts, tag_format); + const expectedData: { posts: BooruPost[] } | null = postExpectedFormat( + booruConfig, + posts, + tag_format, + ); if (!expectedData) continue; @@ -265,7 +307,6 @@ async function handler( }, ); } catch { - continue; } finally { state.tries++; diff --git a/src/routes/[booru]/search.ts b/src/routes/[booru]/search.ts index f801c0e..1078d52 100644 --- a/src/routes/[booru]/search.ts +++ b/src/routes/[booru]/search.ts @@ -1,5 +1,7 @@ import { determineBooru, + getE621Auth, + getGelBooruAuth, postExpectedFormat, tagsToExpectedFormat, } from "@helpers/char"; @@ -13,7 +15,7 @@ const routeDef: RouteDef = { }; async function handler( - _request: Request, + request: Request, _server: BunServer, requestBody: unknown, query: Query, @@ -119,6 +121,22 @@ async function handler( const isE621: boolean = booruConfig.name === "e621.net"; const isGelbooru: boolean = booruConfig.name === "gelbooru.com"; + const gelbooruAuth: Record | null = getGelBooruAuth( + request.headers, + ); + + if (isGelbooru && !gelbooruAuth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing Gelbooru authentication headers", + }, + { + status: 401, + }, + ); + } const formattedTags: string = tags ? tagsToExpectedFormat(tags) : ""; const formattedExcludeTags: string = excludeTags @@ -133,9 +151,11 @@ async function handler( const tagsString: () => string = (): string => { if (formattedTags && formattedExcludeTags) { return `tags=${formattedTags}+-${formattedExcludeTags}`; - } else if (formattedTags) { + } + if (formattedTags) { return `tags=${formattedTags}`; - } else if (formattedExcludeTags) { + } + if (formattedExcludeTags) { return `tags=-${formattedExcludeTags}`; } @@ -162,12 +182,12 @@ async function handler( parts.push("&"); } - if (isGelbooru) { + if (isGelbooru && gelbooruAuth) { parts.push("api_key"); - parts.push(booruConfig.auth?.apiKey || ""); + parts.push(gelbooruAuth.apiKey); parts.push("&"); parts.push("user_id"); - parts.push(booruConfig.auth?.userId || ""); + parts.push(gelbooruAuth.userId); parts.push("&"); } @@ -180,9 +200,31 @@ async function handler( }; try { - const headers: IBooruConfig["auth"] | undefined = booruConfig.auth - ? booruConfig.auth - : undefined; + let headers: Record | undefined; + + if (isE621) { + const e621Auth: Record | null = getE621Auth( + request.headers, + ); + + if (!e621Auth) { + return Response.json( + { + success: false, + code: 401, + error: "Missing E621 authentication headers", + }, + { + status: 401, + }, + ); + } + + headers = { + ...e621Auth, + }; + } + const response: Response = await fetch(url(), { headers, }); @@ -192,9 +234,7 @@ async function handler( { success: false, code: response.status || 500, - error: - response.statusText || - `Could not reach ${booruConfig.name}`, + error: response.statusText || `Could not reach ${booruConfig.name}`, }, { status: response.status || 500, diff --git a/src/server.ts b/src/server.ts index 20c4e01..f7981ac 100644 --- a/src/server.ts +++ b/src/server.ts @@ -58,8 +58,7 @@ class ServerHandler { try { const routeModule: RouteModule = await import(filePath); - const contentType: string | null = - request.headers.get("Content-Type"); + const contentType: string | null = request.headers.get("Content-Type"); const actualContentType: string | null = contentType ? contentType.split(";")[0].trim() : null; @@ -119,10 +118,7 @@ class ServerHandler { params, ); - response.headers.set( - "Content-Type", - routeModule.routeDef.returns, - ); + response.headers.set("Content-Type", routeModule.routeDef.returns); } } } catch (error: unknown) { diff --git a/tsconfig.json b/tsconfig.json index 54370f9..17b2607 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,31 +2,15 @@ "compilerOptions": { "baseUrl": "./", "paths": { - "@/*": [ - "src/*" - ], - "@config/*": [ - "config/*" - ], - "@types/*": [ - "types/*" - ], - "@helpers/*": [ - "src/helpers/*" - ], - "@database/*": [ - "src/database/*" - ], + "@/*": ["src/*"], + "@config/*": ["config/*"], + "@types/*": ["types/*"], + "@helpers/*": ["src/helpers/*"], + "@database/*": ["src/database/*"] }, - "typeRoots": [ - "./src/types", - "./node_modules/@types" - ], + "typeRoots": ["./src/types", "./node_modules/@types"], // Enable latest features - "lib": [ - "ESNext", - "DOM" - ], + "lib": ["ESNext", "DOM"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", @@ -44,11 +28,7 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false, + "noPropertyAccessFromIndexSignature": false }, - "include": [ - "src", - "types", - "config" - ], + "include": ["src", "types", "config"] } diff --git a/types/config.d.ts b/types/config.d.ts index 04b6dce..6aae73e 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -18,7 +18,6 @@ type IBooruConfigMap = { endpoint: string; functions: IBooruDefaults; autocomplete?: string; - auth?: Record; }; }; @@ -29,5 +28,4 @@ type IBooruConfig = { endpoint: string; functions: IBooruDefaults; autocomplete?: string; - auth?: Record; };