New Captcha Package added
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import type {
|
||||
CaptchaConcreteImageStyle,
|
||||
CaptchaImageStyle,
|
||||
} from "../types.ts";
|
||||
|
||||
export const CAPTCHA_CONCRETE_IMAGE_STYLES = [
|
||||
"classic",
|
||||
"collision",
|
||||
"snow",
|
||||
"corrosion",
|
||||
"spiderweb",
|
||||
"cross-shadow",
|
||||
"split",
|
||||
"split2",
|
||||
"cut",
|
||||
"darts",
|
||||
"distortion",
|
||||
"stitch",
|
||||
"striped",
|
||||
"wave",
|
||||
"grid-noise",
|
||||
"scribble",
|
||||
"pixel",
|
||||
"broken-lines",
|
||||
] as const satisfies readonly CaptchaConcreteImageStyle[];
|
||||
|
||||
export const CAPTCHA_IMAGE_STYLES = [
|
||||
"random",
|
||||
...CAPTCHA_CONCRETE_IMAGE_STYLES,
|
||||
] as const satisfies readonly CaptchaImageStyle[];
|
||||
|
||||
const CONCRETE_STYLE_SET = new Set<string>(CAPTCHA_CONCRETE_IMAGE_STYLES);
|
||||
const STYLE_SET = new Set<string>(CAPTCHA_IMAGE_STYLES);
|
||||
|
||||
function styleValues(value: unknown): string[] {
|
||||
if (Array.isArray(value)) return value.flatMap((item) => styleValues(item));
|
||||
if (typeof value !== "string") return [];
|
||||
return value
|
||||
.split(",")
|
||||
.map((item) => item.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function isCaptchaImageStyle(value: unknown): value is CaptchaImageStyle {
|
||||
return typeof value === "string" && STYLE_SET.has(value.trim().toLowerCase());
|
||||
}
|
||||
|
||||
export function normalizeCaptchaImageStyle(
|
||||
value: unknown,
|
||||
fallback: CaptchaImageStyle = "random",
|
||||
): CaptchaImageStyle {
|
||||
if (value === undefined || value === null || value === "") return fallback;
|
||||
const normalized = String(value).trim().toLowerCase();
|
||||
if (!STYLE_SET.has(normalized)) {
|
||||
throw new RangeError(
|
||||
`imageStyle must be one of: ${CAPTCHA_IMAGE_STYLES.join(", ")}`,
|
||||
);
|
||||
}
|
||||
return normalized as CaptchaImageStyle;
|
||||
}
|
||||
|
||||
export function normalizeCaptchaImageStyleList(
|
||||
value: unknown,
|
||||
name: "allowedStyles" | "excludedStyles",
|
||||
): CaptchaConcreteImageStyle[] {
|
||||
const styles: CaptchaConcreteImageStyle[] = [];
|
||||
for (const entry of styleValues(value)) {
|
||||
if (entry === "random") continue;
|
||||
if (!CONCRETE_STYLE_SET.has(entry)) {
|
||||
throw new RangeError(
|
||||
`${name} contains an unknown image style: ${entry}`,
|
||||
);
|
||||
}
|
||||
const style = entry as CaptchaConcreteImageStyle;
|
||||
if (!styles.includes(style)) styles.push(style);
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
|
||||
export interface ResolveCaptchaImageStyleOptions {
|
||||
imageStyle?: unknown;
|
||||
allowedStyles?: unknown;
|
||||
excludedStyles?: unknown;
|
||||
randomizeStyle?: boolean;
|
||||
randomInt(min: number, max: number): number;
|
||||
}
|
||||
|
||||
export interface ResolvedCaptchaImageStyle {
|
||||
requested: CaptchaImageStyle;
|
||||
resolved: CaptchaConcreteImageStyle;
|
||||
pool: CaptchaConcreteImageStyle[];
|
||||
}
|
||||
|
||||
export function resolveCaptchaImageStyle(
|
||||
options: ResolveCaptchaImageStyleOptions,
|
||||
): ResolvedCaptchaImageStyle {
|
||||
const requested = normalizeCaptchaImageStyle(options.imageStyle, "random");
|
||||
const allowed = normalizeCaptchaImageStyleList(
|
||||
options.allowedStyles,
|
||||
"allowedStyles",
|
||||
);
|
||||
const excluded = new Set(
|
||||
normalizeCaptchaImageStyleList(options.excludedStyles, "excludedStyles"),
|
||||
);
|
||||
|
||||
const source = allowed.length
|
||||
? allowed
|
||||
: [...CAPTCHA_CONCRETE_IMAGE_STYLES];
|
||||
const pool = source.filter((style) => !excluded.has(style));
|
||||
|
||||
if (!pool.length) {
|
||||
throw new RangeError(
|
||||
"No CAPTCHA image styles remain after applying allowedStyles and excludedStyles",
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.randomizeStyle && requested !== "random") {
|
||||
if (!pool.includes(requested)) {
|
||||
throw new RangeError(
|
||||
`imageStyle ${requested} is not available in the configured style pool`,
|
||||
);
|
||||
}
|
||||
return { requested, resolved: requested, pool };
|
||||
}
|
||||
|
||||
return {
|
||||
requested,
|
||||
resolved: pool[options.randomInt(0, pool.length - 1)]!,
|
||||
pool,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user