River

River

Build a free personal URL shortener platform with Cloudflare

Short links, also known as short URLs or short codes, refer to URLs that are shorter in form. A short link service can generate a new, shorter URL by shortening a long, ordinary URL, making it easier to share and spread.

The main application scenarios for short links are as follows:

  • SMS Sending

Using short links in SMS can greatly reduce character count; many marketing messages now use short URLs.

  • Community Promotion

Many communities or social websites block long links. With character limits on platforms like Weibo and restrictions on public account keyword links, short URLs can reduce character count and bypass these limitations.

  • WeChat Anti-Blocking

Various blocks exist in WeChat; using short links can avoid exposing the original address keywords and circumvent blocking.

  • Dynamic Codes

Short URLs are fixed and can change the original link by modifying it without needing to change the fixed short URL; the short URL acts as an intermediary. This is mainly used in places where the cost of changing links is high, such as generating QR codes.

PS: Always use a reputable service for short link services to avoid instability.

Here, we utilize the services provided by Cloudflare Workers, which offers a free version with 100,000 requests per day, sufficient for personal use.

The script configuration is based on the open-source GitHub project AoEiuV020/Url-Shorten-Worker

What you need:

  • A registered Cloudflare account
  • Your own domain name, preferably short, resolved by Cloudflare.

Operation Process:

  1. Create a namespace in Workers KV, with any name, for example, link.

image

  1. Create a new service in Workers, with any service name, and select the HTTP router as the starter.
    image
    image
  2. Enter the Workers service, set => Variables => KV Namespace Binding, with the variable name in uppercase LINKS, and the value being the namespace name; then add a series of variables in the environment variables. The variable descriptions are as follows:

Adjust Timeout Settings
Short links generated in demo mode cannot be accessed after timeout,
Timeout settings are invalid in cases of whitelist or correct password,
Modify the variable shorten_timeout at the beginning of the script, in milliseconds, where 0 means no timeout

Adjust Whitelist
Domains in the whitelist ignore timeout for short links,
Modify the variable white_list at the beginning of the script, which is a JSON array; just write the top-level domain, and it will automatically include all second-level domains.

Disable Demo Mode
Only when demo mode is enabled can visitors add non-whitelisted addresses without a password; timeout short links will become invalid,
Modify the variable demo_mode at the beginning of the script, set to true to enable demo, and false to not accept requests without a password and non-whitelisted.

Automatically Delete Demo Records
Whether to automatically delete timeout invalid short link records in demo mode,
Modify the variable remove_completely at the beginning of the script, set to true to automatically delete timeout demo short link records, otherwise, they will only be marked as expired for historical record queries in the background.

Change Password
There is a hidden input box on the webpage to enter a password,
In cases of correct password, it ignores whitelist and timeout settings, and supports custom short links,
Modify the variable password at the beginning of the script; it is recommended to configure this sensitive information directly in the environment variables.

Change Short Link Length
The short link length refers to the length of the randomly generated key, which is the path part of the short link,
If the length is insufficient, duplicates may occur, and in case of duplicates, it will automatically extend,
Modify the variable default_len at the beginning of the script.

Note: Keys are all in uppercase.

image

  1. Return to [Resources], quickly edit, copy the code from index.js in the project, which is the following code content, save and compile.
    image
//index.js source code


// Project name, determines from which project the HTML is fetched,

const github_repo = typeof(GITHUB_REPO)!="undefined" ? GITHUB_REPO

    : 'charlie-king/Url-Shorten-Worker'

// Project version, CDN will cache, so when updated, specify the version,

const github_version = typeof(GITHUB_VERSION)!="undefined" ? GITHUB_VERSION

    : '@main'

// Password, in cases of correct password, it ignores whitelist and timeout settings, and supports custom short links,

const password = typeof(PASSWORD)!="undefined" ? PASSWORD

    : 'AoEiuV020 yes'

// Short link timeout, in milliseconds, supports integer multiplication, 0 means no timeout,

const shorten_timeout = typeof(SHORTEN_TIMEOUT)!="undefined" ? SHORTEN_TIMEOUT.split("*").reduce((a,b)=>parseInt(a)*parseInt(b),1)

    : (1000 * 60 * 10)

// Default short link key length, will automatically extend in case of duplicates,

const default_len = typeof(DEFAULT_LEN)!="undefined" ? parseInt(DEFAULT_LEN)

    : 6

// Set to true to enable demo mode, otherwise requests without a password and non-whitelisted will not be accepted; this allows visitors to try it out, and will expire after timeout,

const demo_mode = typeof(DEMO_MODE)!="undefined" ? DEMO_MODE === 'true'

    : true

// Set to true to automatically delete timeout demo short link records, otherwise, they will only be marked as expired for historical record queries in the background,

const remove_completely = typeof(REMOVE_COMPLETELY)!="undefined" ? REMOVE_COMPLETELY === 'true'

    : true

// Domains in the whitelist ignore timeout, in JSON array format, just write the top-level domain, and it will automatically include all second-level domains,

const white_list = JSON.parse(typeof(WHITE_LIST)!="undefined" ? WHITE_LIST

    : `[

"aoeiuv020.com",

"aoeiuv020.cn",

"aoeiuv020.cc",

"020.name"

    ]`)

// When demo mode is enabled, this notice is displayed on the webpage to prevent abuse, and does not need to specify when it will expire,

const demo_notice = typeof(DEMO_NOTICE)!="undefined" ? DEMO_NOTICE

    : `Note: To prevent the demo service from being abused, all links generated by the demo site may expire at any time. For long-term use, please set it up yourself.`

//console.log(`${github_repo}, ${github_version}, ${password}, ${shorten_timeout}, ${demo_mode}, ${white_list}, ${demo_notice}`)

const html404 = `<!DOCTYPE html>

<body>

  <h1>404 Not Found.</h1>

  <p>The url you visit is not found.</p>

</body>`

  
  

async function randomString(len) {

    let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';    /****Default excludes easily confused characters oOLl,9gq,Vv,Uu,I1****/

    let maxPos = $chars.length;

  let result = '';

  for (i = 0; i < len; i++) {

    result += $chars.charAt(Math.floor(Math.random() * maxPos));

  }

  return result;

}

async function checkURL(url){

    let str=url;

    let Expression=/^http(s)?:\/\/(.*@)?([\w-]+\.)*[\w-]+([_\-.,~!*:#()\w\/?%&=]*)?$/;

    let objExp=new RegExp(Expression);

    if(objExp.test(str)==true){

      if (str[0] == 'h')

        return true;

      else

        return false;

    }else{

        return false;

    }

}

// Check if the domain is in the whitelist, parameters only include the domain part,

async function checkWhite(host){

    return white_list.some((h) => host == h || host.endsWith('.'+h))

}

async function md5(message) {

  const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array

  const hashBuffer = await crypto.subtle.digest('MD5', msgUint8) // hash the message

  const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array

  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string

  

  return hashHex

}

async function checkHash(url, hash) {

    if (!hash) {

        return false

    }

    return (await md5(url+password)) == hash

}

async function save_url(url, key, admin, len) {

  len = len || default_len;

    // If the password is correct and a key is specified, directly override the old value,

    const override = admin && key

    if (!override) {

        // In cases of incorrect password, ignore the specified key,

        key = await randomString(len)

    }

    const is_exists = await load_url(key)

    console.log("key exists " + key + " " + is_exists)

    if (override || !is_exists) {

        var mode = 3

        if (admin) {

            mode = 0

        }

        let value = `${mode};${Date.now()};${url}`

        if (remove_completely && mode != 0 && !await checkWhite(new URL(url).host)) {

          // Use expirationTtl to automatically delete expired records; errors occur if below 60 seconds,

          let ttl = Math.max(60, shorten_timeout / 1000)

          console.log("key auto remove: " + key + ", " + ttl + "s")

          return await LINKS.put(key, value, {expirationTtl: ttl}),key

        } else {

          return await LINKS.put(key, value),key

        }

    } else {

        return await save_url(url, key, admin, len + 1)

    }

}

async function load_url(key) {

    const value = await LINKS.get(key)

    if (!value) {

        return null

    }

    const list = value.split(';')

    console.log("value split " + list)

    var url

    if (list.length == 1) {

        // For old data, temporarily redirect normally,

        url = list[0]

    } else {

        url = list[2]

        const mode = parseInt(list[0])

        const create_time = parseInt(list[1])

        if (mode != 0 && shorten_timeout > 0

            && Date.now() - create_time > shorten_timeout) {

            const host = new URL(url).host

            if (await checkWhite(host)) {

                console.log('white list')

            } else {

                // Handle timeout and not found the same way,

                console.log("shorten timeout")

                return null

            }

        }

    }

    return url

}

async function handleRequest(request) {

  console.log(request)

  if (request.method === "POST") {

    let req=await request.json()

    console.log("url " + req["url"])

    let admin = await checkHash(req["url"], req["hash"])

    console.log("admin " + admin)

    if(!await checkURL(req["url"]) || (!admin && !demo_mode && !await checkWhite(new URL(req["url"]).host))){

    // In non-demo mode, non-whitelisted addresses are treated as illegal addresses,

    return new Response(`{"status":500,"key":": Error: Url illegal."}`, {

      headers: {

      "content-type": "text/html;charset=UTF-8",

      "Access-Control-Allow-Origin":"*",

      "Access-Control-Allow-Methods": "POST",

      },

    })}

    let stat,random_key=await save_url(req["url"], req["key"], admin)

    console.log("stat " + stat)

    if (typeof(stat) == "undefined"){

      return new Response(`{"status":200,"key":"/`+random_key+`"}`, {

      headers: {

      "content-type": "text/html;charset=UTF-8",

      "Access-Control-Allow-Origin":"*",

      "Access-Control-Allow-Methods": "POST",

      },

    })

    }else{

      return new Response(`{"status":200,"key":": Error:Reach the KV write limitation."}`, {

      headers: {

      "content-type": "text/html;charset=UTF-8",

      "Access-Control-Allow-Origin":"*",

      "Access-Control-Allow-Methods": "POST",

      },

    })}

  }else if(request.method === "OPTIONS"){  

      return new Response(``, {

      headers: {

      "content-type": "text/html;charset=UTF-8",

      "Access-Control-Allow-Origin":"*",

      "Access-Control-Allow-Methods": "POST",

      },

    })

  

  }

  

  const requestURL = new URL(request.url)

  const path = requestURL.pathname.split("/")[1]

  console.log(path)

  if(!path){

  

    const html= await fetch(`https://cdn.jsdelivr.net/gh/${github_repo}${github_version}/index.html`)

    const text = (await html.text())

        .replaceAll("###GITHUB_REPO###", github_repo)

        .replaceAll("###GITHUB_VERSION###", github_version)

        .replaceAll("###DEMO_NOTICE###", demo_notice)

    return new Response(text, {

    headers: {

      "content-type": "text/html;charset=UTF-8",

    },

  })

  }

  const url = await load_url(path)

  if (!url) {

    // If not found or timeout, directly return 404,

    console.log('not found')

    return new Response(html404, {

      headers: {

        "content-type": "text/html;charset=UTF-8",

      },

      status: 404

    })

  }

  return Response.redirect(url, 302)

}

  

addEventListener("fetch", async event => {

  event.respondWith(handleRequest(event.request))

})
  1. The final step is to add a custom domain name, which must first be hosted on Cloudflare for resolution. At this point, you can access this domain name to generate short links.
    image
    image

References#

Github Repo not found

The embedded github repo could not be found…

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.