├── .node-version ├── public ├── .assetsignore ├── ads.txt ├── banner.png ├── favicon.ico └── favicon.png ├── src ├── env.d.ts ├── pages │ ├── 404.astro │ └── index.astro ├── config.js ├── worker.js ├── assets │ └── js │ │ ├── utils.js │ │ ├── display.js │ │ └── tool.js ├── components │ ├── Footer.astro │ ├── Header.astro │ ├── Display.astro │ ├── Tool.astro │ ├── Content.astro │ └── Layout.astro └── middleware.js ├── tsconfig.json ├── pnpm-workspace.yaml ├── .vscode ├── extensions.json └── launch.json ├── tailwind.config.js ├── .editorconfig ├── wrangler.jsonc ├── astro.config.js ├── .gitignore ├── biome.json ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /.node-version: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /public/.assetsignore: -------------------------------------------------------------------------------- 1 | _worker.js 2 | _routes.json -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } 4 | -------------------------------------------------------------------------------- /public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-2688278566540909, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - esbuild 3 | - workerd 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miantiao-me/loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong/HEAD/public/banner.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miantiao-me/loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miantiao-me/loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../components/Layout.astro"; 3 | import Display from "../components/Display.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../components/Layout.astro"; 3 | import Content from "../components/Content.astro"; 4 | import Tool from "../components/Tool.astro"; 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const domain = 2 | "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong"; 3 | 4 | export default { 5 | title: "L(o*62).ong", 6 | description: "Make your URL longer", 7 | url: `https://${domain}`, 8 | image: `https://${domain}/banner.png`, 9 | }; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | import { App } from 'astro/app'; 2 | import { handle } from '@astrojs/cloudflare/handler' 3 | 4 | export function createExports(manifest) { 5 | const app = new App(manifest); 6 | return { 7 | default: { 8 | async fetch(request, env, ctx) { 9 | return handle(manifest, app, request, env, ctx); 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/wrangler/config-schema.json", 3 | "name": "looong", 4 | "main": "dist/_worker.js/index.js", 5 | "compatibility_date": "2025-07-12", 6 | "compatibility_flags": [ 7 | "nodejs_compat" 8 | ], 9 | "assets": { 10 | "directory": "./dist", 11 | "binding": "ASSETS" 12 | }, 13 | "minify": true, 14 | "observability": { 15 | "enabled": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /astro.config.js: -------------------------------------------------------------------------------- 1 | import cloudflare from "@astrojs/cloudflare"; 2 | import tailwind from "@astrojs/tailwind"; 3 | import { defineConfig } from "astro/config"; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | output: "server", 8 | adapter: cloudflare({ 9 | imageService: "compile", 10 | workerEntryPoint: { 11 | path: 'src/worker.js', 12 | } 13 | }), 14 | integrations: [tailwind()], 15 | }); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | 27 | .netlify/ 28 | .vercel/ 29 | .zeabur/ 30 | .wrangler/ 31 | design/ -------------------------------------------------------------------------------- /src/assets/js/utils.js: -------------------------------------------------------------------------------- 1 | export function stringToBinary(str) { 2 | return str 3 | .split("") 4 | .map((char) => { 5 | const binary = char.charCodeAt(0).toString(2); 6 | return binary.padStart(16, "0"); 7 | }) 8 | .join("") 9 | .replaceAll("1", "O") 10 | .replaceAll("0", "o"); 11 | } 12 | 13 | export function binaryToString(str) { 14 | const binaryStr = str.replaceAll("O", "1").replaceAll("o", "0"); 15 | let result = ""; 16 | for (let i = 0; i < binaryStr.length; i += 16) { 17 | const byte = binaryStr.slice(i, i + 16); 18 | const charCode = Number.parseInt(byte, 2); 19 | result += String.fromCharCode(charCode); 20 | } 21 | return result; 22 | } 23 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "all": true, 10 | "style": { 11 | "useFilenamingConvention": "off", 12 | "noDefaultExport": "off" 13 | }, 14 | "suspicious": { 15 | "noConsole": "off" 16 | } 17 | } 18 | }, 19 | "vcs": { 20 | "enabled": true, 21 | "clientKind": "git", 22 | "defaultBranch": "main", 23 | "useIgnoreFile": true 24 | }, 25 | "overrides": [ 26 | { 27 | "include": ["*.astro"], 28 | "organizeImports": { 29 | "enabled": false 30 | }, 31 | "linter": { 32 | "rules": { 33 | "correctness": { 34 | "noUnusedImports": "off" 35 | } 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/js/display.js: -------------------------------------------------------------------------------- 1 | import { binaryToString } from "./utils.js"; 2 | 3 | const matchedPath = location.pathname.match(/^\/l(O+)ng$/i); 4 | 5 | if (matchedPath) { 6 | try { 7 | const validUrl = new URL(binaryToString(matchedPath[1])); 8 | if (!validUrl.href.startsWith("http")) { 9 | throw new Error("invalid URL"); 10 | } 11 | const realUrlText = document.getElementById("real-url-text"); 12 | const realUrl = document.getElementById("real-url"); 13 | const diaplayUrl = document.getElementById("display-url"); 14 | realUrlText.textContent = validUrl.href; 15 | realUrl.href = validUrl.href; 16 | diaplayUrl.style.display = "block"; 17 | } catch (e) { 18 | console.warn("binaryToString fail: ", matchedPath, e); 19 | location.replace("/"); 20 | } 21 | } else { 22 | location.replace("/"); 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "long", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "wrangler pages dev ./dist", 10 | "astro": "astro", 11 | "update": "taze -I", 12 | "check": "biome check --unsafe .", 13 | "check:fix": "biome check --write --unsafe ." 14 | }, 15 | "dependencies": { 16 | "@astrojs/check": "^0.9.4", 17 | "@astrojs/cloudflare": "^12.6.0", 18 | "@astrojs/tailwind": "^6.0.2", 19 | "astro": "^5.11.0", 20 | "astro-seo": "^0.8.4", 21 | "tailwindcss": "^3.4.17" 22 | }, 23 | "devDependencies": { 24 | "@biomejs/biome": "^1.9.4", 25 | "taze": "^19.1.0", 26 | "typescript": "^5.8.3", 27 | "wrangler": "^4.24.3" 28 | }, 29 | "packageManager": "pnpm@10.13.1" 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/js/tool.js: -------------------------------------------------------------------------------- 1 | import { stringToBinary } from "./utils.js"; 2 | 3 | const originUrl = document.getElementById("origin-url"); 4 | const longUrl = document.getElementById("long-url"); 5 | 6 | originUrl?.addEventListener("input", () => { 7 | try { 8 | const validUrl = new URL(originUrl.value); 9 | longUrl.value = `${location.origin}/l${stringToBinary(validUrl.href)}ng`; 10 | } catch (e) { 11 | console.error(e); 12 | longUrl.value = ""; 13 | } 14 | }); 15 | 16 | const openUrl = document.getElementById("open-url"); 17 | const copyUrl = document.getElementById("copy-url"); 18 | 19 | openUrl.addEventListener("click", () => { 20 | longUrl.value && window.open(longUrl.value, "_blank"); 21 | }); 22 | copyUrl.addEventListener("click", async () => { 23 | if (longUrl.value) { 24 | await navigator.clipboard.writeText(longUrl.value); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/components/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import config from "../config.js"; 3 | --- 4 | 5 |
6 | 7 |

{config.title}

8 |
9 |

10 | Make your URL 11 |
12 | looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonger 13 |

14 | 32 |
33 | -------------------------------------------------------------------------------- /src/components/Display.astro: -------------------------------------------------------------------------------- 1 |
2 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | import { binaryToString } from "./assets/js/utils.js"; 2 | 3 | const pathRegex = /^\/l(O+)ng$/i; 4 | 5 | async function isSafeUrl( 6 | url, 7 | DoH = "https://family.cloudflare-dns.com/dns-query", 8 | ) { 9 | let safe = false; 10 | try { 11 | const { hostname } = new URL(url); 12 | const res = await fetch(`${DoH}?type=A&name=${hostname}`, { 13 | headers: { 14 | accept: "application/dns-json", 15 | }, 16 | cf: { 17 | cacheEverything: true, 18 | cacheTtlByStatus: { "200-299": 86400 }, 19 | }, 20 | }); 21 | const dnsResult = await res.json(); 22 | if (dnsResult && Array.isArray(dnsResult.Answer)) { 23 | const isBlock = dnsResult.Answer.some( 24 | (answer) => answer.data === "0.0.0.0", 25 | ); 26 | safe = !isBlock; 27 | } 28 | } catch (e) { 29 | console.warn("isSafeUrl fail: ", url, e); 30 | } 31 | return safe; 32 | } 33 | 34 | export async function onRequest(context, next) { 35 | const matchedPath = context?.url?.pathname?.match(pathRegex); 36 | 37 | if (matchedPath) { 38 | try { 39 | const DoH = context?.locals?.runtime?.env?.DOH || import.meta.env.DOH; 40 | const url = binaryToString(matchedPath[1]); 41 | const safe = await isSafeUrl(url, DoH); 42 | if (safe) { 43 | return Response.redirect(url, 308); 44 | } 45 | } catch (_e) { 46 | return next(); 47 | } 48 | } 49 | 50 | return next(); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Tool.astro: -------------------------------------------------------------------------------- 1 |
2 |
3 | Origin URL 4 | 10 |
11 |
12 | Long URL 13 | 19 |
20 | 26 | 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # L(o*62).ong 2 | 3 | 4 | Featured on Hacker News 9 | 10 | 11 | 12 | Featured on ProductHunt 17 | 18 | 19 | ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?style=flat&logo=cloudflare&logoColor=white) 20 | ![Astro](https://img.shields.io/badge/Astro-BC52EE?style=flat&logo=astro&logoColor=white) 21 | ![ChatGPT](https://img.shields.io/badge/ChatGPT-74aa9c?style=flat&logo=openai&logoColor=white) 22 | [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=flat&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/ccbikai) 23 | [![Github Sponsors](https://img.shields.io/badge/Sponsor-30363D?style=flat&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/ccbikai) 24 | ![Better Stack](https://uptime.betterstack.com/status-badges/v1/monitor/1cwe5.svg) 25 | 26 | Make your URL looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonger 27 | 28 | 29 | 30 | ## Issue 31 | 32 | - [The web is not friendly to long domain names](https://github.com/ccbikai/loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong/issues/2) 33 | 34 | --- 35 | 36 | ![Banner](./public/banner.png) 37 | -------------------------------------------------------------------------------- /src/components/Content.astro: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | A domain name can be registered with up to 63 characters, so I registered loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.ong. 10 |
11 | Then I created an interesting URL lengthening tool, which is this page you're 12 | seeing now. 13 |
14 | The maximum length for a domain name is 253 characters, hence Longer than Long was born. 20 |
21 | I encountered quite a few issues during the process, which I've documented 22 | in this Github Issue. Feel free to join the discussion. 28 |

29 |
30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /src/components/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { SEO } from "astro-seo"; 3 | import Footer from "../components/Footer.astro"; 4 | import Header from "../components/Header.astro"; 5 | import config from "../config.js"; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | 36 | 37 | 38 | 39 | 49 |
50 |
51 | 52 |
53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | --------------------------------------------------------------------------------