diff options
| author | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-07-13 23:32:26 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-13 23:32:26 +0530 |
| commit | ce401d03e75bcbfd1cdfa963954e0807219fb80d (patch) | |
| tree | 52a813c1705a952312c20c328b00fa845a3453f9 /src/scripts/common | |
| parent | 98e7019c61ea0ffe344ef7ce809d38d62254a9fc (diff) | |
| parent | dfba7ff6fff7bde33b40a343a2318b35c429b40f (diff) | |
| download | banglar-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')
| -rw-r--r-- | src/scripts/common/login/captcha.ts | 128 | ||||
| -rw-r--r-- | src/scripts/common/login/index.ts | 1 | ||||
| -rw-r--r-- | src/scripts/common/stop-blocking.ts | 52 |
3 files changed, 181 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; +}); diff --git a/src/scripts/common/login/index.ts b/src/scripts/common/login/index.ts new file mode 100644 index 0000000..95b9c5b --- /dev/null +++ b/src/scripts/common/login/index.ts @@ -0,0 +1 @@ +import "@/scripts/common/login/captcha"; diff --git a/src/scripts/common/stop-blocking.ts b/src/scripts/common/stop-blocking.ts new file mode 100644 index 0000000..8588834 --- /dev/null +++ b/src/scripts/common/stop-blocking.ts @@ -0,0 +1,52 @@ +/** + * Proxies jQuery's `.bind()` (for the `"cut copy paste"` event) and + * `.keydown()` so that the website's copy/paste/right-click blocking + * is disabled. Reverts the prototypes after patching to avoid + * interfering with other code. + */ + +type JQueryBindFn = ( + eventName: string, + handler: (event: JQuery.Event) => unknown +) => JQuery; + +type JQueryKeydownFn = ( + handler: (event: JQuery.Event) => unknown +) => JQuery; + +type JQueryPrototypeShape = { + bind: JQueryBindFn; + keydown: JQueryKeydownFn +}; + +const jQueryPrototype = $.prototype as unknown as JQueryPrototypeShape; + +document.addEventListener("DOMContentLoaded", () => { + const proxiedBind = jQueryPrototype.bind; + /** Proxies jQuery `.bind()` to no-op the `"cut copy paste"` event. */ + jQueryPrototype.bind = function( + this: JQuery, + ...args: Parameters<JQueryBindFn> + ): JQuery { + const [eventName, handler] = args; + const noop = (): undefined => undefined; + return proxiedBind.call( + this, + eventName, + eventName.trim() === "cut copy paste" ? noop : handler + ); + }; + + const proxiedKeydown = jQueryPrototype.keydown; + /** Proxies jQuery `.keydown()` so all key presses are allowed. */ + jQueryPrototype.keydown = function( + this: JQuery, + ..._args: Parameters<JQueryKeydownFn> + ): JQuery { + return proxiedKeydown.call(this, () => true); + }; + + jQueryPrototype.bind = proxiedBind; + jQueryPrototype.keydown = proxiedKeydown; + document.oncontextmenu = (): boolean => true; +}); |
