├── API.md ├── LICENSE ├── README.md ├── bootstrap.min.js ├── index-cdn.html ├── index.html ├── main.js ├── no-ref-15s.html ├── no-ref.html ├── popper.min.js ├── result.html ├── styles.css ├── theme ├── imghost │ └── index.html ├── journal │ ├── index.html │ └── result.html ├── pastebin │ └── index.html └── urlcool │ ├── index.html │ └── preview.png └── worker.js /API.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # API documentation 4 | 5 | Short links can be generated in a programmable way by calling the API interface 6 | 7 | ### API call address 8 | 9 | Self-deployed CloudFlare Worker address, for example: https://url.dem0.workers.dev or a self-bound domain name 10 | 11 | ### Calling method: HTTP POST Request format: JSON 12 | Example: 13 | ```` 14 | { 15 | "cmd": "add", 16 | "url": "https://example.com", 17 | "key": "ilikeu", 18 | "password": "bodongshouqulveweifengci" 19 | } 20 | ```` 21 | 22 | ### Request parameters: 23 | ``` 24 | cmd: add | del | qry 25 | url: The long link 26 | key: The short link 27 | password: Authentication 28 | ``` 29 | 30 | ### Example response (JSON): 31 | 32 | ```` 33 | { 34 | "status": 200, 35 | "error": "", 36 | "key": "HcAx62", 37 | "url": "" 38 | } 39 | ```` 40 | 41 | ### Response parameters: 42 | ```` 43 | "status": 200 | 500 44 | "error": error details 45 | "key": The short link 46 | "url": The long link 47 | ```` 48 | 49 | "status": 200 means success, other code means failed. 50 | -------------------------------------------------------------------------------- /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 | # 演示 2 | 短链系统 https://1way.eu.org/bodongshouqulveweifengci 3 | 4 | 网络记事本 Pastebin https://pastebin.icdyct.cloudns.asia/tieludasiliqiuweiyue 5 | 6 | 图床 Image Hosting https://imghost.crazypeace.workers.dev/imghostimghost 7 | 8 | 网络日记本 NetJournal 支持Markdown https://journal.crazypeace.workers.dev/journaljournal 9 | 10 | # 完整的部署教程 11 | https://zelikk.blogspot.com/2022/07/url-shorten-worker-hide-tutorial.html 12 | 13 | ## 如果不想被作者的更新影响 14 | - Fork一份自己的Repo. 15 | 16 | - 在Cloudflare的worker.js中搜索`"https://crazypeace.github.io/Url-Shorten-Worker/" + config.theme + "/index.html"`, 把其中的`crazypeace`改为你自己的, 这样Cloudflare的worker就会拉你自己的这一份index.html 17 | ![image](https://github.com/crazypeace/Url-Shorten-Worker/assets/665889/c98ca134-2809-4490-b9f7-ac27ba735e2e) 18 | 19 | - 在你自己fork出来的这份Repo里, 修改index.html, 搜索`"https://crazypeace.github.io/Url-Shorten-Worker/main.js"`, 把其中的`crazypeace`改为你自己的, index.html就会拉你自己的main.js 20 | ![image](https://github.com/crazypeace/Url-Shorten-Worker/assets/665889/5f283aa2-d57f-4679-a987-757f1590e8f9) 21 | 22 | - 激活你自己的Repo的GitHub Pages功能. (具体操作请google, 不详细展开了) 23 | 24 | # 在原版基础上的修改说明 25 | 直接访问域名返回404。在KV中设置一个entry,保存秘密path,只有访问这个path才显示使用页面。 26 | https://zelikk.blogspot.com/2022/07/url-shorten-worker-hide-tutorial.html 27 | 28 | 支持自定义短链 29 | https://zelikk.blogspot.com/2022/07/url-shorten-worker-custom.html 30 | 31 | API 不公开服务 32 | https://zelikk.blogspot.com/2022/07/url-shorten-worker-api-password.html 33 | 34 | 页面缓存设置过的短链 35 | https://zelikk.blogspot.com/2022/08/url-shorten-worker-localstorage.html 36 | 37 | 长链接文本框预搜索localStorage 38 | https://zelikk.blogspot.com/2022/08/url-shorten-worker-bootstrap-list-group-oninput.html 39 | 40 | 增加按钮可以删除某条短链 41 | https://zelikk.blogspot.com/2022/08/url-shorten-worker-delete-kv-localstorage.html 42 | 43 | 访问计数功能 可查询短链 成为功能完整的短链API系统 44 | https://zelikk.blogspot.com/2023/11/url-shorten-worker-visit-count-api-api.html 45 | 46 | 阅后即焚功能, 可制作一次性二维码 47 | https://zelikk.blogspot.com/2023/11/url-shorten-worker-snapchat-mode.html 48 | 49 | 增加读取 KV 中全部记录的功能 50 | https://zelikk.blogspot.com/2024/01/url-shorten-worker-load-cloudflare-kv.html 51 | 52 | 变身网络记事本 Pastebin 53 | https://zelikk.blogspot.com/2024/01/url-shorten-worker-pastebin.html 54 | 55 | 保护 'password' key 56 | https://zelikk.blogspot.com/2024/01/url-shorten-worker-password-protect-keylist.html 57 | 58 | 变身图床 Image Hosting 59 | https://zelikk.blogspot.com/2024/01/url-shorten-worker-image-hosting-base64.html 60 | 61 | 变身网络日志本 支持 Markdown 62 | https://zelikk.blogspot.com/2024/02/url-shorten-worker-netjournal.html 63 | https://zelikk.blogspot.com/2024/02/url-shorten-worker-netjournal-markdown.html 64 | https://zelikk.blogspot.com/2024/04/url-shorten-worker-netjournal-markdown.html 65 | 66 | # 用你的STAR告诉我这个Repo对你有用 Welcome STARs! :) 67 | [![Stargazers over time](https://starchart.cc/crazypeace/Url-Shorten-Worker.svg)](https://starchart.cc/crazypeace/Url-Shorten-Worker) 68 | -------------------------------------------------------------------------------- /bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Y.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Se,popperConfig:null},Fe="show",Ue="out",We={HIDE:"hide"+Oe,HIDDEN:"hidden"+Oe,SHOW:"show"+Oe,SHOWN:"shown"+Oe,INSERTED:"inserted"+Oe,CLICK:"click"+Oe,FOCUSIN:"focusin"+Oe,FOCUSOUT:"focusout"+Oe,MOUSEENTER:"mouseenter"+Oe,MOUSELEAVE:"mouseleave"+Oe},qe="fade",Me="show",Ke=".tooltip-inner",Qe=".arrow",Be="hover",Ve="focus",Ye="click",ze="manual",Xe=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Me))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(qe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,this._getPopperConfig(a)),g(o).addClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===Ue&&e._leave(null,e)};if(g(this.tip).hasClass(qe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){function e(){n._hoverState!==Fe&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),g(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()}var n=this,i=this.getTipElement(),o=g.Event(this.constructor.Event.HIDE);if(g(this.element).trigger(o),!o.isDefaultPrevented()){if(g(i).removeClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ye]=!1,this._activeTrigger[Ve]=!1,this._activeTrigger[Be]=!1,g(this.tip).hasClass(qe)){var r=_.getTransitionDurationFromElement(i);g(i).one(_.TRANSITION_END,e).emulateTransitionEnd(r)}else e();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Pe+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ke)),this.getTitle()),g(t).removeClass(qe+" "+Me)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=we(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t=t||("function"==typeof this.config.title?this.config.title.call(this.element):this.config.title)},t._getPopperConfig=function(t){var e=this;return l({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Qe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},{},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,{},e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Re[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==ze){var e=t===Be?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Be?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),this._hideModalHandler=function(){i.element&&i.hide()},g(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");!this.element.getAttribute("title")&&"string"==t||(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ve:Be]=!0),g(e.getTipElement()).hasClass(Me)||e._hoverState===Fe?e._hoverState=Fe:(clearTimeout(e._timeout),e._hoverState=Fe,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Fe&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ve:Be]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=Ue,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===Ue&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==je.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,{},e,{},"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(Ae,t,this.constructor.DefaultType),t.sanitize&&(t.template=we(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Le);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(qe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ne),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ne,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return xe}},{key:"NAME",get:function(){return Ae}},{key:"DATA_KEY",get:function(){return Ne}},{key:"Event",get:function(){return We}},{key:"EVENT_KEY",get:function(){return Oe}},{key:"DefaultType",get:function(){return He}}]),i}();g.fn[Ae]=Xe._jQueryInterface,g.fn[Ae].Constructor=Xe,g.fn[Ae].noConflict=function(){return g.fn[Ae]=ke,Xe._jQueryInterface};var $e="popover",Ge="bs.popover",Je="."+Ge,Ze=g.fn[$e],tn="bs-popover",en=new RegExp("(^|\\s)"+tn+"\\S+","g"),nn=l({},Xe.Default,{placement:"right",trigger:"click",content:"",template:''}),on=l({},Xe.DefaultType,{content:"(string|element|function)"}),rn="fade",sn="show",an=".popover-header",ln=".popover-body",cn={HIDE:"hide"+Je,HIDDEN:"hidden"+Je,SHOW:"show"+Je,SHOWN:"shown"+Je,INSERTED:"inserted"+Je,CLICK:"click"+Je,FOCUSIN:"focusin"+Je,FOCUSOUT:"focusout"+Je,MOUSEENTER:"mouseenter"+Je,MOUSELEAVE:"mouseleave"+Je},hn=function(t){function i(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}(i,t);var e=i.prototype;return e.isWithContent=function(){return this.getTitle()||this._getContent()},e.addAttachmentClass=function(t){g(this.getTipElement()).addClass(tn+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},e.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(an),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ln),e),t.removeClass(rn+" "+sn)},e._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},e._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(en);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t 2 | 3 | 4 | 5 | 6 | 7 | Shorter URL 8 | 9 | 10 | 11 | 12 |
13 |
14 |
A URL Shortener created using free Cloudflare worker and KV
15 |
16 |
17 | 18 |
19 | 20 | Long URL 21 | 22 | 23 |
24 | 25 |
26 | 27 | Custom Short URL 28 | 29 | 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 | password 39 | 40 | 41 |
42 | 43 |
44 | 45 | 48 |
49 | 50 |
51 |
LocalStorage List
52 |
53 |
54 |
55 | 56 | 57 | 58 |
59 | 60 |
61 |
62 |
63 |
64 | 65 | 66 | https://1way.eu.org/4sure 67 |
68 |
69 | https://en.y2mate.is/s36/youtube-to-mp3.html 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 | 93 | 94 | 95 | 96 | 97 | 98 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Shorter URL 8 | 9 | 12 | 13 | 14 |
15 |
16 |
A URL Shortener created using free Cloudflare worker and KV
17 |
18 |
19 | 20 |
21 | 22 | Long URL 23 | 24 | 25 |
26 | 27 |
28 | 29 | Custom Short URL 30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 | password 41 | 42 | 43 |
44 | 45 |
46 | 47 | 50 |
51 | 52 |
53 |
LocalStorage List
54 |
55 |
56 |
57 | 58 | 59 | 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | https://1way.eu.org/4sure 69 |
70 |
71 | https://en.y2mate.is/s36/youtube-to-mp3.html 72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | 80 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | let res 2 | 3 | let apiSrv = window.location.pathname 4 | let password_value = document.querySelector("#passwordText").value 5 | // let apiSrv = "https://journal.crazypeace.workers.dev" 6 | // let password_value = "journaljournal" 7 | 8 | // 这是默认行为, 在不同的index.html中可以设置为不同的行为 9 | // This is default, you can define it to different funciton in different theme index.html 10 | let buildValueItemFunc = buildValueTxt 11 | 12 | function shorturl() { 13 | if (document.querySelector("#longURL").value == "") { 14 | alert("Url cannot be empty!") 15 | return 16 | } 17 | 18 | // 短链中不能有空格 19 | // key can't have space in it 20 | document.getElementById('keyPhrase').value = document.getElementById('keyPhrase').value.replace(/\s/g, "-"); 21 | 22 | document.getElementById("addBtn").disabled = true; 23 | document.getElementById("addBtn").innerHTML = 'Please wait...'; 24 | fetch(apiSrv, { 25 | method: 'POST', 26 | headers: { 'Content-Type': 'application/json' }, 27 | body: JSON.stringify({ cmd: "add", url: document.querySelector("#longURL").value, key: document.querySelector("#keyPhrase").value, password: password_value }) 28 | }).then(function (response) { 29 | return response.json(); 30 | }).then(function (myJson) { 31 | res = myJson; 32 | document.getElementById("addBtn").disabled = false; 33 | document.getElementById("addBtn").innerHTML = 'Shorten it'; 34 | 35 | // 成功生成短链 Succeed 36 | if (res.status == "200") { 37 | let keyPhrase = res.key; 38 | let valueLongURL = document.querySelector("#longURL").value; 39 | // save to localStorage 40 | localStorage.setItem(keyPhrase, valueLongURL); 41 | // add to urlList on the page 42 | addUrlToList(keyPhrase, valueLongURL) 43 | 44 | document.getElementById("result").innerHTML = window.location.protocol + "//" + window.location.host + "/" + res.key; 45 | } else { 46 | document.getElementById("result").innerHTML = res.error; 47 | } 48 | 49 | // 弹出消息窗口 Popup the result 50 | var modal = new bootstrap.Modal(document.getElementById('resultModal')); 51 | modal.show(); 52 | 53 | }).catch(function (err) { 54 | alert("Unknow error. Please retry!"); 55 | console.log(err); 56 | document.getElementById("addBtn").disabled = false; 57 | document.getElementById("addBtn").innerHTML = 'Shorten it'; 58 | }) 59 | } 60 | 61 | function copyurl(id, attr) { 62 | let target = null; 63 | 64 | if (attr) { 65 | target = document.createElement('div'); 66 | target.id = 'tempTarget'; 67 | target.style.opacity = '0'; 68 | if (id) { 69 | let curNode = document.querySelector('#' + id); 70 | target.innerText = curNode[attr]; 71 | } else { 72 | target.innerText = attr; 73 | } 74 | document.body.appendChild(target); 75 | } else { 76 | target = document.querySelector('#' + id); 77 | } 78 | 79 | try { 80 | let range = document.createRange(); 81 | range.selectNode(target); 82 | window.getSelection().removeAllRanges(); 83 | window.getSelection().addRange(range); 84 | document.execCommand('copy'); 85 | window.getSelection().removeAllRanges(); 86 | // console.log('Copy success') 87 | } catch (e) { 88 | console.log('Copy error') 89 | } 90 | 91 | if (attr) { 92 | // remove temp target 93 | target.parentElement.removeChild(target); 94 | } 95 | } 96 | 97 | function loadUrlList() { 98 | // 清空列表 99 | let urlList = document.querySelector("#urlList") 100 | while (urlList.firstChild) { 101 | urlList.removeChild(urlList.firstChild) 102 | } 103 | 104 | // 文本框中的长链接 105 | let longUrl = document.querySelector("#longURL").value 106 | // console.log(longUrl) 107 | 108 | // 遍历localStorage 109 | let len = localStorage.length 110 | // console.log(+len) 111 | for (; len > 0; len--) { 112 | let keyShortURL = localStorage.key(len - 1) 113 | let valueLongURL = localStorage.getItem(keyShortURL) 114 | 115 | // 如果长链接为空,加载所有的localStorage 116 | // If the long url textbox is empty, load all in localStorage 117 | // 如果长链接不为空,加载匹配的localStorage 118 | // If the long url textbox is not empty, only load matched item in localStorage 119 | if (longUrl == "" || (longUrl == valueLongURL)) { 120 | addUrlToList(keyShortURL, valueLongURL) 121 | } 122 | } 123 | } 124 | 125 | function addUrlToList(shortUrl, longUrl) { 126 | let urlList = document.querySelector("#urlList") 127 | 128 | let child = document.createElement('div') 129 | child.classList.add("mb-3", "list-group-item") 130 | 131 | let keyItem = document.createElement('div') 132 | keyItem.classList.add("input-group") 133 | 134 | // 删除按钮 Remove item button 135 | let delBtn = document.createElement('button') 136 | delBtn.setAttribute('type', 'button') 137 | delBtn.classList.add("btn", "btn-danger", "rounded-bottom-0") 138 | delBtn.setAttribute('onclick', 'deleteShortUrl(\"' + shortUrl + '\")') 139 | delBtn.setAttribute('id', 'delBtn-' + shortUrl) 140 | delBtn.innerText = "X" 141 | keyItem.appendChild(delBtn) 142 | 143 | // 查询访问次数按钮 Query visit times button 144 | let qryCntBtn = document.createElement('button') 145 | qryCntBtn.setAttribute('type', 'button') 146 | qryCntBtn.classList.add("btn", "btn-info") 147 | qryCntBtn.setAttribute('onclick', 'queryVisitCount(\"' + shortUrl + '\")') 148 | qryCntBtn.setAttribute('id', 'qryCntBtn-' + shortUrl) 149 | qryCntBtn.innerText = "?" 150 | keyItem.appendChild(qryCntBtn) 151 | 152 | // 短链接信息 Short url 153 | let keyTxt = document.createElement('span') 154 | keyTxt.classList.add("form-control", "rounded-bottom-0") 155 | keyTxt.innerText = window.location.protocol + "//" + window.location.host + "/" + shortUrl 156 | keyItem.appendChild(keyTxt) 157 | 158 | // 显示二维码按钮 159 | let qrcodeBtn = document.createElement('button') 160 | qrcodeBtn.setAttribute('type', 'button') 161 | qrcodeBtn.classList.add("btn", "btn-info") 162 | qrcodeBtn.setAttribute('onclick', 'buildQrcode(\"' + shortUrl + '\")') 163 | qrcodeBtn.setAttribute('id', 'qrcodeBtn-' + shortUrl) 164 | qrcodeBtn.innerText = "QR" 165 | keyItem.appendChild(qrcodeBtn) 166 | 167 | child.appendChild(keyItem) 168 | 169 | // 插入一个二级码占位 170 | let qrcodeItem = document.createElement('div'); 171 | qrcodeItem.setAttribute('id', 'qrcode-' + shortUrl) 172 | child.appendChild(qrcodeItem) 173 | 174 | // 长链接信息 Long url 175 | child.appendChild(buildValueItemFunc(longUrl)) 176 | 177 | urlList.append(child) 178 | } 179 | 180 | function clearLocalStorage() { 181 | localStorage.clear() 182 | } 183 | 184 | function deleteShortUrl(delKeyPhrase) { 185 | // 按钮状态 Button Status 186 | document.getElementById("delBtn-" + delKeyPhrase).disabled = true; 187 | document.getElementById("delBtn-" + delKeyPhrase).innerHTML = ''; 188 | 189 | // 从KV中删除 Remove item from KV 190 | fetch(apiSrv, { 191 | method: 'POST', 192 | headers: { 'Content-Type': 'application/json' }, 193 | body: JSON.stringify({ cmd: "del", key: delKeyPhrase, password: password_value }) 194 | }).then(function (response) { 195 | return response.json(); 196 | }).then(function (myJson) { 197 | res = myJson; 198 | 199 | // 成功删除 Succeed 200 | if (res.status == "200") { 201 | // 从localStorage中删除 202 | localStorage.removeItem(delKeyPhrase) 203 | 204 | // 加载localStorage 205 | loadUrlList() 206 | 207 | document.getElementById("result").innerHTML = "Delete Successful" 208 | } else { 209 | document.getElementById("result").innerHTML = res.error; 210 | } 211 | 212 | // 弹出消息窗口 Popup the result 213 | var modal = new bootstrap.Modal(document.getElementById('resultModal')); 214 | modal.show(); 215 | 216 | }).catch(function (err) { 217 | alert("Unknow error. Please retry!"); 218 | console.log(err); 219 | }) 220 | } 221 | 222 | function queryVisitCount(qryKeyPhrase) { 223 | // 按钮状态 Button Status 224 | document.getElementById("qryCntBtn-" + qryKeyPhrase).disabled = true; 225 | document.getElementById("qryCntBtn-" + qryKeyPhrase).innerHTML = ''; 226 | 227 | // 从KV中查询 Query from KV 228 | fetch(apiSrv, { 229 | method: 'POST', 230 | headers: { 'Content-Type': 'application/json' }, 231 | body: JSON.stringify({ cmd: "qry", key: qryKeyPhrase + "-count", password: password_value }) 232 | }).then(function (response) { 233 | return response.json(); 234 | }).then(function (myJson) { 235 | res = myJson; 236 | 237 | // 成功查询 Succeed 238 | if (res.status == "200") { 239 | document.getElementById("qryCntBtn-" + qryKeyPhrase).innerHTML = res.url; 240 | } else { 241 | document.getElementById("result").innerHTML = res.error; 242 | // 弹出消息窗口 Popup the result 243 | var modal = new bootstrap.Modal(document.getElementById('resultModal')); 244 | modal.show(); 245 | } 246 | 247 | }).catch(function (err) { 248 | alert("Unknow error. Please retry!"); 249 | console.log(err); 250 | }) 251 | } 252 | 253 | function query1KV() { 254 | let qryKeyPhrase = document.getElementById("keyForQuery").value; 255 | if (qryKeyPhrase == "") { 256 | return 257 | } 258 | 259 | // 从KV中查询 Query from KV 260 | fetch(apiSrv, { 261 | method: 'POST', 262 | headers: { 'Content-Type': 'application/json' }, 263 | body: JSON.stringify({ cmd: "qry", key: qryKeyPhrase, password: password_value }) 264 | }).then(function (response) { 265 | return response.json(); 266 | }).then(function (myJson) { 267 | res = myJson; 268 | 269 | // 成功查询 Succeed 270 | if (res.status == "200") { 271 | document.getElementById("longURL").value = res.url; 272 | document.getElementById("keyPhrase").value = qryKeyPhrase; 273 | // 触发input事件 274 | document.getElementById("longURL").dispatchEvent(new Event('input', { 275 | bubbles: true, 276 | cancelable: true, 277 | })) 278 | } else { 279 | document.getElementById("result").innerHTML = res.error; 280 | // 弹出消息窗口 Popup the result 281 | var modal = new bootstrap.Modal(document.getElementById('resultModal')); 282 | modal.show(); 283 | } 284 | 285 | }).catch(function (err) { 286 | alert("Unknow error. Please retry!"); 287 | console.log(err); 288 | }) 289 | } 290 | 291 | function loadKV() { 292 | //清空本地存储 293 | clearLocalStorage(); 294 | 295 | // 从KV中查询, cmd为 "qryall", 查询全部 296 | fetch(apiSrv, { 297 | method: 'POST', 298 | headers: { 'Content-Type': 'application/json' }, 299 | body: JSON.stringify({ cmd: "qryall", password: password_value }) 300 | }).then(function (response) { 301 | return response.json(); 302 | }).then(function (myJson) { 303 | res = myJson; 304 | // 成功查询 Succeed 305 | if (res.status == "200") { 306 | 307 | // 遍历kvlist 308 | res.kvlist.forEach(item => { 309 | keyPhrase = item.key; 310 | valueLongURL = item.value; 311 | // save to localStorage 312 | localStorage.setItem(keyPhrase, valueLongURL); 313 | }); 314 | 315 | } else { 316 | document.getElementById("result").innerHTML = res.error; 317 | // 弹出消息窗口 Popup the result 318 | var modal = new bootstrap.Modal(document.getElementById('resultModal')); 319 | modal.show(); 320 | } 321 | }).catch(function (err) { 322 | alert("Unknow error. Please retry!"); 323 | console.log(err); 324 | }) 325 | } 326 | 327 | // 生成二维码 328 | function buildQrcode(shortUrl) { 329 | // 感谢项目 https://github.com/lrsjng/jquery-qrcode 330 | var options = { 331 | // render method: 'canvas', 'image' or 'div' 332 | render: 'canvas', 333 | 334 | // version range somewhere in 1 .. 40 335 | minVersion: 1, 336 | maxVersion: 40, 337 | 338 | // error correction level: 'L', 'M', 'Q' or 'H' 339 | ecLevel: 'Q', 340 | 341 | // offset in pixel if drawn onto existing canvas 342 | left: 0, 343 | top: 0, 344 | 345 | // size in pixel 346 | size: 256, 347 | 348 | // code color or image element 349 | fill: '#000', 350 | 351 | // background color or image element, null for transparent background 352 | background: null, 353 | 354 | // content 355 | // 要转换的文本 356 | text: window.location.protocol + "//" + window.location.host + "/" + shortUrl, 357 | 358 | // corner radius relative to module width: 0.0 .. 0.5 359 | radius: 0, 360 | 361 | // quiet zone in modules 362 | quiet: 0, 363 | 364 | // modes 365 | // 0: normal 366 | // 1: label strip 367 | // 2: label box 368 | // 3: image strip 369 | // 4: image box 370 | mode: 0, 371 | 372 | mSize: 0.1, 373 | mPosX: 0.5, 374 | mPosY: 0.5, 375 | 376 | label: 'no label', 377 | fontname: 'sans', 378 | fontcolor: '#000', 379 | 380 | image: null 381 | }; 382 | $("#qrcode-" + shortUrl.replace(/(:|\.|\[|\]|,|=|@)/g, "\\$1").replace(/(:|\#|\[|\]|,|=|@)/g, "\\$1") ).empty().qrcode(options); 383 | } 384 | 385 | function buildValueTxt(longUrl) { 386 | let valueTxt = document.createElement('div') 387 | valueTxt.classList.add("form-control", "rounded-top-0") 388 | valueTxt.innerText = longUrl 389 | return valueTxt 390 | } 391 | 392 | document.addEventListener('DOMContentLoaded', function() { 393 | var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')) 394 | var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { 395 | return new bootstrap.Popover(popoverTriggerEl) 396 | }); 397 | 398 | loadUrlList(); 399 | }); -------------------------------------------------------------------------------- /no-ref-15s.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Url-Shorten-Worker 6 | 7 | 8 | 9 | 10 | 11 |

Redirecting..
{__FINAL_LINK__}

12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /no-ref.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Url-Shorten-Worker 6 | 7 | 8 | 9 | 10 | 11 |

Redirecting..
{__FINAL_LINK__}

12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2019 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge}); 5 | //# sourceMappingURL=popper.min.js.map 6 | -------------------------------------------------------------------------------- /result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Url-Shorten-Worker 6 | 7 | 8 | 9 | 10 | 11 |

Redirecting..
{__FINAL_LINK__}

12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | cursor: default; 5 | } 6 | body { 7 | display: -webkit-box; 8 | display: flex; 9 | -webkit-box-align: center; 10 | align-items: center; 11 | -webkit-box-pack: center; 12 | justify-content: center; 13 | vertical-align: center; 14 | flex-wrap: wrap; 15 | align-content: center; 16 | 17 | color: #2a2b2c; 18 | background-color: #ebedee; 19 | } 20 | img { 21 | width: auto; 22 | max-width: 100%; 23 | } 24 | .card { 25 | background-color: transparent; 26 | width: 768px; 27 | } 28 | .form-control { 29 | cursor: auto; 30 | } 31 | .img-thumbnail { 32 | max-height: 320px; 33 | } 34 | .list-group-item { 35 | font-family: "Source Code Pro", Menlo, Monaco, Consolas, "Courier New", monospace; 36 | } 37 | @media (max-width: 769px) { 38 | .card { 39 | width: 80%; 40 | } 41 | } 42 | @media (max-width: 420px) { 43 | .card { 44 | width: 95%; 45 | } 46 | } 47 | @media (prefers-color-scheme: dark) { 48 | body { 49 | color: #d9d9d9; 50 | background: #1b1b1b; 51 | } 52 | .card { 53 | background-color: #252d38; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /theme/imghost/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Hosting 8 | 9 | 12 | 13 | 14 |
15 |
16 |
图片转为base64编码
17 |
18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 |
短链系统变身图床Imgage Hosting
42 |
43 |
44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 | 文件名 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 本系统密码 62 | 63 |
64 | 65 |
66 | 67 | 70 |
71 | 72 |
73 |
LocalStorage List
74 |
75 |
76 |
77 | 78 | 79 | 80 |
81 | 82 |
83 |
84 |
85 |
86 | 87 | 88 | https://1way.eu.org/4sure 89 |
90 |
91 | https://en.y2mate.is/s36/youtube-to-mp3.html 92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | 115 | 116 |
117 |
How to use
118 |
119 |
120 | 121 |
122 | 123 |
124 | 125 |
126 |
127 | 128 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 191 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /theme/journal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Net Journal 8 | 9 | 12 | 13 | 14 |
15 | 16 |
17 |
Load 1 KV
18 |
19 |
20 |
21 | 22 |
23 | 24 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
短链系统变身日记本NetJournal
32 |
33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 | 记事本密码 Key 44 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |
52 | 本系统密码 Password 53 | 54 |
55 | 56 |
57 | 58 | 61 |
62 | 63 |
64 |
View MarkDown
65 |
66 |
67 |
68 |
69 |
70 | 71 |
72 |
LocalStorage List
73 |
74 |
75 |
76 | 77 | 78 | 79 |
80 | 81 |
82 |
83 |
84 |
85 | 86 | 87 | https://1way.eu.org/4sure 88 |
89 |
90 | https://en.y2mate.is/s36/youtube-to-mp3.html 91 |
92 |
93 |
94 |
95 |
96 |
97 | 98 |
99 | 100 | 115 | 116 |
117 |
How to use
118 |
119 |
120 | 121 |
122 | 123 |
124 | 125 |
126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 136 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /theme/journal/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Url-Shorten-Worker 6 | 7 | 8 | 9 |
10 | 11 | 14 | 15 | 16 | 17 | 29 | 30 | Fork me on GitHub 31 | 32 | 33 | -------------------------------------------------------------------------------- /theme/pastebin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Paste Bin 8 | 9 | 12 | 13 | 14 |
15 | 16 |
17 |
Load 1 KV
18 |
19 |
20 | 21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 |
短链系统变身网络记事本PasteBin
32 |
33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 记事本密码 Key 42 | 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | 本系统密码 Password 51 | 52 |
53 | 54 |
55 | 56 | 59 |
60 | 61 |
62 |
LocalStorage List
63 |
64 |
65 |
66 | 67 | 68 | 69 |
70 | 71 |
72 |
73 |
74 |
75 | 76 | 77 | https://1way.eu.org/4sure 78 |
79 |
80 | https://en.y2mate.is/s36/youtube-to-mp3.html 81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 |
89 | 90 | 105 | 106 |
107 |
How to use
108 |
109 |
110 | 111 |
112 | 113 |
114 | 115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /theme/urlcool/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | Shorter Url 14 | 175 | 176 | 177 |
178 | 179 | 180 | 181 | 182 |
183 | 184 |
Shorten your URLs !
185 | 186 |
Please enter the long URL to be shortened :
187 |
188 | 189 | 190 |
191 |
192 | Powered by xyTom 193 |
194 |

195 |
196 |
197 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /theme/urlcool/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazypeace/Url-Shorten-Worker/3f2fa736f51fb16b4510f0ccc78ac9335799f1f7/theme/urlcool/preview.png -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | password: "", // 管理面板使用密码 // if password != null, then use this config; otherwise, read password from KV. 3 | result_page: false, // 是否用特定的result页面来显示value // After get the value from KV, if use a page to show the result. 4 | theme: "", // 管理面板的主题 // Homepage theme, use the empty value for default theme. To use urlcool theme, please fill with "theme/urlcool" . 5 | cors: true, // 是否允许CORS使用API // Allow Cross-origin resource sharing for API requests. 6 | unique_link: false, // 一个长链是否只有唯一的短链(会增加写入的使用量) // If it is true, the same long url will be shorten into the same short url 7 | custom_link: true, // 允许自定义短链 // Allow users to customize the short url. 8 | overwrite_kv: false, // 允许覆盖已存在的key // Allow user to overwrite an existed key. 9 | snapchat_mode: false, // 短链只能访问一次(访问后就删除了) // The link will be distroyed after access. 10 | visit_count: false, // 使用记数(会大大增加写入的使用量, 多人共用不推荐打开) // Count visit times. 11 | load_kv: false, // 从KV加载全部数据(自用推荐打开, 多人共用会看到别人的数据) // Load all from Cloudflare KV 12 | system_type: "shorturl", // 系统的功能定义 // shorturl, imghost, other types {pastebin, journal} 13 | } 14 | 15 | // key in protect_keylist can't read, add, del from UI and API 16 | const protect_keylist = [ 17 | "password", 18 | ] 19 | 20 | let index_html = "https://crazypeace.github.io/Url-Shorten-Worker/" + config.theme + "/index.html" 21 | let result_html = "https://crazypeace.github.io/Url-Shorten-Worker/" + config.theme + "/result.html" 22 | 23 | const html404 = ` 24 | 25 | 26 |

404 Not Found.

27 |

The url you visit is not found.

28 |

Fork me on GitHub

29 | 30 | ` 31 | 32 | let response_header = { 33 | "Content-type": "text/html;charset=UTF-8;application/json", 34 | } 35 | 36 | if (config.cors) { 37 | response_header = { 38 | "Content-type": "text/html;charset=UTF-8;application/json", 39 | "Access-Control-Allow-Origin": "*", 40 | "Access-Control-Allow-Methods": "POST", 41 | "Access-Control-Allow-Headers": "Content-Type", 42 | } 43 | } 44 | 45 | function base64ToBlob(base64String) { 46 | var parts = base64String.split(';base64,'); 47 | var contentType = parts[0].split(':')[1]; 48 | var raw = atob(parts[1]); 49 | var rawLength = raw.length; 50 | var uInt8Array = new Uint8Array(rawLength); 51 | for (var i = 0; i < rawLength; ++i) { 52 | uInt8Array[i] = raw.charCodeAt(i); 53 | } 54 | return new Blob([uInt8Array], { type: contentType }); 55 | } 56 | 57 | async function randomString(len) { 58 | len = len || 6; 59 | let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /*去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 *** Easily confused characters removed */ 60 | let maxPos = chars.length; 61 | let result = ''; 62 | for (i = 0; i < len; i++) { 63 | result += chars.charAt(Math.floor(Math.random() * maxPos)); 64 | } 65 | return result; 66 | } 67 | 68 | async function sha512(url) { 69 | url = new TextEncoder().encode(url) 70 | 71 | const url_digest = await crypto.subtle.digest( 72 | { 73 | name: "SHA-512", 74 | }, 75 | url, // The data you want to hash as an ArrayBuffer 76 | ) 77 | const hashArray = Array.from(new Uint8Array(url_digest)); // convert buffer to byte array 78 | const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); 79 | //console.log(hashHex) 80 | return hashHex 81 | } 82 | 83 | async function checkURL(URL) { 84 | let str = URL; 85 | let Expression = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/; 86 | let objExp = new RegExp(Expression); 87 | if (objExp.test(str) == true) { 88 | if (str[0] == 'h') 89 | return true; 90 | else 91 | return false; 92 | } else { 93 | return false; 94 | } 95 | } 96 | 97 | async function save_url(URL) { 98 | let random_key = await randomString() 99 | let is_exist = await LINKS.get(random_key) 100 | // console.log(is_exist) 101 | if (is_exist == null) { 102 | return await LINKS.put(random_key, URL), random_key 103 | } 104 | else { 105 | save_url(URL) 106 | } 107 | } 108 | 109 | async function is_url_exist(url_sha512) { 110 | let is_exist = await LINKS.get(url_sha512) 111 | // console.log(is_exist) 112 | if (is_exist == null) { 113 | return false 114 | } else { 115 | return is_exist 116 | } 117 | } 118 | 119 | // 系统密码 120 | async function system_password() { 121 | // 配置中的passoword为空 config.password is NULL 122 | if (config.password.trim().length === 0 ) { 123 | // 查KV中的password对应的值 Query "password" in KV 124 | return await LINKS.get("password"); 125 | } 126 | else { 127 | return config.password.trim(); 128 | } 129 | } 130 | 131 | async function handleRequest(request) { 132 | // console.log(request) 133 | 134 | // 系统密码 135 | const password_value = await system_password(); 136 | 137 | /************************/ 138 | // 以下是API接口的处理 Below is operation for API 139 | 140 | if (request.method === "POST") { 141 | let req = await request.json() 142 | // console.log(req) 143 | 144 | let req_cmd = req["cmd"] 145 | let req_url = req["url"] 146 | let req_key = req["key"] 147 | let req_password = req["password"] 148 | 149 | /* 150 | console.log(req_cmd) 151 | console.log(req_url) 152 | console.log(req_key) 153 | console.log(req_password) 154 | */ 155 | 156 | if (req_password != password_value) { 157 | return new Response(`{"status":500,"key": "", "error":"Error: Invalid password."}`, { 158 | headers: response_header, 159 | }) 160 | } 161 | 162 | if (req_cmd == "add") { 163 | if ((config.system_type == "shorturl") && !await checkURL(req_url)) { 164 | return new Response(`{"status":500, "url": "` + req_url + `", "error":"Error: Url illegal."}`, { 165 | headers: response_header, 166 | }) 167 | } 168 | 169 | let stat, random_key 170 | if (config.custom_link && (req_key != "")) { 171 | // Refuse 'password" as Custom shortURL 172 | if (protect_keylist.includes(req_key)) { 173 | return new Response(`{"status":500,"key": "` + req_key + `", "error":"Error: Key in protect_keylist."}`, { 174 | headers: response_header, 175 | }) 176 | } 177 | 178 | let is_exist = await is_url_exist(req_key) 179 | if ((!config.overwrite_kv) && (is_exist)) { 180 | return new Response(`{"status":500,"key": "` + req_key + `", "error":"Error: Specific key existed."}`, { 181 | headers: response_header, 182 | }) 183 | } else { 184 | random_key = req_key 185 | stat, await LINKS.put(req_key, req_url) 186 | } 187 | } else if (config.unique_link) { 188 | let url_sha512 = await sha512(req_url) 189 | let url_key = await is_url_exist(url_sha512) 190 | if (url_key) { 191 | random_key = url_key 192 | } else { 193 | stat, random_key = await save_url(req_url) 194 | if (typeof (stat) == "undefined") { 195 | await LINKS.put(url_sha512, random_key) 196 | // console.log() 197 | } 198 | } 199 | } else { 200 | stat, random_key = await save_url(req_url) 201 | } 202 | // console.log(stat) 203 | if (typeof (stat) == "undefined") { 204 | return new Response(`{"status":200, "key":"` + random_key + `", "error": ""}`, { 205 | headers: response_header, 206 | }) 207 | } else { 208 | return new Response(`{"status":500, "key": "", "error":"Error: Reach the KV write limitation."}`, { 209 | headers: response_header, 210 | }) 211 | } 212 | } else if (req_cmd == "del") { 213 | // Refuse to delete 'password' entry 214 | if (protect_keylist.includes(req_key)) { 215 | return new Response(`{"status":500, "key": "` + req_key + `", "error":"Error: Key in protect_keylist."}`, { 216 | headers: response_header, 217 | }) 218 | } 219 | 220 | await LINKS.delete(req_key) 221 | 222 | // 计数功能打开的话, 要把计数的那条KV也删掉 Remove the visit times record 223 | if (config.visit_count) { 224 | await LINKS.delete(req_key + "-count") 225 | } 226 | 227 | return new Response(`{"status":200, "key": "` + req_key + `", "error": ""}`, { 228 | headers: response_header, 229 | }) 230 | } else if (req_cmd == "qry") { 231 | // Refuse to query 'password' 232 | if (protect_keylist.includes(req_key)) { 233 | return new Response(`{"status":500,"key": "` + req_key + `", "error":"Error: Key in protect_keylist."}`, { 234 | headers: response_header, 235 | }) 236 | } 237 | 238 | let value = await LINKS.get(req_key) 239 | if (value != null) { 240 | let jsonObjectRetrun = JSON.parse(`{"status":200, "error":"", "key":"", "url":""}`); 241 | jsonObjectRetrun.key = req_key; 242 | jsonObjectRetrun.url = value; 243 | return new Response(JSON.stringify(jsonObjectRetrun), { 244 | headers: response_header, 245 | }) 246 | } else { 247 | return new Response(`{"status":500, "key": "` + req_key + `", "error":"Error: Key not exist."}`, { 248 | headers: response_header, 249 | }) 250 | } 251 | } else if (req_cmd == "qryall") { 252 | if ( !config.load_kv) { 253 | return new Response(`{"status":500, "error":"Error: Config.load_kv false."}`, { 254 | headers: response_header, 255 | }) 256 | } 257 | 258 | let keyList = await LINKS.list() 259 | if (keyList != null) { 260 | // 初始化返回数据结构 Init the return struct 261 | let jsonObjectRetrun = JSON.parse(`{"status":200, "error":"", "kvlist": []}`); 262 | 263 | for (var i = 0; i < keyList.keys.length; i++) { 264 | let item = keyList.keys[i]; 265 | // Hide 'password' from the query all result 266 | if (protect_keylist.includes(item.name)) { 267 | continue; 268 | } 269 | // Hide '-count' from the query all result 270 | if (item.name.endsWith("-count")) { 271 | continue; 272 | } 273 | 274 | let url = await LINKS.get(item.name); 275 | 276 | let newElement = { "key": item.name, "value": url }; 277 | // 填充要返回的列表 Fill the return list 278 | jsonObjectRetrun.kvlist.push(newElement); 279 | } 280 | 281 | return new Response(JSON.stringify(jsonObjectRetrun) , { 282 | headers: response_header, 283 | }) 284 | } else { 285 | return new Response(`{"status":500, "error":"Error: Load keyList failed."}`, { 286 | headers: response_header, 287 | }) 288 | } 289 | 290 | } 291 | 292 | } else if (request.method === "OPTIONS") { 293 | return new Response(``, { 294 | headers: response_header, 295 | }) 296 | } 297 | 298 | /************************/ 299 | // 以下是浏览器直接访问worker页面的处理 Below is operation for browser visit worker page 300 | 301 | const requestURL = new URL(request.url) 302 | let path = requestURL.pathname.split("/")[1] 303 | path = decodeURIComponent(path); 304 | const params = requestURL.search; 305 | 306 | // console.log(path) 307 | // 如果path为空, 即直接访问本worker 308 | // If visit this worker directly (no path) 309 | if (!path) { 310 | // return Response.redirect("https://zelikk.blogspot.com/search/label/Url-Shorten-Worker", 302) 311 | // /* 312 | return new Response(html404, { 313 | headers: response_header, 314 | status: 404 315 | }) 316 | // */ 317 | } 318 | 319 | // 如果path符合password 显示操作页面index.html 320 | // if path equals password, return index.html 321 | if (path == password_value) { 322 | let index = await fetch(index_html) 323 | index = await index.text() 324 | index = index.replace(/__PASSWORD__/gm, password_value) 325 | // 操作页面文字修改 326 | // index = index.replace(/短链系统变身/gm, "") 327 | return new Response(index, { 328 | headers: response_header, 329 | }) 330 | } 331 | 332 | // 在KV中查询 短链接 对应的原链接 333 | // Query the value(long url) in KV by key(short url) 334 | let value = await LINKS.get(path); 335 | // console.log(value) 336 | 337 | // 如果path是'password', 让查询结果为空, 不然直接就把password查出来了 338 | // Protect password. If path equals 'password', set result null 339 | if (protect_keylist.includes(path)) { 340 | value = "" 341 | } 342 | 343 | if (!value) { 344 | // KV中没有数据, 返回404 345 | // If request not in KV, return 404 346 | return new Response(html404, { 347 | headers: response_header, 348 | status: 404 349 | }) 350 | } 351 | 352 | // 计数功能 353 | if (config.visit_count) { 354 | // 获取并增加访问计数 355 | let count = await LINKS.get(path + "-count"); 356 | if (count === null) { 357 | await LINKS.put(path + "-count", "1"); // 初始化为1,因为这是首次访问 358 | } else { 359 | count = parseInt(count) + 1; 360 | await LINKS.put(path + "-count", count.toString()); 361 | } 362 | } 363 | 364 | // 如果阅后即焚模式 365 | if (config.snapchat_mode) { 366 | // 删除KV中的记录 367 | // Remove record before jump to long url 368 | await LINKS.delete(path) 369 | } 370 | 371 | // 带上参数部分, 拼装要跳转的最终网址 372 | // URL to jump finally 373 | if (params) { 374 | value = value + params 375 | } 376 | 377 | // 如果自定义了结果页面 378 | if (config.result_page) { 379 | let result_page_html = await fetch(result_html) 380 | let result_page_html_text = await result_page_html.text() 381 | result_page_html_text = result_page_html_text.replace(/{__FINAL_LINK__}/gm, value) 382 | return new Response(result_page_html_text, { 383 | headers: response_header, 384 | }) 385 | } 386 | 387 | // 以下是不使用自定义结果页面的处理 388 | // 作为一个短链系统, 需要跳转 389 | if (config.system_type == "shorturl") { 390 | return Response.redirect(value, 302) 391 | } else if (config.system_type == "imghost") { 392 | // 如果是图床 393 | var blob = base64ToBlob(value) 394 | return new Response(blob, { 395 | // 图片不能指定content-type为 text/plain 396 | }) 397 | } else { 398 | // 如果只是一个单纯的key-value系统, 简单的显示value就行了 399 | return new Response(value, { 400 | headers: { 401 | "Content-type": "text/plain;charset=UTF-8;", 402 | }, 403 | }) 404 | } 405 | } 406 | 407 | addEventListener("fetch", async event => { 408 | event.respondWith(handleRequest(event.request)) 409 | }) 410 | --------------------------------------------------------------------------------