textXform = { "a": "\u{10900}", "b": "\u{10901}", "c": "\u{10902}", "d": "\u{10903}", "e": "\u{10904}", "f": "\u{10905}", "g": "\u{10906}", "h": "\u{10907}", "i": "\u{10908}", "j": "\u{10909}", "k": "\u{1090A}", "l": "\u{1090B}", "m": "\u{1090C}", "n": "\u{1090D}", "o": "\u{1090E}", "p": "\u{1090F}", "q": "\u{10910}", "r": "\u{10911}", "s": "\u{10912}", "t": "\u{10913}", "u": "\u{10914}", "v": "\u{10915}", ".": "\u{1091F}", } class DroseraCaptcha extends HTMLElement { constructor() { super(); } onSquareClick(event) { const square = event.target; if (square.dataset.active === undefined) { square.dataset.active = "1"; } else if (square.dataset.active == "1") { square.dataset.active = "2"; } else if (square.dataset.active == "2") { square.dataset.active = "3"; } else { delete square.dataset.active; } event.preventDefault(); } onSquareReset(event) { const square = event.target; delete square.dataset.active; event.preventDefault(); } onSquareContext(event) { event.preventDefault(); } onResetCaptcha(event) { const grid = event.target.parentNode.getElementsByClassName("dsc-inner")[0]; for (const child of grid.childNodes) { if (!child.disabled) { delete child.dataset.active; } } } onSubmitCaptcha(event) { const grid = event.target.parentNode.getElementsByClassName("dsc-inner")[0]; const output = grid.childNodes.values().map(square => square.dataset.active ?? "0").join(""); const params = new URLSearchParams({ size: this.size, label: this.label, lock: this.lock, answer: output }) console.log("/captcha?" + params.toString()) const request = new Request("/captcha?" + params.toString()); this.outer.disabled = true; window.fetch(request).then(response => { if (response.ok) { this.outer.disabled = false; this.outer.dataset.solved = ""; if (this.reload) { location.reload(); } } else { this.outer.disabled = false; } }) } connectedCallback() { const shadow = this.attachShadow({ mode: "open" }); const linkElem = document.createElement("link"); linkElem.setAttribute("rel", "stylesheet"); linkElem.setAttribute("href", "/static/style/captcha.css"); shadow.appendChild(linkElem); const outer = document.createElement("fieldset"); outer.className = "dsc-outer"; if (this.dataset.solved !== undefined) { outer.dataset.solved = ""; } this.reload = this.dataset.reload !== undefined; const resetBtn = document.createElement("button"); resetBtn.innerText = "Reset" resetBtn.addEventListener("click", this.onResetCaptcha); const submitBtn = document.createElement("button"); submitBtn.className = "dsc-submit"; submitBtn.innerText = "Submit" submitBtn.addEventListener("click", this.onSubmitCaptcha.bind(this)); const inner = document.createElement("div"); inner.style.gridTemplateColumns = `repeat(${this.dataset.size}, 1fr)` inner.className = "dsc-inner"; for (var alp = 0; alp < this.dataset.size; alp++) { for (var bet = 0; bet < this.dataset.size; bet++) { const square = document.createElement("button"); square.tabIndex = 0; square.addEventListener("click", this.onSquareClick); square.addEventListener("auxclick", this.onSquareReset); square.addEventListener("contextmenu", this.onSquareContext); const label = document.createElement("span"); const shift = alp * this.dataset.size + bet; label.innerText = textXform[this.dataset.label.at(shift)]; const lock = this.dataset.lock.at(shift); if (lock != ".") { square.dataset.active = lock; square.disabled = true; } square.appendChild(label); inner.appendChild(square); } } this.size = this.dataset.size; this.label = this.dataset.label; this.lock = this.dataset.lock; this.outer = outer; this.inner = inner; outer.appendChild(resetBtn); outer.appendChild(inner); outer.appendChild(submitBtn); shadow.appendChild(outer); } } customElements.define("drosera-captcha", DroseraCaptcha);