├── !.glitch-deploy.js ├── .gitignore ├── .glitch-assets ├── .public ├── bundle.css ├── bundle.js ├── favicon │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── ms-icon-70x70.png │ └── z.zip ├── html │ ├── app.html │ ├── channel.html │ ├── cookie.html │ ├── player-inside.html │ ├── player.html │ ├── result-item.html │ ├── result-list.html │ └── what.html ├── index.html ├── loading.gif ├── logo.png ├── logo.svg ├── outdated-browser.html └── style.css ├── README.md ├── build ├── components │ ├── filters.html │ ├── indicators.html │ └── loader.html ├── cookie-what.html ├── cookie.html ├── css │ ├── lib │ │ ├── bs-shards.css │ │ ├── neumorphism-dark.css │ │ └── neumorphism-light.css │ └── style.sass.css ├── html │ ├── 404.html │ ├── app.html │ ├── channel.html │ ├── error.html │ ├── history-list.html │ ├── player.html │ ├── result-item.html │ ├── result-list.html │ └── start.html ├── index.html ├── js │ ├── _HISTORY.js │ ├── _PLAY.js │ ├── _SEARCH.js │ ├── bg-old.js │ ├── bg.js │ ├── client.js │ ├── routing.js │ └── tools.js ├── outdated-browser.html └── ui-words.json ├── package-lock.json ├── package.json ├── server.js └── shrinkwrap.yaml /!.glitch-deploy.js: -------------------------------------------------------------------------------- 1 | //© glitch-deploy by blubbll 2 | { 3 | require("child_process").exec("pnpm i glob ftp"); 4 | 5 | //imports 6 | const fs = require("fs"), 7 | path = require("path"), 8 | glob = require("glob"), 9 | ftpClient = require("ftp"); 10 | //tool 11 | const fontMap = new Map(); 12 | fontMap.set( 13 | "mathMono", 14 | new Map([ 15 | ["A", "𝙰"], 16 | ["B", "𝙱"], 17 | ["C", "𝙲"], 18 | ["D", "𝙳"], 19 | ["E", "𝙴"], 20 | ["F", "𝙵"], 21 | ["G", "𝙶"], 22 | ["H", "𝙷"], 23 | ["I", "𝙸"], 24 | ["J", "𝙹"], 25 | ["K", "𝙺"], 26 | ["L", "𝙻"], 27 | ["M", "𝙼"], 28 | ["N", "𝙽"], 29 | ["O", "𝙾"], 30 | ["P", "𝙿"], 31 | ["Q", "𝚀"], 32 | ["R", "𝚁"], 33 | ["S", "𝚂"], 34 | ["T", "𝚃"], 35 | ["U", "𝚄"], 36 | ["V", "𝚅"], 37 | ["W", "𝚆"], 38 | ["X", "𝚇"], 39 | ["Y", "𝚈"], 40 | ["Z", "𝚉"], 41 | ["a", "𝚊"], 42 | ["b", "𝚋"], 43 | ["c", "𝚌"], 44 | ["d", "𝚍"], 45 | ["e", "𝚎"], 46 | ["f", "𝚏"], 47 | ["g", "𝚐"], 48 | ["h", "𝚑"], 49 | ["i", "𝚒"], 50 | ["j", "𝚓"], 51 | ["k", "𝚔"], 52 | ["l", "𝚕"], 53 | ["m", "𝚖"], 54 | ["n", "𝚗"], 55 | ["o", "𝚘"], 56 | ["p", "𝚙"], 57 | ["q", "𝚚"], 58 | ["r", "𝚛"], 59 | ["s", "𝚜"], 60 | ["t", "𝚝"], 61 | ["u", "𝚞"], 62 | ["v", "𝚟"], 63 | ["w", "𝚠"], 64 | ["x", "𝚡"], 65 | ["y", "𝚢"], 66 | ["z", "𝚣"], 67 | ["0", "𝟶"], 68 | ["1", "𝟷"], 69 | ["2", "𝟸"], 70 | ["3", "𝟹"], 71 | ["4", "𝟺"], 72 | ["5", "𝟻"], 73 | ["6", "𝟼"], 74 | ["7", "𝟽"], 75 | ["8", "𝟾"], 76 | ["9", "𝟿"] 77 | ]) 78 | ); 79 | const toMono = o => ( 80 | fontMap.get("mathMono").forEach((e, n) => { 81 | o = o.replace(new RegExp(n, "g"), e); 82 | }), 83 | o 84 | ); 85 | const deploy = options => 86 | new Promise(async (resolve, reject) => { 87 | const icon = { 88 | self: "🛠️", 89 | dir: "📁", 90 | file: "📄", 91 | up: "↗️", 92 | ok: "✅", 93 | rem: "🗑️", 94 | add: "✨", 95 | env: "🔑" 96 | }; 97 | //construct client 98 | const c = new ftpClient(); 99 | //clear existing files 100 | const _clear = () => 101 | new Promise((resolve, reject) => { 102 | let oldfiles = 0, 103 | oldfilesgone = 0, 104 | oldfolders = 0, 105 | oldfoldersgone = 0; 106 | c.list("/", 0, function(err, list) { 107 | list.forEach(file => { 108 | //delete remote folders 109 | if (file.type === "d") { 110 | if (!["..", "."].includes(file.name)) { 111 | oldfolders++; 112 | c.rmdir(file.name, () => { 113 | oldfoldersgone++; 114 | options.verbose && 115 | console.log( 116 | toMono( 117 | `${icon.self}${icon.rem}${icon.dir}deleting remote folder '${file.name}'...` 118 | ) 119 | ); 120 | if (oldfilesgone + oldfoldersgone === oldfiles + oldfolders) 121 | resolve(); 122 | }); 123 | } 124 | //delete remote files 125 | } else { 126 | if (file.name !== ".ftpquota") { 127 | //skip indeletable file 128 | oldfiles++; 129 | c.delete(file.name, () => { 130 | options.verbose && 131 | console.log( 132 | toMono( 133 | `${icon.self}${icon.rem}${icon.file}deleting remote file '${file.name}'...` 134 | ) 135 | ); 136 | oldfilesgone++; 137 | if (oldfilesgone + oldfoldersgone === oldfiles + oldfolders) 138 | resolve(); 139 | }); 140 | } 141 | } 142 | }); 143 | }); 144 | }); 145 | //deploy .env 146 | const _syncenv = async apply => { 147 | const f = `${__dirname}/.env`, //where file is locally 148 | rf = `.dpl.env`, //where to put file to on remote directory 149 | rfl = `${__dirname}/${rf}`; //location of remote file when uploaded 150 | const _put = () => 151 | new Promise((resolve, reject) => { 152 | c.put(f, rf, err => { 153 | if (!err) { 154 | options.verbose && 155 | console.log( 156 | toMono(`${icon.self}${icon.add}${icon.env}deployed .env!`) 157 | ); 158 | return resolve(); 159 | } else 160 | return reject( 161 | `deploy error: ${err} while uploading ${f} to ${rf}` 162 | ); 163 | }); 164 | }); 165 | const _apply = () => 166 | new Promise((resolve, reject) => { 167 | //loop through lines 168 | require("fs") 169 | .readFileSync(`${__dirname}/${rf}`, "utf-8") 170 | .split(/\r?\n/) 171 | .forEach(line => { 172 | if (line.includes("=") && !line.includes("#")) { 173 | const _var = line.split("=")[0]; 174 | const _val = line.split("=")[1]; 175 | //if env not exist, apply value 176 | if (!process.env[_var]) { 177 | process.env[_var] = 178 | _val.startsWith('"') && _val.endsWith('"') 179 | ? _val.slice(1, -1) 180 | : _val; 181 | options.verbose && 182 | console.log( 183 | toMono( 184 | `${icon.self}${icon.add} Applied ${_var} with value ${_val}!` 185 | ) 186 | ); 187 | } 188 | } 189 | }); 190 | options.verbose && 191 | console.log( 192 | toMono( 193 | `${icon.self}${icon.add}${icon.env}Applied .env from file ${rfl}!` 194 | ) 195 | ); 196 | //fs.unlinkSync(rfl); //delete file after applying again 197 | resolve(); 198 | }); 199 | return !apply ? await _put() : await _apply(); 200 | }; 201 | 202 | //deploy data 203 | const _deploy = () => 204 | new Promise((resolve, reject) => { 205 | glob(`${__dirname}/**`, async (er, files) => { 206 | const lfiles = [], //local files 207 | lfolders = []; //local folders 208 | //put local files into correct array 209 | files.forEach(fd => { 210 | if ( 211 | !fd.startsWith(".") && 212 | ![ 213 | "./data", 214 | "/app", 215 | "/app/node_modules", 216 | "/app/package-lock.json" 217 | ].includes(fd) 218 | ) { 219 | //blacklist 220 | path.extname(fd).length ? lfiles.push(fd) : lfolders.push(fd); 221 | } 222 | }); 223 | //make dirs on remote 224 | const _upfolders = dirs => 225 | new Promise((resolve, reject) => { 226 | let newfolders = 0, 227 | newfoldersdone = 0; 228 | dirs.forEach(dir => { 229 | const folder = `${dir.split("/app/")[1]}`; 230 | options.verbose && 231 | console.log( 232 | toMono( 233 | `${icon.self}${icon.add}${icon.dir}creating remote dir '${folder}'...` 234 | ) 235 | ); 236 | newfolders++; 237 | c.mkdir(folder, true, () => { 238 | newfoldersdone++; 239 | if (newfoldersdone === newfolders) { 240 | resolve(); 241 | } 242 | }); 243 | }); 244 | }); 245 | //upload files 246 | const _upfiles = files => 247 | new Promise((resolve, reject) => { 248 | let newfiles = 0, 249 | newfilesdone = 0; 250 | let curr = 0; 251 | files.forEach(file => { 252 | const rf = file.startsWith("/app/") 253 | ? file.split(/app(.*)/)[1] 254 | : file; //calculate remote path 255 | if (fs.existsSync(file)) { 256 | //maybe-redundant local-exist check 257 | newfiles++; 258 | options.verbose && 259 | console.log( 260 | toMono( 261 | `${icon.self}${icon.up}${icon.file}loading '${file}' to '${rf}'...` 262 | ) 263 | ); 264 | c.put(file, rf, err => { 265 | newfilesdone++; 266 | if (newfilesdone === newfiles) { 267 | resolve(); 268 | } 269 | if (err) 270 | return reject( 271 | `deploy error: '${err}' while uploading ${file} to ${rf}` 272 | ); 273 | }); 274 | } 275 | }); 276 | }); 277 | //wait for folders and files 278 | [await _upfolders(lfolders), await _upfiles(lfiles)]; 279 | { 280 | const d = 281 | lfolders.length > 0 282 | ? `${lfolders.length} folder${lfolders.length > 1 && "s"}` 283 | : void 0; 284 | const f = 285 | lfiles.length > 0 286 | ? `${lfiles.length} file${lfiles.length > 1 && "s"}` 287 | : void 0; 288 | resolve({ 289 | d, 290 | f 291 | }); 292 | } 293 | }); 294 | }); 295 | //actual start 296 | if (process.env.PROJECT_DOMAIN) { 297 | //is glitch project 298 | console.log( 299 | toMono( 300 | `${icon.self}Starting deployment${ 301 | options.clear ? " (➕remote clear)" : "" 302 | }${options.env ? " (➕.env copy)" : ""}...` 303 | ) 304 | ); 305 | c.on("ready", async () => { 306 | //deploy FROM glitch 307 | options.clear && (await _clear()); 308 | const dr = await _deploy(); //deploy result 309 | options.env && (await _syncenv()); 310 | console.log( 311 | toMono( 312 | `${icon.self}${icon.ok}${ 313 | dr.f && dr.d ? `${dr.f} & ${dr.d}` : dr.f 314 | }${options.env ? " and .env" : ""} deployed!` 315 | ) 316 | ); 317 | c.end(); 318 | return resolve(); 319 | }); 320 | c.connect(options.ftp); 321 | } else { 322 | await _syncenv(true); 323 | return resolve(); 324 | } 325 | }); 326 | process.on("unhandledRejection", err => { 327 | const self = __filename; 328 | //if error came from this module 329 | if (err && err.stack && err.stack.includes(`(${self}`)) { 330 | const msg = `❌${self}${err.lineNumer ? err.lineNumber : ""}: '${ 331 | err.message ? err.message : err 332 | }'`; 333 | console.warn(msg); 334 | fs.writeFileSync("Err.txt", msg); 335 | } 336 | }); 337 | module.exports = deploy; 338 | } 339 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/!dist 2 | 3 | git version 2.18.0 4 | -------------------------------------------------------------------------------- /.glitch-assets: -------------------------------------------------------------------------------- 1 | {"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"} 2 | {"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"} 3 | {"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"} 4 | {"uuid":"adSBq97hhhpFNUna","deleted":true} 5 | {"uuid":"adSBq97hhhpFNUnb","deleted":true} 6 | {"uuid":"adSBq97hhhpFNUnc","deleted":true} 7 | {"name":"25848f4ff0a58576fe5555bc097dfe75.ico.zip","date":"2020-01-15T07:43:09.768Z","url":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2F25848f4ff0a58576fe5555bc097dfe75.ico.zip","type":"application/x-zip-compressed","size":148859,"thumbnail":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2Fthumbnails%2F25848f4ff0a58576fe5555bc097dfe75.ico.zip","thumbnailWidth":210,"thumbnailHeight":210,"uuid":"DNKANK5OYjhzeKXA"} 8 | {"name":"Download (22).png","date":"2020-01-19T15:15:45.327Z","url":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2FDownload%20(22).png","type":"image/png","size":60723,"imageWidth":1000,"imageHeight":1000,"thumbnail":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2Fthumbnails%2FDownload%20(22).png","thumbnailWidth":330,"thumbnailHeight":330,"uuid":"wsQCBGnSq3NJqNVU"} 9 | {"name":"Lines-5s-1084px.gif","date":"2020-01-28T07:59:40.741Z","url":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2FLines-5s-1084px.gif","type":"image/gif","size":1423578,"imageWidth":204,"imageHeight":204,"thumbnail":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2FLines-5s-1084px.gif","thumbnailWidth":204,"thumbnailHeight":204,"uuid":"ZjZil9MHEvjlRswy"} 10 | {"name":"Lines-16.7s-1084px.gif","date":"2020-01-28T08:20:51.814Z","url":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2FLines-16.7s-1084px.gif","type":"image/gif","size":9849926,"imageWidth":478,"imageHeight":478,"thumbnail":"https://cdn.glitch.com/03ae9c88-79e4-4632-ac9c-8adb8178b2c5%2Fthumbnails%2FLines-16.7s-1084px.gif","thumbnailWidth":330,"thumbnailHeight":330,"uuid":"Kci1ZxryHb1PHAll"} 11 | -------------------------------------------------------------------------------- /.public/bundle.css: -------------------------------------------------------------------------------- 1 | /*!💜I love you monad.*/ 2 | :root{--color-accent-main: #1b294b;--color-font-light: mintcream;--color-font-dark: rgba(24, 24, 24)}param{display:inline-block;padding-right:5px;padding-left:5px}main{height:intrinsic;height:-moz-max-content;height:-webkit-max-content;height:max-content}body{background-blend-mode:hard-light}html,body{background:var(--color-accent-main);position:absolute;height:100%;width:100%;overflow:hidden;font-family:"Roboto", sans-serif}.text-accent{color:var(--color-accent-main) !important}.text-black{colro:black}@supports (-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em)){.backdrop-blur{background-color:rgba(255,255,255,0.5);-webkit-backdrop-filter:blur(0.75rem);backdrop-filter:blur(0.75rem)}}@supports not ((-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em))){.backdrop-blur{background-color:rgba(0,0,0,0.9) !important;color:white !important}}.burger-flag{margin-bottom:4px;width:13.13px;height:14px !important;opacity:0.5}#top,#left{z-index:3;background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.1) !important}#dynamic-logo{color:var(--color-font-light);font-family:consolas;filter:drop-shadow(0px 0px 8px rgba(0,0,0,0.42));cursor:pointer}#dynamic-logo:hover{filter:drop-shadow(0px 0px 8px rgba(255,255,255,0.42))}#dynamic-logo:hover>.alpha-mask>.alpha-original{filter:brightness(1.2) contrast(3)}#dynamic-logo:hover>.alpha-mask>.alpha-target{opacity:0}#dynamic-logo>.alpha-mask{mask-image:url(./logo.png);-webkit-mask-image:url(./logo.png);mask-mode:alpha;-webkit-mask-mode:alpha;mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-repeat:round;height:37px;width:37px}#dynamic-logo>.alpha-mask>.alpha-original{width:37px;height:37px;opacity:0.42;filter:brightness(1.5) contrast(3)}#dynamic-logo>.alpha-mask>.alpha-target{transition:opacity 499ms ease;opacity:1;width:37px;height:37px;mix-blend-mode:color}#top .btn{background:var(--color-accent-main);color:var(--color-font-light)}#top #toggle-left{cursor:pointer}#top #toggle-left:hover{background:rgba(255,255,255,0.5)}#top #toggle-left:active{background:rgba(255,255,255,0.75)}#top #search input{background:rgba(255,255,255,0.3);color:var(--color-font-light)}#top #search #search-btn button{border:none}#top #search input::placeholder{color:var(--color-font-light)}#top #search input :-ms-input-placeholder{color:var(--color-font-light)}#top #search input ::-ms-input-placeholder{color:var(--color-font-light)}#top #search input ::-ms-clear{display:none}.live-search{background:rgba(0,0,0,0.25);backdrop-filter:blur(3rem)}.live-search>div{background:rgba(255,255,255,0.1);padding:0.4rem 0.8rem;color:snow}.live-search>div.selected{background:var(--color-accent-main)}.live-search>div:hover{background:rgba(0,0,255,0.1) !important;color:white}.live-search>div:active{background:rgba(0,0,0,0.25) !important;color:mintcream}#left{z-index:3;transition:left ease-out 299ms}#left[expanded="false"]{left:-100%}#left[expanded="true"]{left:0}#left .btn{background:rgba(0,0,0,0.1) !important;color:rgba(255,255,255,0.75) !important;border-color:rgba(255,255,255,0.1) !important}#left .btn:hover,#left .btn:focus{background:rgba(255,255,255,0.3) !important}#left .btn:active{background:rgba(255,255,255,0.5) !important}footer#footer{background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.1) !important;color:var(--color-font-dark);z-index:3;width:100%;background-color:#f5f5f5}#view-inner.wait{cursor:wait;pointer-events:none}#canvas{z-index:0;position:fixed}#filters{z-index:2}#filters #filters-inner{border-color:rgba(255,255,255,0.1) !important;color:white}#results{background:rgba(0,0,0,0.3);-webkit-flex:1 0 0;-ms-flex:1 0 0;-mz-flex:1 0 0;flex:1 0 0;overflow-y:auto}#results.grow{overflow:hidden}#results.grow .card.growing{z-index:1;overflow:visible !important}#results.grow .card.growing img{filter:blur(0.05rem) !important;z-index:1;transform:scale(25);position:fixed;align-self:center;margin:50% auto;left:10%;top:10%}#results #results-inner{height:-moz-available;height:-webkit-fill-available;height:fill-available}#results #results-inner .card{box-shadow:0 0 0.1rem 1rem rgba(0,0,0,0.1);cursor:pointer;overflow:hidden;background:rgba(0,0,0,0.3);border-radius:0.6rem;transition:transform 10ms ease-out}#results #results-inner .card figure{margin-bottom:0}#results #results-inner .card figure figcaption{border:0.1rem solid rgba(255,255,255,0.2);border:none;width:-moz-available;width:-webkit-fill-available;width:fill-available}#results #results-inner .card figure figcaption item-title{background:rgba(255,255,255,0.8);display:flex;font-size:0.9rem;padding:0.3rem 1rem;color:black}#results #results-inner .card figure figcaption item-title:hover{opacity:1 !important}#results #results-inner .card figure figcaption duration{font-size:0.8rem;margin-top:-2rem;margin-left:0.4rem;opacity:1;padding:0.1rem 0.3rem;border-radius:0.25rem;background:rgba(0,0,0,0.84);color:white}#results #results-inner .card figure figcaption duration duration-word{display:none}#results #results-inner .card figure figcaption duration duration-detailed{display:none}#results #results-inner .card figure figcaption duration:hover{opacity:1 !important}#results #results-inner .card figure img{transition:transform 299ms ease-in;height:18vh;filter:grayscale(0.6);object-fit:cover}#results #results-inner .card figure:hover figcaption duration{opacity:0.4}#results #results-inner .card figure:hover figcaption item-title{opacity:0.4}#results #results-inner .card:hover{z-index:1;transform:scale(1.06)}#results #results-inner .card:hover img{filter:none}#results #results-inner .card:hover .card-body{background:white}#results #results-inner .card:hover duration{opacity:1}#results #results-inner .card:hover duration duration-word{display:inline-block}#results #results-inner .card:hover duration duration-detailed{display:inline-block}#results #results-inner .card .card-body{background:rgba(255,255,255,0.8)}#results::-webkit-scrollbar{width:16px;height:16px}#results::-webkit-scrollbar-track{background:#E0E0E0}#results::-webkit-scrollbar-thumb{background:#9E9E9E}#results::-webkit-scrollbar-thumb:hover{background:#BDBDBD}lapis-player{display:flex;max-height:75%;width:100%}lapis-player audio{display:none}lapis-player lapis-muted{display:none;width:inherit;height:100%;background:yellow;position:absolute;color:black;align-items:center;justify-content:center;z-index:4;cursor:pointer}lapis-player lapis-muted:hover{filter:contrast(0.8)}lapis-player lapis-muted:focus,lapis-player lapis-muted:active{filter:contrast(0.75)}lapis-player poster{display:none}lapis-player video,lapis-player #mep_0{height:inherit;width:inherit;max-height:inherit;min-width:0 !important;border:0.1rem solid rgba(255,255,255,0.1)}lapis-player .afterglow__control-bar,lapis-player .afterglow__title-bar{backdrop-filter:blur(1rem);padding:0.5vh 0;position:absolute;width:calc(100% + 60px);left:-30px;z-index:-1;background:rgba(0,0,0,0.5);border:0.1rem solid rgba(255,255,255,0.1)}lapis-player .afterglow__control-bar{height:4.9rem;backdrop-filter:blur(1rem);bottom:0}lapis-player .afterglow__title-bar{height:5rem;display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,0.9);backdrop-filter:blur(1rem);top:0} 3 | -------------------------------------------------------------------------------- /.public/bundle.js: -------------------------------------------------------------------------------- 1 | /*!💜I love you monad.*/ 2 | var S_ITER$0="undefined"!=typeof Symbol&&Symbol&&Symbol.iterator||"@@iterator",S_MARK$0="undefined"!=typeof Symbol&&Symbol&&Symbol.__setObjectSetter__;function GET_ITER$0(e){if(e){if(Array.isArray(e))return 0;var t;if(S_MARK$0&&S_MARK$0(e),"object"==typeof e&&"function"==typeof(t=e[S_ITER$0]))return S_MARK$0&&S_MARK$0(void 0),t.call(e);if(S_MARK$0&&S_MARK$0(void 0),e+""=="[object Generator]")return e}throw new Error(e+" is not iterable")}var this$0=this,$=document.querySelector.bind(document),$$=document.querySelectorAll.bind(document),NProgress=window.NProgress,createThumbs=window.createThumbs,debounce=window.debounce,fetch=window.fetch,Fullscreen=window.Fullscreen,getL=window.getL,getSize=window.getSize,loadImage=window.loadImage;function detectIEEdge(){var e=window.navigator.userAgent,t=e.indexOf("MSIE ");if(t>0)return parseInt(e.substring(t+5,e.indexOf(".",t)),10);if(e.indexOf("Trident/")>0){var n=e.indexOf("rv:");return parseInt(e.substring(n+3,e.indexOf(".",n)),10)}var o=e.indexOf("Edge/");return o>0&&parseInt(e.substring(o+5,e.indexOf(".",o)),10)}createThumbs=function(e){var t,n,o,r,i="";for(n=(o=0===(t=GET_ITER$0(e)))?e.length:void 0;o?tsize-indicator"))))?o.length:void 0;n?e=0,that.isFirefox=void 0!==window.InstallTrigger,that.isSafari=/constructor/i.test(window.HTMLElement)||"[object SafariRemoteNotification]"===(!window.safari||void 0!==window.safari&&window.safari.pushNotification).toString(),that.isIE=!!document.documentMode,that.isEdge=!that.isIE&&!!window.StyleMedia,that.isChrome=!(!window.chrome||!window.chrome.webstore&&!window.chrome.runtime),that.isEdgeChromium=that.isChrome&&-1!=navigator.userAgent.indexOf("Edg"),that.isBlink=(that.isChrome||that.isOpera)&&!!window.CSS,window.Browser=that,(speed={_i:0,_arrTimes:[],doTest:function(){var e=this,t=(String.fromCharCode(12448+96*Math.random()),new Image),n=(new Date).getTime();if(this._i=250&&(t="slow"),e>=150&&e<=250&&(t="medium"),e<=150&&(t="fast")),e&&e.toFixed(2),this.speed=t,console.debug("Test done:"),console.debug({avg:e,speed:t}),console.debug("\n"),n._i=0,n._arrTimes=[],n._loop&&setTimeout((function(){console.debug("Testing..."),n.doTest()}),n.config.interval)},stopLoop:function(){return this._loop=!1,"loop stopped"},startLoop:function(){console.debug("Testing..."),this._loop=!0,this._i=0;navigator.connection||navigator.mozConnection||navigator.webkitConnection;return this._i=0,this.doTest((function(e){this.setResult(e)})),"loop started"},config:{image:"//blubbll.b-cdn.net/speed.jpg",times:3,interval:6e4}}).startLoop();var autocomplete=window.autocomplete,alert=window.alert,done=(createThumbs=window.createThumbs,debounce=window.debounce,window.done),HOST=(fetch=window.fetch,getL=window.getL,getSize=window.getSize,window.HOST),REGION=window.REGION,load=window.load,moment=window.moment,speed=window.speed,T=window.T,afterglow=window.afterglow,Browser=window.Browser,Player=(Fullscreen=window.Fullscreen,waitForElement=window.waitForElement,$=document.querySelector.bind(document),$$=document.querySelectorAll.bind(document),window.Player);Player={open:function(){$("#view-inner").insertAdjacentHTML("beforeend",T.PLAYER),$("#view-inner").classList.remove("wait"),$("#filters").style.setProperty("display","none","important"),$("#results").style.setProperty("display","none","important")},close:function(){$("#player").remove(),$("#filters").style.setProperty("display",""),$("#results").style.setProperty("display","")},openFromResult:function(e){$("#view-inner").classList.add("wait"),$("#results").classList.add("grow"),e.classList.add("growing"),setTimeout((function(){Player.play(e.getAttribute("video-id")),$("#results").classList.remove("grow"),e.classList.remove("growing")}),799)},play:function(e){Player.open(),afterglow.initVideoElements();var t=$("video"),n=$("audio");fetch(HOST+"/api/"+REGION+"/video/"+e).then((function(e){return e.text()})).then((function(e){var o,r,i,a,s="undefined"!=typeof Symbol&&Symbol&&Symbol.iterator||"@@iterator",l="undefined"!=typeof Symbol&&Symbol&&Symbol.__setObjectSetter__;var c=JSON.parse(e),d=c.title;$("poster>img").setAttribute("srcset",createThumbs(c.videoThumbnails));var u,f={low:c.formatStreams[0],AUDIOS:[],VIDEOS:[],CURRENT:{AUDIO:"",VIDEO:""}};for(r=(i=0===(o=function(e){if(e){if(Array.isArray(e))return 0;var t;if(l&&l(e),"object"==typeof e&&"function"==typeof(t=e[s]))return l&&l(void 0),t.call(e);if(l&&l(void 0),e+""=="[object Generator]")return e}throw new Error(e+" is not iterable")}(a=c.adaptiveFormats)))?a.length:void 0;i?oimg").currentSrc;$("video").outerHTML='',t=$("video"),n.src=f.CURRENT.AUDIO.url,t.src=f.CURRENT.VIDEO.url,afterglow.initialized=!1,afterglow.init(),Object.defineProperty(t,"volume",{set:function(e){t._volume=e,n.volume=e},get:function(){return t._volume}}),Object.defineProperty(t,"muted",{set:function(e){t._muted=e,n.muted=e},get:function(){return t._muted}}),t._play=t.play,Object.defineProperty(t,"play",{get:function(){if(n.paused&&n.play(),n.paused){console.debug("muted"),$("#mep_0").insertAdjacentHTML("afterbegin","");var e=$("lapis-muted");e.innerHTML="THE VIDEO IS MUTED. CLICK ME TO UNMUTE",e.style.display="flex",e.addEventListener("click",(function(){e.remove(),n.muted=!1,t.currentTime=0,n.play(),!n.muted&&!n.paused&&console.debug("unmuted.")}))}return t._play}}),t._pause=t.pause,Object.defineProperty(t,"pause",{get:function(){return n.pause(),t._pause}}),t.addEventListener("loadedmetadata",(function(e){console.log("loaded");try{t.play()}catch(e){console.warn(e)}$(".afterglow__controls").insertAdjacentHTML("afterbegin",'
'+d+"
"),$(".afterglow__controls").insertAdjacentHTML("beforeend",'
');var n=function(){var t=e.target,n=$("lapis-player").getAttribute("ratio")||t.videoHeight/t.videoWidth;$("lapis-player").setAttribute("ratio",n),$("#mep_0").style.height=$("#mep_0").clientWidth*n+"px"};setTimeout(n,0),window.addEventListener("resize",n)}),!1),t.load();setInterval((function(){n.currentTime/t.currentTime>1.1&&(t.currentTime=n.currentTime)}),2999);void 0!==window.orientation&&(document.title=d,document.addEventListener("fullscreenchange",(function(){document.fullscreen||!t.pause||n.paused||n.pause()}),!1),t.addEventListener("play",(function(){n.play()})),t.addEventListener("pause",(function(){!n.paused&&n.pause()})),$(".afterglow__top-control-bar").innerHTML=$(".afterglow__top-control-bar").innerHTML,$(".afterglow__button.afterglow__fullscreen-toggle").addEventListener("click",(function(e){e.target;document.fullscreen?Fullscreen.exit():setTimeout((function(){$("#mep_0").classList.remove("afterglow__container"),Fullscreen.enter($("video"))}))})))}else{var w=$("video"),h=$("audio");document.title=d,h.addEventListener("play",(function(){w.paused&&w.play()})),h.addEventListener("pause",(function(){!w.paused&&w.pause()})),w.addEventListener("play",(function(){h.paused&&h.play()})),w.addEventListener("pause",(function(){!h.paused&&h.pause()})),$("video").outerHTML="",$("video").src=f.LOW}}))}};$=document.querySelector.bind(document),$$=document.querySelectorAll.bind(document),autocomplete=window.autocomplete,alert=window.alert,createThumbs=window.createThumbs,debounce=window.debounce,done=window.done,fetch=window.fetch,getL=window.getL,getSize=window.getSize,HOST=window.HOST,REGION=window.REGION,load=window.load;var lazyload=window.lazyload,numeral=(moment=window.moment,window.numeral),setupSearch=(T=window.T,waitForElement=window.waitForElement,window.setupSearch),SEARCH=window.SEARCH;setupSearch=function(){var e="undefined"!=typeof Symbol&&Symbol&&Symbol.iterator||"@@iterator",t="undefined"!=typeof Symbol&&Symbol&&Symbol.__setObjectSetter__;$("#view-inner").innerHTML=T.RESULTS,SEARCH=function(n){var o=document.getElementById("results");if("true"!==o.getAttribute("search-active")){var r=+o.getAttribute("page")||0;switch(0===r?($("#view-inner").innerHTML=T.RESULTS,(o=document.getElementById("results")).setAttribute("state","search-fresh"),r=1):o.getAttribute("q")===n?(o.setAttribute("state","search-continue"),r+=1):($("#view-inner").innerHTML=T.RESULTS,(o=document.getElementById("results")).setAttribute("state","search-new"),r=1),o.setAttribute("search-active",!0),o.setAttribute("page",r),o.getAttribute("state")){case"search-fresh":console.log("Loading fresh results for query:",{q:n,"":"..."});break;case"search-new":console.log("Loading results for new query:",{q:n,"":"..."});break;case"search-continue":console.log("Loading continued results for query:",{q:n,page:r,"":"..."})}"search-continue"!==o.getAttribute("state")&&o.addEventListener("scroll",(function(e){var t=o;"true"!==o.getAttribute("search-active")&&t.scrollTop+t.clientHeight>=t.scrollHeight-t.scrollHeight/10&&SEARCH(t.getAttribute("q"))})),o.setAttribute("q",n),fetch(HOST+"/api/"+REGION+"/search/"+n+"/"+r).then((function(e){return e.text()})).then((function(n){var o,r,i,a=JSON.parse(n);for(r=(i=0===(o=function(n){if(n){if(Array.isArray(n))return 0;var o;if(t&&t(n),"object"==typeof n&&"function"==typeof(o=n[e]))return t&&t(void 0),o.call(n);if(t&&t(void 0),n+""=="[object Generator]")return n}throw new Error(n+" is not iterable")}(a)))?a.length:void 0;i?oimg"))}(i?a[o++]:r.value);o=r=i=void 0,document.getElementById("results").setAttribute("search-active",!1)}))}};var n=document.getElementById("search-input");getL(),autocomplete({input:n,showOnFocus:!0,minLength:1,className:"live-search backdrop-blur",debounceWaitMs:299,fetch:function(n,o){var r=n.toLowerCase();r&&fetch(HOST+"/api/"+REGION+"/complete/"+r).then((function(e){return e.text()})).then((function(r){var i=JSON.parse(r);if(200===i.code){var a=[],s=[];a=[n].concat(function(n,o){if(n){if(Array.isArray(n))return o?n.slice():n;var r,i;if(t&&t(n),"object"==typeof n&&"function"==typeof(o=n[e])?(r=o.call(n),i=[]):n+""=="[object Generator]"&&(r=n,i=[]),t&&t(void 0),i){for(;!0!==(o=r.next()).done;)i.push(o.value);return i}}throw new Error(n+" is not iterable")}(i.data.filter((function(e){return e.length})))),(a=Object.values(Object.fromEntries(a.map((function(e){return[e.toLowerCase(),e]}))))).forEach((function(e){s.push({label:e,value:e})})),o(s)}else 404===i.code&&o([{label:n,value:n.toLowerCase()}])}))},onSelect:function(e){SEARCH(e.label)}});$("#search-input").addEventListener("focus",(function(){"xs"===getSize()&&($("#search").style.setProperty("margin-left",0,"important"),$("#dynamic-logo").style.setProperty("display","none","important"),$("#search").classList.add("col-11"),$("#search-btn").classList.add("d-none"),$("#search-input").classList.add("rounded"))})),$("#search-input").addEventListener("blur",(function(){"xs"===getSize()&&($("#search").style.setProperty("margin-left",""),$("#dynamic-logo").style.setProperty("display",""),$("#search").classList.remove("col-11"),$("#search-btn").classList.remove("d-none"),$("#search-input").classList.remove("rounded"))}))};debounce=window.debounce;var initBg=window.initBg,define=($=document.querySelector.bind(document),$$=document.querySelectorAll.bind(document),window.define),SimplexNoise=window.SimplexNoise;!function(){var e=.5*(Math.sqrt(3)-1),t=(3-Math.sqrt(3))/6,n=1/6,o=(Math.sqrt(5)-1)/4,r=(5-Math.sqrt(5))/20;function i(e){e||(e=Math.random),this.p=a(e),this.perm=new Uint8Array(512),this.permMod12=new Uint8Array(512);for(var t=0;t<512;t++)this.perm[t]=this.p[255&t],this.permMod12[t]=this.perm[t]%12}function a(e){var t,n=new Uint8Array(256);for(t=0;t<256;t++)n[t]=t;for(t=0;t<255;t++){var o=t+~~(e()*(256-t)),r=n[t];n[t]=n[o],n[o]=r}return n}i.prototype={grad3:new Float32Array([1,1,0,-1,1,0,1,-1,0,-1,-1,0,1,0,1,-1,0,1,1,0,-1,-1,0,-1,0,1,1,0,-1,1,0,1,-1,0,-1,-1]),grad4:new Float32Array([0,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,1,0,1,1,1,0,1,-1,1,0,-1,1,1,0,-1,-1,-1,0,1,1,-1,0,1,-1,-1,0,-1,1,-1,0,-1,-1,1,1,0,1,1,1,0,-1,1,-1,0,1,1,-1,0,-1,-1,1,0,1,-1,1,0,-1,-1,-1,0,1,-1,-1,0,-1,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,0]),noise2D:function(n,o){var r,i,a=this.permMod12,s=this.perm,l=this.grad3,c=0,d=0,u=0,f=(n+o)*e,m=Math.floor(n+f),w=Math.floor(o+f),h=(m+w)*t,p=n-(m-h),g=o-(w-h);p>g?(r=1,i=0):(r=0,i=1);var v=p-r+t,y=g-i+t,b=p-1+2*t,S=g-1+2*t,E=255&m,T=255&w,$=.5-p*p-g*g;if($>=0){var L=3*a[E+s[T]];c=($*=$)*$*(l[L]*p+l[L+1]*g)}var O=.5-v*v-y*y;if(O>=0){var A=3*a[E+r+s[T+i]];d=(O*=O)*O*(l[A]*v+l[A+1]*y)}var I=.5-b*b-S*S;if(I>=0){var _=3*a[E+1+s[T+1]];u=(I*=I)*I*(l[_]*b+l[_+1]*S)}return 70*(c+d+u)},noise3D:function(e,t,o){var r,i,a,s,l,c,d,u,f,m,w=this.permMod12,h=this.perm,p=this.grad3,g=(e+t+o)*(1/3),v=Math.floor(e+g),y=Math.floor(t+g),b=Math.floor(o+g),S=(v+y+b)*n,E=e-(v-S),T=t-(y-S),$=o-(b-S);E>=T?T>=$?(l=1,c=0,d=0,u=1,f=1,m=0):E>=$?(l=1,c=0,d=0,u=1,f=0,m=1):(l=0,c=0,d=1,u=1,f=0,m=1):T<$?(l=0,c=0,d=1,u=0,f=1,m=1):E<$?(l=0,c=1,d=0,u=0,f=1,m=1):(l=0,c=1,d=0,u=1,f=1,m=0);var L=E-l+n,O=T-c+n,A=$-d+n,I=E-u+2*n,_=T-f+2*n,R=$-m+2*n,M=E-1+.5,x=T-1+.5,H=$-1+.5,C=255&v,N=255&y,P=255&b,D=.6-E*E-T*T-$*$;if(D<0)r=0;else{var F=3*w[C+h[N+h[P]]];r=(D*=D)*D*(p[F]*E+p[F+1]*T+p[F+2]*$)}var U=.6-L*L-O*O-A*A;if(U<0)i=0;else{var q=3*w[C+l+h[N+c+h[P+d]]];i=(U*=U)*U*(p[q]*L+p[q+1]*O+p[q+2]*A)}var z=.6-I*I-_*_-R*R;if(z<0)a=0;else{var j=3*w[C+u+h[N+f+h[P+m]]];a=(z*=z)*z*(p[j]*I+p[j+1]*_+p[j+2]*R)}var k=.6-M*M-x*x-H*H;if(k<0)s=0;else{var G=3*w[C+1+h[N+1+h[P+1]]];s=(k*=k)*k*(p[G]*M+p[G+1]*x+p[G+2]*H)}return 32*(r+i+a+s)},noise4D:function(e,t,n,i){this.permMod12;var a,s,l,c,d,u,f,m,w,h,p,g,v,y,b,S,E,T=this.perm,$=this.grad4,L=(e+t+n+i)*o,O=Math.floor(e+L),A=Math.floor(t+L),I=Math.floor(n+L),_=Math.floor(i+L),R=(O+A+I+_)*r,M=e-(O-R),x=t-(A-R),H=n-(I-R),C=i-(_-R),N=0,P=0,D=0,F=0;M>x?N++:P++,M>H?N++:D++,M>C?N++:F++,x>H?P++:D++,x>C?P++:F++,H>C?D++:F++;var U=M-(u=N>=3?1:0)+r,q=x-(f=P>=3?1:0)+r,z=H-(m=D>=3?1:0)+r,j=C-(w=F>=3?1:0)+r,k=M-(h=N>=2?1:0)+2*r,G=x-(p=P>=2?1:0)+2*r,B=H-(g=D>=2?1:0)+2*r,V=C-(v=F>=2?1:0)+2*r,W=M-(y=N>=1?1:0)+3*r,K=x-(b=P>=1?1:0)+3*r,Y=H-(S=D>=1?1:0)+3*r,J=C-(E=F>=1?1:0)+3*r,Z=M-1+4*r,Q=x-1+4*r,X=H-1+4*r,ee=C-1+4*r,te=255&O,ne=255&A,oe=255&I,re=255&_,ie=.6-M*M-x*x-H*H-C*C;if(ie<0)a=0;else{var ae=T[te+T[ne+T[oe+T[re]]]]%32*4;a=(ie*=ie)*ie*($[ae]*M+$[ae+1]*x+$[ae+2]*H+$[ae+3]*C)}var se=.6-U*U-q*q-z*z-j*j;if(se<0)s=0;else{var le=T[te+u+T[ne+f+T[oe+m+T[re+w]]]]%32*4;s=(se*=se)*se*($[le]*U+$[le+1]*q+$[le+2]*z+$[le+3]*j)}var ce=.6-k*k-G*G-B*B-V*V;if(ce<0)l=0;else{var de=T[te+h+T[ne+p+T[oe+g+T[re+v]]]]%32*4;l=(ce*=ce)*ce*($[de]*k+$[de+1]*G+$[de+2]*B+$[de+3]*V)}var ue=.6-W*W-K*K-Y*Y-J*J;if(ue<0)c=0;else{var fe=T[te+y+T[ne+b+T[oe+S+T[re+E]]]]%32*4;c=(ue*=ue)*ue*($[fe]*W+$[fe+1]*K+$[fe+2]*Y+$[fe+3]*J)}var me=.6-Z*Z-Q*Q-X*X-ee*ee;if(me<0)d=0;else{var we=T[te+1+T[ne+1+T[oe+1+T[re+1]]]]%32*4;d=(me*=me)*me*($[we]*Z+$[we+1]*Q+$[we+2]*X+$[we+3]*ee)}return 27*(a+s+l+c+d)}},i._buildPermutationTable=a,void 0!==define&&define.amd&&define((function(){return i})),"undefined"!=typeof exports?exports.SimplexNoise=i:"undefined"!=typeof window&&(window.SimplexNoise=i),"undefined"!=typeof module&&(module.exports=i)}(),initBg=function(){var e,t,n,o,r,i,a={},s=function(){"use strict";(e={}).__proto__={a:n={}},e.a;var e,n,o=Object.defineProperty,r=Object.getOwnPropertyDescriptor,s={};function l(e,t,n){this.x=e,this.y=t,this.R=n}return o(l,"prototype",{configurable:!1,enumerable:!1,writable:!1}),s.draw=function(){t.save();var e="rgb(21, 21, "+Math.round(42*(i.noise2D(this.x/a.noiseZoom,this.y/a.noiseZoom)+1))+")";t.fillStyle=e,t.strokeStyle=e,t.translate(this.x,this.y),t.beginPath();for(var n=0;n<6;n++){var o=Math.PI/3*n+Math.PI/6,r=Math.cos(o)*this.R,s=Math.sin(o)*this.R;t.lineTo(r,s)}t.closePath(),t.stroke(),t.fill(),t.restore()},function(e,t){for(var n in t)t.hasOwnProperty(n)&&o(e,n,r(t,n))}(l.prototype,s),s=void 0,l}();function l(){n=e.width=window.innerWidth,o=e.height=window.innerHeight,t.lineCap="round",t.lineJoin="round",c()}function c(){i=new SimplexNoise,t.lineWidth=1,a.noiseZoom=400*Math.random()+200,a.size=19*Math.random()+16,function(){r=[];for(var e=a.size,t=e/Math.cos(Math.PI/6),i=2*e/Math.sqrt(3),l=n/(2*e)+1,c=o/t,d=0;d 2 | #ffffff -------------------------------------------------------------------------------- /.public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /.public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /.public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /.public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/favicon.ico -------------------------------------------------------------------------------- /.public/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /.public/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /.public/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /.public/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /.public/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /.public/favicon/z.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/favicon/z.zip -------------------------------------------------------------------------------- /.public/html/app.html: -------------------------------------------------------------------------------- 1 |
2 |
10 |
Welcome to the lapisTube project.
Go search for a YouTube® Video using the search bar above or come visit us again later and replace the DOMAIN part (youtube.com) with (lapis.tube) to view your vid on here.
All page Elements are auto-translated with gtranslate.
If there's something you want to be manually adjusted, you can open an issue or a pull request at GitHub: /lapis-tube.

© Monad & Blu; Content by YouTube, LLC

18 | -------------------------------------------------------------------------------- /.public/html/channel.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/html/channel.html -------------------------------------------------------------------------------- /.public/html/cookie.html: -------------------------------------------------------------------------------- 1 |
🍪 Cookie & data usage notice

You neeed to accept the use of cookies and third-party services (like the youtube video servers) to use this service.
More information here: #what?

6 | -------------------------------------------------------------------------------- /.public/html/player-inside.html: -------------------------------------------------------------------------------- 1 |
Current Time 0:00
Duration Time 0:31
Loaded: 0%
Progress: 0%
Mute
41 | -------------------------------------------------------------------------------- /.public/html/player.html: -------------------------------------------------------------------------------- 1 |
{{title}}
6 | -------------------------------------------------------------------------------- /.public/html/result-item.html: -------------------------------------------------------------------------------- 1 |
{{title}}
Length: {{duration}}({{duration-detailed}})
 {{author}}

 {{views}}

{{preview-desc}}

4 | -------------------------------------------------------------------------------- /.public/html/result-list.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /.public/html/what.html: -------------------------------------------------------------------------------- 1 |
📈 usage of data

We use gTranslate for our application translations. You can check out their terms here.
While search results from the live search are proxified and thus anonymized, they might still get stored on google's servers.
Also due to the Videos being directly loaded off from google's servers, your connection data might get stored too.
You can check out the YouTube policies and read how YouTube will handle this data.
When you visit this web application, your location gets anonymously resolved into a location by using the Databases of MaxMind (we only use your external IP adress).
We currently use your region (as indicated by the logo coloring) to show search results and video for your current country.

5 | -------------------------------------------------------------------------------- /.public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 𝑙𝑎𝑝𝑖𝑠Tube 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /.public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/loading.gif -------------------------------------------------------------------------------- /.public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/.public/logo.png -------------------------------------------------------------------------------- /.public/outdated-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | outdated Browser 6 | 8 | 17 | 18 |
Oops, it seems that your browser version is unsupported
You are using one of the web browsers that we don't support
To use this page, please update your browser or download the latest stable version of one of the below.
You can click on one to get to the installer website.
Chrome
Firefox
Edge
25 | 26 | 27 | -------------------------------------------------------------------------------- /.public/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-accent-main: #1b294b; 3 | --color-font-light: mintcream; 4 | --color-font-dark: rgba(24, 24, 24); } 5 | 6 | /* override */ 7 | param { 8 | display: inline-block; 9 | padding-right: 5px; 10 | padding-left: 5px; } 11 | 12 | html, 13 | body { 14 | background: var(--color-accent-main); 15 | position: absolute; 16 | height: 100%; 17 | width: 100%; } 18 | 19 | .text-accent { 20 | color: var(--color-accent-main); } 21 | 22 | /* if backdrop support: very transparent and blurred */ 23 | @supports (-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em)) { 24 | .backdrop-blur { 25 | background-color: rgba(255, 255, 255, 0.5); 26 | -webkit-backdrop-filter: blur(0.75rem); 27 | backdrop-filter: blur(0.75rem); } } 28 | 29 | /**************************/ 30 | /**************************/ 31 | @supports not ((-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em))) { 32 | /* slightly transparent, dark fallback */ 33 | .backdrop-blur { 34 | background-color: rgba(0, 0, 0, 0.9) !important; 35 | color: white !important; } } 36 | 37 | /**************************/ 38 | /**************************/ 39 | .burger-flag { 40 | margin-bottom: 4px; 41 | width: 13.13px; 42 | height: 14px !important; 43 | opacity: 0.5; } 44 | 45 | #top, 46 | #left { 47 | background: rgba(255, 255, 255, 0.1); 48 | border-color: rgba(255, 255, 255, 0.1) !important; } 49 | 50 | #logo { 51 | color: var(--color-font-light); 52 | font-family: consolas; 53 | /* #yt-lang { 54 | background-repeat: no-repeat; 55 | -webkit-background-clip: text; 56 | background-size: contain; 57 | -webkit-text-fill-color: rgba(255, 255, 255, 0.5); 58 | background-position-x: center; 59 | background-position-y: 2px; 60 | filter: drop-shadow(0px 0px 3px black) contrast(2); 61 | }*/ 62 | /*#logo img{ 63 | height: 36px; 64 | width: 36px; 65 | background-repeat: no-repeat; 66 | -webkit-background-clip: unset; 67 | background-size: 25px 25px; 68 | background-blend-mode: color; 69 | background-position: center; 70 | }*/ } 71 | #logo .dynamic-logo { 72 | filter: drop-shadow(0px 0px 8px rgba(0, 0, 0, 0.42)); 73 | cursor: pointer; 74 | /*show original on hover */ } 75 | #logo .dynamic-logo:hover { 76 | filter: drop-shadow(0px 0px 8px rgba(255, 255, 255, 0.42)); } 77 | #logo .dynamic-logo:hover > .alpha-mask > .alpha-original { 78 | filter: brightness(1.2) contrast(3); } 79 | #logo .dynamic-logo:hover > .alpha-mask > .alpha-target { 80 | opacity: 0; } 81 | #logo .dynamic-logo > .alpha-mask { 82 | mask-image: url(./logo.png); 83 | -webkit-mask-image: url(./logo.png); 84 | mask-mode: alpha; 85 | -webkit-mask-mode: alpha; 86 | mask-repeat: no-repeat; 87 | -webkit-mask-repeat: no-repeat; 88 | mask-repeat: no-repeat; 89 | -webkit-mask-repeat: round; 90 | height: 42px; 91 | width: 42px; 92 | /* important: must be before alpha-target in html structure (or use z-index: > x) */ } 93 | #logo .dynamic-logo > .alpha-mask > .alpha-original { 94 | width: 42px; 95 | height: 42px; 96 | opacity: 0.42; 97 | filter: brightness(1.5) contrast(3); } 98 | #logo .dynamic-logo > .alpha-mask > .alpha-target { 99 | transition: opacity 499ms ease; 100 | opacity: 1; 101 | width: 42px; 102 | height: 42px; 103 | position: absolute; 104 | mix-blend-mode: color; } 105 | 106 | #top { 107 | /* Chrome, Firefox, Opera, Safari 10.1+ */ } 108 | #top .btn { 109 | background: var(--color-accent-main); 110 | color: var(--color-font-light); } 111 | #top #toggle-left { 112 | cursor: pointer; } 113 | #top #toggle-left:hover { 114 | background: rgba(255, 255, 255, 0.5); } 115 | #top #toggle-left:active { 116 | background: rgba(255, 255, 255, 0.75); } 117 | #top input { 118 | background: rgba(255, 255, 255, 0.3); 119 | color: var(--color-font-light); } 120 | #top input { 121 | /* Internet Explorer 10-11 */ 122 | /* Microsoft Edge */ 123 | /*ie no clear btn */ } 124 | #top input::placeholder { 125 | color: var(--color-font-light); } 126 | #top input :-ms-input-placeholder { 127 | color: var(--color-font-light); } 128 | #top input ::-ms-input-placeholder { 129 | color: var(--color-font-light); } 130 | #top input ::-ms-clear { 131 | display: none; } 132 | 133 | .live-search { 134 | background: rgba(255, 255, 255, 0.1); } 135 | .live-search > div { 136 | background: rgba(255, 255, 255, 0.1); 137 | padding: 0.4rem 0.8rem; 138 | color: snow; } 139 | .live-search > div.selected { 140 | background: rgba(255, 255, 255, 0.25); } 141 | .live-search > div:hover { 142 | background: rgba(0, 0, 255, 0.1) !important; 143 | color: white; } 144 | .live-search > div:active { 145 | background: rgba(255, 255, 255, 0.6) !important; 146 | color: mintcream; } 147 | 148 | /**************************/ 149 | /**************************/ 150 | #left { 151 | transition: left ease-out 299ms; } 152 | #left[expanded="false"] { 153 | left: -100%; } 154 | #left[expanded="true"] { 155 | left: 0; } 156 | #left .btn { 157 | background: rgba(0, 0, 0, 0.1) !important; 158 | color: rgba(255, 255, 255, 0.75) !important; 159 | border-color: rgba(255, 255, 255, 0.1) !important; } 160 | #left .btn:hover, #left .btn:focus { 161 | background: rgba(255, 255, 255, 0.3) !important; } 162 | #left .btn:active { 163 | background: rgba(255, 255, 255, 0.5) !important; } 164 | 165 | /**************************/ 166 | /**************************/ 167 | footer#footer { 168 | background: rgba(255, 255, 255, 0.1); 169 | border-color: rgba(255, 255, 255, 0.1) !important; 170 | color: var(--color-font-dark); } 171 | 172 | /**************************/ 173 | /**************************/ 174 | #inner { 175 | overflow-y: auto; } 176 | 177 | #results { 178 | background: rgba(0, 0, 0, 0.3); 179 | height: 75vh; } 180 | #results::-webkit-scrollbar { 181 | width: 16px; 182 | height: 16px; } 183 | #results::-webkit-scrollbar-track { 184 | background: #E0E0E0; } 185 | #results::-webkit-scrollbar-thumb { 186 | background: #9E9E9E; } 187 | #results::-webkit-scrollbar-thumb:hover { 188 | background: #BDBDBD; } 189 | 190 | #filters { 191 | background: rgba(255, 255, 255, 0.75); } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A fancy and open alternative YouTube client. 2 | ================= 3 | 4 | - Built with some of the latest JavaScripts 5 | 6 | - Transmuxing highest available audio & video streams on supported platforms (not safari eh) 7 | 8 | - Region-dependant search results and UI using maxmind & gtranslate 9 | 10 | - livesearch 11 | 12 | - afterglow player 13 | 14 | - custom community-driven Categories (coming up) 15 | 16 | - using Invidiou.us and YT Servers 17 | 18 | [https://lapis.tube](https://lapis.tube) -------------------------------------------------------------------------------- /build/components/filters.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /build/components/indicators.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /build/components/loader.html: -------------------------------------------------------------------------------- 1 | 12 | 24 |
25 | 38 | 43 | 47 | 56 | 57 |
58 | 59 | 75 | -------------------------------------------------------------------------------- /build/cookie-what.html: -------------------------------------------------------------------------------- 1 |
5 |
9 |
10 |
14 | 📈 usage of data 15 |
16 |
17 |
20 |

21 | We use gTranslate for our application translations. You can check 22 | out their terms here.
23 | While search results from the live search are proxified and thus 24 | anonymized, they might still get stored on google's servers.
25 | Also due to the Videos being directly loaded off from google's servers, 26 | your connection data might get stored too.
27 | You can check out the 28 | YouTube policies 29 | and read how YouTube will handle this data. 30 |
31 | When you visit this web application, your location gets anonymously 32 | resolved into a location by using the Databases of 33 | MaxMind 34 | (we only use your external IP adress).
35 | We currently use your region (as indicated by the logo coloring) to show 36 | search results and video for your current country. 37 |

38 |
39 | 51 |
52 |
53 | -------------------------------------------------------------------------------- /build/cookie.html: -------------------------------------------------------------------------------- 1 | 6 |
10 |
11 |
15 | 🍪 Cookie & data usage notice 16 |
17 |
18 |
21 |

25 | You neeed to accept the use of cookies and third-party services (like 26 | the youtube video servers) to use this service.
27 | More information here: #what? 28 |

29 |
30 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /build/css/lib/bs-shards.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/build/css/lib/bs-shards.css -------------------------------------------------------------------------------- /build/css/lib/neumorphism-dark.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/build/css/lib/neumorphism-dark.css -------------------------------------------------------------------------------- /build/css/lib/neumorphism-light.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/build/css/lib/neumorphism-light.css -------------------------------------------------------------------------------- /build/css/style.sass.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-font-light: mintcream; 3 | --color-font-dark: rgba(24, 24, 24); 4 | } 5 | 6 | /* override */ 7 | param { 8 | display: inline-block; 9 | padding-right: 5px; 10 | padding-left: 5px; 11 | } 12 | 13 | /* smooth scroll */ 14 | * { 15 | scroll-behavior: smooth; 16 | } 17 | @media (prefers-reduced-motion: reduce) { 18 | * { 19 | scroll-behavior: auto; 20 | } 21 | } 22 | 23 | /* no long-click to dl on images */ 24 | img { 25 | pointer-events: none; 26 | } 27 | 28 | /* inherit height (make main height fit to content inside) */ 29 | main { 30 | height: inherit; 31 | } 32 | 33 | @keyframes placeHolderShimmer { 34 | 0% { 35 | background-position: -468px 0; 36 | } 37 | 100% { 38 | background-position: 468px 0; 39 | } 40 | } 41 | 42 | .close { 43 | z-index: 3000; 44 | &:hover { 45 | background-color: yellow; 46 | } 47 | } 48 | 49 | html, 50 | body { 51 | position: absolute; 52 | height: 100%; 53 | width: 100%; 54 | overflow: hidden; 55 | /*font-family: "Oxygen", sans-serif !important;*/ 56 | font-family: "Roboto", sans-serif; 57 | } 58 | 59 | .text-accent { 60 | color: var(--color-accent-main) !important; 61 | } 62 | 63 | .text-black { 64 | colro: black; 65 | } 66 | 67 | /* if backdrop support: very transparent and blurred */ 68 | @supports ( 69 | (-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em)) 70 | ) { 71 | .backdrop-blur { 72 | background-color: rgba(255, 255, 255, 0.5); 73 | -webkit-backdrop-filter: blur(0.75rem); 74 | backdrop-filter: blur(0.75rem); 75 | } 76 | } 77 | 78 | /**************************/ 79 | //404/////////////////////// 80 | /**************************/ 81 | .bsod { 82 | max-height: 80vh; 83 | $light-grey: #e0e2f4; 84 | $grey: #aaaaaa; 85 | $blue: #0414a7; 86 | width: 90%; 87 | padding: 25%; 88 | align-self: center; 89 | background: $blue; 90 | background: rgba(0, 0, 255, 0.75); 91 | /* mix-blend-mode: color-burn; */ 92 | backdrop-filter: blur(3px); 93 | p { 94 | color: $light-grey; 95 | } 96 | .neg { 97 | text-align: center; 98 | color: $blue; 99 | 100 | .bg { 101 | background: $grey; 102 | padding: 0 15px 2px 13px; 103 | } 104 | } 105 | .title { 106 | margin-bottom: 50px; 107 | } 108 | .nav { 109 | margin-top: 35px; 110 | text-align: center; 111 | 112 | .link { 113 | background: none !important; 114 | border: unset; /* reset / override bs */ 115 | color: $light-grey; 116 | text-decoration: none; 117 | border-style: double; 118 | 119 | padding: 0 9px 2px 8px; 120 | 121 | &:hover, 122 | &:focus { 123 | background: $grey !important; 124 | color: $blue !important; 125 | } 126 | } 127 | } 128 | } 129 | 130 | /**************************/ 131 | //BACKDROP-BLUR&FALLBACK//// 132 | /**************************/ 133 | @supports not ( 134 | (-webkit-backdrop-filter: blur(2em)) or (backdrop-filter: blur(2em)) 135 | ) { 136 | /* slightly transparent, dark fallback */ 137 | .backdrop-blur { 138 | background-color: rgba(0, 0, 0, 0.9) !important; 139 | color: white !important; 140 | } 141 | } 142 | 143 | /**************************/ 144 | //NAVBAR TOP//////////////// 145 | /**************************/ 146 | #top, 147 | #left { 148 | z-index: 3; 149 | background: rgba(255, 255, 255, 0.1); 150 | border-color: rgba(255, 255, 255, 0.1) !important; 151 | } 152 | 153 | #dynamic-logo { 154 | color: var(--color-font-light); 155 | font-family: consolas; 156 | filter: drop-shadow(0px 0px 8px rgba(0, 0, 0, 0.42)); 157 | $width: 37px; 158 | $height: 37px; /* should be less high than search bar to prevent hopping on input focussing */ 159 | cursor: pointer; 160 | /*show original on hover */ 161 | &:hover { 162 | filter: drop-shadow(0px 0px 8px rgba(255, 255, 255, 0.42)); 163 | > .alpha-mask { 164 | > .alpha-original { 165 | filter: brightness(1.2) contrast(3); 166 | } 167 | > .alpha-target { 168 | opacity: 0; 169 | } 170 | } 171 | } 172 | > .alpha-mask { 173 | mask-image: var(--alpha-logo); 174 | -webkit-mask-image: var(--alpha-logo); 175 | mask-mode: alpha; 176 | -webkit-mask-mode: alpha; 177 | mask-repeat: no-repeat; 178 | -webkit-mask-repeat: no-repeat; 179 | mask-repeat: no-repeat; 180 | -webkit-mask-repeat: round; 181 | height: $height; 182 | width: $width; 183 | /* important: must be before alpha-target in html structure (or use z-index: > x) */ 184 | > .alpha-original { 185 | width: $width; 186 | height: $height; 187 | opacity: 0.42; 188 | filter: brightness(1.5) contrast(3); 189 | } 190 | > .alpha-target { 191 | transition: opacity 199ms linear; 192 | will-change: opacity; 193 | opacity: 1; 194 | width: $width; 195 | height: $height; 196 | mix-blend-mode: color; 197 | } 198 | } 199 | } 200 | 201 | #top { 202 | & .btn { 203 | background: var(--color-accent-main); 204 | color: var(--color-font-light); 205 | } 206 | 207 | #toggle-left { 208 | cursor: pointer; 209 | &:hover { 210 | background: rgba(255, 255, 255, 0.5); 211 | } 212 | &:active { 213 | background: rgba(255, 255, 255, 0.75); 214 | } 215 | } 216 | 217 | #search { 218 | & input { 219 | background: rgba(255, 255, 255, 0.3); 220 | color: var(--color-font-light); 221 | } 222 | 223 | #search-btn button { 224 | /* prevent search from jumping on inputfocus (caused by different height when button is gone) */ 225 | border: none; 226 | } 227 | 228 | //Platzhalter-Styling & Fixes 229 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 230 | & input { 231 | &::placeholder { 232 | color: var(--color-font-light); 233 | } 234 | 235 | /* Internet Explorer 10-11 */ 236 | :-ms-input-placeholder { 237 | color: var(--color-font-light); 238 | } 239 | 240 | /* Microsoft Edge */ 241 | ::-ms-input-placeholder { 242 | color: var(--color-font-light); 243 | } 244 | /*ie no clear btn */ 245 | ::-ms-clear { 246 | display: none; 247 | } 248 | } 249 | } 250 | } 251 | 252 | //SUCHLEISTE 253 | 254 | .live-search { 255 | background: rgba(0, 0, 0, 0.25); 256 | backdrop-filter: blur(3rem); 257 | 258 | > div { 259 | background: rgba(255, 255, 255, 0.1); 260 | padding: 0.4rem 0.8rem; 261 | color: snow; 262 | } 263 | > div.selected { 264 | background: var(--color-accent-main); 265 | } 266 | 267 | > div:hover { 268 | background: rgba(0, 0, 255, 0.1) !important; 269 | color: white; 270 | } 271 | 272 | > div:active { 273 | background: rgba(0, 0, 0, 0.25) !important; 274 | color: mintcream; 275 | } 276 | } 277 | 278 | /**************************/ 279 | //NAVBAR LEFT/////////////// 280 | /**************************/ 281 | #left { 282 | z-index: 3; 283 | transition: left linear 299ms; 284 | will-change: left; 285 | &[expanded="false"] { 286 | left: -100%; 287 | } 288 | &[expanded="true"] { 289 | left: 0; 290 | } 291 | & .btn { 292 | background: rgba(0, 0, 0, 0.1) !important; 293 | color: rgba(255, 255, 255, 0.75) !important; 294 | border-color: rgba(255, 255, 255, 0.1) !important; 295 | 296 | &:hover, 297 | &:focus { 298 | background: rgba(255, 255, 255, 0.3) !important; 299 | } 300 | &:active { 301 | background: rgba(255, 255, 255, 0.5) !important; 302 | } 303 | } 304 | } 305 | /**************************/ 306 | //FOOTER//////////////////// 307 | /**************************/ 308 | footer#footer { 309 | background: rgba(255, 255, 255, 0.1); 310 | border-color: rgba(255, 255, 255, 0.1) !important; 311 | color: var(--color-font-dark); 312 | z-index: 3; 313 | 314 | width: 100%; 315 | 316 | background-color: #f5f5f5; 317 | } 318 | 319 | /**************************/ 320 | //CONTENT/////////////////// 321 | /**************************/ 322 | content { 323 | position: absolute; 324 | width: inherit; 325 | height: inherit; 326 | z-index: 1; 327 | } 328 | views { 329 | filter: none; 330 | transition: filter 299ms linear; 331 | will-change: filter; 332 | &.wait { 333 | filter: blur(5px); 334 | cursor: wait; 335 | pointer-events: none; 336 | touch-action: none; 337 | } 338 | } 339 | /* canvas for bg */ 340 | #canvas { 341 | z-index: 0; 342 | position: fixed; 343 | width: 100%; 344 | height: 100%; 345 | } 346 | 347 | [loading] { 348 | animation-duration: 1.25s; 349 | animation-fill-mode: forwards; 350 | animation-iteration-count: infinite; 351 | animation-name: placeHolderShimmer; 352 | animation-timing-function: linear; 353 | background: darkgray; 354 | background: linear-gradient(to right, #eeeeee 10%, #dddddd 18%, #eeeeee 33%); 355 | background-size: 800px 104px; 356 | height: 100px; 357 | position: relative; 358 | } 359 | 360 | #player { 361 | #player-inner { 362 | max-height: 82vh; 363 | transform: scale(1); 364 | transition: transform 299ms linear; 365 | will-change: transform; 366 | &[closing] { 367 | transform: scale(0.9); 368 | } 369 | } 370 | .card { 371 | .card-body { 372 | flex: 0; 373 | -ms-flex: 0; 374 | } 375 | height: inherit; 376 | #player-desc { 377 | height: inherit; 378 | width: inherit; 379 | overflow-y: auto; 380 | } 381 | /* yt/invidious html desciption fixes */ 382 | #eow-description { 383 | overflow-y: auto; 384 | margin-bottom: 0; 385 | } 386 | } 387 | } 388 | 389 | #results-inner.scrolling { 390 | -webkit-filter: url(#scrollblur); 391 | filter: url(#scrollblur); 392 | } 393 | 394 | [role="listpage"] { 395 | background: rgba(0, 0, 0, 0.3); 396 | $flex: 1 0 0; 397 | -webkit-flex: $flex; 398 | 399 | -ms-flex: $flex; 400 | -mz-flex: $flex; 401 | flex: $flex; 402 | overflow-y: auto; 403 | 404 | .card { 405 | will-change: opacity, transform; 406 | transition: opacity 299ms linear; 407 | } 408 | /*fake-grow before popping into player*/ 409 | &.grow { 410 | overflow: hidden; 411 | .card { 412 | transition: transform 299ms linear; 413 | } 414 | .card.growing { 415 | pointer-events: none; 416 | transform: scale(1.1); 417 | z-index: 1; 418 | overflow: visible !important; 419 | } 420 | } 421 | 422 | &.fading { 423 | overflow: hidden; 424 | .card { 425 | opacity: 0; 426 | } 427 | } 428 | 429 | [role="listpage/filters"] { 430 | z-index: 2; 431 | place-self: flex-end; 432 | align-items: center; 433 | [role="listpage/filters/inner"] { 434 | [role="listpage/filters/info"] { 435 | white-space: nowrap; 436 | margin: 0 2rem; 437 | } 438 | border-color: rgba(255, 255, 255, 0.1) !important; 439 | color: white; 440 | } 441 | } 442 | 443 | [role="listpage/inner"] { 444 | height: -moz-available; 445 | height: -webkit-fill-available; 446 | height: fill-available; 447 | 448 | .card { 449 | /*box-shadow: 0 0 0.1rem 1rem rgba(0, 0, 0, 0.1);*/ 450 | box-shadow: 0 0 0.1rem 1rem rgba(255, 255, 255, 0.1); 451 | cursor: pointer; 452 | overflow: hidden; 453 | background: rgba(0, 0, 0, 0.3); 454 | border-radius: 0.3rem; 455 | figure { 456 | margin-bottom: 0; 457 | figcaption { 458 | border: 0.1rem solid rgba(255, 255, 255, 0.2); 459 | border: none; 460 | 461 | width: -moz-available; 462 | width: -webkit-fill-available; 463 | width: fill-available; 464 | /* preview title */ 465 | item-title { 466 | background: rgba(255, 255, 255, 0.8); 467 | display: flex; 468 | font-size: 0.9rem; 469 | padding: 0.3rem 1rem; 470 | color: black; 471 | /*show on direct hovering */ 472 | &:hover { 473 | opacity: 1 !important; 474 | } 475 | } 476 | duration { 477 | font-size: 0.8rem; 478 | margin-top: -2rem; 479 | margin-left: 0.4rem; 480 | opacity: 1; 481 | 482 | padding: 0.1rem 0.3rem; 483 | border-radius: 0.25rem; 484 | background: rgba(0, 0, 0, 0.84); 485 | color: white; 486 | 487 | duration-word { 488 | display: none; 489 | } 490 | duration-detailed { 491 | display: none; 492 | } 493 | /*show on direct hovering */ 494 | &:hover { 495 | opacity: 1 !important; 496 | } 497 | } 498 | } 499 | img { 500 | height: 17vh; 501 | border: 0.1rem solid rgba(255, 255, 255, 0.8); 502 | border-right: none; 503 | border-left: none; 504 | filter: grayscale(0.6); 505 | object-fit: cover; 506 | } 507 | 508 | /* highlight image on hover */ 509 | &:hover { 510 | figcaption { 511 | duration { 512 | opacity: 0.8; 513 | backdrop-filter: blur(2rem); 514 | background: rgba(0, 0, 0, 0.25); 515 | color: white; 516 | } 517 | } 518 | } 519 | } 520 | 521 | /* enlarge result card on hover */ 522 | &:hover { 523 | z-index: 1; 524 | 525 | transform: scale(1.05); 526 | img { 527 | filter: none; 528 | } 529 | .card-body { 530 | background: white; 531 | } 532 | figcaption { 533 | item-title { 534 | background: white; 535 | } 536 | duration { 537 | duration-word { 538 | display: inline-block; 539 | } 540 | opacity: 1; 541 | duration-detailed { 542 | display: inline-block; 543 | } 544 | } 545 | } 546 | } 547 | 548 | .card-body { 549 | background: rgba(255, 255, 255, 0.8); 550 | } 551 | } 552 | } 553 | } 554 | 555 | * { 556 | ::-webkit-scrollbar { 557 | width: 16px; 558 | height: 16px; 559 | } 560 | 561 | ::-webkit-scrollbar-track { 562 | background: #E0E0E0; 563 | } 564 | 565 | ::-webkit-scrollbar-thumb { 566 | background: #9E9E9E; 567 | height: 5vh !important; 568 | } 569 | ::-webkit-scrollbar-thumb:hover { 570 | background: #BDBDBD; 571 | } 572 | } 573 | 574 | /* PLAYER ELEMENTS */ 575 | lapis-player { 576 | display: flex; 577 | max-height: 75vh; 578 | width: 100%; 579 | /*PLAYER*/ 580 | audio { 581 | display: none; 582 | } 583 | video { 584 | position: absolute; 585 | width: 100%; 586 | } 587 | 588 | poster { 589 | width: inherit; 590 | height: 50vh; 591 | overflow: hidden; 592 | img { 593 | height: inherit; 594 | width: inherit; 595 | } 596 | canvas { 597 | height: inherit; 598 | width: inherit; 599 | object-fit: cover; 600 | position: absolute; 601 | mix-blend-mode: hard-light; 602 | transform: scale(1.01); 603 | } 604 | } 605 | 606 | lapis-warning { 607 | &[name="nosource"] { 608 | background: red; 609 | cursor: no-drop; 610 | } 611 | &[name="muted"] { 612 | background: yellow; 613 | cursor: pointer; 614 | } 615 | font-size: 150%; 616 | font-weight: bold; 617 | display: none; 618 | left: 0; 619 | top: 0; 620 | height: 100%; 621 | width: inherit; 622 | position: absolute; 623 | color: black; 624 | align-items: center; 625 | justify-content: center; 626 | z-index: 4; 627 | cursor: pointer; 628 | 629 | &:hover { 630 | filter: contrast(0.8); 631 | } 632 | &:focus, 633 | &:active { 634 | filter: contrast(0.75); 635 | } 636 | } 637 | 638 | /*https://codepen.io/BainjaminLafalize/pen/GiJwn*/ 639 | /*https://codepen.io/Varo/pen/vLdvMK*/ 640 | 641 | .afterglow__video { 642 | display: none; 643 | /* show player title in fullscreen only */ 644 | .afterglow__title-bar { 645 | display: none; 646 | } 647 | &:fullscreen .afterglow__title-bar { 648 | display: flex; 649 | } 650 | } 651 | video, 652 | .afterglow__video { 653 | height: inherit; 654 | width: inherit; 655 | max-height: inherit; 656 | min-width: 0 !important; 657 | } 658 | 659 | /*.afterglow__container video { 660 | object-fit: fill; 661 | }*/ 662 | 663 | /* customized afterglow player */ 664 | .afterglow__control-bar, 665 | .afterglow__title-bar { 666 | backdrop-filter: blur(1rem); 667 | padding: 0.5vh 0; 668 | position: absolute; 669 | width: calc(100% + 60px); 670 | left: -30px; 671 | z-index: -1; 672 | background: rgba(0, 0, 0, 0.5); 673 | border: 0.1rem solid rgba(255, 255, 255, 0.1); 674 | } 675 | 676 | /* fancy controlbar */ 677 | .afterglow__control-bar { 678 | height: 4.9rem; 679 | backdrop-filter: blur(1rem); 680 | bottom: 0; 681 | } 682 | /* fancy titlebar */ 683 | .afterglow__title-bar { 684 | height: 5rem; 685 | display: flex; 686 | align-items: center; 687 | justify-content: center; 688 | color: rgba(255, 255, 255, 0.9); 689 | backdrop-filter: blur(1rem); 690 | top: 0; 691 | } 692 | /* custom fullscreen toggle */ 693 | .afterglow__top-control-bar { 694 | backdrop-filter: blur(2rem); 695 | 696 | > a { 697 | height: 2vmax; 698 | background-size: inherit; 699 | 700 | width: 2vmax; 701 | &:hover { 702 | height: 4vmax; 703 | width: 4vmax; 704 | filter: brightness(50%) sepia(100) saturate(5) hue-rotate(18deg); 705 | } 706 | margin-left: 0; 707 | } 708 | } 709 | } 710 | -------------------------------------------------------------------------------- /build/html/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Error  –    404

4 |

The requested resource could not be found, to continue:

5 |

6 | * Return to the start.
7 | * Go back
8 | * Open an issue about this error and try later. 9 |

10 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /build/html/app.html: -------------------------------------------------------------------------------- 1 | 5 |
6 |
7 |
12 | 13 |
14 | 15 | 28 | 29 | 49 |
50 |
51 | 52 |
53 | 83 | 86 | 87 |
88 |
89 |
90 |

91 | © Monad & Blu; Content by 92 | YouTube LLC 93 |

94 |
95 |
96 |
97 | -------------------------------------------------------------------------------- /build/html/channel.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blubbll/lapis-tube/8677421a8438de89699aa1804a2f4f08eb49d894/build/html/channel.html -------------------------------------------------------------------------------- /build/html/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Error  5 |

6 |

An error has occured, to continue:

7 |

8 | * Return to the start.
9 | * Reload the current page
10 | * Open an issue about this error and try later. 11 |

12 | 39 |
40 |
41 | -------------------------------------------------------------------------------- /build/html/history-list.html: -------------------------------------------------------------------------------- 1 | 6 |
11 |
16 |

17 | Watched videos ↴ 18 |

19 | 22 |
23 |
24 |
29 |
30 | -------------------------------------------------------------------------------- /build/html/player.html: -------------------------------------------------------------------------------- 1 | 5 |
9 |
12 |
13 |
16 |
{{title}}
17 | 25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 | 58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /build/html/result-item.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 |
9 | 10 |
{{title}}
11 |
12 |
13 | 14 | 15 |
16 | 17 | Length: 18 | {{duration}}({{duration-detailed}}) 22 | 23 |
24 |
25 | 26 |
27 |
28 | 32 |  {{author}} 33 | 34 |

35 |  {{views}} 36 |

37 |
38 | 39 |

40 | {{preview-desc}} 41 |

42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /build/html/result-list.html: -------------------------------------------------------------------------------- 1 | 6 |
11 |
16 |

17 | Search results ↴ 18 |

19 | 22 |
23 |
24 |
25 | Search for: 26 | 33 |
34 |
35 | Order by 36 | 44 |
45 |
46 |
47 |
48 |
49 |
54 |
55 | -------------------------------------------------------------------------------- /build/html/start.html: -------------------------------------------------------------------------------- 1 |
6 |
9 | Welcome to the lapisTube project.
10 | Go search for a YouTube® Video using the search bar above or come 11 | visit us again later and replace the DOMAIN part (youtube.com) with 12 | (lapis.tube) to view your vid on here.
13 | All page Elements are auto-translated with gtranslate.
14 | If there's something you want to be manually adjusted, you can open an 15 | issue or a pull request at GitHub: 16 | /lapis-tube. 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 56 | 62 | 68 | 74 | 75 | 76 | 77 | 81 | 82 | 83 | 𝑙𝑎𝑝𝑖𝑠Tube 84 | 85 | 91 | 92 | 93 | 94 | 98 | 99 | 103 | 104 | 108 | 109 | 113 | 114 | 115 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 130 | 131 | 135 | 136 | 137 | 138 | 139 | 143 | 144 | 145 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 177 | 178 | 179 | 180 | 181 | {{indicators}} {{filters}} 182 | 183 | 184 | 185 | 186 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /build/js/_HISTORY.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | //get generic 7 | const { 8 | lscache, 9 | waitForElement, 10 | getSize, 11 | getL, 12 | fetch, 13 | debounce, 14 | alert, 15 | done, 16 | initBg, 17 | createThumbs, 18 | autocomplete, 19 | applyWords, 20 | moment, 21 | numeral, 22 | load, 23 | lazyload 24 | } = window; 25 | //set generic 26 | let { app_start, route, PouchDB } = window; 27 | //-// 28 | const _L = new Proxy( 29 | {}, 30 | { 31 | get: (obj, prop, val) => { 32 | { 33 | const _app = "L"; 34 | 35 | !window[_app] && [(window[_app] = {})]; 36 | !window[_app][prop] && [(window[_app][prop] = {})]; 37 | for (const key of Object.keys(window.L)) { 38 | window[_app][key] = window[key]; 39 | } 40 | } 41 | return window[prop]; 42 | }, 43 | set: function(obj, prop, val) { 44 | const _app = "L"; 45 | window[prop] = val; 46 | !window[_app] && [(window[_app] = {})], 47 | !window[_app][prop] && [(window[_app][prop] = {})]; 48 | window[_app][prop] = val; 49 | } 50 | } 51 | ); 52 | //get Elements of L 53 | const { GEO, URL, REGION, Player, setActiveView, T, UI } = _L; 54 | //set Elements of L 55 | let { setupHistory, showHistory, clearHistory } = _L; 56 | 57 | clearHistory = () => { 58 | lscache.setBucket("history"); 59 | lscache.flush(); 60 | }; 61 | 62 | showHistory = () => { 63 | setActiveView("history"); 64 | 65 | document.title = UI.titles.history; 66 | 67 | history.pushState(null, null, `${URL.LOCAL}/history`); 68 | 69 | //clear old results 70 | $("#history-inner").innerHTML = ""; 71 | 72 | let page = $("#history-inner").getAttribute("page") || 0; 73 | 74 | //get total history 75 | const keys = Object.keys(localStorage).filter( 76 | key => 77 | key.startsWith("lscache-history") && !key.endsWith("-cacheexpiration") 78 | ); 79 | 80 | if (keys.length) { 81 | for (const key of keys.reverse()) { 82 | const vid = JSON.parse(localStorage.getItem(key)); 83 | 84 | //build results 85 | let srcSet = createThumbs(vid.videoThumbnails); 86 | 87 | const getDurationDetailed = () => { 88 | let detailed = moment 89 | .utc(moment.duration(vid.lengthSeconds, "seconds").asMilliseconds()) 90 | .format("HH:mm:ss"); 91 | //slice empty hours 92 | detailed.startsWith("00:") && [(detailed = detailed.slice(3))]; 93 | //slice empty minutes 94 | detailed.startsWith("0") && [(detailed = detailed.slice(1))]; 95 | 96 | return detailed; 97 | }; 98 | //CONSTRUCT HTML 99 | let HTML = T.RESULT 100 | //FILL title 101 | .replace("{{title}}", vid.title) 102 | //fill previewset placeholder 103 | .replace("{{preview-set}}", srcSet.slice(0, -1)) 104 | //FILL DESCRIPTION 105 | .replace("{{preview-desc}}", vid.description) 106 | //FILL DURATION 107 | .replace( 108 | "{{duration}}", 109 | moment.duration(vid.lengthSeconds * 1000).humanize() 110 | ) //DURATION DETAILED 111 | .replace("{{duration-detailed}}", getDurationDetailed()) 112 | //FILL video id 113 | .replace("{{video}}", vid.videoId) 114 | //FILL AUTHOR 115 | .replace("{{author}}", vid.author) 116 | //FILL AUTHOR id 117 | .replace("{{authorid}}", vid.authorId) 118 | //FILL VIEWS (formatted) 119 | .replace("{{views}}", numeral(vid.viewCount || 0).format(`0.a`)); 120 | 121 | //render result 122 | $("#history-inner").insertAdjacentHTML("beforeend", HTML); 123 | 124 | //setup lazyloading 125 | lazyload($$("figure>img")); 126 | } 127 | } else { 128 | console.debug("Nothing found :("); 129 | $( 130 | "#history-inner" 131 | ).innerHTML = `${UI.warnings.nothings}`; 132 | } 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /build/js/_SEARCH.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | //get generic 7 | const { 8 | Browser, 9 | lscache, 10 | waitForElement, 11 | getSize, 12 | getL, 13 | fetch, 14 | debounce, 15 | alert, 16 | done, 17 | initBg, 18 | createThumbs, 19 | autocomplete, 20 | applyWords, 21 | moment, 22 | numeral, 23 | load, 24 | lazyload 25 | } = window; 26 | //set generic 27 | let { app_start, route, showError } = window; 28 | //-// 29 | const _L = new Proxy( 30 | {}, 31 | { 32 | get: (obj, prop, val) => { 33 | { 34 | const _app = "L"; 35 | 36 | !window[_app] && [(window[_app] = {})]; 37 | !window[_app][prop] && [(window[_app][prop] = {})]; 38 | for (const key of Object.keys(window.L)) { 39 | window[_app][key] = window[key]; 40 | } 41 | } 42 | return window[prop]; 43 | }, 44 | set: function(obj, prop, val) { 45 | const _app = "L"; 46 | window[prop] = val; 47 | !window[_app] && [(window[_app] = {})], 48 | !window[_app][prop] && [(window[_app][prop] = {})]; 49 | window[_app][prop] = val; 50 | } 51 | } 52 | ); 53 | //get Elements of L 54 | const { GEO, URL, REGION, Player, setActiveView, T, UI } = _L; 55 | //set Elements of L 56 | let { SEARCH, setupSearch } = _L; 57 | 58 | setupSearch = () => { 59 | //$("#view-inner").innerHTML = T.RESULTS; 60 | if ($("#results")) { 61 | console.error("search has been setup already!"); 62 | 63 | return false; 64 | } 65 | //do actual search 66 | SEARCH = str => { 67 | setActiveView("results"); 68 | 69 | let results = $("#results"); 70 | 71 | if (results && results.getAttribute("search-active") === "true") return; 72 | 73 | let page = results ? +results.getAttribute("page") : 0; 74 | 75 | $("views").classList.add("wait"); 76 | 77 | //no search, no paging 78 | if (page === 0) { 79 | //construct result base 80 | setActiveView("results"); 81 | results = $("#results"); 82 | results.setAttribute("state", "search-fresh"); 83 | //we're on page 1 84 | page = 1; 85 | } else { 86 | //same querystring, new page 87 | if (results.getAttribute("q") === str) { 88 | results.setAttribute("state", "search-continue"); 89 | page = page + 1; 90 | 91 | //update page title 92 | document.title = `${UI.titles.moreresults} "${str}"`; 93 | } else { 94 | //new search 95 | results.setAttribute("state", "search-new"); 96 | page = 1; 97 | 98 | history.pushState(null, null, `${URL.LOCAL}/search/${str}`); 99 | 100 | //update page title 101 | document.title = `${UI.titles.results} "${str}"`; 102 | 103 | //scroll to top (prevents loading of new pages) 104 | $("#results").scrollTop = 0; 105 | //clear search results 106 | $("#results-inner").innerHTML = ""; 107 | } 108 | } 109 | 110 | //store querystring for later 111 | results.setAttribute("q", str); 112 | 113 | //search active 114 | results.setAttribute("search-active", true); 115 | 116 | //sync page 117 | results.setAttribute("page", page); 118 | 119 | //debug msgs 120 | switch (results.getAttribute("state")) { 121 | case "search-fresh": 122 | { 123 | console.debug(`Loading fresh results for query:`, { 124 | q: str, 125 | "": "..." 126 | }); 127 | } 128 | break; 129 | case "search-new": 130 | { 131 | console.debug(`Loading results for new query:`, { 132 | q: str, 133 | "": "..." 134 | }); 135 | } 136 | break; 137 | case "search-continue": 138 | { 139 | console.debug(`Loading continued results for query:`, { 140 | q: str, 141 | page: page, 142 | "": "..." 143 | }); 144 | } 145 | break; 146 | } 147 | 148 | //Load more results (prevent rebind on infiniscroll) 149 | if (!results.scrollSetupDone) { 150 | results.addEventListener("scroll", e => { 151 | const that = results; 152 | 153 | if ( 154 | results.getAttribute("search-active") !== "true" && 155 | that.scrollTop + that.clientHeight >= 156 | that.scrollHeight - that.scrollHeight / 10 157 | ) { 158 | //search more 159 | SEARCH(that.getAttribute("q")); 160 | } 161 | }); 162 | //fancy scrolling 163 | Browser.isChrome && 164 | results.addEventListener("scroll", e => { 165 | const that = results; 166 | 167 | { 168 | clearTimeout(results.scrollTimer); 169 | $("#results-inner").classList.add("scrolling"); 170 | results.scrollTimer = setTimeout(() => { 171 | $("#results-inner").classList.remove("scrolling"); 172 | }, 0); 173 | } 174 | }); 175 | results.scrollSetupDone = true; 176 | } 177 | 178 | fetch(`${URL.API}/${REGION}/search/${str}/${page}`) 179 | .then(res => res.text()) 180 | .then(raw => { 181 | let _results = JSON.parse(raw); 182 | let HTML = ""; 183 | 184 | //build results 185 | for (const result of _results) { 186 | let srcSet = createThumbs(result.videoThumbnails); 187 | 188 | const getDurationDetailed = () => { 189 | let detailed = moment 190 | .utc( 191 | moment 192 | .duration(result.lengthSeconds, "seconds") 193 | .asMilliseconds() 194 | ) 195 | .format("HH:mm:ss"); 196 | //slice empty hours 197 | detailed.startsWith("00:") && [(detailed = detailed.slice(3))]; 198 | //slice empty minutes 199 | detailed.startsWith("0") && [(detailed = detailed.slice(1))]; 200 | 201 | return detailed; 202 | }; 203 | //CONSTRUCT HTML 204 | let HTML = T.RESULT 205 | //FILL title 206 | .replace("{{title}}", result.title) 207 | //fill previewset placeholder 208 | .replace("{{preview-set}}", srcSet.slice(0, -1)) 209 | //FILL DESCRIPTION 210 | .replace("{{preview-desc}}", result.description) 211 | //FILL DURATION 212 | .replace( 213 | "{{duration}}", 214 | moment.duration(result.lengthSeconds * 1000).humanize() 215 | ) //DURATION DETAILED 216 | .replace("{{duration-detailed}}", getDurationDetailed()) 217 | //FILL video id 218 | .replace("{{video}}", result.videoId) 219 | //FILL AUTHOR 220 | .replace("{{author}}", result.author) 221 | //FILL AUTHOR id 222 | .replace("{{authorid}}", result.authorId) 223 | //FILL VIEWS (formatted) 224 | .replace( 225 | "{{views}}", 226 | numeral(result.viewCount || 0).format(`0.a`) 227 | ); 228 | 229 | //render result 230 | $("#results-inner").insertAdjacentHTML("beforeend", HTML); 231 | 232 | //setup lazyloading 233 | lazyload($$("figure>img")); 234 | } 235 | document 236 | .getElementById("results") 237 | .setAttribute("search-active", false); 238 | 239 | setTimeout(() => $("views").classList.remove("wait"), 749); 240 | }) 241 | .catch(e => { 242 | showError(); 243 | }); 244 | }; 245 | 246 | //LIVESEARCH 247 | { 248 | const input = document.getElementById("search-input"); 249 | const l = getL(); 250 | const auto = autocomplete({ 251 | input: input, 252 | showOnFocus: true, //focus = show suggestions 253 | minLength: 1, 254 | className: "live-search backdrop-blur", //class 255 | debounceWaitMs: 299, //wait 256 | fetch: (input, update) => { 257 | //input 258 | const text = input.toLowerCase(); 259 | 260 | text && 261 | fetch(`${URL.API}/${REGION}/complete/${text}`) 262 | .then(res => res.text()) 263 | .then(raw => { 264 | const result = JSON.parse(raw); 265 | 266 | if (result.code === 200) { 267 | //construct output (with input at top for convenience) 268 | let suggData = [], 269 | suggOutput = []; 270 | 271 | //skip empty 272 | suggData = [input, ...result.data.filter(v => v.length)]; 273 | 274 | //skip dupes 275 | suggData = Object.values( 276 | Object.fromEntries(suggData.map(s => [s.toLowerCase(), s])) 277 | ); 278 | 279 | //loop and push 280 | suggData.forEach(sugg => { 281 | suggOutput.push({ label: sugg, value: sugg }); 282 | }); 283 | 284 | update(suggOutput); 285 | } else if (result.code === 404) { 286 | update([{ label: input, value: input.toLowerCase() }]); 287 | } 288 | }) 289 | .catch(e => { 290 | showError(); 291 | }); 292 | }, 293 | onSelect: item => { 294 | $("#search-input").value = item.label; 295 | SEARCH(item.label); 296 | } 297 | }); 298 | } 299 | 300 | //set Input on mobile focus to fullwidth 301 | { 302 | $("#search-input").addEventListener("focus", () => { 303 | if (getSize() === "xs") { 304 | //remove margin to logo (we dont need it here) 305 | $("#search").style.setProperty("margin-left", 0, "important"); 306 | //hide dynamic logo 307 | $("#dynamic-logo").style.setProperty("display", "none", "important"); 308 | //make search wider 309 | $("#search").classList.add("col-11"); 310 | //hide submit btn and then... 311 | $("#search-btn").classList.add("d-none"); 312 | //...fixaroo round (cuz missing submit btn) 313 | $("#search-input").classList.add("rounded"); 314 | } 315 | }); 316 | 317 | $("#search-input").addEventListener("blur", () => { 318 | if (getSize() === "xs") { 319 | //reset margin left override 320 | $("#search").style.setProperty("margin-left", ""); 321 | //show dynamic logo again 322 | $("#dynamic-logo").style.setProperty("display", ""); 323 | //reset searchelement to "normal" sizes 324 | $("#search").classList.remove("col-11"); 325 | //show search btn again and... 326 | $("#search-btn").classList.remove("d-none"); 327 | //... make it round again :3 328 | $("#search-input").classList.remove("rounded"); 329 | } 330 | }); 331 | } 332 | }; 333 | } 334 | -------------------------------------------------------------------------------- /build/js/bg.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | //get generic 4 | const { 5 | alert, 6 | Browser, 7 | debounce, 8 | devicePixelRatio, 9 | Event, 10 | WebGL2RenderingContext 11 | } = window; 12 | //set generic 13 | let { initBg } = window; 14 | //-// 15 | const _L = new Proxy( 16 | {}, 17 | { 18 | get: (obj, prop, val) => { 19 | { 20 | const _app = "L"; 21 | 22 | !window[_app] && [(window[_app] = {})]; 23 | !window[_app][prop] && [(window[_app][prop] = {})]; 24 | for (const key of Object.keys(window.L)) { 25 | window[_app][key] = window[key]; 26 | } 27 | } 28 | return window[prop]; 29 | }, 30 | set: function(obj, prop, val) { 31 | const _app = "L"; 32 | window[prop] = val; 33 | !window[_app] && [(window[_app] = {})], 34 | !window[_app][prop] && [(window[_app][prop] = {})]; 35 | window[_app][prop] = val; 36 | } 37 | } 38 | ); 39 | let { LOADED } = _L; 40 | const $ = document.querySelector.bind(document); 41 | const $$ = document.querySelectorAll.bind(document); 42 | initBg = () => { 43 | "use strict"; 44 | const canvas = document.createElement("canvas"); 45 | canvas.id = "canvas"; 46 | document.body.append(canvas); 47 | const gl = canvas.getContext("webgl2", { antialias: false }); 48 | ping(gl); 49 | window.addEventListener("resize", () => fscn(gl)); 50 | // --- vshader, share among basic geoms 51 | const vss = `#version 300 es 52 | in vec4 P; 53 | in lowp vec4 C; 54 | uniform mat4 M0; 55 | uniform mat4 M1; 56 | out lowp vec4 vC; 57 | void main() { 58 | // trnp is used just becuz i hvn't made "matlib" in js.... 59 | gl_Position = transpose(M0) * transpose(M1) * P; 60 | vC = C; 61 | } 62 | `; 63 | const fss = `#version 300 es 64 | precision mediump float; 65 | out vec4 o; 66 | in lowp vec4 vC; 67 | void main() { 68 | o = vC; 69 | } 70 | `; 71 | let prg; 72 | try { 73 | prg = mkprg(gl, vss, fss); 74 | } catch (e) { 75 | alert("failed to mk prg, see console, bye"); 76 | throw e; 77 | } 78 | var Box; 79 | (function(Box_1) { 80 | // --- internal 81 | const vrts = new Float32Array([ 82 | -0.5, 83 | -0.5, 84 | -0.5, 85 | +0.5, 86 | -0.5, 87 | -0.5, 88 | +0.5, 89 | +0.5, 90 | -0.5, 91 | -0.5, 92 | +0.5, 93 | -0.5, 94 | -0.5, 95 | -0.5, 96 | +0.5, 97 | +0.5, 98 | -0.5, 99 | +0.5, 100 | +0.5, 101 | +0.5, 102 | +0.5, 103 | -0.5, 104 | +0.5, 105 | +0.5 106 | ]); 107 | const idxs = new Uint16Array([ 108 | 1, 109 | 5, 110 | 6, 111 | 1, 112 | 6, 113 | 2, 114 | 4, 115 | 0, 116 | 3, 117 | 4, 118 | 3, 119 | 7, 120 | 3, 121 | 2, 122 | 6, 123 | 3, 124 | 6, 125 | 7, 126 | 4, 127 | 5, 128 | 1, 129 | 4, 130 | 1, 131 | 0, 132 | 5, 133 | 4, 134 | 7, 135 | 5, 136 | 7, 137 | 6, 138 | 0, 139 | 1, 140 | 2, 141 | 0, 142 | 2, 143 | 3 144 | ]); 145 | const shader = prg; 146 | const info = qprg(shader); 147 | // share among all boxes 148 | let vrtsObj; 149 | let idxsObj; 150 | function mk( 151 | gl, 152 | color = [0, 0, 0, 0] // rgba in [0,255] 153 | ) { 154 | if (!vrtsObj) { 155 | vrtsObj = gl.createBuffer(); 156 | gl.bindBuffer(gl.ARRAY_BUFFER, vrtsObj); 157 | gl.bufferData(gl.ARRAY_BUFFER, vrts, gl.STATIC_DRAW); 158 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 159 | } 160 | if (!idxsObj) { 161 | idxsObj = gl.createBuffer(); 162 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxsObj); 163 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, idxs, gl.STATIC_DRAW); 164 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 165 | } 166 | // --- vao (each inst has own vao) 167 | const vao = gl.createVertexArray(); 168 | gl.bindVertexArray(vao); 169 | // --- "attrib vec4 P" 170 | gl.bindBuffer(gl.ARRAY_BUFFER, vrtsObj); 171 | gl.enableVertexAttribArray(info.a.P); 172 | gl.vertexAttribPointer(info.a.P, 3, gl.FLOAT, false, 0, 0); 173 | // --- draw order 174 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxsObj); 175 | // --- "atttib lowp vec4 color" 176 | const clrs = new Uint8Array([ 177 | ...color, 178 | ...color, 179 | ...color, 180 | ...color, 181 | 0, 182 | 0, 183 | 0, 184 | 255, 185 | 0, 186 | 0, 187 | 0, 188 | 255, 189 | 0, 190 | 0, 191 | 0, 192 | 255, 193 | 0, 194 | 0, 195 | 0, 196 | 255 197 | ]); 198 | const clrsObj = gl.createBuffer(); 199 | gl.bindBuffer(gl.ARRAY_BUFFER, clrsObj); 200 | gl.bufferData(gl.ARRAY_BUFFER, clrs, gl.STATIC_DRAW); 201 | gl.enableVertexAttribArray(info.a.C); 202 | gl.vertexAttribPointer(info.a.C, 4, gl.UNSIGNED_BYTE, true, 0, 0); 203 | // --- done (unbind vao) 204 | gl.bindVertexArray(null); 205 | return new Box(gl, vao); 206 | } 207 | Box_1.mk = mk; 208 | // internal 209 | class Box { 210 | constructor(gl, vao) { 211 | this.gl = gl; 212 | this.vao = vao; 213 | this.mat = identity4(); 214 | this.pos = new Float32Array([0, 0, 0]); 215 | this.avel = new Float32Array([0, 0, 0]); 216 | } 217 | render(proj) { 218 | this.gl.useProgram(shader); 219 | this.gl.bindVertexArray(this.vao); 220 | this.gl.uniformMatrix4fv(info.u.M0, false, proj); 221 | this.gl.uniformMatrix4fv(info.u.M1, false, this.mat); 222 | this.gl.drawElements( 223 | this.gl.TRIANGLES, 224 | 36, 225 | this.gl.UNSIGNED_SHORT, 226 | 0 227 | ); 228 | this.gl.bindVertexArray(null); 229 | } 230 | } 231 | })(Box || (Box = {})); 232 | // ---- preload 233 | // --- projector stub --- 234 | const orthoProj = ortho(); 235 | let proj = clone4(orthoProj); 236 | const fillContain = (r, proj) => { 237 | r > 1 238 | ? mul4(scale4(1 / r, 1, 1), orthoProj, proj) 239 | : mul4(scale4(1, r, 1), orthoProj, proj); 240 | }; 241 | const fillCover = (r, proj) => { 242 | r > 1 243 | ? mul4(scale4(1, r, 1), orthoProj, proj) 244 | : mul4(scale4(1 / r, 1, 1), orthoProj, proj); 245 | }; 246 | let frozen = false; 247 | window.addEventListener("resize", () => { 248 | frozen = false; 249 | fillCover(canvas.width / canvas.height, proj); 250 | }); 251 | // --- boxes 252 | const boxes = []; 253 | const W = 40, 254 | H = 40, 255 | D = 1; 256 | const RANGE = [-1, 1]; 257 | const len = RANGE[1] - RANGE[0]; 258 | const sc = 0.1; 259 | for (let k = 0; k < D; ++k) { 260 | for (let j = 0; j < H; ++j) { 261 | for (let i = 0; i < W; ++i) { 262 | const color = [(i / W) * 256, (j / H) * 256, 255, 10]; 263 | const box = Box.mk(gl, color); 264 | box.pos[0] = RANGE[0] + len * (i / (W - 1)); 265 | box.pos[1] = RANGE[0] + len * (j / (H - 1)); 266 | box.pos[2] = 0; 267 | mul4c( 268 | [ 269 | move4v(box.pos), 270 | rx4(Math.PI / 6), 271 | ry4(Math.PI * 2 * Math.random()), 272 | rz4(Math.PI / 6 + Math.random() * Math.PI), 273 | scale4(sc, sc, sc * 2) 274 | ], 275 | box.mat 276 | ); 277 | box.avel[2] = 0.005 + Math.random() * 0.005; 278 | boxes.push(box); 279 | } 280 | } 281 | } 282 | // ---- render 283 | gl.clearColor(0, 0, 0, 1); 284 | // gl.enable(gl.CULL_FACE); 285 | gl.enable(gl.DEPTH_TEST); 286 | gl.enable(gl.BLEND); 287 | gl.blendFunc(gl.SRC_ALPHA, gl.DST_ALPHA); 288 | function render(gl, dt) { 289 | gl.clear(gl.COLOR_BUFFER_BIT); 290 | for (const box of boxes) { 291 | box.render(proj); 292 | mul4c( 293 | [ 294 | move4v(box.pos), 295 | // rx4(0.01), 296 | rz4(box.avel[2]), 297 | move4(-box.pos[0], -box.pos[1], -box.pos[2]), 298 | clone4(box.mat) 299 | ], 300 | box.mat 301 | ); 302 | } 303 | } 304 | // trigger once 305 | window.dispatchEvent(new Event("resize")); 306 | raf(render); 307 | setTimeout(() => render, 999); 308 | function raf(f) { 309 | const sch = 310 | "requestPostAnimationFrame" in window 311 | ? window.requestPostAnimationFrame 312 | : window.requestAnimationFrame; 313 | let t0 = Date.now(); 314 | (function g() { 315 | if (!frozen) { 316 | //sch(g); 317 | frozen=true; 318 | let now = Date.now(); 319 | f(gl, (now - t0) / 1e3); 320 | t0 = now; 321 | if (Browser.isMobileChrome || Browser.isSafari) frozen = true; 322 | } 323 | })(); 324 | } 325 | // helper: make shader program 326 | function mkprg(gl, vss, fss) { 327 | const vs = gl.createShader(gl.VERTEX_SHADER); 328 | gl.shaderSource(vs, vss); 329 | gl.compileShader(vs); 330 | const fs = gl.createShader(gl.FRAGMENT_SHADER); 331 | gl.shaderSource(fs, fss); 332 | gl.compileShader(fs); 333 | const prg = gl.createProgram(); 334 | gl.attachShader(prg, vs); 335 | gl.attachShader(prg, fs); 336 | gl.linkProgram(prg); 337 | let ok = false; 338 | if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) { 339 | console.error("vs er", gl.getShaderInfoLog(vs)); 340 | console.error("fs er", gl.getShaderInfoLog(fs)); 341 | console.error("prg er", gl.getProgramInfoLog(prg)); 342 | } else { 343 | ok = true; 344 | } 345 | gl.detachShader(prg, vs); 346 | gl.deleteShader(vs); 347 | gl.detachShader(prg, fs); 348 | gl.deleteShader(fs); 349 | if (!ok) { 350 | throw "shd prg link err"; 351 | } 352 | return prg; 353 | } 354 | // helper: query shader program (locations) 355 | function qprg(prg) { 356 | const u = {}; 357 | const a = {}; 358 | for ( 359 | let i = 0; 360 | i < 361 | (gl === null || gl === void 0 362 | ? void 0 363 | : gl.getProgramParameter(prg, gl.ACTIVE_UNIFORMS)); 364 | ++i 365 | ) { 366 | const { name } = 367 | gl === null || gl === void 0 ? void 0 : gl.getActiveUniform(prg, i); 368 | const loc = 369 | gl === null || gl === void 0 370 | ? void 0 371 | : gl.getUniformLocation(prg, name); // is object .. 372 | u[name] = loc; 373 | } 374 | for ( 375 | let i = 0; 376 | i < 377 | (gl === null || gl === void 0 378 | ? void 0 379 | : gl.getProgramParameter(prg, gl.ACTIVE_ATTRIBUTES)); 380 | ++i 381 | ) { 382 | const { name } = 383 | gl === null || gl === void 0 ? void 0 : gl.getActiveAttrib(prg, i); 384 | const loc = 385 | gl === null || gl === void 0 386 | ? void 0 387 | : gl.getAttribLocation(prg, name); // is number .. 388 | a[name] = loc; 389 | } 390 | return { a, u }; 391 | } 392 | // helper: fill entire screen 393 | function fscn(gl) { 394 | const c = gl.canvas; 395 | if (c instanceof HTMLCanvasElement) { 396 | const { width, height } = getComputedStyle(c); 397 | c.width = Math.ceil(parseFloat(width)) * devicePixelRatio; 398 | c.height = Math.ceil(parseFloat(height)) * devicePixelRatio; 399 | gl.viewport(0, 0, c.width, c.height); 400 | } else { 401 | throw new Error("offscreen? nah, bye"); 402 | } 403 | } 404 | // helper: asserts gl is webgl2 rendering ctx 405 | function ping(x) { 406 | if (!(x instanceof WebGL2RenderingContext)) { 407 | throw new Error("wgl2 ctx, nah"); 408 | } 409 | } 410 | // -------------- matlib stub ------------- 411 | // helper: create mat4 (row major) 412 | // I will transpose the matrix in shader. Hah 413 | // 414 | // maths | 0 1 2 3 | === [0, 1, 2, 3 415 | // | 4 5 6 7 | 4, 5, 6, 7 416 | // | 8 9 a b | .. 417 | // | c d e f | ] 418 | function mat4(xs) { 419 | return new Float32Array(xs); 420 | } 421 | function identity4() { 422 | return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); 423 | } 424 | function clone4(m) { 425 | return Float32Array.from(m); 426 | } 427 | // mul mat m and mat n, put result into mat o 428 | // m n 429 | // | 0 1 2 3 || 0 1 2 3 | 430 | // | 4 5 6 7 || 4 5 6 7 | 431 | // | 8 9 a b || 8 9 a b | 432 | // | c d e f || c d e f | 433 | function mul4(m, n, o) { 434 | o || (o = identity4()); 435 | // row 1 436 | o[0] = m[0] * n[0] + m[1] * n[4] + m[2] * n[8] + m[3] * n[0xc]; 437 | o[1] = m[0] * n[1] + m[1] * n[5] + m[2] * n[9] + m[3] * n[0xd]; 438 | o[2] = m[0] * n[2] + m[1] * n[6] + m[2] * n[0xa] + m[3] * n[0xe]; 439 | o[3] = m[0] * n[3] + m[1] * n[7] + m[2] * n[0xb] + m[3] * n[0xf]; 440 | // row 2 441 | o[4] = m[4] * n[0] + m[5] * n[4] + m[6] * n[8] + m[7] * n[0xc]; 442 | o[5] = m[4] * n[1] + m[5] * n[5] + m[6] * n[9] + m[7] * n[0xd]; 443 | o[6] = m[4] * n[2] + m[5] * n[6] + m[6] * n[0xa] + m[7] * n[0xe]; 444 | o[7] = m[4] * n[3] + m[5] * n[7] + m[6] * n[0xb] + m[7] * n[0xf]; 445 | // row 3 446 | o[8] = m[8] * n[0] + m[9] * n[4] + m[0xa] * n[8] + m[0xb] * n[0xc]; 447 | o[9] = m[8] * n[1] + m[9] * n[5] + m[0xa] * n[9] + m[0xb] * n[0xd]; 448 | o[0xa] = m[8] * n[2] + m[9] * n[6] + m[0xa] * n[0xa] + m[0xb] * n[0xe]; 449 | o[0xb] = m[8] * n[3] + m[9] * n[7] + m[0xa] * n[0xb] + m[0xb] * n[0xf]; 450 | // row 4 451 | o[0xc] = m[0xc] * n[0] + m[0xd] * n[4] + m[0xe] * n[8] + m[0xf] * n[0xc]; 452 | o[0xd] = m[0xc] * n[1] + m[0xd] * n[5] + m[0xe] * n[9] + m[0xf] * n[0xd]; 453 | o[0xe] = 454 | m[0xc] * n[2] + m[0xd] * n[6] + m[0xe] * n[0xa] + m[0xf] * n[0xe]; 455 | o[0xf] = 456 | m[0xc] * n[3] + m[0xd] * n[7] + m[0xe] * n[0xb] + m[0xf] * n[0xf]; 457 | return o; 458 | } 459 | // mul array of mats, starting from end.. 460 | function mul4c(ms, o) { 461 | o || (o = identity4()); 462 | let i = ms.length - 2; 463 | let n = Float32Array.from(ms[i + 1]); // copy 464 | while (i >= 0) { 465 | mul4(ms[i], n, o); 466 | n.set(o); 467 | --i; 468 | } 469 | return o; 470 | } 471 | // stub.. (because vertices already in clipspace, LOL) 472 | function ortho() { 473 | return mat4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); 474 | } 475 | function rz4(a) { 476 | const c = Math.cos(a); 477 | const s = Math.sin(a); 478 | return mat4([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); 479 | } 480 | function rx4(a) { 481 | const c = Math.cos(a); 482 | const s = Math.sin(a); 483 | return mat4([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); 484 | } 485 | function ry4(a) { 486 | const c = Math.cos(a); 487 | const s = Math.sin(a); 488 | return mat4([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); 489 | } 490 | function scale4(sx, sy, sz) { 491 | return mat4([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]); 492 | } 493 | function move4(mx, my, mz) { 494 | return mat4([1, 0, 0, mx, 0, 1, 0, my, 0, 0, 1, mz, 0, 0, 0, 1]); 495 | } 496 | function move4v(xs) { 497 | return mat4([1, 0, 0, xs[0], 0, 1, 0, xs[1], 0, 0, 1, xs[2], 0, 0, 0, 1]); 498 | } 499 | }; 500 | window.done = () => 501 | setTimeout(() => { 502 | $("aside[name=loader]").style.display = "none"; 503 | //remove blur class 504 | $("content").classList.remove("loading"); 505 | LOADED = true; 506 | 507 | //Control. 508 | for (const key of Object.keys(window.L)) { 509 | window.L[key] = window[key]; 510 | } 511 | }, 999); 512 | } 513 | -------------------------------------------------------------------------------- /build/js/client.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | //RESET 7 | location.hash = ""; 8 | //welcome to the console 9 | { 10 | const acc = getComputedStyle(document.documentElement).getPropertyValue( 11 | "--color-accent-main" 12 | ), 13 | args = [ 14 | `\n%c %c %c➤ %c𝑙𝑎𝑝𝑖𝑠Tube %c %c ${location.href} %c %c ♥ %c \n`, 15 | `background: ${acc}; padding:5px 0;`, 16 | `background: black; padding:5px 0;`, 17 | `color: blue; background: black; padding:5px 0;`, 18 | `color: white; background: black; padding:5px 0;`, 19 | `background: ${acc}; padding:5px 0;`, 20 | `background: black; padding:5px 0;`, 21 | `background: ${acc}; padding:5px 0;`, 22 | "color: purple; background: #fff; padding:5px 0;", 23 | `background: ${acc}; padding:5px 0;` 24 | ]; 25 | 26 | window.console.log(...args); 27 | } 28 | 29 | //get generic 30 | const { 31 | lscache, 32 | waitForElement, 33 | getSize, 34 | getL, 35 | fetch, 36 | debounce, 37 | alert, 38 | done, 39 | initBg, 40 | autocomplete, 41 | applyWords, 42 | moment, 43 | numeral, 44 | load, 45 | lazyload, 46 | setupSearch 47 | } = window; 48 | //set generic 49 | let { app_start, route } = window; 50 | //-// 51 | const _L = new Proxy( 52 | {}, 53 | { 54 | get: (obj, prop, val) => { 55 | { 56 | const _app = "L"; 57 | 58 | !window[_app] && [(window[_app] = {})]; 59 | !window[_app][prop] && [(window[_app][prop] = {})]; 60 | for (const key of Object.keys(window.L)) { 61 | window[_app][key] = window[key]; 62 | } 63 | } 64 | return window[prop]; 65 | }, 66 | set: function(obj, prop, val) { 67 | const _app = "L"; 68 | window[prop] = val; 69 | !window[_app] && [(window[_app] = {})], 70 | !window[_app][prop] && [(window[_app][prop] = {})]; 71 | window[_app][prop] = val; 72 | } 73 | } 74 | ); 75 | //get Elements of L 76 | const { URL, Player, setActiveView, SEARCH, T, UI } = _L; 77 | //set Elements of L 78 | let { GEO, REGION } = _L; 79 | 80 | //anti-hostname (allow inplace editing though) 81 | !location.host !== "gtranslate.io" && 82 | !location.host.endsWith("glitch.me") && 83 | window.self === window.top && 84 | location.hostname.split(".").length === 3 && 85 | location.hostname.split(".") !== 86 | getL()[ 87 | location.replace( 88 | `${location.protocol}//${location.hostname 89 | .split(".") 90 | .splice(1) 91 | .join(".")}` + 92 | `${ 93 | location.hostname.split(".")[0] 94 | ? "?hl=" + location.hostname.split(".")[0] 95 | : "" 96 | }` 97 | ) 98 | ]; 99 | 100 | //update size attribute 101 | window.onresize = debounce(() => { 102 | $("body").setAttribute("size", getSize()); 103 | }, 999); 104 | 105 | //navi switch 106 | { 107 | document.addEventListener("click", e => { 108 | const that = $("#toggle-left"); 109 | 110 | //click anywhere to close 111 | if (!$("#left[expanded=true]")) 112 | if (!that || (that && !that.contains(e.target))) 113 | ////////////////////////////////////////////////////////////// 114 | return false; 115 | ////////////////////////////////////////////////////////////// 116 | 117 | const el = $("#left"); 118 | //slide 119 | el.setAttribute( 120 | "expanded", 121 | el.getAttribute("expanded") === "false" ? "true" : "false" 122 | ); 123 | setTimeout(() => { 124 | //hide 125 | el.getAttribute("expanded") === "false" 126 | ? el.classList.add("d-none") 127 | : el.classList.remove("d-none"); 128 | }, 199); 129 | }); 130 | } 131 | 132 | //go to home 133 | { 134 | document.addEventListener("click", e => { 135 | const that = $("#dynamic-logo"); 136 | ////////////////////////////////////////////////////////////// 137 | if (!that || (that && !that.contains(e.target))) return false; 138 | ////////////////////////////////////////////////////////////// 139 | 140 | setActiveView("start"); 141 | }); 142 | } 143 | 144 | { 145 | console.debug("Client starting up"); 146 | //LOAD TEMPLATES 147 | { 148 | //template remote path 149 | const tr = `${URL.HOST}/html`; 150 | 151 | app_start = () => { 152 | //template var inmemory 153 | const T = (window.T = {}); 154 | 155 | //received files by "tr" are translated! 156 | Promise.all( 157 | [ 158 | tr + "/ui-words.html", 159 | URL.API + "/geoip", 160 | tr + "/templates.html" 161 | ].map(url => fetch(url).then(resp => resp.text())) 162 | ) 163 | .then(tx => { 164 | applyWords(tx[0]); 165 | 166 | GEO = JSON.parse(tx[1]); 167 | REGION = GEO.country_code.toLowerCase(); 168 | console.debug(`Your Geo Information by Maxmind: `, GEO); 169 | console.debug(`Your browser language: `, getL()); 170 | 171 | //loop received html and fill template vars 172 | for (const template of new DOMParser() 173 | .parseFromString(tx[2], "text/html") 174 | .querySelectorAll("template")) { 175 | const _html = template.innerHTML; 176 | switch (template.getAttribute("name")) { 177 | case "app": 178 | T.WRAPPER = _html.replace(/{{cdn}}/gi, URL.CDN); 179 | break; 180 | case "404": 181 | T[404] = _html; 182 | break; 183 | case "error": 184 | T.ERROR = _html; 185 | break; 186 | case "channel": 187 | T.CHANNEL = _html; 188 | break; 189 | case "player": 190 | T.PLAYER = _html; 191 | break; 192 | case "history-list": 193 | T.HISTORY = _html; 194 | break; 195 | case "result-list": 196 | T.RESULTS = _html; 197 | break; 198 | case "result-item": 199 | T.RESULT = _html; 200 | break; 201 | case "start": 202 | T.START = _html; 203 | break; 204 | } 205 | } 206 | 207 | setupClient(); 208 | }) 209 | .catch(e => { 210 | console.warn(e); 211 | alert("WEBSITE FAILED LOADING. PRESS OK TO TRY AGAIN!"); 212 | setTimeout(() => { 213 | //location.reload(true), 4999; 214 | }); 215 | }); 216 | }; 217 | 218 | lscache.setBucket("tracking"); 219 | if (lscache.get("cookie-accepted")) { 220 | app_start(); 221 | } else { 222 | fetch(URL.HOST + "/cookie.html") 223 | .then(res => res.text()) 224 | .then(html => { 225 | $("content").innerHTML = html.replace(/{{local}}/gi, URL.LOCAL); 226 | done(); 227 | }); 228 | } 229 | } 230 | } 231 | ////// 232 | const demo = () => { 233 | const q = "New americana"; 234 | $("#top").value = q; 235 | 236 | SEARCH(q); 237 | 238 | const sel = ".card[data-video=b-eYbUVZedY]"; 239 | 240 | setTimeout( 241 | () => 242 | waitForElement(sel).then(() => { 243 | $(sel).click(); 244 | }), 245 | 999 246 | ); 247 | }; 248 | 249 | window.onhashchange = () => { 250 | const tr = URL.HOST; 251 | switch (location.hash) { 252 | case "#cookie-what?": 253 | case "#what?": 254 | { 255 | fetch(tr + "/cookie-what.html") 256 | .then(res => res.text()) 257 | .then(html => { 258 | $("[pop]").outerHTML = html; 259 | done(); 260 | }); 261 | } 262 | break; 263 | default: { 264 | } 265 | } 266 | switch (location.hash) { 267 | case "#cookie": 268 | { 269 | fetch(tr + "/cookie.html") 270 | .then(res => res.text()) 271 | .then(html => { 272 | $("[pop]").outerHTML = html.replace(/{{local}}/gi, URL.LOCAL); 273 | done(); 274 | }); 275 | } 276 | break; 277 | default: { 278 | } 279 | } 280 | }; 281 | 282 | //COOKIE OK 283 | document.addEventListener("click", e => { 284 | const that = $("#usage-accept"); 285 | ////////////////////////////////////////////////////////////// 286 | if (!that || (that && !that.contains(e.target))) return false; 287 | ////////////////////////////////////////////////////////////// 288 | 289 | lscache.setBucket("tracking"); 290 | lscache.set("cookie-accepted", true, 60 * 24 * 31); //expires in 1 month 291 | history.pushState(null, null, URL.LOCAL); 292 | app_start(); 293 | //location.reload(true); 294 | }); 295 | 296 | //wreadyy 297 | const setupClient = () => { 298 | //fill content (first step in app setup) 299 | $("content").innerHTML = T.WRAPPER; 300 | 301 | setActiveView("start"); 302 | 303 | //show content 304 | $("wrapper").style.display = "block"; 305 | 306 | //setup bg 307 | initBg(); 308 | 309 | waitForElement("views").then(element => { 310 | const _L = getL() || "en"; 311 | //SETUP MOMENTJS LANGUAGE 312 | moment.locale(_L); 313 | 314 | //SETUP SEARCH 315 | setupSearch(); 316 | 317 | //sync browser-language 318 | $("#yt-lang").innerText = _L; 319 | //sync region (language background clip) 320 | 321 | document.documentElement.style.setProperty( 322 | "--alpha-logo", 323 | `url(${URL.CDN}/logo.png)` 324 | ); 325 | 326 | for (const img of $$("#dynamic-logo .alpha-target")) { 327 | img.src = `https://raw.githubusercontent.com/legacy-icons/famfamfam-flags/master/dist/png/${GEO.country_code.toLowerCase()}.png`; 328 | } 329 | $("#dynamic-logo").setAttribute( 330 | "title", 331 | `${UI.labels.region}: ${GEO.country}` 332 | ); 333 | $("#yt-lang").setAttribute("title", `App ${UI.labels.language}: ${_L}`); 334 | 335 | //show ui 336 | done(); 337 | 338 | //small search demo 339 | //demo(); 340 | 341 | console.debug("Client done"); 342 | 343 | //routing 344 | setTimeout(route($("meta[name=from]").getAttribute("value"))); 345 | }); 346 | }; 347 | } 348 | -------------------------------------------------------------------------------- /build/js/routing.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | //get generic 7 | const { waitForElement } = window; 8 | //set generic 9 | let {} = window; 10 | //-// 11 | const _L = new Proxy( 12 | {}, 13 | { 14 | get: (obj, prop, val) => { 15 | { 16 | const _app = "L"; 17 | 18 | !window[_app] && [(window[_app] = {})]; 19 | !window[_app][prop] && [(window[_app][prop] = {})]; 20 | for (const key of Object.keys(window.L)) { 21 | window[_app][key] = window[key]; 22 | } 23 | } 24 | return window[prop]; 25 | }, 26 | set: function(obj, prop, val) { 27 | const _app = "L"; 28 | window[prop] = val; 29 | !window[_app] && [(window[_app] = {})], 30 | !window[_app][prop] && [(window[_app][prop] = {})]; 31 | window[_app][prop] = val; 32 | } 33 | } 34 | ); 35 | //get Elements of L 36 | const { URL, Player, setActiveView, SEARCH, T, showHistory } = _L; 37 | //set Elements of L 38 | let { route, showError } = _L; 39 | 40 | //get page querystring 41 | { 42 | window.onpopstate = () => { 43 | !$.query && [($.query = {})]; 44 | //fix title ¯\_(ツ)_/¯ 45 | { 46 | const t = $("title").innerText; 47 | (document.title = ""), (document.title = t); 48 | } 49 | var match, 50 | pl = /\+/g, // Regex for replacing addition symbol with a space 51 | search = /([^&=]+)=?([^&]*)/g, 52 | decode = s => { 53 | return decodeURIComponent(s.replace(pl, " ")); 54 | }, 55 | query = window.location.search.substring(1); 56 | while ((match = search.exec(query))) 57 | $.query[decode(match[1])] = decode(match[2]); 58 | }; 59 | } 60 | 61 | showError = info => { 62 | setActiveView("error"); 63 | info && [($("#error-custom").innerHTML = `- ${info}`)]; 64 | }; 65 | 66 | route = to => { 67 | if (to) { 68 | console.debug(`Processing route ${to}...`); 69 | const args = to.split("/")[2]; 70 | //process route when app's ready 71 | waitForElement("views").then(() => { 72 | $("views").classList.remove("wait"); 73 | switch (`/${to.split("/")[1]}`) { 74 | case "/v": 75 | { 76 | const id = args; 77 | console.debug("attempting to play requested video", id); 78 | //waitForElement("views").then(Player.play(id)); 79 | Player.play(id); 80 | } 81 | break; 82 | 83 | case "/history": 84 | { 85 | showHistory(); 86 | } 87 | break; 88 | case "/search": 89 | { 90 | const q = args; 91 | console.debug("attempting to search", q); 92 | //waitForElement("views").then(Player.play(id)); 93 | SEARCH(q); 94 | } 95 | break; 96 | 97 | default: { 98 | //atomar routing based on array 99 | if ( 100 | ["/watch"].some((val, i, arr) => val === to || val.startsWith(to)) 101 | ) { 102 | if (to.startsWith("/watch?v=")) { 103 | const id = to.split("/watch?v=")[1]; 104 | 105 | //from yt vid 106 | console.debug( 107 | "attempting to play requested video (direct call from yt)", 108 | id 109 | ); 110 | setActiveView("start"); 111 | history.pushState(null, null, URL.HOST); 112 | 113 | Player.play(id); 114 | } 115 | } else { 116 | console.warn("Page not found"); 117 | setTimeout(() => { 118 | setActiveView("404"); 119 | }); 120 | } 121 | } 122 | } 123 | }); 124 | } 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /build/js/tools.js: -------------------------------------------------------------------------------- 1 | //©Blu2020 2 | { 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | //get generic 7 | const { 8 | NProgress, 9 | app_start, 10 | lscache, 11 | alert, 12 | done, 13 | initBg, 14 | autocomplete, 15 | moment, 16 | numeral, 17 | load, 18 | lazyload, 19 | setupSearch 20 | } = window; 21 | //set generic 22 | let { 23 | fetch, 24 | applyWords, 25 | createThumbs, 26 | Fullscreen, 27 | clickSound, 28 | waitForElement, 29 | abortFetches, 30 | debounce, 31 | getBase64Image, 32 | getL, 33 | getSize 34 | } = window; 35 | //-// 36 | const _L = new Proxy( 37 | {}, 38 | { 39 | get: (obj, prop, val) => { 40 | { 41 | const _app = "L"; 42 | 43 | !window[_app] && [(window[_app] = {})]; 44 | !window[_app][prop] && [(window[_app][prop] = {})]; 45 | for (const key of Object.keys(window.L)) { 46 | window[_app][key] = window[key]; 47 | } 48 | } 49 | return window[prop]; 50 | }, 51 | set: function(obj, prop, val) { 52 | const _app = "L"; 53 | window[prop] = val; 54 | !window[_app] && [(window[_app] = {})], 55 | !window[_app][prop] && [(window[_app][prop] = {})]; 56 | window[_app][prop] = val; 57 | } 58 | } 59 | ); 60 | //get Elements of L 61 | const { GEO, LOADED, Player, SEARCH, route, T } = _L; 62 | //set Elements of L 63 | let { setActiveView, addView, URL, UI } = _L; 64 | 65 | //custom UI counds 66 | { 67 | const audio = new Audio( 68 | "https://lapistube.b-cdn.net/176727_3249786-lq.mp3" 69 | ); 70 | clickSound = () => { 71 | audio.play(); 72 | }; 73 | } 74 | 75 | //polyfill v 76 | if (!Object.fromEntries) { 77 | Object.defineProperty(Object, "fromEntries", { 78 | value(entries) { 79 | if (!entries || !entries[Symbol.iterator]) { 80 | throw new Error( 81 | "Object.fromEntries() requires a single iterable argument" 82 | ); 83 | } 84 | 85 | const o = {}; 86 | 87 | Object.keys(entries).forEach(key => { 88 | const [k, v] = entries[key]; 89 | 90 | o[k] = v; 91 | }); 92 | 93 | return o; 94 | } 95 | }); 96 | } 97 | 98 | //gtranslate-hack (from html to json) 99 | applyWords = htmlString => { 100 | const words = new DOMParser() 101 | .parseFromString(htmlString, "text/html") 102 | .querySelector("ui-words"); 103 | //recreate var if broken / non-existant 104 | !UI && [(UI = {})]; 105 | //loop over categories 106 | for (const _cat of words.querySelectorAll("cat")) { 107 | const cat = _cat.getAttribute("name"); 108 | //loop over keys in category 109 | for (const _key of _cat.querySelectorAll("key")) { 110 | const key = _key.getAttribute("name"); 111 | const val = _cat.querySelector(`key[name=${key}]`).innerText; 112 | //put values into json 113 | !UI[cat] && [(UI[cat] = {})], (UI[cat][key] = val); 114 | } 115 | } 116 | }; 117 | 118 | //get page language 119 | getL = () => { 120 | let L = navigator.language || navigator.userLanguage; 121 | 122 | //language has seperator 123 | L.includes("-") && [(L = L.split("-")[0])]; 124 | 125 | const gt_languages = [ 126 | "af", 127 | "sq", 128 | "am", 129 | "ar", 130 | "hy", 131 | "az", 132 | "eu", 133 | "be", 134 | "bn", 135 | "bs", 136 | "bg", 137 | "ca", 138 | "ceb", 139 | "ny", 140 | "zh-CN", 141 | "zh-TW", 142 | "co", 143 | "hr", 144 | "cs", 145 | "da", 146 | "nl", 147 | "en", 148 | "eo", 149 | "et", 150 | "tl", 151 | "fi", 152 | "fr", 153 | "fy", 154 | "gl", 155 | "ka", 156 | "de", 157 | "el", 158 | "gu", 159 | "ht", 160 | "ha", 161 | "haw", 162 | "iw", 163 | "hi", 164 | "hmn", 165 | "hu", 166 | "is", 167 | "ig", 168 | "id", 169 | "ga", 170 | "it", 171 | "ja", 172 | "jw", 173 | "kn", 174 | "kk", 175 | "km", 176 | "ko", 177 | "ku", 178 | "ky", 179 | "lo", 180 | "la", 181 | "lv", 182 | "lt", 183 | "lb", 184 | "mk", 185 | "mg", 186 | "ms", 187 | "ml", 188 | "mt", 189 | "mi", 190 | "mr", 191 | "mn", 192 | "my", 193 | "ne", 194 | "no", 195 | "ps", 196 | "fa", 197 | "pl", 198 | "pt", 199 | "pa", 200 | "ro", 201 | "ru", 202 | "sm", 203 | "gd", 204 | "sr", 205 | "st", 206 | "sn", 207 | "sd", 208 | "si", 209 | "sk", 210 | "sl", 211 | "so", 212 | "es", 213 | "su", 214 | "sw", 215 | "sv", 216 | "tg", 217 | "ta", 218 | "te", 219 | "th", 220 | "tr", 221 | "uk", 222 | "ur", 223 | "uz", 224 | "vi", 225 | "cy", 226 | "xh", 227 | "yi", 228 | "yo", 229 | "zu" 230 | ]; 231 | 232 | return gt_languages.includes(L) ? L.toLowerCase() : ""; 233 | }; 234 | 235 | URL = { 236 | HOST: location.host.endsWith("glitch.me") 237 | ? //DEV ENV in ORIGINAL 238 | location.origin 239 | : //GT EDITOR 240 | location.hostname === "gtranslate.io" 241 | ? location.href.split("/edit/")[1] 242 | : //TRANSLATED 243 | `${ 244 | getL() === "en" //don't do .en-subdomain 245 | ? location.origin 246 | : "//" + getL() + "." + location.hostname 247 | }`, 248 | CDN: $("base").href, 249 | LOCAL: location.origin, 250 | API: `${location.origin}/api` 251 | }; 252 | 253 | //add HTML to view system 254 | addView = HTML => 255 | $("views") && $("views").insertAdjacentHTML("beforeend", HTML); 256 | 257 | /* hide views but (id) */ 258 | setActiveView = id => { 259 | //add view if not existing yet 260 | if (!$(`view[id="${id}"]`)) { 261 | addView(T[id.toUpperCase()]); 262 | } 263 | 264 | //set active view (for async callbacks) 265 | $("views").setAttribute("active", id); 266 | for (const view of $$("views>view")) 267 | view.id !== id //hide all views but (id) 268 | ? [view.style.setProperty("display", "none", "important")] 269 | : [(view.style.display = "")]; 270 | }; 271 | 272 | //detect ie & edge (ded browsers) 273 | const detectIEEdge = () => { 274 | const ua = window.navigator.userAgent; 275 | 276 | const msie = ua.indexOf("MSIE "); 277 | if (msie > 0) { 278 | // IE 10 or older => return version number 279 | return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10); 280 | } 281 | 282 | const trident = ua.indexOf("Trident/"); 283 | if (trident > 0) { 284 | // IE 11 => return version number 285 | const rv = ua.indexOf("rv:"); 286 | return parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10); 287 | } 288 | 289 | const edge = ua.indexOf("Edge/"); 290 | if (edge > 0) { 291 | // Edge => return version number 292 | return parseInt(ua.substring(edge + 5, ua.indexOf(".", edge)), 10); 293 | } 294 | 295 | // other browser 296 | return false; 297 | }; 298 | 299 | //not working with corbs 300 | getBase64Image = img => { 301 | var canvas = document.createElement("canvas"); 302 | canvas.width = img.width; 303 | canvas.height = img.height; 304 | var ctx = canvas.getContext("2d"); 305 | ctx.drawImage(img, 0, 0); 306 | var dataURL = canvas.toDataURL("image/png"); 307 | return dataURL.replace(/^data:image\/(png|jpg);base64,/, ""); 308 | }; 309 | 310 | //create video thum srcset 311 | createThumbs = thumbsData => { 312 | let srcSet = ""; 313 | //build thumbnails 314 | for (const [i, thumb] of thumbsData.entries()) { 315 | if (i !== 0) 316 | //skip first 317 | srcSet += `${thumb.url}\t${thumb.width}w${ 318 | i !== thumbsData ? ",\n" : "" 319 | }`; 320 | } 321 | return srcSet; 322 | }; 323 | 324 | //(promise) wait until element appears on dom 325 | waitForElement = selector => { 326 | return new Promise((resolve, reject) => { 327 | var element = document.querySelector(selector); 328 | 329 | if (element) { 330 | resolve(element); 331 | return; 332 | } 333 | 334 | var observer = new MutationObserver(mutations => { 335 | mutations.forEach(mutation => { 336 | var nodes = Array.from(mutation.addedNodes); 337 | for (var node of nodes) { 338 | if (node.matches && node.matches(selector)) { 339 | observer.disconnect(); 340 | resolve(node); 341 | return; 342 | } 343 | } 344 | }); 345 | }); 346 | 347 | observer.observe(document.documentElement, { 348 | childList: true, 349 | subtree: true 350 | }); 351 | }); 352 | }; 353 | 354 | getSize = () => { 355 | for (const indicator of $$("size-indicators>size-indicator")) { 356 | //get current "visible" indicator based on bootstrap's css rules 357 | if (getComputedStyle(indicator).display === "flex") { 358 | return indicator.classList[0].split("-")[1].replace("flex", "xs"); 359 | } 360 | } 361 | }; 362 | 363 | if (detectIEEdge()) { 364 | const LLegacy = 365 | ((navigator.language || navigator.userLanguage).indexOf("-") 366 | ? (navigator.language || navigator.userLanguage).split("-")[0] 367 | : navigator.language || navigator.userLanguage 368 | ).toLowerCase() || "en"; 369 | let host; 370 | if (location.host.indexOf("glitch.me") === -1) { 371 | host = location.protocol + "//" + LLegacy + "." + location.hostname; 372 | } else host = location.protocol + "//" + location.hostname; 373 | location.replace(host + "/outdated-browser.html"); 374 | } 375 | 376 | //DEBOUNCE 377 | debounce = (func, wait, immediate) => { 378 | let timeout; 379 | 380 | return () => { 381 | const context = this; 382 | const args = context.arguments; 383 | 384 | const later = () => { 385 | timeout = null; 386 | if (!immediate) func.apply(context, args); 387 | }; 388 | 389 | const callNow = immediate && !timeout; 390 | 391 | clearTimeout(timeout); 392 | 393 | timeout = setTimeout(later, wait); 394 | 395 | if (callNow) func.apply(context, args); 396 | }; 397 | }; 398 | 399 | const fetchControllers = []; 400 | 401 | //abort, abort abort :D 402 | abortFetches = () => { 403 | NProgress.done(); 404 | for (const fetchId in fetchControllers) { 405 | const fetchItem = fetchControllers[fetchId]; 406 | fetchItem.status === "active" && [(fetchItem.status = "aborted")]; 407 | } 408 | console.debug("fetchcontrollers were aborted"); 409 | }; 410 | 411 | //FETCH WITH NPROGRESS- - - - 412 | { 413 | NProgress.configure({ showSpinner: false }); 414 | //backup fetch 415 | const ofetch = window.fetch; 416 | //override fetch 417 | fetch = (url, options) => { 418 | const fetchId = +new Date(); 419 | fetchControllers[fetchId] = { status: "active", url, options }; 420 | 421 | //start proc (if not silent) 422 | !(typeof options != "undefined" && !options.silent && !$("#nprogress")) && 423 | LOADED && 424 | NProgress.start(); 425 | return ofetch(url, options).then(response => { 426 | //start proc (if not silent) 427 | !( 428 | typeof options != "undefined" && 429 | !options.silent && 430 | !$("#nprogress") 431 | ) && 432 | LOADED && 433 | NProgress.done(); 434 | if (fetchControllers[fetchId].status !== "aborted") { 435 | fetchControllers[fetchId].status = "done"; 436 | return response; 437 | } else throw "aborted"; 438 | }); 439 | }; 440 | } 441 | 442 | /* 443 | tools 444 | */ 445 | Fullscreen = { 446 | enter: elem => { 447 | if (elem.requestFullscreen) { 448 | elem.requestFullscreen(); 449 | } else if (elem.mozRequestFullScreen) { 450 | /* Firefox */ 451 | elem.mozRequestFullScreen(); 452 | } else if (elem.webkitRequestFullscreen) { 453 | /* Chrome, Safari and Opera */ 454 | elem.webkitRequestFullscreen(); 455 | } else if (elem.msRequestFullscreen) { 456 | /* IE/Edge */ 457 | elem.msRequestFullscreen(); 458 | } 459 | }, 460 | exit: () => { 461 | if (document.exitFullscreen) { 462 | document.exitFullscreen(); 463 | } else if (document.mozCancelFullScreen) { 464 | /* Firefox */ 465 | document.mozCancelFullScreen(); 466 | } else if (document.webkitExitFullscreen) { 467 | /* Chrome, Safari and Opera */ 468 | document.webkitExitFullscreen(); 469 | } else if (document.msExitFullscreen) { 470 | /* IE/Edge */ 471 | document.msExitFullscreen(); 472 | } 473 | } 474 | }; 475 | 476 | HTMLElement.prototype.onEvent = (eventType, callBack, useCapture) => { 477 | this.addEventListener(eventType, callBack, useCapture); 478 | if (!this.myListeners) { 479 | this.myListeners = []; 480 | } 481 | this.myListeners.push({ eType: eventType, callBack: callBack }); 482 | return this; 483 | }; 484 | 485 | HTMLElement.prototype.removeListeners = () => { 486 | if (this.myListeners) { 487 | for (var i = 0; i < this.myListeners.length; i++) { 488 | this.removeEventListener( 489 | this.myListeners[i].eType, 490 | this.myListeners[i].callBack 491 | ); 492 | } 493 | delete this.myListeners; 494 | } 495 | }; 496 | 497 | { 498 | let that = {}; 499 | // Opera 8.0+ 500 | that.isOpera = 501 | (!!window.opr && !!window.opr.addons) || 502 | !!window.opera || 503 | navigator.userAgent.indexOf(" OPR/") >= 0; 504 | 505 | // Firefox 1.0+ 506 | that.isFirefox = typeof window.InstallTrigger !== "undefined"; 507 | 508 | // Safari 3.0+ "[object HTMLElementConstructor]" 509 | that.isSafari = 510 | /constructor/i.test(window.HTMLElement) || 511 | (function(p) { 512 | return p.toString() === "[object SafariRemoteNotification]"; 513 | })( 514 | !window["safari"] || 515 | (typeof window.safari !== "undefined" && 516 | window.safari.pushNotification) 517 | ); 518 | 519 | // Internet Explorer 6-11 520 | that.isIE = /*@cc_on!@*/ false || !!document.documentMode; 521 | 522 | // Edge 20+ 523 | that.isEdge = !that.isIE && !!window.StyleMedia; 524 | 525 | // Chrome 1 - 71 526 | that.isChrome = 527 | !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime); 528 | 529 | //mobile Chrome 530 | that.isMobileChrome = 531 | !!window.chrome && 532 | navigator.userAgent.includes("Chrome") && 533 | typeof window.orientation !== "undefined"; 534 | 535 | // Edge (based on chromium) detection 536 | that.isEdgeChromium = 537 | that.isChrome && navigator.userAgent.indexOf("Edg") != -1; 538 | 539 | // Blink engine detection 540 | that.isBlink = (that.isChrome || that.isOpera) && !!window.CSS; 541 | window.Browser = that; 542 | } 543 | 544 | const speed = { 545 | _i: 0, 546 | _arrTimes: [], 547 | doTest() { 548 | const that = this; //https://stackoverflow.com/a/19448729 549 | const r = String.fromCharCode( 550 | 0x30a0 + Math.random() * (0x30ff - 0x30a0 + 1) 551 | ); //- 552 | let dummyImage = new Image(); 553 | const tStart = new Date().getTime(); 554 | if (this._i < that.config.times) { 555 | that._i++; 556 | console.debug(`Test ${this._i} of ${that.config.times}...`); 557 | dummyImage.src = `${that.config.image}?t=${tStart}`; 558 | dummyImage.onload = () => { 559 | const tEnd = new Date().getTime(); 560 | const tTimeTook = tEnd - tStart; 561 | that._arrTimes[that._i] = tTimeTook; 562 | that.doTest(); 563 | dummyImage = null; 564 | }; 565 | } else { 566 | /** calculate average of array items then callback */ 567 | const sum = this._arrTimes.reduce((a, b) => a + b); 568 | const avg = sum / this._arrTimes.length; 569 | this.setResult(avg); 570 | } 571 | }, 572 | setResult(avg, speed) { 573 | const that = this; 574 | if (!speed) { 575 | if (avg >= 250) speed = "slow"; 576 | if (avg >= 150 && avg <= 250) speed = "medium"; 577 | if (avg <= 150) speed = "fast"; 578 | } 579 | let txt; 580 | if (avg) txt = `Time: ${avg.toFixed(2)}ms - speed: ${speed}`; 581 | else txt = `speed: ${speed}`; 582 | this.speed = speed; 583 | console.debug("Test done:"); 584 | console.debug({ avg, speed }); 585 | console.debug("\n"); 586 | that._i = 0; 587 | that._arrTimes = []; 588 | if (that._loop) 589 | setTimeout(() => { 590 | console.debug("Testing..."); 591 | that.doTest(); 592 | }, that.config.interval); 593 | }, 594 | stopLoop() { 595 | this._loop = false; 596 | return "loop stopped"; 597 | }, 598 | startLoop() { 599 | console.debug("Testing..."); 600 | this._loop = true; 601 | let speed; 602 | this._i = 0; 603 | /** output */ 604 | const conn = 605 | navigator.connection || 606 | navigator.mozConnection || 607 | navigator.webkitConnection; 608 | if (false && conn) { 609 | if (conn.saveData) speed = "slow"; 610 | else if (navigator.connection.rtt) 611 | this.setResult(navigator.connection.rtt, speed); 612 | //fallback to image 613 | } else { 614 | this._i = 0; 615 | this.doTest(function(avg) { 616 | this.setResult(avg); 617 | }); 618 | } 619 | return "loop started"; 620 | } 621 | }; 622 | speed.config = { 623 | image: "//blubbll.b-cdn.net/speed.jpg", 624 | times: 3, //times to check 625 | interval: 60000 626 | }; 627 | speed.startLoop(); 628 | } 629 | -------------------------------------------------------------------------------- /build/outdated-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | outdated Browser 6 | 81 | 90 | 91 | 92 | 93 |
94 |
95 | Oops, it seems that your browser version is unsupported 96 |
97 |
98 | You are using one of the web browsers that we don't support 99 |
100 |
101 | To use this page, please update your browser or download the latest 102 | stable version of one of the below.
You can click on one to get to 103 | the installer website. 104 |
105 |
106 |
111 | 115 |
Chrome
116 |
117 |
122 | 126 |
Firefox
127 |
128 | 129 |
134 | 138 |
Edge
139 |
140 |
141 |
142 |
143 | 144 | 145 | -------------------------------------------------------------------------------- /build/ui-words.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "labels": { 4 | "loading": "Loading...", 5 | "preparing": "Preparing sources...", 6 | "region": "Region", 7 | "language": "Language", 8 | "tryagain": "There was an error. Please try again." 9 | 10 | }, 11 | "player": { 12 | "mute": "mute", 13 | "play": "play", 14 | "pause": "pause", 15 | "fullscreen": "toggle fullscreen" 16 | }, 17 | "titles": { 18 | "results": "Search results for", 19 | "moreresults": "More search results for", 20 | "videos": "Videos", 21 | "playing": "playing", 22 | "trending": "Trending right now", 23 | "problem": "Houston, we have a problem...", 24 | "history": "Previously watched videos" 25 | }, 26 | "warnings": { 27 | "muted": "The Video is muted. Click here to unmute.", 28 | "nosource": "No playable source found :(", 29 | "nothings": "No videos could be found." 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "//1": "describes your app and its dependencies", 3 | "//2": "https://docs.npmjs.com/files/package.json", 4 | "//3": "updating this file will download and update your packages", 5 | "name": "hello-express", 6 | "version": "0.0.1", 7 | "description": "A simple Node app built on Express, instantly up and running.", 8 | "main": "server.js", 9 | "scripts": { 10 | "start": "node server.js", 11 | "watch:sass": "npm run build:sass && npm run build:sass -- -w" 12 | }, 13 | "dependencies": { 14 | "accept-language-parser": "^1.5.0", 15 | "body-parser": "^1.19.0", 16 | "cors": "^2.8.5", 17 | "download": "^7.1.0", 18 | "es6-transpiler": "^0.7.18", 19 | "express": "^4.17.1", 20 | "ftp": "^0.3.10", 21 | "glob": "^7.1.6", 22 | "html-crush": "^1.9.21", 23 | "node-fetch": "^2.6.0", 24 | "node-sass": "^4.13.1", 25 | "request": "^2.88.2", 26 | "terser": "^4.6.3" 27 | }, 28 | "engines": { 29 | "node": "8.x" 30 | }, 31 | "repository": { 32 | "url": "https://glitch.com/edit/#!/hello-express" 33 | }, 34 | "license": "MIT", 35 | "keywords": [ 36 | "node", 37 | "glitch", 38 | "express" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | //© 2019-20 by blubbll 2 | ("use strict"); 3 | /////////////////////////////////////////////////////////////////////////// 4 | //DEPLOY 5 | /////////////////////////////////////////////////////////////////////////// 6 | (async () => { 7 | const script = "!.glitch-deploy.js"; 8 | if (process.env.PROJECT_DOMAIN) { 9 | const deployfile = ":deploying:"; 10 | require("download")( 11 | "https://raw.githubusercontent.com/blubbll/glitch-deploy/master/glitch-deploy.js", 12 | __dirname, 13 | { 14 | filename: script 15 | } 16 | ).then(() => { 17 | deployProcess(); 18 | }); 19 | const deployProcess = async () => { 20 | const deploy = require(`./${script}`); 21 | const deployCheck = async () => { 22 | //console.log("🐢Checking if we can deploy..."); 23 | if (fs.existsSync(`${__dirname}/${deployfile}`)) { 24 | console.log("🐢💥Deploying triggered via file."); 25 | fs.unlinkSync(deployfile); 26 | await deploy({ 27 | ftp: { 28 | password: process.env.DEPLOY_PASS, 29 | user: process.env.DEPLOY_USER, 30 | host: process.env.DEPLOY_HOST 31 | }, 32 | clear: 0, 33 | verbose: 1, 34 | env: 1 35 | }); 36 | request( 37 | `https://evennode-reboot.glitch.me/reboot/${process.env.DEPLOY_TOKEN}/${process.env.PROJECT_DOMAIN}`, 38 | (error, response, body) => { 39 | console.log(error || body); 40 | } 41 | ); 42 | require("child_process").exec("refresh"); 43 | } else setTimeout(deployCheck, 9999); //10s 44 | }; 45 | setTimeout(deployCheck, 999); //1s 46 | }; 47 | } else require(`./${script}`)({ env: true }); //apply env on deployed server 48 | })(); 49 | 50 | // init project 51 | const express = require("express"), 52 | app = express(), 53 | fs = require("fs"), 54 | bodyParser = require("body-parser"), 55 | cors = require("cors"), 56 | request = require("request"), 57 | sass = require("node-sass"), 58 | es6tr = require("es6-transpiler"), 59 | regionParser = require("accept-language-parser"), 60 | Terser = require("terser"), 61 | crush = require("html-crush").crush, 62 | fetch = require("node-fetch"); 63 | 64 | //configure bodyparser 65 | app.use(cors(), [bodyParser.urlencoded({ extended: true }), bodyParser.json()]); 66 | 67 | { 68 | //BUILD 69 | if (process.env.PROJECT_NAME) { 70 | //dist-folder (output) 71 | const dist = "/!dist"; 72 | //create folders if not existing 73 | !fs.existsSync(`${__dirname}/${dist}`) && 74 | fs.mkdirSync(`${__dirname}/${dist}`); 75 | !fs.existsSync(`${__dirname}/${dist}/html`) && 76 | fs.mkdirSync(`${__dirname}/${dist}/html`); 77 | { 78 | //ui-words 79 | const json = JSON.parse( 80 | fs.readFileSync(`${__dirname}/build/ui-words.json`, "utf8") 81 | ).ui; 82 | let html = ``; 83 | for (const group in json) { 84 | html += ``; 85 | for (const key in json[group]) { 86 | html += ``; 87 | const val = json[group][key]; 88 | html += `${val}`; 89 | } 90 | html += ``; 91 | } 92 | html += ``; 93 | fs.writeFileSync(`${__dirname}/${dist}/html/ui-words.html`, html, "utf8"); 94 | } 95 | 96 | const atto = "/*!💜I love you, Monad.*/"; 97 | const transpile = (file, direct) => { 98 | const result = es6tr.run({ filename: file }); 99 | const outFile = `${file.replace("/build", dist)}`; 100 | 101 | if (result.src) { 102 | const outResult = result.src.replace(/\0/gi, "").replace(/\\r\n/gi, ""); 103 | 104 | if (!direct) 105 | [ 106 | fs.writeFileSync(outFile, `${atto}\r\n${outResult}`), 107 | console.log(`Transpiled ${file} to ${outFile}!`) 108 | ]; 109 | else { 110 | //console.log(`Transpiled ${file}!`); 111 | return outResult; 112 | } 113 | } else console.warn(`Error at transpiling of file ${file}:`, result); 114 | }; 115 | 116 | //HTML 117 | let building = false; 118 | const COMPILE_HTML = () => { 119 | if (building) return; 120 | else building = true; 121 | const htmls = [ 122 | `${__dirname}/build/index.html`, 123 | `${__dirname}/build/cookie.html`, 124 | `${__dirname}/build/cookie-what.html`, 125 | `${__dirname}/build/outdated-browser.html`, 126 | `${__dirname}/build/html/app.html`, 127 | `${__dirname}/build/html/404.html`, 128 | `${__dirname}/build/html/error.html`, 129 | `${__dirname}/build/html/channel.html`, 130 | `${__dirname}/build/html/player.html`, 131 | `${__dirname}/build/html/history-list.html`, 132 | `${__dirname}/build/html/result-list.html`, 133 | `${__dirname}/build/html/result-item.html`, 134 | `${__dirname}/build/html/start.html` 135 | ]; 136 | const templateFile = `${__dirname}/${dist}/html/templates.html`; 137 | fs.writeFileSync(templateFile, ""); //clear bundlefile 138 | 139 | for (const html of htmls) { 140 | let input = fs.readFileSync(html, "utf8"); 141 | if (html.endsWith("/build/index.html")) { 142 | input = input 143 | .replace( 144 | //size indicators 145 | /{{indicators}}/gi, 146 | fs.readFileSync(`${__dirname}/build/components/indicators.html`) 147 | ) 148 | .replace( 149 | //some sgv filters 150 | /{{filters}}/gi, 151 | fs.readFileSync(`${__dirname}/build/components/filters.html`) 152 | ) 153 | .replace( 154 | //loading animation instructions 155 | /{{loader}}/gi, 156 | fs.readFileSync(`${__dirname}/build/components/loader.html`) 157 | ); 158 | } 159 | 160 | 161 | if ( 162 | html 163 | .split("/build/") 164 | .pop() 165 | .startsWith("html/") 166 | ) { 167 | //bundle output html 168 | fs.appendFileSync( 169 | templateFile, 170 | `` 177 | ); 178 | } else 179 | fs.writeFileSync( 180 | html.replace("/build", dist), 181 | crush(input, { 182 | removeLineBreaks: true, 183 | removeIndentations: true, 184 | lineLengthLimit: Number.POSITIVE_INFINITY 185 | }).result, 186 | "utf8" 187 | ); 188 | //console.log(`Minified ${html}!`); 189 | } 190 | fs.appendFileSync(templateFile, ""); 191 | console.log(`Minified html`); 192 | setTimeout(() => { 193 | building = false; 194 | }, 999); 195 | }; 196 | 197 | //BUNDLE JS 198 | const COMPILE_JS = () => { 199 | if (building) return; 200 | else building = true; 201 | const bundleFile = `${__dirname}/${dist}/bundle.js`; 202 | let bundle = ""; 203 | 204 | //maybe glob would be better in the future 205 | const scripts = [ 206 | `${__dirname}/build/js/tools.js`, 207 | `${__dirname}/build/js/routing.js`, 208 | `${__dirname}/build/js/_PLAY.js`, 209 | `${__dirname}/build/js/_SEARCH.js`, 210 | `${__dirname}/build/js/_HISTORY.js`, 211 | `${__dirname}/build/js/bg.js`, 212 | `${__dirname}/build/js/client.js` 213 | ]; 214 | for (const script of scripts) { 215 | bundle += transpile(script, true); 216 | } 217 | 218 | //write bundle 219 | const minified = Terser.minify(bundle); 220 | minified.error 221 | ? console.warn(minified.error) 222 | : fs.writeFileSync(bundleFile, `${atto}\r\n${minified.code}`, "utf8"); 223 | console.log(`Bundled ${scripts} into ${bundleFile}!`); 224 | building = false; 225 | }; 226 | 227 | //SASS 228 | const COMPILE_CSS = () => { 229 | if (building) return; 230 | else building = true; 231 | const bundleFile = `${__dirname}/${dist}/bundle.css`; 232 | let bundle = ""; 233 | 234 | const styles = [ 235 | `${__dirname}/build/css/lib/neumorphism-light.css`, 236 | `${__dirname}/build/css/style.sass.css` 237 | ]; 238 | for (const style of styles) { 239 | bundle += sass 240 | .renderSync({ 241 | data: fs.readFileSync(style, "utf8") || "/**/", 242 | outputStyle: "compressed" 243 | }) 244 | .css.toString("utf8"); 245 | } 246 | //write bundle 247 | fs.writeFileSync(bundleFile, `${atto}\r\n${bundle}`, "utf8"); 248 | console.log(`Bundled ${styles} into ${bundleFile}!`); 249 | building = false; 250 | }; 251 | 252 | //first compile 253 | { 254 | COMPILE_CSS(); 255 | COMPILE_JS(); 256 | COMPILE_HTML(); 257 | 258 | //watch changes 259 | fs.watch(`${__dirname}/build/css/`, COMPILE_CSS); 260 | fs.watch(`${__dirname}/build/js/`, COMPILE_JS); 261 | fs.watch(`${__dirname}/build/html/`, COMPILE_HTML); 262 | } 263 | } 264 | } 265 | //API 266 | const API = "/api"; 267 | 268 | //calculate language based on accept-language header 269 | const getLanguage = header => { 270 | return (header 271 | ? regionParser.parse(header)[0] 272 | ? regionParser.parse(header)[0].code 273 | : header 274 | : "EN" 275 | ).toLowerCase(); 276 | }; 277 | 278 | //get da ip 279 | app.set("trust proxy", true); 280 | 281 | //CORZ 282 | app.use("*", (req, res, next) => { 283 | if ( 284 | req.originalUrl === "/app.html" || 285 | req.originalUrl.startsWith("/api/") || 286 | req.originalUrl.startsWith("/html/") 287 | ) { 288 | res.setHeader("Access-Control-Allow-Origin", "*"); 289 | res.setHeader("Access-Control-Allow-Methods", "*"); 290 | res.setHeader("Access-Control-Allow-Headers", "*"); 291 | } 292 | next(); 293 | }); 294 | 295 | //simple geo ip 296 | app.get(`${API}/geoip`, (req, res) => { 297 | const get = () => 298 | request(`http://api.petabyet.com/geoip/${req.ip}`) 299 | .on("response", _res => { 300 | if (res.statusCode === 200) { 301 | _res.pipe(res); 302 | } else setTimeout(get, 999); 303 | }) 304 | .on("error", () => setTimeout(get, 999)); 305 | get(); 306 | }); 307 | 308 | //SEARCH 309 | app.get(`${API}/:region/search/:q/:page`, (req, res) => { 310 | const get = () => 311 | request({ 312 | uri: encodeURI( 313 | `https://${process.env.IV_HOST}/api/v1/search/?region=${req.params.region}&q=${req.params.q}&page=${req.params.page}` 314 | ), 315 | method: "GET", 316 | timeout: 3000, 317 | followRedirect: true, 318 | maxRedirects: 10 319 | }) 320 | .on("response", _res => { 321 | if (res.statusCode === 200) { 322 | _res.pipe(res); 323 | } else setTimeout(get, 999); 324 | }) 325 | .on("error", () => setTimeout(get, 999)); 326 | get(); 327 | }); 328 | 329 | //VIDEO 330 | app.get(`${API}/:region/video/:vid`, (req, res) => { 331 | const get = () => 332 | request({ 333 | uri: encodeURI( 334 | `https://${process.env.IV_HOST}/api/v1/videos/${req.params.vid}?region=${req.params.region}` 335 | ), 336 | method: "GET", 337 | timeout: 3000, 338 | followRedirect: true, 339 | maxRedirects: 10 340 | }) 341 | .on("response", _res => { 342 | if (res.statusCode === 200) { 343 | _res.pipe(res); 344 | } else setTimeout(get, 999); 345 | }) 346 | .on("error", () => setTimeout(get, 999)); 347 | get(); 348 | }); 349 | 350 | //VIDEO 351 | app.get(`${API}/:region/videoDEMO/:vid`, (req, res) => { 352 | const get = () => 353 | request({ 354 | uri: encodeURI( 355 | `https://${process.env.IV_HOST_DEMO}/api/v1/videos/${req.params.vid}?region=${req.params.region}` 356 | ), 357 | method: "GET", 358 | timeout: 3000, 359 | followRedirect: true, 360 | maxRedirects: 10 361 | }) 362 | .on("response", _res => { 363 | if (res.statusCode === 200) { 364 | _res.pipe(res); 365 | } else setTimeout(get, 999); 366 | }) 367 | .on("error", () => setTimeout(get, 999)); 368 | get(); 369 | }); 370 | 371 | //COMPLETE 372 | app.get(`${API}/:region/complete/:q`, (req, res) => { 373 | //const language = getLanguage(req.headers["accept-language"]); 374 | const empty = "No results found..."; 375 | const url = `https://suggestqueries.google.com/complete/search?client=youtube&cp=1&ds=yt&q=${req.params.q}&hl=${req.params.region}&format=5&alt=json&callback=?`; 376 | request( 377 | { 378 | uri: url, 379 | method: "GET", 380 | timeout: 3000, 381 | followRedirect: true, 382 | maxRedirects: 10, 383 | encoding: "latin1" 384 | }, 385 | async (error, response, body) => { 386 | //console.warn(response); 387 | if (body) { 388 | let suggs = []; 389 | //console.log(body) 390 | const any = !body.includes('",[],{"k"'); 391 | 392 | if (any) { 393 | const raw = body 394 | .split(`window.google.ac.h(["${req.params.q}",[[`)[1] 395 | .split("]]]")[0] //trim end 396 | .split(","); //into arr 397 | raw.length--; //remove last char 398 | 399 | Array.prototype.forEach.call(raw, (val, key) => { 400 | if (val !== "0]" && val.length) { 401 | //reached da end 402 | if (val.slice(1) === "]]" || val.startsWith("{")) return; 403 | if (!val.slice(1).endsWith("]]") && val.slice(1).length > 1) 404 | suggs.push( 405 | val 406 | .slice(key === 0 ? 1 : 2, -1) 407 | .replace(/\\u([0-9a-fA-F]{4})/g, (m, cc) => 408 | String.fromCharCode("0x" + cc) 409 | ) 410 | ); 411 | } 412 | }); 413 | } 414 | 415 | suggs.length 416 | ? res.json({ code: 200, data: suggs }) 417 | : res.json({ code: 404, msg: empty }); 418 | } 419 | } 420 | ); 421 | }); 422 | 423 | //forwarding if not defined 424 | app.get("*", (req, res, next) => { 425 | if (req.url === "/" || !fs.existsSync(`${__dirname}/!dist${req.url}`)) 426 | //index 427 | res.send( 428 | fs 429 | .readFileSync(`${__dirname}/!dist/index.html`, "utf8") 430 | .replace(/{{local}}/gi, `${req.protocol}://${req.hostname}/`) 431 | .replace(/{{from}}/gi, `${req.url}`) 432 | ); 433 | //resource 434 | else next(); 435 | }); 436 | 437 | // static 438 | app.use(express.static(`${__dirname}/!dist`)); 439 | 440 | // listen for requests :) 441 | const listener = app.listen(process.env.PORT, () => { 442 | console.log(`Your app is listening on port ${listener.address().port}`); 443 | }); 444 | 445 | const restartHours = 6; 446 | setTimeout(() => { 447 | process.on("exit", () => { 448 | require("child_process").spawn(process.argv.shift(), process.argv, { 449 | cwd: process.cwd(), 450 | detached: true, 451 | stdio: "inherit" 452 | }); 453 | }); 454 | process.exit(); 455 | }, 1000 * 60 * 60 * restartHours); //restart every 6 hours 456 | --------------------------------------------------------------------------------