├── API.md
├── LICENSE
├── README.md
├── g.index.html
├── g.index.js
├── index.html
├── index.js
└── main.js
/API.md:
--------------------------------------------------------------------------------
1 | # API接口文档
2 |
3 | 可以通过调用API接口,使用可编程的方式生成短链接
4 |
5 | ### 接口调用地址
6 |
7 | 自行部署的CloudFlare Worker地址,例如:https://url.dem0.workers.dev 或是自行绑定的域名
8 |
9 | ### 调用方式:HTTP POST 请求格式: JSON
10 | 示例:
11 | ```
12 | {
13 | "url": "https://example.com"
14 | }
15 | ```
16 |
17 | ### 请求参数:
18 |
19 | | 参数名 | 类型 | 说明 |是否必须|示例|
20 | | :----:| :----: | :----: | :----: | :----: |
21 | | url | string | 网址(需包括http://或https://) |必须|https://example.com|
22 |
23 | ### 响应示例 (JSON):
24 |
25 | ```
26 | {
27 | "status": 200,
28 | "key": "/demo"
29 | }
30 | ```
31 |
32 | ### 响应参数:
33 | | 参数名 | 类型 | 说明 |示例|
34 | | :----:| :----: | :----: | :----: |
35 | |status|int| 状态码:200为调用成功|200|
36 | |key|string| 短链接后缀:需要自行添加域名前缀|/xxxxxx|
37 |
38 | 注:接口只会返回短链接对应的key值,实际使用中需要添加对应的域名前缀,如:示例中返回的key参数是 "/demo" ,则我们需要添加 "https://url.dem0.workers.dev" 作为前缀,将其补全成完整的url即可使用,即:https://url.dem0.workers.dev/demo
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 xyTom
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Url-Shorten-By-CF-Worker
2 | A URL Shortener Powered by Cloudflare Worker with password protection feature
3 |
4 | This project is based on the work done by xyTom/Url-Shorten-Worker. I added a small javascript to prompt password to verify the user since ideally you do not want this service to be completely public because this kind of url shorten site usually will gett abused usage as the original author faced. Again, this is a simple javascript and no security consideration. Once tested with a better code, it will be replaced right away.
5 |
6 |
7 | Cloudflare works has 100k/day requests limistation, which is enough for a small project to use.
8 |
9 | # API [中文API文档](API.md)
10 |
11 | # Getting start
12 | 去Workers KV中创建一个命名空间
13 | ## 1. Go to Workers KV and create a namespace.
14 |
15 |
16 |
17 |
18 |
19 | ## 2. Create a new worker.
20 |
21 |
22 |
23 |
24 |
25 | 去Worker的Settings选选项卡中绑定KV Namespace
26 | ## 3. Bind an instance of a KV Namespace to access its data in this new created Worker.
27 |
28 |
29 |
30 |
31 |
32 |
33 | 其中Variable name填写`LINKS`, KV namespace填写你刚刚创建的命名空间
34 | ## 4.Where Variable name should set as `LINKS` and KV namespace is the namespace you just created in the first step.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 复制本项目中的`index.js`的代码到Cloudflare Worker
43 | ## 5. Copy the `index.js` code from this project to Cloudflare Worker.
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 点击Save and Deploy
52 | ## 6. Click Save and Deploy
53 |
54 |
55 |
56 |
57 |
58 |
59 | # Demo
60 |
61 | - https://go.51sec.org/
62 |
63 |
64 |
65 |
66 | ### Note: Because someone abuse this demo website, all the generated link will automatically expired after 24 hours. For long-term use, please deploy your own. To test this demo site, please use code 'cool'. There is a space in the prompt textbox. You might want to delete that space first then enter the password.
67 |
68 | 注意:由于该示例服务被人滥用,用于转发诈骗网站,故所有由demo网站生成的链接24小时后会自动失效,如需长期使用请自行搭建。
69 |
70 |
71 |
72 | # Example Code for Authentication
73 | This code has been put into index.html file. You might want to change it based on your needs.
74 |
75 | ```
76 |
87 |
88 | ```
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
The url you visit is not found.
5 | ` 6 | 7 | const statichtml = "https://xiaohan17.oss-cn-hangzhou.aliyuncs.com/xiaohan-Shorten-URL.html" 8 | 9 | 10 | async function randomString (len) { 11 | len = len || 6 12 | let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz23456789' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ 13 | let maxPos = $chars.length 14 | let result = '' 15 | for (i = 0; i < len; i++) { 16 | result += $chars.charAt(Math.floor(Math.random() * maxPos)) 17 | } 18 | return result 19 | } 20 | async function checkURL (URL) { 21 | let str = URL 22 | let Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/ 23 | let objExp = new RegExp(Expression) 24 | return objExp.test(str) && str[0] === 'h' 25 | } 26 | async function save_url (URL, shortStr) { 27 | console.log("shortStr:", shortStr) 28 | let random_key 29 | if (!shortStr) { 30 | random_key = await randomString() 31 | } else { 32 | random_key = shortStr 33 | } 34 | let is_exist = await LINKS.get(random_key) 35 | console.log(is_exist) 36 | if (is_exist == null) { 37 | // 正常,直接放入 38 | let stat = await LINKS.put(random_key, URL) 39 | if (typeof (stat) === "undefined") return random_key 40 | else return stat 41 | } else if (!shortStr) { 42 | // 生成的random_key重复了,递归调用 43 | return save_url(URL, null) 44 | } else 45 | // 自定义的路径已经存在了 46 | return -1 47 | 48 | } 49 | async function handleRequest (request) { 50 | console.log(request) 51 | if (request.method === "POST") { 52 | let req = await request.json() 53 | console.log(req["url"]) 54 | if (!await checkURL(req["url"])) 55 | return new Response(`{"msg":"Wrong URL"}`, { status: 400, headers: { "Content-Type": "application/json" } }) 56 | let random_key = await save_url(req["url"], req["shortStr"]) 57 | console.log(random_key) 58 | // 放成功了 59 | if (Object.prototype.toString.call(random_key) === "[object String]") 60 | return new Response(`{"data":{"shortUrl":"${random_key}"}}`, { status: 200, headers: { "Content-Type": "application/json" } }) 61 | // 自定义的路径重复了 62 | else if (random_key === -1) 63 | return new Response(`{"msg":"Customized URL used"}`, { status: 400, headers: { "Content-Type": "application/json" } }) 64 | // 没测试k-v满了之后会怎么样,如果有错的话put时应该会有返回(猜测(懒 65 | else return new Response(`{"msg":"k-v daily quota is reached. Please come back next day!"}`, { status: 500, headers: { "Content-Type": "application/json" } }) 66 | } 67 | const requestURL = new URL(request.url) 68 | // todo 规范路径的'/' 69 | const path = requestURL.pathname.toString().substring(1); 70 | console.log(path) 71 | if (!path) { 72 | 73 | const html = await fetch(statichtml) 74 | 75 | return new Response(await html.text(), { 76 | headers: { 77 | "content-type": "text/html;charset=UTF-8", 78 | }, 79 | }) 80 | } 81 | const value = await LINKS.get(path) 82 | console.log(value) 83 | 84 | 85 | const location = value 86 | if (location) { 87 | return Response.redirect(location, 302) 88 | 89 | } 90 | // If request not in kv, return 404 91 | return new Response(html404, { 92 | headers: { 93 | "content-type": "text/html;charset=UTF-8", 94 | }, 95 | status: 404 96 | }) 97 | } 98 | 99 | 100 | 101 | addEventListener("fetch", async event => { 102 | event.respondWith(handleRequest(event.request)) 103 | }) 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |The url you visit is not found.
5 | ` 6 | 7 | 8 | async function randomString(len) { 9 | len = len || 6; 10 | let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; 11 | /****Removed confusing letters an numbers, oOLl,9gq,Vv,Uu,I1****/ 12 | 13 | let maxPos = $chars.length; 14 | let result = ''; 15 | for (i = 0; i < len; i++) { 16 | result += $chars.charAt(Math.floor(Math.random() * maxPos)); 17 | } 18 | return result; 19 | } 20 | async function checkURL(URL){ 21 | let str=URL; 22 | let Expression=/http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/; 23 | let objExp=new RegExp(Expression); 24 | if(objExp.test(str)==true){ 25 | if (str[0] == 'h') 26 | return true; 27 | else 28 | return false; 29 | }else{ 30 | return false; 31 | } 32 | } 33 | async function save_url(URL){ 34 | let random_key=await randomString() 35 | let is_exist=await LINKS.get(random_key) 36 | console.log(is_exist) 37 | if (is_exist == null) 38 | return await LINKS.put(random_key, URL),random_key 39 | else 40 | save_url(URL) 41 | } 42 | async function handleRequest(request) { 43 | console.log(request) 44 | if (request.method === "POST") { 45 | let req=await request.json() 46 | console.log(req["url"]) 47 | if(!await checkURL(req["url"])){ 48 | return new Response(`{"status":500,"key":": Error: Url illegal."}`, { 49 | headers: { 50 | "content-type": "text/html;charset=UTF-8", 51 | "Access-Control-Allow-Origin":"*", 52 | "Access-Control-Allow-Methods": "POST", 53 | }, 54 | })} 55 | let stat,random_key=await save_url(req["url"]) 56 | console.log(stat) 57 | if (typeof(stat) == "undefined"){ 58 | return new Response(`{"status":200,"key":"/`+random_key+`"}`, { 59 | headers: { 60 | "content-type": "text/html;charset=UTF-8", 61 | "Access-Control-Allow-Origin":"*", 62 | "Access-Control-Allow-Methods": "POST", 63 | }, 64 | }) 65 | }else{ 66 | return new Response(`{"status":200,"key":": Error:Reach the KV write limitation."}`, { 67 | headers: { 68 | "content-type": "text/html;charset=UTF-8", 69 | "Access-Control-Allow-Origin":"*", 70 | "Access-Control-Allow-Methods": "POST", 71 | }, 72 | })} 73 | }else if(request.method === "OPTIONS"){ 74 | return new Response(``, { 75 | headers: { 76 | "content-type": "text/html;charset=UTF-8", 77 | "Access-Control-Allow-Origin":"*", 78 | "Access-Control-Allow-Methods": "POST", 79 | }, 80 | }) 81 | 82 | } 83 | 84 | const requestURL = new URL(request.url) 85 | const path = requestURL.pathname.split("/")[1] 86 | console.log(path) 87 | if(!path){ 88 | 89 | const html= await fetch("https://cdn.jsdelivr.net/gh/51sec/Url-Shorten-By-CF-Worker@main/index.html") 90 | /****customized index.html at main branch, easier to edit it****/ 91 | 92 | return new Response(await html.text(), { 93 | headers: { 94 | "content-type": "text/html;charset=UTF-8", 95 | }, 96 | }) 97 | } 98 | const value = await LINKS.get(path) 99 | console.log(value) 100 | 101 | 102 | const location = value 103 | if (location) { 104 | return Response.redirect(location, 302) 105 | 106 | } 107 | // If request not in kv, return 404 108 | return new Response(html404, { 109 | headers: { 110 | "content-type": "text/html;charset=UTF-8", 111 | }, 112 | status: 404 113 | }) 114 | } 115 | 116 | 117 | 118 | addEventListener("fetch", async event => { 119 | event.respondWith(handleRequest(event.request)) 120 | }) 121 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | let res 2 | function shorturl() { 3 | if(document.querySelector("#text").value==""){ 4 | alert("Url cannot be empty!") 5 | return 6 | } 7 | 8 | document.getElementById("searchbtn").disabled=true; 9 | document.getElementById("searchbtn").innerHTML='Please wait...'; 10 | fetch(window.location.pathname, { 11 | method: 'POST', 12 | headers: { 'Content-Type': 'application/json' }, 13 | body: JSON.stringify({ url: document.querySelector("#text").value }) 14 | }).then(function(response) { 15 | return response.json(); 16 | }) 17 | .then(function(myJson) { 18 | res = myJson; 19 | document.getElementById("searchbtn").disabled=false; 20 | document.getElementById("searchbtn").innerHTML=' Shorten it'; 21 | if(res.key!=="") 22 | document.getElementById("result").innerHTML=window.location.host+res.key; 23 | $('#exampleModal').modal('show') 24 | }).catch(function(err){alert("Unknow error. Please retry!"); 25 | console.log(err); 26 | document.getElementById("searchbtn").disabled=false; 27 | document.getElementById("searchbtn").innerHTML=' Shorten it';}) 28 | } 29 | function copyurl (id, attr) { 30 | let target = null; 31 | 32 | if (attr) { 33 | target = document.createElement('div'); 34 | target.id = 'tempTarget'; 35 | target.style.opacity = '0'; 36 | if (id) { 37 | let curNode = document.querySelector('#' + id); 38 | target.innerText = curNode[attr]; 39 | } else { 40 | target.innerText = attr; 41 | } 42 | document.body.appendChild(target); 43 | } else { 44 | target = document.querySelector('#' + id); 45 | } 46 | 47 | try { 48 | let range = document.createRange(); 49 | range.selectNode(target); 50 | window.getSelection().removeAllRanges(); 51 | window.getSelection().addRange(range); 52 | document.execCommand('copy'); 53 | window.getSelection().removeAllRanges(); 54 | console.log('Copy success') 55 | } catch (e) { 56 | console.log('Copy error') 57 | } 58 | 59 | if (attr) { 60 | // remove temp target 61 | target.parentElement.removeChild(target); 62 | } 63 | } 64 | $(function () { 65 | $('[data-toggle="popover"]').popover() 66 | }) 67 | --------------------------------------------------------------------------------