NoCaptcha:拦截人类的机人验证

你还在为你的网站拦不住人类而烦恼吗?

快来使用机人验证吧

demo:https://filecdn.runoneall.nyc.mn/Ncaptcha/

原始码:

  • -

    ncaptcha.html


    html</s><i> </i>&lt;link rel="stylesheet" href="./ncaptcha.css"&gt; &lt;script src="./ncaptcha.js"&gt;&lt;/script&gt; &lt;div class="ncaptcha"&gt;&lt;/div&gt;<i> </i><e>
  • -

    ncaptcha.css

    ```css .ncaptcha-container { width: 300px; height: 80px;
    position: relative;
    
    border: 1px solid #d3d3d3;
    border-radius: 3px;
    box-shadow: 0 0 4px 1px rgba(0, 0, 0, .08);
    background: #f9f9f9;
    
    margin-top: 6px;
    margin-bottom: 6px;
    padding-left: 12px;
    
    display: flex;
    align-items: center;
    flex-direction: row;
    

    }

    .ncaptcha-error {
    position: absolute;
    color: red;
    left: 0;
    top: 0;
    font-family: Roboto, arial, sans-serif;
    }

    .ncaptcha-checkbox {
    width: 24px;
    height: 24px;
    border: 2px solid #c1c1c1;
    border-radius: 2px;
    background-color: #fff;
    }

    .ncaptcha-spinner {
    width: 24px;
    height: 24px;
    border: 5px solid #4d90fe;
    border-radius: 24px;
    border-bottom-color: transparent;
    border-left-color: transparent;

    animation: spinner-spin linear 2.5s infinite;
    

    }

    .ncaptcha-checkmark>img {
    width: 38px;
    height: 38px;
    margin-right: -8px;
    }

    .ncaptcha-text {
    margin-left: 12px;
    font-size: 16px;
    font-family: Roboto, arial, sans-serif;
    }

    .ncaptcha-info {
    margin-left: auto;
    margin-right: 12px;

    display: flex;
    flex-direction: column;
    align-items: center;
    
    font-family: Roboto, arial, sans-serif;
    font-size: 10px;
    color: #555;
    

    }

    .ncaptcha-info>div {
    font-weight: 400;
    margin-top: 2px;
    }

    @keyframes spinner-spin {
    0% {
    transform: rotateZ(0deg)
    }

    10% {
        transform: rotateZ(135deg)
    }
    
    25% {
        transform: rotateZ(245deg)
    }
    
    60% {
        transform: rotateZ(700deg)
    }
    
    75% {
        transform: rotateZ(810deg)
    }
    
    to {
        transform: rotateZ(1080deg)
    }
    

    }

    /* Popup window */

    .ncaptcha-dialog::backdrop {
    background: rgb(255, 255, 255);
    opacity: 0.5;
    }

    .ncaptcha-dialog {
    border: 0px;
    padding: 0px;
    box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.08);
    }

    .ncaptcha-dialog[open] {
    animation: fadeIn 0.3s ease;
    }

    @keyframes fadeIn {
    from {
    opacity: 0;
    }

    to {
        opacity: 1;
    }
    

    }

    .ncaptcha-question {
    width: fit-content;
    border: 1px solid rgb(204, 204, 204);
    padding: 7px;
    }

    .ncaptcha-actions {
    border: 1px solid rgb(204, 204, 204);
    border-top: 0px;
    padding: 7px;

    display: flex;
    

    }

    .ncaptcha-title {
    width: 352px;
    height: 100px;
    background-color: #1a73e8;

    font-family: Roboto, arial, sans-serif;
    color: white;
    
    display: flex;
    flex-direction: column;
    
    padding-left: 24px;
    padding-right: 24px;
    padding-top: 24px;
    

    }

    .ncaptcha-title>strong {
    font-size: 28px;
    }

    .ncaptcha-images {
    margin-top: 7px;
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    width: 100%;
    }

    .ncaptcha-image {
    flex: auto;
    min-width: 30%;
    aspect-ratio: 1 / 1;

    background-size: 100% 100%;
    
    transition: all 0.1s ease;
    

    }

    .ncaptcha-verify-btn {
    cursor: pointer;
    height: 42px;
    min-width: 100px;
    margin-left: auto;
    border: 0;
    background: #1a73e8;
    color: white;
    font-family: Roboto, arial, sans-serif;
    font-size: 14px;
    border-radius: 2px;
    padding-right: 10px;
    padding-left: 10px;
    }

    .ncaptcha-verify-btn:disabled {
    background-color: rgba(73, 143, 225, 0.50);
    cursor: default;
    }

    .ncaptcha-select {
    width: 32px;
    height: 32px;
    background-color: white;
    border-radius: 32px;
    position: absolute;
    top: -16px;
    left: -16px;
    }
    ```

  • -

    ncaptcha.js

    ```javascript (function () {
    if (!window.OffscreenCanvas) {
        alert("nCAPTCHA: Your browser does not support OffscreenCanvas. Please update your browser.")
    }
    if (typeof HTMLDialogElement !== 'function') {
        alert("nCAPTCHA: Your browser does not support HTML dialog. Please update your browser.")
    }
    
    let API = "https://ncaptcha.1000005.xyz";
    
    let html = `
    &lt;div class="ncaptcha-base"&gt;
    
    &lt;input name="ncaptcha-response" class="ncaptcha-response" style="display: none;"&gt;
    
    &lt;div class="ncaptcha-container"&gt;
    &lt;span class="ncaptcha-error"&gt;&lt;/span&gt;
    
    &lt;div class="ncaptcha-checkbox"&gt;&lt;/div&gt;
    
    &lt;div class="ncaptcha-spinner" style="display: none;"&gt;&lt;/div&gt;
    
    &lt;div class="ncaptcha-checkmark" style="display: none;"&gt;
    &lt;img src="./checkmark.svg"&gt;
    &lt;/div&gt;
    
    &lt;div class="ncaptcha-text"&gt;I'm not a human&lt;/div&gt;
    
    &lt;div class="ncaptcha-info"&gt;
    &lt;img src="./icon.svg" width="32px" height="32px" alt="ncaptcha-icon"&gt;
    &lt;div&gt;nCAPTCHA&lt;/div&gt;
    &lt;div&gt;Source code&lt;/div&gt;
    &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;dialog class="ncaptcha-dialog"&gt;
    
    &lt;div class="ncaptcha-question"&gt;
    &lt;div class="ncaptcha-title"&gt;
    Select all images with
    &lt;strong&gt;&lt;/strong&gt;
    Click verify once there are none left.
    &lt;/div&gt;
    &lt;div class="ncaptcha-images"&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;div class="ncaptcha-image"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class="ncaptcha-actions"&gt;
    &lt;button class="ncaptcha-verify-btn" type="button"&gt;VERIFY&lt;/button&gt;
    &lt;/div&gt;
    
    &lt;/dialog&gt;
    
    &lt;/div&gt;
    `;
    
    document.addEventListener("DOMContentLoaded", () =&gt; {
    
        document.querySelectorAll(".ncaptcha").forEach((each) =&gt; {
            each.innerHTML = html;
    
            let checkbox = each.querySelector(".ncaptcha-checkbox");
            let spinner = each.querySelector(".ncaptcha-spinner");
            let checkmark = each.querySelector(".ncaptcha-checkmark");
    
            let siwtchState = (state) =&gt; {
                checkbox.style.display = "none";
                spinner.style.display = "none";
                checkmark.style.display = "none";
                switch (state) {
                    case "checkbox":
                        checkbox.style.display = "";
                        break;
                    case "spinner":
                        spinner.style.display = "";
                        break;
                    case "checkmark":
                        checkmark.style.display = "";
                        break;
                }
            }
    
            // draw a base64 encoded PNG image to context
            let drawImage = (ctx, img, x, y) =&gt; {
                return new Promise((resolve) =&gt; {
                    let i = new Image();
                    i.onload = () =&gt; {
                        ctx.drawImage(i, x, y, 200, 200, 0, 0, 200, 200);
                        resolve()
                    }
                    i.src = "data:image/png;base64," + img;
            })
        }
    
            let dialog = each.querySelector(".ncaptcha-dialog");
    
            checkbox.addEventListener("click", async () =&gt; {
                siwtchState("spinner");
    
                let challengeId = "";
                let answers = [];
    
                // clear error message
                each.querySelector(".ncaptcha-error").innerHTML = "";
    
                // clear all event listners
                let clone = dialog.cloneNode(true);
                dialog.parentElement.replaceChild(clone, dialog);
                dialog = clone;
    
                // enable the button
                dialog.querySelector(".ncaptcha-verify-btn").removeAttribute("disabled");
    
                // close the dialog when clicking outside
                dialog.addEventListener("click", (e) =&gt; {
                    if (e.target.className === "ncaptcha-dialog")
                        dialog.close();
            })
    
                // obtain challenge
                let resp = await (await fetch(API + "/challenge", {
                    mode: "no-cors"
            })).json();
    
            challengeId = resp["id"];
            dialog.querySelector(".ncaptcha-title&gt;strong").textContent = resp["select"];
    
            // split the image and draw to tiles
            let canvas = new OffscreenCanvas(200,
                200);
            let context = canvas.getContext("2d");
    
            let coordinates = [
                [0, 0], [200, 0], [400, 0],
                [0, 200], [200, 200], [400, 200],
                [0, 400], [200, 400], [400, 400]
            ];
    
            let tiles = dialog.querySelectorAll(".ncaptcha-image");
            for (let i = 0; i &lt; tiles.length; i++) {
                await drawImage(context, resp["challenge"], coordinates[i][0], coordinates[i][1])
                let blob = await canvas.convertToBlob();
                tiles.item(i).style.background = "url(" + URL.createObjectURL(blob) + ")";
                tiles.item(i).style["background-size"] = "cover";
    
                // reset selected tiles
                tiles.item(i).innerHTML = "";
                tiles.item(i).style.transform = "";
            }
    
            // click to select a tile
            for (let i = 0; i &lt; tiles.length; i++) {
                tiles.item(i).addEventListener("click", (e) =&gt; {
                    if (answers.includes(i)) {
                        // unselect
                        e.target.style.transform = "";
                        e.target.innerHTML = "";
                        answers = answers.filter((n) =&gt; n !== i);
                    } else {
                        // select
                        e.target.style.transform = "scale(0.8)";
                        setTimeout(() =&gt; {
                            e.target.innerHTML = `&lt;img class="ncaptcha-select" src="./checkmark-circle.svg"&gt;`;
                        }, 100);
                        answers.push(i);
                    }
                });
            }
    
            // submit answer
            dialog.querySelector(".ncaptcha-verify-btn").addEventListener("click",
                async () =&gt; {
                    dialog.querySelector(".ncaptcha-verify-btn").setAttribute("disabled", true);
    
                    answers.sort((a, b) =&gt; a - b);
    
                    let body = new FormData();
                    body.set("challenge", challengeId);
                    body.set("ans", answers.join(","));
    
                    let resp = await (await fetch(API + "/answer", {
                        method: "POST",
                        body: body
                    })).text();
    
                    if (resp.startsWith("TOKEN_")) {
                        each.querySelector(".ncaptcha-response").value = resp.substring(6);
                        siwtchState("checkmark");
                    } else {
                        each.querySelector(".ncaptcha-error").textContent = resp;
                    }
    
                    dialog.close();
    
                });
    
            dialog.showModal();
            siwtchState("checkbox");
        })
    });
    

    });

    })()
    ```

  • 使用到的图标

  • -

    icon.svg

    [upl-image-preview url=https://s.rmimg.com/2024-11-04/1730704331-965520-icon.svg]

  • -

    checkmark.svg
    [upl-image-preview url=https://s.rmimg.com/2024-11-04/1730704366-873610-checkmark.svg]

  • -

    checkmark-circle.svg
    [upl-image-preview url=https://s.rmimg.com/2024-11-04/1730704395-516797-checkmark-circle.svg]

  • [“NoCaptcha\uff1a\u62e6\u622a\u4eba\u7c7b\u7684\u4eba\u673a\u9a8c\u8bc1”,“NoCaptcha\uff1a\u62e6\u622a\u4eba\u7c7b\u7684\u673a\u4eba\u9a8c\u8bc1”]

    整活…

    太有活了哈哈哈哈

    [[2],[2,40]]

    点验证半天没反应,控制台一看fetch直接404摊牌,再一看URL原来是因为后面带上了别的符号:ac02:

    @“Yucho”#p139568

    还在修,等等

    完蛋了,我居然过了…

    [upl-image-preview url=https://s.rmimg.com/2024-11-04/1730718397-933647-screenshot-20241104-190550-trebuchet.png]


    @“Yucho”#p139568

    现在可以了,去试试

    [upl-image-preview url=https://s.rmimg.com/2024-11-04/1730720036-375357-screenshot-20241104-193210-trebuchet.png]

    又过了一次...
    我有可能太像机器人了,简称:人机
    :xhj01::xhj11: