[
  {
    "path": "README.md",
    "content": "# ooo\nThe ultimate url lengthner\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\n    <meta name=\"description\" content=\"Stop using boring short URLs, lengthen your URLs now!\">\n\n    <title>ooooooooooooooooooooooo.ooo | URL Lengthner</title>\n    <link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />\n\n\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;800&display=block\" rel=\"stylesheet\" />\n\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css\">\n    <link rel=\"stylesheet\" href=\"/style.css?v=2\" />\n\n    <script defer src=\"/ooo.js?v=2\"></script>\n    <script defer src=\"/index.js?v=2\"></script>\n</head>\n\n<body>\n    <div class=\"container\">\n        <div class=\"content\">\n            <h1>Looooooooooooooooooooooonger</h1>\n            <div class=\"d-flex\">\n                <input type=\"text\" class=\"input-url\" id=\"input-url\" placeholder=\"Lengthen yooour URL!\"\n                    autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" />\n                <button id=\"ooo-button\" onclick=\"oooify()\">oOo</button>\n            </div>\n            <div id=\"output-div\" style=\"display: none;\">\n                <h2 style=\"margin-bottom: 1em;\">Yooour new url:</h2>\n                <div style=\"word-wrap: break-word; font-size: 1.2em\">\n                    <a id=\"output-url\" target=\"_blank\"></a>\n                </div>\n                <div class=\"tooltip\">\n                    <button id=\"copy-button\" onclick=\"copy()\">\n                        Cooopy\n                    </button>\n                    <span class=\"tooltip-text\">Cooopied!</span>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <footer>\n        made with\n        <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"#EF3E36\" viewBox=\"0 0 16 16\"\n            style=\"height: 1.2em; vertical-align: middle\">\n            <path fill-rule=\"evenodd\" d=\"M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314z\" />\n        </svg>\n        by\n        <a target=\"_blank\" href=\"https://luca.gg\"> luca.gg </a>\n        <span style=\"margin: 0 0.4em;\">|</span>\n        <a target=\"_blank\" href=\"https://github.com/lucaceriani/ooo\"> source </a>\n        <span style=\"margin: 0 0.4em;\">|</span>\n        <a target=\"_blank\" href=\"https://ko-fi.com/lucaceriani\">buy me a coffee</a>\n    </footer>\n</body>\n\n</html>"
  },
  {
    "path": "public/index.js",
    "content": "const input = document.getElementById(\"input-url\");\nconst output = document.getElementById(\"output-url\");\nconst oooBtn = document.getElementById(\"ooo-button\");\nconst copyBtn = document.getElementById(\"copy-button\");\n\n// init\ninput.addEventListener(\"keydown\", e => e.code == \"Enter\" ? oooify() : null)\n\n// mouse enter listener\n\nlet oooBtnInt;\n\noooBtn.addEventListener(\"mouseenter\", () => oooBtnInt = setInterval(() => {\n    console.log(\"a\");\n    if (oooBtn.innerText == \"oOo\") oooBtn.innerText = \"OoO\";\n    else oooBtn.innerText = \"oOo\";\n}, 200));\n\noooBtn.addEventListener(\"mouseleave\", () => {\n    clearInterval(oooBtnInt);\n    // oooBtn.innerText = \"oOo\";\n});\n\n\nfunction oooify() {\n\n    if (window.inBlinking) return\n\n    try {\n        new URL(input.value)\n    } catch (e) {\n\n        window.inBlinking = true\n\n        input.style.opacity = 1\n        input.disabled = true\n\n        let oldValue = input.value\n        input.value = \"Invalid URL! Noooooooooooo D:\"\n\n        let times = 0\n\n        let i = setInterval(async () => {\n            if (parseInt(input.style.opacity) == 1)\n                input.style.opacity = 0.2;\n            else\n                input.style.opacity = 1;\n\n            if (++times == 6) {\n                clearInterval(i)\n                input.value = oldValue\n                input.disabled = false\n                input.focus()\n                inBlinking = false\n            }\n        }, 150)\n        return\n    }\n\n    const domain = location.host;\n\n    let url = new OOO().encodeUrl(input.value.trim());\n    url = `${location.protocol}//${domain}/${url}`;\n\n    // show the output div\n    document.getElementById(\"output-div\").style.display = \"block\";\n\n    output.innerHTML = url;\n    output.setAttribute(\"href\", url);\n\n    input.value = \"\";\n}\n\nfunction copy() {\n    const el = document.createElement('textarea');\n    el.value = output.innerHTML;\n    document.body.appendChild(el);\n    el.select();\n    document.execCommand('copy');\n    document.body.removeChild(el);\n\n    copyBtn.parentNode.setAttribute(\"data-showme\", \"\");\n    setTimeout(() => copyBtn.parentNode.removeAttribute(\"data-showme\"), 1000);\n};\n"
  },
  {
    "path": "public/ooo.js",
    "content": "class OOO {\n    enc = [\"o\", \"ο\", \"о\", \"ᴏ\"]\n    //           006f 03bf 043e 1d0f\n    dec = {\n        \"o\": \"0\",\n        \"ο\": \"1\",\n        \"о\": \"2\",\n        \"ᴏ\": \"3\"\n    }\n\n    ver = {\n        \"oooo\": true\n    }\n\n    currVer = \"oooo\"\n\n    removeAndCheckVersion(ooo) {\n        if (this.ver[ooo.substring(0, 4)]) {\n            return ooo.substring(4)\n        } else {\n            return null\n        }\n    }\n\n    addVersion(ooo) {\n        return this.currVer + ooo\n    }\n\n    encodeUrl(url) {\n        // get utf8 array\n        let unversioned = this.toUTF8Array(url)\n            // convert to string with base 4\n            // padstart very important! otherwise missing leading 0s\n            .map(n => n.toString(4).padStart(4, \"0\"))\n            // convert to array of characters\n            .join(\"\").split(\"\")\n            // map to the o's\n            .map(x => this.enc[parseInt(x)])\n            // join into single string\n            .join(\"\")\n\n        return this.addVersion(unversioned)\n    }\n\n    decodeUrl(ooo) {\n\n        ooo = this.removeAndCheckVersion(ooo)\n        if (ooo === null) return\n\n        // get the base 4 string representation of the url\n        let b4str = ooo.split(\"\").map(x => this.dec[x]).join(\"\")\n\n        let utf8arr = []\n\n        // parse 4 characters at a time (255 in b10 = 3333 in b4)\n        // remember adding leading 0s padding\n        for (let i = 0; i < b4str.length; i += 4)\n            utf8arr.push(parseInt(b4str.substring(i, i + 4), 4))\n\n        return this.Utf8ArrayToStr(utf8arr)\n    }\n\n\n    // from https://gist.github.com/joni/3760795\n    toUTF8Array(str) {\n        var utf8 = [];\n        for (var i = 0; i < str.length; i++) {\n            var charcode = str.charCodeAt(i);\n            if (charcode < 0x80) utf8.push(charcode);\n            else if (charcode < 0x800) {\n                utf8.push(0xc0 | (charcode >> 6),\n                    0x80 | (charcode & 0x3f));\n            }\n            else if (charcode < 0xd800 || charcode >= 0xe000) {\n                utf8.push(0xe0 | (charcode >> 12),\n                    0x80 | ((charcode >> 6) & 0x3f),\n                    0x80 | (charcode & 0x3f));\n            }\n            // surrogate pair\n            else {\n                i++;\n                charcode = ((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)\n                utf8.push(0xf0 | (charcode >> 18),\n                    0x80 | ((charcode >> 12) & 0x3f),\n                    0x80 | ((charcode >> 6) & 0x3f),\n                    0x80 | (charcode & 0x3f));\n            }\n        }\n        return utf8;\n    }\n\n    // from https://gist.github.com/wumingdan/759564f6cb887a55bceb\n    Utf8ArrayToStr(array) {\n        var out, i, len, c;\n        var char2, char3;\n\n        out = \"\";\n        len = array.length;\n        i = 0;\n        while (i < len) {\n            c = array[i++];\n            switch (c >> 4) {\n                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:\n                    // 0xxxxxxx\n                    out += String.fromCharCode(c);\n                    break;\n                case 12: case 13:\n                    // 110x xxxx   10xx xxxx\n                    char2 = array[i++];\n                    out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));\n                    break;\n                case 14:\n                    // 1110 xxxx  10xx xxxx  10xx xxxx\n                    char2 = array[i++];\n                    char3 = array[i++];\n                    out += String.fromCharCode(((c & 0x0F) << 12) |\n                        ((char2 & 0x3F) << 6) |\n                        ((char3 & 0x3F) << 0));\n                    break;\n            }\n        }\n\n        return out;\n    }\n\n}\n"
  },
  {
    "path": "public/style.css",
    "content": ":root {\n    --bright: #ffde03;\n    --light: #fff3a8;\n    --dark: #100700;\n    --fs: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif,\n        \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n    --br: 5px;\n}\n\n\n* {\n    box-sizing: border-box;\n}\n\n.container {\n    margin-left: auto;\n    margin-right: auto;\n    width: 100%;\n    max-width: 1200px;\n    padding: 1em;\n}\n\n.d-flex {\n    display: flex !important;\n}\n\n.input-url {\n    width: 100%;\n    margin-right: 1em;\n    display: inline-block;\n    padding: 0.6em 0.8em;\n}\n\na {\n    text-decoration: underline;\n    color: inherit;\n}\n\nfooter {\n    text-align: center;\n    margin: 1.5em 0;\n    font-size: 80%;\n}\n\nhtml {\n    overflow-x: hidden;\n}\n\nbody {\n    overflow-x: hidden;\n    font-weight: 300;\n    background-color: var(--bright);\n    font-family: var(--fs);\n}\n\n/* footer sticky */\n\nbody {\n    display: flex;\n    min-height: 100vh;\n    flex-direction: column;\n}\n\ndiv.container {\n    flex: 1;\n}\n\n/* end footer */\n\nh1 {\n    font-weight: 800;\n    font-size: 4em;\n}\n\nh2 {\n    font-size: 1.5em;\n    font-weight: 800;\n}\n\ninput,\ninput:focus,\ninput:focus-visible,\n.form-control,\nbutton {\n    outline: none;\n    font-family: var(--fs);\n    background-color: var(--light);\n    border: 1px var(--dark);\n    border-radius: var(--br);\n    font-size: 1.2em;\n}\n\n::placeholder {\n    color: rgba(0, 0, 0, 0.5);\n}\n\n.form-control {\n    /* ovverride also :focus and :active */\n    background-color: var(--light) !important;\n    box-shadow: none !important;\n\n    font-size: 1.4em;\n    padding: 1em !important;\n}\n\nbutton {\n    border-radius: var(--br);\n    background-color: var(--dark);\n    color: var(--light);\n    margin: 1px;\n    cursor: pointer;\n    transition: 0.1s ease-in-out all;\n}\n\nbutton:hover {\n    box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 0.22);\n    transform: translate(-2px, -2px);\n}\n\n#ooo-button {\n    font-weight: 800;\n    width: 4em;\n}\n\n#copy-button {\n    font-weight: 800;\n    padding: 0.5em;\n    margin-top: 2em;\n}\n\n#output-div {\n    margin-top: 3em;\n}\n\n/* Tooltip container */\n.tooltip {\n    position: relative;\n    display: inline-block;\n}\n\n/* Tooltip text */\n.tooltip .tooltip-text {\n    visibility: hidden;\n    width: 120px;\n    background-color: rgba(0, 0, 0, 0.8);\n    color: #fff;\n    text-align: center;\n    padding: 1em 0.5em;\n    border-radius: 6px;\n\n    position: absolute;\n    z-index: 1;\n\n    top: 0;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n\n/* Show the tooltip text when you mouse over the tooltip container */\n.tooltip[data-showme] .tooltip-text {\n    visibility: visible;\n}\n\n.tooltip .tooltip-text::after {\n    content: \" \";\n    position: absolute;\n    top: 100%;\n    left: 50%;\n    margin-left: -5px;\n    border-width: 5px;\n    border-style: solid;\n    border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;\n}"
  },
  {
    "path": "worker.js",
    "content": "\n/**\n * V1 of OOO - cloudflare worker to redirect traffic to correct url\n */\nclass OOO {\n    enc = [\"o\", \"ο\", \"о\", \"ᴏ\"]\n    //           006f 03bf 043e 1d0f\n    dec = {\n        \"o\": \"0\",\n        \"ο\": \"1\",\n        \"о\": \"2\",\n        \"ᴏ\": \"3\"\n    }\n\n    ver = {\n        \"oooo\": true\n    }\n\n    currVer = \"oooo\"\n\n    removeAndCheckVersion(ooo) {\n        if (this.ver[ooo.substring(0, 4)]) {\n            return ooo.substring(4)\n        } else {\n            return null\n        }\n    }\n\n    addVersion(ooo) {\n        return this.currVer + ooo\n    }\n\n    encodeUrl(url) {\n        // get utf8 array\n        let unversioned = this.toUTF8Array(url)\n            // convert to string with base 4\n            // padstart very important! otherwise missing leading 0s\n            .map(n => n.toString(4).padStart(4, \"0\"))\n            // convert to array of characters\n            .join(\"\").split(\"\")\n            // map to the o's\n            .map(x => this.enc[parseInt(x)])\n            // join into single string\n            .join(\"\")\n\n        return this.addVersion(unversioned)\n    }\n\n    decodeUrl(ooo) {\n\n        ooo = this.removeAndCheckVersion(ooo)\n        if (ooo === null) return\n\n        // get the base 4 string representation of the url\n        let b4str = ooo.split(\"\").map(x => this.dec[x]).join(\"\")\n\n        let utf8arr = []\n\n        // parse 4 characters at a time (255 in b10 = 3333 in b4)\n        // remember adding leading 0s padding\n        for (let i = 0; i < b4str.length; i += 4)\n            utf8arr.push(parseInt(b4str.substring(i, i + 4), 4))\n\n        return this.Utf8ArrayToStr(utf8arr)\n    }\n\n\n    // from https://gist.github.com/joni/3760795\n    toUTF8Array(str) {\n        var utf8 = [];\n        for (var i = 0; i < str.length; i++) {\n            var charcode = str.charCodeAt(i);\n            if (charcode < 0x80) utf8.push(charcode);\n            else if (charcode < 0x800) {\n                utf8.push(0xc0 | (charcode >> 6),\n                    0x80 | (charcode & 0x3f));\n            }\n            else if (charcode < 0xd800 || charcode >= 0xe000) {\n                utf8.push(0xe0 | (charcode >> 12),\n                    0x80 | ((charcode >> 6) & 0x3f),\n                    0x80 | (charcode & 0x3f));\n            }\n            // surrogate pair\n            else {\n                i++;\n                charcode = ((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)\n                utf8.push(0xf0 | (charcode >> 18),\n                    0x80 | ((charcode >> 12) & 0x3f),\n                    0x80 | ((charcode >> 6) & 0x3f),\n                    0x80 | (charcode & 0x3f));\n            }\n        }\n        return utf8;\n    }\n\n    // from https://gist.github.com/wumingdan/759564f6cb887a55bceb\n    Utf8ArrayToStr(array) {\n        var out, i, len, c;\n        var char2, char3;\n\n        out = \"\";\n        len = array.length;\n        i = 0;\n        while (i < len) {\n            c = array[i++];\n            switch (c >> 4) {\n                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:\n                    // 0xxxxxxx\n                    out += String.fromCharCode(c);\n                    break;\n                case 12: case 13:\n                    // 110x xxxx   10xx xxxx\n                    char2 = array[i++];\n                    out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));\n                    break;\n                case 14:\n                    // 1110 xxxx  10xx xxxx  10xx xxxx\n                    char2 = array[i++];\n                    char3 = array[i++];\n                    out += String.fromCharCode(((c & 0x0F) << 12) |\n                        ((char2 & 0x3F) << 6) |\n                        ((char3 & 0x3F) << 0));\n                    break;\n            }\n        }\n\n        return out;\n    }\n\n}\n\nasync function handleRequest(request) {\n  let reqUrl = new URL(request.url)\n  let redirectLocation = new OOO().decodeUrl(decodeURI(reqUrl.pathname.replace(\"/\", \"\")))\n  \n  let response;\n  try {\n    response = Response.redirect(redirectLocation, 302)\n  } catch(e) {\n    response = new Response(null, {status: 400})\n  }\n\n  return response\n}\n\naddEventListener(\"fetch\", async event => {\n  event.respondWith(handleRequest(event.request))\n})\n"
  }
]