aboutsummaryrefslogtreecommitdiffstats
path: root/src/scripts/common/login/captcha.ts
diff options
context:
space:
mode:
authorArpit Chakladar <arpitchakladar+git@gmail.com>2026-07-13 23:32:26 +0530
committerGitHub <noreply@github.com>2026-07-13 23:32:26 +0530
commitce401d03e75bcbfd1cdfa963954e0807219fb80d (patch)
tree52a813c1705a952312c20c328b00fa845a3453f9 /src/scripts/common/login/captcha.ts
parent98e7019c61ea0ffe344ef7ce809d38d62254a9fc (diff)
parentdfba7ff6fff7bde33b40a343a2318b35c429b40f (diff)
downloadbanglar-bhumi-utils-ce401d03e75bcbfd1cdfa963954e0807219fb80d.tar.gz
banglar-bhumi-utils-ce401d03e75bcbfd1cdfa963954e0807219fb80d.zip
Merge pull request #6 from arpitchakladar/adding-documentation-comments
Adding documentation comments
Diffstat (limited to 'src/scripts/common/login/captcha.ts')
-rw-r--r--src/scripts/common/login/captcha.ts128
1 files changed, 128 insertions, 0 deletions
diff --git a/src/scripts/common/login/captcha.ts b/src/scripts/common/login/captcha.ts
new file mode 100644
index 0000000..a63aa24
--- /dev/null
+++ b/src/scripts/common/login/captcha.ts
@@ -0,0 +1,128 @@
+import { observeDOM } from "@/shared/observe-dom";
+import { OCRRequest, OCRResponse } from "@/shared/ocr-request";
+
+/**
+ * Pre-processes a CAPTCHA image on a canvas by removing grayish
+ * background noise while preserving dark pixels, producing a clean
+ * binary image suitable for OCR.
+ *
+ * @param ctx - The 2D rendering context of the canvas.
+ * @param width - Canvas width in pixels.
+ * @param height - Canvas height in pixels.
+ */
+export function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number): void {
+ const imgData = ctx.getImageData(0, 0, width, height);
+ const data = imgData.data;
+ const radius = 1;
+
+ /** Returns true when all three channels are below 50 (very dark). */
+ function isBlack(r: number, g: number, b: number): boolean {
+ return r < 50 && g < 50 && b < 50;
+ }
+
+ /** Returns true when the colour is a mid-range grey (no strong hue). */
+ function isGrayish(r: number, g: number, b: number): boolean {
+ return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200;
+ }
+
+ /**
+ * Checks whether a black pixel exists within `radius` pixels of (x, y).
+ * Used to preserve dark structures when removing grey noise.
+ */
+ function hasNearbyBlack(x: number, y: number): boolean {
+ for (let dx = -radius; dx <= radius; dx++) {
+ for (let dy = -radius; dy <= radius; dy++) {
+ const nx = x + dx;
+ const ny = y + dy;
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
+ const i = (ny * width + nx) * 4;
+ if (isBlack(data[i], data[i + 1], data[i + 2])) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const i = (y * width + x) * 4;
+ const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
+ if (isGrayish(r, g, b) && !hasNearbyBlack(x, y)) {
+ data[i] = data[i + 1] = data[i + 2] = 255;
+ }
+ }
+ }
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const i = (y * width + x) * 4;
+ const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
+
+ if (isGrayish(r, g, b)) {
+ data[i] = data[i + 1] = data[i + 2] = 0; // make black
+ }
+ }
+ }
+ ctx.putImageData(imgData, 0, 0);
+}
+
+observeDOM(() => {
+ const loginFormElement = document.querySelector<HTMLFormElement>("#loginform");
+
+ if (loginFormElement) {
+ const captchaImg = loginFormElement.querySelector<HTMLImageElement>("#captchaImg");
+ const captchaInput = loginFormElement.querySelector<HTMLInputElement>("#txtInput");
+
+ if (!captchaImg || !captchaInput)
+ return false;
+
+ // Wait for image to load to get correct dimensions
+ captchaImg.addEventListener("load", function(): void {
+ const { width, height } = captchaImg;
+
+ // 1. Create canvas
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ canvas.style.display = "block";
+
+ // 2. Copy styles (optional)
+ canvas.style.cssText = getComputedStyle(captchaImg).cssText;
+ canvas.style.display = "none";
+
+ // 3. Insert canvas before image
+ captchaImg.parentNode?.querySelectorAll("canvas")
+ .forEach((c) => { c.remove(); });
+ captchaImg.parentNode?.insertBefore(canvas, captchaImg);
+
+ // 4. Hide image
+ captchaImg.style.display = "none";
+
+ // 5. Draw the image onto the canvas
+ const ctx = canvas.getContext("2d");
+ if (!ctx)
+ return;
+ ctx.drawImage(captchaImg, 0, 0);
+ prepareCaptcha(ctx, canvas.width, canvas.height);
+ canvas.style.display = "";
+ const dataURL = canvas.toDataURL("image/png");
+
+ // Perform OCR
+ chrome.runtime.sendMessage<OCRRequest, OCRResponse>(
+ { type: "OCR", dataURL },
+ function(ocrResponse): void {
+ if (ocrResponse.success) {
+ const textResult = ocrResponse.text.trim();
+ if (ocrResponse.confidence < 80 || textResult.length !== 6)
+ // Reset the captcha if confidence is low
+ captchaImg.src = `generateCaptcha?${new Date().getTime().toString()}`;
+ else
+ captchaInput.value = textResult;
+ }
+ }
+ );
+ });
+ }
+
+ // This runs forever
+ return false;
+});