an http based roguelike rpg about jelqing
Blame: src/ui.html
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 1 | <!DOCTYPE html> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 2 | <html> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 3 | <head> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 4 | <meta charset="utf-8"> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 5 | <title>jelqd</title> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 6 | <style> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 7 | .frame { border: 2px inset; background: #fff; height: 85vh; overflow: scroll; padding: 4px; } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 8 | #log { white-space: pre-wrap; margin: 0; } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 9 | pre, li, p, td, label { overflow-wrap: anywhere; } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 10 | </style> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 11 | </head> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 12 | <body> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 13 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 14 | <h1>jelqd</h1> |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 15 | <p>A roguelike played over HTTP. Pick a resource, pick a method, send it. The log on the right is everything the server has said to you. |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 16 | Language <select id="locale"><option>en-GB</option><option>en-US</option><option>en-AU</option></select></p> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 17 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 18 | <table width="100%" cellspacing="6"> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 19 | <tr> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 20 | <td width="45%" valign="top"> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 21 | <div class="frame"> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 22 | |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 23 | <div id="alerts"></div> |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 24 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 25 | <form id="form"> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 26 | <h3>Method</h3> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 27 | <div id="methods"></div> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 28 | <datalist id="excuses"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 29 | <option value="changing"><option value="homework"><option value="pulled a muscle"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 30 | <option value="tidying"><option value="the washing up"><option value="fed the cat"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 31 | <option value="revising"><option value="on a call"><option value="wrapping presents"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 32 | <option value="folding the washing"><option value="hanging the lights"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 33 | <option value="helping Nan"><option value="peeling sprouts"><option value="the truth"> |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 34 | </datalist> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 35 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 36 | <h3>Request</h3> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 37 | <table id="fields" cellpadding="2"></table> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 38 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 39 | <p><input type="submit" id="send" value="Send" style="font-size: 1.3em"></p> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 40 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 41 | <h3>Stats</h3> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 42 | <table id="stats" border="1" cellspacing="0" cellpadding="3"></table> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 43 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 44 | <h3>Resource</h3> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 45 | <div id="targets"></div> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 46 | </form> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 47 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 48 | <p><small>Every request goes through <code>/ui/send</code>, which replays it inside the server, so the page can show redirects and send methods and headers that browsers refuse.</small></p> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 49 | </div> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 50 | </td> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 51 | <td valign="top"> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 52 | <div class="frame" id="frame"><pre id="log"></pre></div> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 53 | </td> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 54 | </tr> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 55 | </table> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 56 | |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 57 | <script> |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 58 | const REL = "https://jelq.dev/rels/"; |
4347977b8192 | atagen | 2026-10-11 04:56 UTC | 59 | const METHODS = ["JELQ", "HOLD", "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE"]; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 60 | const ALL_LUBES = ["", "application/baby-oil", "application/coconut-oil", "application/lotion", "application/vaseline", "text/plain; charset=spit", "image/jpeg"]; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 61 | const DEATHS = ["vascular-event", "caught", "opened", "banned", "degloved", "zip", "watermelon", "run-ended"]; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 62 | let stocked = null; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 63 | const STATS = [ |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 64 | ["x-floor", "Floor"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 65 | ["x-self-reported-length", "Self-reported length"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 66 | ["x-girth", "Girth"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 67 | ["x-foreskin", "Foreskin"], |
5dfebcc0ac43 | atagen | 2026-10-11 05:28 UTC | 68 | ["x-friction", "Friction"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 69 | ["x-eq", "EQ (aim for 50 to 70)"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 70 | ["x-stamina", "Stamina"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 71 | ["x-suspicion", "Suspicion"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 72 | ["x-shame", "Shame"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 73 | ["x-cash", "Cash"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 74 | ]; |
f39b10e20a0b | atagen | 2026-10-10 14:33 UTC | 75 | const MEASURED = ["x-length", "Length"]; |
f39b10e20a0b | atagen | 2026-10-10 14:33 UTC | 76 | const QUIET = new Set(["link", "set-cookie", "server", "x-powered-by", "cache-control", "content-length", "content-type", "date"].concat(STATS.map(s => s[0]), MEASURED[0])); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 77 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 78 | const FIELDS = { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 79 | seed: { label: "seed (unranked)", kind: "text" }, |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 80 | technique: { label: "technique", kind: "select" }, |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 81 | also: { label: "combo with", kind: "select" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 82 | intensity: { label: "intensity, 1 to 10", kind: "number", value: "5" }, |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 83 | lube: { label: "Content-Type", header: "Content-Type", kind: "select" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 84 | patch: { label: "body", kind: "textarea", value: '[{"op": "test", "path": "/length", "value": 6}]' }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 85 | range: { label: "Range", header: "Range", kind: "text", placeholder: "inches=0-2" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 86 | ifMatch: { label: "If-Match", header: "If-Match", kind: "text" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 87 | ifNoneMatch: { label: "If-None-Match", header: "If-None-Match", kind: "text" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 88 | ifModifiedSince: { label: "If-Modified-Since", header: "If-Modified-Since", kind: "text", placeholder: "an HTTP-date" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 89 | proxy: { label: "Proxy-Authorization", header: "Proxy-Authorization", kind: "text", placeholder: "Ruler 5.4" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 90 | upgrade: { label: "Upgrade", header: "Upgrade", kind: "select", options: ["", "underhand", "overhand"] }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 91 | authorization: { label: "Authorization", header: "Authorization", kind: "text", placeholder: 'Excuse "homework"' }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 92 | extra: { label: "other headers, one per line", kind: "textarea", placeholder: "Expect: gains" }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 93 | }; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 94 | const BY_TARGET = { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 95 | "runs POST": ["seed"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 96 | "run PATCH": ["patch"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 97 | "length GET": ["range"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 98 | "length HEAD": ["range"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 99 | "foreskin DELETE": ["ifMatch"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 100 | "elder GET": ["proxy", "ifModifiedSince"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 101 | "grip GET": ["upgrade"], |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 102 | "diagnosis GET": ["authorization", "ifNoneMatch"], |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 103 | }; |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 104 | const GROUPS = [ |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 105 | ["play", "Play"], |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 106 | ["shop", "Shop"], |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 107 | ["you", "You"], |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 108 | ["said", "From the last response"], |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 109 | ["reading", "Reading"], |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 110 | ["meta", "Meta"], |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 111 | ]; |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 112 | const GROUP_OF = { run: "play", floor: "play", drawer: "play", shop: "play", "shop-item": "shop", kitchen: "play", portal: "play", elder: "play", diagnosis: "play", length: "you", foreskin: "you", grip: "you", inventory: "you", footage: "you", techniques: "reading", thread: "reading" }; |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 113 | const NAMES = { run: "the run", thread: "the 2009 forum thread" }; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 114 | const GLOBALS = [ |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 115 | { key: "runs", group: "play", name: "start a new run", path: "/runs", methods: ["POST"] }, |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 116 | { key: "global", group: "meta", name: "leaderboard", path: "/leaderboard", methods: ["GET", "HEAD", "OPTIONS"] }, |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 117 | { key: "global", group: "meta", name: "graveyard", path: "/graveyard", methods: ["GET", "HEAD", "OPTIONS"] }, |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 118 | ]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 119 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 120 | let run = null; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 121 | let linked = []; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 122 | let techniques = []; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 123 | let offered = []; |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 124 | let challenged = false; |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 125 | const etags = {}; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 126 | let target = "/runs"; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 127 | let method = "POST"; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 128 | const values = {}; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 129 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 130 | const $ = id => document.getElementById(id); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 131 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 132 | function short(path) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 133 | return path.replace(/[0-9a-f]{32}/, id => id.slice(0, 6) + "…"); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 134 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 135 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 136 | function el(tag, props, ...children) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 137 | const node = Object.assign(document.createElement(tag), props || {}); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 138 | for (const child of children) node.append(child); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 139 | return node; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 140 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 141 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 142 | function titled(text) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 143 | return text.charAt(0).toUpperCase() + text.slice(1); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 144 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 145 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 146 | function technique(name) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 147 | return techniques.find(t => t.name === name); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 148 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 149 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 150 | function clashes(a, b) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 151 | const rules = [a.lubricant, b.lubricant]; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 152 | return rules.includes("required") && rules.includes("forbidden"); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 153 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 154 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 155 | // The options a field can take right now, or null for free text. |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 156 | function options(name) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 157 | if (name === "technique") return techniques.map(t => t.name); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 158 | if (name === "also") { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 159 | const first = technique(fieldValue("technique")); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 160 | if (!first || !first.lubricant) return [""]; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 161 | const partners = techniques.filter(t => t.lubricant && t.name !== first.name && !clashes(first, t)); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 162 | return [""].concat(partners.map(t => t.name)); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 163 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 164 | if (name === "lube") { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 165 | const lubes = stocked === null ? ALL_LUBES : ALL_LUBES.filter(l => l === "" || l.includes("/") && !l.startsWith("application/") || stocked.includes(l.replace("application/", ""))); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 166 | return lubeRule() === "required" ? lubes.filter(Boolean) : lubes; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 167 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 168 | return FIELDS[name].options || null; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 169 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 170 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 171 | function raw(name) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 172 | return values[name] ?? FIELDS[name].value ?? ""; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 173 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 174 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 175 | function fieldValue(name) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 176 | const allowed = options(name); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 177 | const value = raw(name); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 178 | if (allowed && !allowed.includes(value)) return allowed[0] ?? ""; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 179 | return value; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 180 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 181 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 182 | function lubeRule() { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 183 | const picked = [technique(fieldValue("technique")), technique(fieldValue("also"))].filter(Boolean); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 184 | if (!picked.some(t => t.lubricant)) return "none"; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 185 | for (const rule of ["forbidden", "required"]) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 186 | if (picked.some(t => t.lubricant === rule)) return rule; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 187 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 188 | return "optional"; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 189 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 190 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 191 | function allowedMethods(t) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 192 | return t.key === "run" && !techniques.length ? t.methods.filter(m => m !== "JELQ") : t.methods; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 193 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 194 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 195 | function targets() { |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 196 | const listed = run ? linked : []; |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 197 | const known = new Set(listed.concat(GLOBALS).map(t => t.path)); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 198 | const fresh = offered.filter((t, i) => !known.has(t.path) && offered.findIndex(o => o.path === t.path) === i); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 199 | return listed.concat(fresh, GLOBALS); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 200 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 201 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 202 | function current() { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 203 | return targets().find(t => t.path === target) || GLOBALS[0]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 204 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 205 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 206 | function chooseTarget(t) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 207 | const allowed = allowedMethods(t); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 208 | target = t.path; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 209 | method = allowed.includes("JELQ") ? "JELQ" : allowed.find(m => m !== "HEAD" && m !== "OPTIONS") || allowed[0]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 210 | render(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 211 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 212 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 213 | function renderStats(headers) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 214 | const table = $("stats"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 215 | table.replaceChildren(); |
f39b10e20a0b | atagen | 2026-10-10 14:33 UTC | 216 | const stats = STATS.map(s => s[0] === "x-self-reported-length" && headers && MEASURED[0] in headers ? MEASURED : s); |
f39b10e20a0b | atagen | 2026-10-10 14:33 UTC | 217 | for (let i = 0; i < stats.length; i += 2) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 218 | const row = el("tr"); |
f39b10e20a0b | atagen | 2026-10-10 14:33 UTC | 219 | for (const [name, label] of stats.slice(i, i + 2)) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 220 | const value = headers ? headers[name] : undefined; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 221 | row.append(el("th", { align: "left", textContent: label }), el("td", { textContent: value === undefined ? "—" : value })); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 222 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 223 | table.append(row); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 224 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 225 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 226 | |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 227 | function renderAlerts(list) { |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 228 | const box = $("alerts"); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 229 | box.replaceChildren(); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 230 | const alerts = []; |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 231 | for (const [name, value] of list || []) { |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 232 | if (name === "via") alerts.push(titled(value.split(" ").pop()) + " is at the door."); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 233 | if (name === "x-observed-by") alerts.push((value === "cat" ? "The cat" : titled(value)) + " is watching."); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 234 | if (name === "warning") { |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 235 | const text = (value.match(/"([^"]*)"/) || [, value])[1]; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 236 | alerts.push(titled(text) + "."); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 237 | } |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 238 | } |
e76ab808e7ad | atagen | 2026-10-10 13:46 UTC | 239 | const stamina = (list || []).find(([name]) => name === "x-stamina"); |
e76ab808e7ad | atagen | 2026-10-10 13:46 UTC | 240 | if (stamina && stamina[1].startsWith("0/")) alerts.push("Exhausted. Wait out the refractory period, then GET the run."); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 241 | for (const alert of alerts) box.append(el("h2", { textContent: alert })); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 242 | } |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 243 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 244 | function renderTargets() { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 245 | const box = $("targets"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 246 | box.replaceChildren(); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 247 | const all = targets(); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 248 | for (const [group, title] of GROUPS) { |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 249 | const members = all.filter(t => t.group === group); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 250 | if (!members.length) continue; |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 251 | box.append(el("h4", { textContent: title })); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 252 | for (const t of members) { |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 253 | const radio = el("input", { type: "radio", name: "target", checked: t.path === target, onchange: () => chooseTarget(t) }); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 254 | box.append(el("label", {}, radio, " " + t.name + " ", el("small", { textContent: short(t.path) })), el("br")); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 255 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 256 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 257 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 258 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 259 | function renderMethods() { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 260 | const box = $("methods"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 261 | box.replaceChildren(); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 262 | const allowed = allowedMethods(current()); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 263 | for (const m of METHODS.filter(m => allowed.includes(m))) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 264 | const radio = el("input", { type: "radio", name: "method", checked: m === method, onchange: () => { method = m; render(); } }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 265 | const label = el("label", {}, radio, " "); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 266 | label.append(m === "JELQ" ? el("big", {}, el("b", { textContent: "JELQ" })) : m); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 267 | box.append(label, " "); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 268 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 269 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 270 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 271 | function fieldNames() { |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 272 | const t = current(); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 273 | const key = t.key + " " + method; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 274 | let names = BY_TARGET[key] || []; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 275 | if (key === "run JELQ") { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 276 | const chosen = technique(fieldValue("technique")); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 277 | names = ["technique"]; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 278 | if (options("also").length > 1) names.push("also"); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 279 | if (chosen && chosen.lubricant) names.push("intensity"); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 280 | if (["required", "optional"].includes(lubeRule())) names.push("lube"); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 281 | } |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 282 | const scoped = run && !["runs", "global"].includes(t.key); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 283 | const asked = challenged && scoped && !names.includes("authorization"); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 284 | return (asked ? ["authorization"] : []).concat(names); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 285 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 286 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 287 | function renderFields() { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 288 | const table = $("fields"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 289 | table.replaceChildren(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 290 | for (const name of fieldNames()) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 291 | const f = FIELDS[name]; |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 292 | let value = fieldValue(name); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 293 | if (name === "ifMatch" && !value) value = values.ifMatch = etags[current().path] || ""; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 294 | const choices = options(name); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 295 | let input; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 296 | if (choices) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 297 | input = el("select", {}, ...choices.map(o => el("option", { value: o, textContent: o || "(none)", selected: o === value }))); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 298 | } else if (f.kind === "textarea") { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 299 | input = el("textarea", { rows: 3, cols: 40, value, placeholder: f.placeholder || "" }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 300 | } else { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 301 | input = el("input", { type: f.kind, value, placeholder: f.placeholder || "", size: 30 }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 302 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 303 | input.oninput = () => { values[name] = input.value; }; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 304 | input.onchange = () => { values[name] = input.value; if (choices) renderFields(); }; |
7d3fb63208bf | atagen | 2026-10-11 04:38 UTC | 305 | if (name === "authorization") input.setAttribute("list", "excuses"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 306 | table.append(el("tr", {}, el("td", { align: "right", valign: "top", textContent: f.label }), el("td", {}, input))); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 307 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 308 | const extra = el("textarea", { rows: 3, cols: 40, value: raw("extra"), placeholder: FIELDS.extra.placeholder }); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 309 | extra.oninput = () => { values.extra = extra.value; }; |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 310 | const more = el("details", { open: Boolean(values.extra) }, el("summary", { textContent: "more headers" }), extra); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 311 | table.append(el("tr", {}, el("td"), el("td", {}, more))); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 312 | $("send").value = method + " " + short(current().path); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 313 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 314 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 315 | function render() { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 316 | if (!allowedMethods(current()).includes(method)) return chooseTarget(current()); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 317 | renderTargets(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 318 | renderMethods(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 319 | renderFields(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 320 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 321 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 322 | function parseLinks(header) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 323 | const resources = []; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 324 | const moves = []; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 325 | for (const match of header.matchAll(/<([^>]*)>((?:\s*;\s*[a-z-]+="[^"]*")*)/g)) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 326 | const params = Object.fromEntries([...match[2].matchAll(/([a-z-]+)="([^"]*)"/g)].map(p => [p[1], p[2]])); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 327 | const name = (params.rel || "").replace(REL, ""); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 328 | if (name === "technique") { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 329 | moves.push({ name: params.title, lubricant: params.lubricant || null }); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 330 | continue; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 331 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 332 | const methods = (params.allow || "GET").split(",").map(s => s.trim()); |
d46f9d0b953a | atagen | 2026-10-10 14:41 UTC | 333 | resources.push({ key: name, group: GROUP_OF[name] || "play", name: NAMES[name] || params.title || name, path: match[1], methods }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 334 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 335 | return [resources, moves]; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 336 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 337 | |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 338 | function hrefs(value, found) { |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 339 | if (value && typeof value === "object") { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 340 | if (typeof value.href === "string") { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 341 | const name = value.title || value.item || value.name || value.href.split("/").pop(); |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 342 | found.push({ key: "offered", group: "said", name, path: value.href, methods: [value.method || "GET"] }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 343 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 344 | for (const key in value) hrefs(value[key], found); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 345 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 346 | return found; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 347 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 348 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 349 | function log(text) { |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 350 | $("log").prepend(text + "\n\n"); |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 351 | $("frame").scrollTop = 0; |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 352 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 353 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 354 | function describe(r) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 355 | const lines = ["< " + r.status + " " + r.reason]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 356 | for (const [name, value] of r.headers) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 357 | if (!QUIET.has(name)) lines.push("< " + name + ": " + short(value)); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 358 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 359 | let body = null; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 360 | try { body = JSON.parse(r.body); } catch (e) {} |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 361 | if (body && typeof body === "object") { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 362 | const rest = Object.assign({}, body); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 363 | if (rest.detail) lines.push(" " + rest.detail); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 364 | for (const key of ["type", "title", "status", "detail"]) delete rest[key]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 365 | if (Object.keys(rest).length) lines.push(short(JSON.stringify(rest, null, 2)).replace(/^/gm, " ")); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 366 | } else if (r.body) { |
e76ab808e7ad | atagen | 2026-10-10 13:46 UTC | 367 | const range = (r.headers.find(h => h[0] === "content-range") || [])[1] || ""; |
e76ab808e7ad | atagen | 2026-10-10 13:46 UTC | 368 | const partial = r.status === 206 && range.match(/^bytes (\d+)-(\d+)\/(\d+)$/); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 369 | lines.push(" " + r.body); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 370 | if (partial) lines.push(" [bytes " + partial[1] + "-" + partial[2] + " of " + partial[3] + ". Half-mast.]"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 371 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 372 | return lines.join("\n"); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 373 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 374 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 375 | function absorb(r, sentMethod, sentPath) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 376 | const headers = Object.fromEntries(r.headers.map(([n, v]) => [n.toLowerCase(), v])); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 377 | if (headers.etag) etags[sentPath] = headers.etag; |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 378 | const digest = (headers["www-authenticate"] || "").match(/nonce="([^"]*)"/); |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 379 | if (digest && !(values.authorization || "").includes(digest[1])) { |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 380 | values.authorization = 'Digest username="", nonce="' + digest[1] + '"'; |
ec6fd4bb5b60 | atagen | 2026-10-11 04:34 UTC | 381 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 382 | let body = null; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 383 | try { body = JSON.parse(r.body); } catch (e) {} |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 384 | const problem = body && typeof body.type === "string" ? body.type.split("/").pop() : null; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 385 | const started = r.status === 201 && (sentPath === "/runs" || sentPath.startsWith("/runs?")); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 386 | |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 387 | if (started) { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 388 | run = headers.location; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 389 | log("--- a new run has started at " + short(run) + " ---"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 390 | } |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 391 | if ("x-eq" in headers) { |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 392 | renderStats(headers); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 393 | renderAlerts(r.headers); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 394 | challenged = r.status === 401 || "via" in headers; |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 395 | } |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 396 | if (headers.link) [linked, techniques] = parseLinks(headers.link); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 397 | if (run) { |
139164489032 | atagen | 2026-10-11 05:46 UTC | 398 | const active = run; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 399 | fetch("/ui/send", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ method: "GET", path: run + "/inventory", headers: [], body: "" }) }) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 400 | .then(r => r.json()) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 401 | .then(r => { |
139164489032 | atagen | 2026-10-11 05:46 UTC | 402 | if (run !== active) return; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 403 | try { stocked = (JSON.parse(r.body).items || []).map(i => i.name); render(); } catch (e) {} |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 404 | }); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 405 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 406 | offered = body ? hrefs(body, []) : []; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 407 | if (headers.location && !started) { |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 408 | offered.unshift({ key: "offered", group: "said", name: short(headers.location).split("/").pop() + " (Location)", path: headers.location, methods: ["GET"] }); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 409 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 410 | const abandoned = sentMethod === "DELETE" && sentPath === run && r.status === 204; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 411 | if (run && (abandoned || DEATHS.includes(problem))) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 412 | log("--- the run has ended ---"); |
431c1bc8ded1 | atagen | 2026-10-10 13:20 UTC | 413 | renderAlerts(null); |
4b690ff501b8 | atagen | 2026-10-10 13:27 UTC | 414 | challenged = false; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 415 | run = null; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 416 | linked = []; |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 417 | techniques = []; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 418 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 419 | if (started || !targets().some(t => t.path === target)) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 420 | const home = linked.find(t => t.key === "run"); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 421 | chooseTarget(home || GLOBALS[0]); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 422 | } else { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 423 | render(); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 424 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 425 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 426 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 427 | async function go(event) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 428 | event.preventDefault(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 429 | const t = current(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 430 | const key = t.key + " " + method; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 431 | const value = name => fieldValue(name).trim(); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 432 | const headers = []; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 433 | let path = t.path; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 434 | let body = ""; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 435 | for (const name of fieldNames()) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 436 | const f = FIELDS[name]; |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 437 | if (f.header && value(name)) headers.push([f.header, value(name)]); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 438 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 439 | if (fieldNames().includes("upgrade") && value("upgrade")) headers.push(["Connection", "upgrade"]); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 440 | for (const line of raw("extra").split("\n")) { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 441 | const at = line.indexOf(":"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 442 | if (at > 0) headers.push([line.slice(0, at).trim(), line.slice(at + 1).trim()]); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 443 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 444 | if (key === "run JELQ") { |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 445 | const names = fieldNames(); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 446 | const chosen = [value("technique")].concat(names.includes("also") && value("also") ? [value("also")] : []); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 447 | path += "?technique=" + encodeURIComponent(chosen.join(",")); |
58e1fb3943e6 | atagen | 2026-10-10 13:30 UTC | 448 | if (names.includes("intensity")) path += "&intensity=" + encodeURIComponent(value("intensity")); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 449 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 450 | if (key === "runs POST" && value("seed")) path += "?seed=" + encodeURIComponent(value("seed")); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 451 | if (method === "PATCH") { |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 452 | headers.push(["Content-Type", "application/json-patch+json"]); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 453 | body = value("patch"); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 454 | } |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 455 | |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 456 | const sent = ["> " + method + " " + short(path)].concat(headers.map(([name, v]) => "> " + name + ": " + v)).join("\n"); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 457 | headers.push(["Accept-Language", $("locale").value]); |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 458 | let r; |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 459 | try { |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 460 | const response = await fetch("/ui/send", { |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 461 | method: "POST", |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 462 | headers: { "Content-Type": "application/json" }, |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 463 | body: JSON.stringify({ method, path, headers, body }), |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 464 | }); |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 465 | r = await response.json(); |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 466 | } catch (error) { |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 467 | log(sent + "\n! The server did not answer. Is jelqd still running? (" + error.message + ")"); |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 468 | return; |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 469 | } |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 470 | if (!Array.isArray(r.headers)) { |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 471 | log(sent + "\n! The page could not read that reply:\n " + JSON.stringify(r)); |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 472 | return; |
7288fc269bb6 | atagen | 2026-10-10 13:36 UTC | 473 | } |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 474 | log(sent + "\n" + describe(r)); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 475 | absorb(r, method, path); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 476 | } |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 477 | |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 478 | $("form").onsubmit = go; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 479 | $("locale").value = localStorage.getItem("locale") || "en-GB"; |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 480 | $("locale").onchange = () => localStorage.setItem("locale", $("locale").value); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 481 | renderStats(null); |
185851447ceb | atagen | 2026-10-10 12:58 UTC | 482 | render(); |
585e6b7ce086 | atagen | 2026-10-10 13:43 UTC | 483 | log("Welcome. Choose \"start a new run\" and send POST to begin. The newest turn is always at the top."); |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 484 | </script> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 485 | </body> |
99524cf9f17c | atagen | 2026-10-10 10:53 UTC | 486 | </html> |