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