diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05940cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +bin +obj +publish +npm/node_modules +npm/package-lock.json +npm/dist +thumbs.db +.env diff --git a/cross.json b/cross.json new file mode 100644 index 0000000..0d8f6d8 --- /dev/null +++ b/cross.json @@ -0,0 +1,29 @@ +{ + "$schema": "https:\/\/crosslang.tesseslanguage.com\/\/schema\/cross-json-schema.json", + "info": { + "short_name": "changeme", + "short_name_pretty": "Change Me", + "type": "webapp" + }, + "name": "tytd-submission-box", + "prebuild": [ + { + "commands": [ + [ + "npm", + "install" + ], + [ + "npm", + "run", + "publish" + ] + ], + "res": [ + "npm\/dist" + ], + "workdir": "npm" + } + ], + "version": "1.0.0.0-prod" +} \ No newline at end of file diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..30b9847 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "beercss": "^4.0.21" + }, + "devDependencies": { + "esbuild": "^0.28.0" + }, + "scripts": { + "publish": "esbuild --minify --sourcemap --define:'process.env.NODE_ENV=\"production\"' --bundle --outdir=dist web.mjs --loader:.svg=file --loader:.woff2=file" + } +} diff --git a/npm/web.css b/npm/web.css new file mode 100644 index 0000000..b227c7b --- /dev/null +++ b/npm/web.css @@ -0,0 +1,78 @@ +:root, +body.light { + --primary:#855400; +--on-primary:#ffffff; +--primary-container:#ffddb7; +--on-primary-container:#2a1700; +--secondary:#705b41; +--on-secondary:#ffffff; +--secondary-container:#fcdebc; +--on-secondary-container:#281805; +--tertiary:#53643e; +--on-tertiary:#ffffff; +--tertiary-container:#d6e9b9; +--on-tertiary-container:#121f03; +--error:#ba1a1a; +--on-error:#ffffff; +--error-container:#ffdad6; +--on-error-container:#410002; +--background:#fffbff; +--on-background:#1f1b16; +--surface:#fff8f4; +--on-surface:#1f1b16; +--surface-variant:#f0e0d0; +--on-surface-variant:#504539; +--outline:#827568; +--outline-variant:#d4c4b5; +--shadow:#000000; +--scrim:#000000; +--inverse-surface:#352f2a; +--inverse-on-surface:#f9efe7; +--inverse-primary:#ffb95c; +--surface-dim:#e2d8d1; +--surface-bright:#fff8f4; +--surface-container-lowest:#ffffff; +--surface-container-low:#fcf2ea; +--surface-container:#f6ece4; +--surface-container-high:#f0e6de; +--surface-container-highest:#ebe1d9; +} + +body.dark { + --primary:#ffb95c; +--on-primary:#462a00; +--primary-container:#653e00; +--on-primary-container:#ffddb7; +--secondary:#dfc2a2; +--on-secondary:#3f2d17; +--secondary-container:#57432b; +--on-secondary-container:#fcdebc; +--tertiary:#bacd9f; +--on-tertiary:#263514; +--tertiary-container:#3c4c28; +--on-tertiary-container:#d6e9b9; +--error:#ffb4ab; +--on-error:#690005; +--error-container:#93000a; +--on-error-container:#ffb4ab; +--background:#1f1b16; +--on-background:#ebe1d9; +--surface:#17130e; +--on-surface:#ebe1d9; +--surface-variant:#504539; +--on-surface-variant:#d4c4b5; +--outline:#9c8e80; +--outline-variant:#504539; +--shadow:#000000; +--scrim:#000000; +--inverse-surface:#ebe1d9; +--inverse-on-surface:#352f2a; +--inverse-primary:#855400; +--surface-dim:#17130e; +--surface-bright:#3e3833; +--surface-container-lowest:#110e09; +--surface-container-low:#1f1b16; +--surface-container:#231f1a; +--surface-container-high:#2e2924; +--surface-container-highest:#39342f; +} \ No newline at end of file diff --git a/npm/web.mjs b/npm/web.mjs new file mode 100644 index 0000000..e9dfa20 --- /dev/null +++ b/npm/web.mjs @@ -0,0 +1,2 @@ +import 'beercss' +import './web.css' \ No newline at end of file diff --git a/res/ratelimit.html b/res/ratelimit.html new file mode 100644 index 0000000..b2ec5d2 --- /dev/null +++ b/res/ratelimit.html @@ -0,0 +1,17 @@ + + + + + + + tytd-submission-box - {title} + + + +
+

You are rate limited

+

Please wait 10 or so seconds

+ Home +
+ + \ No newline at end of file diff --git a/src/index.tcross b/src/index.tcross new file mode 100644 index 0000000..18cc6d8 --- /dev/null +++ b/src/index.tcross @@ -0,0 +1,138 @@ +func template(title,body) +{ + + + + + + + + TYTD Submission Box - {title} + + + +
+ +
+ + +
+} +func TYTD.GetVideoId(v) +{ + func IsValidId(_v) + { + if(_v.Count != 11) return false; + each(var item : _v) + { + if(!(item.IsLetter() || item.IsDigit() || item == '-' || item == '_')) return false; + } + return true; + } + if(TypeOf(v) != "String") return null; + if(IsValidId(v)) return v; + each(var __re : ["youtube\\..+?/watch.*?v=(.*?)(?:&|/|$)","youtu\\.be/(.*?)(?:\\?|&|/|$)","youtube\\..+?/embed/(.*?)(?:\\?|&|/|$)","youtube\\..+?/shorts/(.*?)(?:\\?|&|/|$)","youtube\\..+?/live/(.*?)(?:\\?|&|/|$)"]) + { + var __r = new Regex(__re); + var __s = __r.Search(v); + if(__s.Count == 2) + { + __r=__s[1].Text; + if(IsValidId(__r)) + { + return __r; + } + } + } + return null; + +} +func Index(ctx) +{ + ctx.WithDebug(true); + if(ctx.Method == "POST") + { + const v = ctx.QueryParams.TryGetFirst("v"); + if(!TypeIsString(v)) + { + ctx.WithMimeType("text/html").SendText( + template( + "v is not defined", + +

Error

+ The post request did not contain the {"\"v\""} parameter + Go Home +
+ ) + ); + return true; + } + + const url = TYTD.GetVideoId(v); + if(!TypeIsString(url)) + { + ctx.WithMimeType("text/html").SendText( + template( + "Not a valid url", + +

Error

+ The url must be a valid YouTube video + Go Home +
+ ) + ); + return true; + } + + if(needsToRateLimit()) + { + ctx.SendRedirect("/ratelimit",303); + return true; + } + try { + const tytdReq = Net.Http.MakeRequest( + $"{tytd_url}.TrimEnd('/')/api/v1/add", + { + Method = "POST", + RequestHeaders = [ + {Key = "Authorization",Value=$"Bearer {tytd_apikey}"} + ], + Body = new JsonHttpRequestBody( + [ + { + url = url, + res = "LowVideo" + } + ] + ) + } + ); + + tytdReq.ReadToString(); + }catch(ex) {} + + ctx.SendRedirect("/",303); + return true; + } + ctx.WithMimeType("text/html").SendText( + template( + "Add a video", +
+
+
+ download +
Add a video to my YouTube Downloader
+
+ +
+
+
+ ) + ); + return true; +} \ No newline at end of file diff --git a/src/program.tcross b/src/program.tcross new file mode 100644 index 0000000..2c3552d --- /dev/null +++ b/src/program.tcross @@ -0,0 +1,68 @@ +class MyWebApp { + private fileServer; + private mountable; + private pages; + + public MyWebApp() + { + this.fileServer = new FileServer(embeddir("/"), true, false); + + this.mountable = new MountableServer((ctx)=>{ + const page = this.pages.[ctx.Path]; + if(TypeIsDefined(page)) return page(ctx); + return false; + }); + this.mountable.Mount("/dist/",this.fileServer); + this.pages = { + .["/"] = Index, + .["/ratelimit"] = RateLimit + }; + } + + public Handle(ctx) + { + return this.mountable.Handle(ctx); + } + public Close() + { + this.mountable = null; + } +} +const tytd_url = Env["TYTD_URL"]; +const tytd_apikey = Env["TYTD_APIKEY"]; +func WebAppMain(args) +{ + if(!TypeIsString(tytd_url) || !TypeIsString(tytd_apikey)) + { + Console.WriteLine("Define the environment variables TYTD_URL and TYTD_APIKEY"); + return 1; + } + + return new MyWebApp(); +} + +func RateLimit(ctx) +{ + ctx.WithMimeType("text/html").SendStream(embedstrm("ratelimit.html")); + return true; +} + +const mtx = new Mutex(); +var lastRequest = 0; + +func needsToRateLimit() +{ + const time = DateTime.Now; + + mtx.Lock(); + const lastTime = lastRequest; + mtx.Unlock(); + + if(lastTime + 10 > time) + return true; + + mtx.Lock(); + lastRequest = time; + mtx.Unlock(); + return false; +} \ No newline at end of file