Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<img src="https://raw.githubusercontent.com/sinedied/mini-scraper/refs/heads/main/pic.jpg" alt="picture of a scraped boxart" width="180" align="right">

Artwork scraper for [MinUI](https://github.com/shauninman/MinUI), [NextUI](https://github.com/LoveRetro/NextUI) and [muOS](https://muos.dev/).
Artwork scraper for [MinUI](https://github.com/shauninman/MinUI), [NextUI](https://github.com/LoveRetro/NextUI), [muOS](https://muos.dev/), and [Onion](https://onionui.github.io/).

> [!NOTE]
> MinUI does't officially support boxarts, but still has [some support for it as stated by its author](https://www.reddit.com/r/SBCGaming/comments/1hycyqx/minui_box_art/).
Expand Down Expand Up @@ -48,7 +48,7 @@ When running the scraper, you can pass the following options:
- `-w, --width <size>`: Max width of the image (default: 300)
- `-h, --height <size>`: Max height of the image
- `-t, --type <type>`: Type of image to scrape (can be `boxart`, `snap`, `title`, `box+snap`, `box+title`) (default: `boxart`)
- `-o, --output <format>`: Artwork format (can be (`minui`, `nextui`, `muos`, `anbernic`) (default: `minui`)
- `-o, --output <format>`: Artwork format (can be (`minui`, `nextui`, `muos`, `anbernic`, `onion`) (default: `minui`)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README option description has mismatched parentheses: it says "(can be (" but never closes that parenthetical. Please fix the formatting so the supported formats list is clearly delimited (e.g., remove the extra parenthesis or add the missing closing one).

Suggested change
- `-o, --output <format>`: Artwork format (can be (`minui`, `nextui`, `muos`, `anbernic`, `onion`) (default: `minui`)
- `-o, --output <format>`: Artwork format (can be `minui`, `nextui`, `muos`, `anbernic`, `onion`) (default: `minui`)

Copilot uses AI. Check for mistakes.
- `-a, --ai`: Use AI for advanced matching (default: false)
- `-m, --ai-model <name>`: Ollama model to use for AI matching (default: `gemma2:2b`)
- `-r, --regions <regions>`: Preferred regions to use for AI matching (default: `World,Europe,USA,Japan`)
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"minui",
"nextui",
"muos",
"anbernic",
"onion",
"scraper",
"boxart",
"ai"
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function run(args: string[] = process.argv) {
.option('-w, --width <size>', 'Max width of the image', Number.parseFloat, 300)
.option('-h, --height <size>', 'Max height of the image', Number.parseFloat)
.option('-t, --type <type>', 'Art type (boxart, snap, title, box+snap, box+title)', 'boxart')
.option('-o, --output <format>', 'Artwork format (minui, nextui, muos, anbernic)', 'minui')
.option('-o, --output <format>', 'Artwork format (minui, nextui, muos, anbernic, onion)', 'minui')
.option('-a, --ai', 'Use AI for advanced matching', false)
.option('-m, --ai-model <name>', 'Ollama model to use for AI matching', 'gemma2:2b')
.option('-r, --regions <regions>', 'Preferred regions to use for AI matching', 'World,Europe,USA,Japan')
Expand Down
8 changes: 7 additions & 1 deletion src/format/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export enum Format {
MinUI = 'minui',
NextUI = 'nextui',
MuOS = 'muos',
Anbernic = 'anbernic'
Anbernic = 'anbernic',
Onion = 'onion'
}

export type SeparateArtworksFunction = (options: Options) => Promise<boolean>;
Expand Down Expand Up @@ -53,6 +54,11 @@ export async function getOutputFormat(options: Options): Promise<OutputFormat> {
return anbernic.default;
}

case Format.Onion: {
const onion = await import('./onion.js');
return onion.default;
}
Comment on lines +57 to +60
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI runs npm test, which executes the CLI with the default output format. The new Format.Onion dynamic import path is therefore not exercised by the automated test run, so regressions (e.g. import/exports mismatches at runtime) could slip through. Consider adding a small test invocation that runs the CLI with --output onion (even a --cleanup run) to cover this code path.

Copilot uses AI. Check for mistakes.

// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
default: {
throw new Error(`Unknown format: ${options.output}`);
Expand Down
55 changes: 55 additions & 0 deletions src/format/onion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import path from 'node:path';
import fs from 'node:fs/promises';
import createDebug from 'debug';
import glob from 'fast-glob';
import { getArtTypes } from '../libretro.js';
import { type Options } from '../options.js';
import { composeImageTo, resizeImageTo } from '../image.js';
import { type ArtType } from '../art.js';

const debug = createDebug('onion');
const boxartFolder = 'boxart';

export async function useSeparateArtworks(_options: Options) {
return false;
}

export async function getArtPath(filePath: string, _machine: string, _type?: ArtType) {
const fileName = path.basename(filePath, path.extname(filePath));
return path.join(path.dirname(filePath), boxartFolder, `${fileName}.png`);
}

export async function exportArtwork(
art1Url: string | undefined,
art2Url: string | undefined,
artPath: string,
options: Options
) {
const artTypes = getArtTypes(options);
if (artTypes.art2 && (art1Url ?? art2Url)) {
debug(`Found art URL(s): "${art1Url}" / "${art2Url}"`);
await composeImageTo(art1Url, art2Url, artPath, { width: options.width, height: options.height });
} else if (art1Url) {
debug(`Found art URL: "${art1Url}"`);
await resizeImageTo(art1Url, artPath, { width: options.width, height: options.height });
} else {
return false;
}

return true;
}

export async function cleanupArtwork(targetPath: string, _romFolders: string[], _options: Options) {
const boxartFolders = await glob([`**/${boxartFolder}`], { onlyDirectories: true, cwd: targetPath });
await Promise.all(boxartFolders.map(async (boxartFolder) => fs.rm(boxartFolder, { recursive: true })));
console.info(`Removed ${boxartFolders.length} ${boxartFolder} folders`);
}

const onion = {
useSeparateArtworks,
getArtPath,
exportArtwork,
cleanupArtwork
};

export default onion;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * as minui from './format/minui.js';
export * as nextui from './format/nextui.js';
export * as muos from './format/muos.js';
export * as anbernic from './format/anbernic.js';
export * as onion from './format/onion.js';