an http based roguelike rpg about jelqing
Blame: src/http/links.rs
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 1 | use std::fmt::Write as _; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 2 | |
a11497af08aa | atagen | 2026-10-13 12:04 UTC | 3 | use super::{run::RunResource, runs}; |
a11497af08aa | atagen | 2026-10-13 12:04 UTC | 4 | use crate::game::{Branch, Run, SHOP}; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 5 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 6 | pub const REL_BASE: &str = "https://jelq.dev/rels/"; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 7 | |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 8 | /// One RFC 8288 link. `allow`, `lubricant` and `intensity` are extension attributes (§3), so a client never |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 9 | /// has to guess what a target accepts. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 10 | struct Link<'a> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 11 | href: &'a str, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 12 | rel: &'a str, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 13 | allow: &'a str, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 14 | title: Option<&'a str>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 15 | lubricant: Option<&'a str>, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 16 | intensity: Option<&'a str>, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 17 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 18 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 19 | impl Link<'_> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 20 | fn write(&self, out: &mut String) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 21 | if !out.is_empty() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 22 | out.push_str(", "); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 23 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 24 | let _ = write!(out, "<{}>; rel=\"{REL_BASE}{}\"", self.href, self.rel); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 25 | if let Some(title) = self.title { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 26 | let _ = write!(out, "; title=\"{title}\""); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 27 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 28 | let _ = write!(out, "; allow=\"{}\"", self.allow); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 29 | if let Some(rule) = self.lubricant { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 30 | let _ = write!(out, "; lubricant=\"{rule}\""); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 31 | } |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 32 | if let Some(range) = self.intensity { |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 33 | let _ = write!(out, "; intensity=\"{range}\""); |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 34 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 35 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 36 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 37 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 38 | pub fn header(run: &Run) -> String { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 39 | let reach = run.reach(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 40 | let mut out = String::new(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 41 | let resources: [(&str, &RunResource, bool); 15] = [ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 42 | ("", &runs::RUN, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 43 | ("floor", &runs::FLOOR, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 44 | ("length", &runs::LENGTH, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 45 | ("foreskin", &runs::FORESKIN, reach.foreskin), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 46 | ("inventory", &runs::INVENTORY, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 47 | ("techniques", &runs::TECHNIQUES, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 48 | ("drawer", &runs::DRAWER, reach.drawer), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 49 | ("kitchen", &runs::KITCHEN, reach.kitchen), |
a11497af08aa | atagen | 2026-10-13 12:04 UTC | 50 | ("thread", &runs::THREAD, reach.thread), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 51 | ("grip", &runs::GRIP, true), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 52 | ("shop", &runs::SHOP, reach.shop), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 53 | ("portal", &runs::PORTAL, reach.portal), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 54 | ("elder", &runs::ELDER, reach.elder), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 55 | ("footage", &runs::FOOTAGE, reach.footage), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 56 | ("diagnosis", &runs::DIAGNOSIS, reach.diagnosis), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 57 | ]; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 58 | for (rel, target, _) in resources.iter().filter(|(_, _, relevant)| *relevant) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 59 | let href = if rel.is_empty() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 60 | run.path("") |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 61 | } else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 62 | run.path(&format!("/{rel}")) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 63 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 64 | Link { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 65 | href: &href, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 66 | rel: if rel.is_empty() { "run" } else { rel }, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 67 | allow: &target.resource.allow(run), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 68 | title: None, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 69 | lubricant: None, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 70 | intensity: None, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 71 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 72 | .write(&mut out); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 73 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 74 | if reach.branch_pending { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 75 | for (branch, title) in [ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 76 | (Branch::Forum, "Read the forum"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 77 | (Branch::CloseTab, "Close the tab"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 78 | ] { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | Link { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 80 | href: &run.path(&format!("/branches/{}", branch.slug())), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 81 | rel: "branch", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 82 | allow: "POST", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 83 | title: Some(title), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 84 | lubricant: None, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 85 | intensity: None, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 86 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 87 | .write(&mut out); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 88 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 89 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 90 | let run_path = run.path(""); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 91 | for technique in run.techniques_now() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 92 | let name = technique.name(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 93 | Link { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 94 | href: &format!("{run_path}?technique={name}"), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 95 | rel: "technique", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 96 | allow: "JELQ", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 97 | title: Some(name), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 98 | lubricant: technique.lubricant(), |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 99 | intensity: technique.intensity(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 100 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 101 | .write(&mut out); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 102 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 103 | if reach.shopping { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 104 | for item in SHOP { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 105 | let Some(price) = run.price(item) else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 106 | continue; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 107 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 108 | Link { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 109 | href: &run.path(&format!("/shop/{}", item.slug())), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 110 | rel: "shop-item", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 111 | allow: "POST", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 112 | title: Some(&format!("{} for {price}", item.slug())), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 113 | lubricant: None, |
e028a4184d00 | atagen | 2026-10-14 10:49 UTC | 114 | intensity: None, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 115 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 116 | .write(&mut out); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 117 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 118 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 119 | out |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 120 | } |