Link every item and relic on /Warframe to the wiki

The tables name a part and say where it drops, which is half of what you want to know when you have never heard of it. The other half is the wiki, so every item and relic name now carries a [wiki] link: the item heading, each relic under From relics, and each reward in a relic's contents. The links go through MediaWiki's go search rather than straight to /w/, because the wiki documents the thing and not the piece. There is a Gyre Prime page but no Gyre Prime Neuroptics Blueprint one, and a direct link to the item name is a 404. WikiTitle trims a name back to something likely to be a title - everything up to and including Prime, minus relic, cache and blueprint suffixes, leading amounts and Railjack marks - and the go search covers the rest: an exact title redirects to the page, and anything the trimming gets wrong lands on search results for it. Measured against the live wiki over eighty sampled names, 77 go straight to a page and the other three degrade to a search rather than a dead end. The trimming rules are all things the sample turned up. A bare leading number only counts as an amount in front of Endo or Credits, because stripping it from 3 Day Affinity Booster left Day Affinity Booster, which is worse than the name it started with; N Day comes off separately and leaves Affinity Booster, which is a page. The mark on a Railjack component comes off rather than being spelled properly, since the wiki covers every mark of Lavan Plating on one page and has nothing at Lavan Plating Mk III. Twenty-nine rewards are relics handed out already refined, so the refinement comes off too and Axi S20 Relic (Radiant) reaches Axi S20. Nothing is added to the match list. It already holds up to forty names, and a second link on each row would clutter the densest part of the page for a link you can reach by clicking through. Trailing components are only stripped ahead of Blueprint for the same reason the go search is there at all: Wolf Sledge Head would resolve by taking Head off, but doing that everywhere risks trimming a name that genuinely ends in one, and a confidently wrong page is worse than a search. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

author
Marijn Besseling <njirambem@gmail.com> · 2026-09-14 18:35 UTC
commit
0882c1477a0c0a2a49cbc75c6db18ece5857ec9d
parent
f4327415ca
tree
browse at this commit

4 files changed +122 -2

Blog/Components/Pages/Warframe.razor +9 -2

@@ -44,7 +44,10 @@
44 44
45 45 @if (Item is { } item)
46 46 {
47 - <h2 class="item-name">@item.Name</h2>
47 + <h2 class="item-name">
48 + @item.Name
49 + <a class="wiki" href="@WikiLink(item.Name)" rel="external noreferrer">[wiki]</a>
50 + </h2>
48 51
49 52 @if (item.Sources.Count == 0 && !item.FromRelics)
50 53 {
@@ -64,6 +67,7 @@
64 67 <div class="relic">
65 68 <h3>
66 69 <a href="@ItemLink(line.Relic)">@line.Relic</a>
70 + <a class="wiki" href="@WikiLink(line.Relic)" rel="external noreferrer">[wiki]</a>
67 71 <span class="muted">@line.Rarity</span>
68 72 <span>@Percent(line.Intact) intact → @Percent(line.Radiant) radiant</span>
69 73 </h3>
@@ -149,7 +153,10 @@
149 153 @foreach (var line in item.Contents)
150 154 {
151 155 <tr>
152 - <td><a href="@ItemLink(line.Item)">@line.Item</a></td>
156 + <td>
157 + <a href="@ItemLink(line.Item)">@line.Item</a>
158 + <a class="wiki" href="@WikiLink(line.Item)" rel="external noreferrer">[wiki]</a>
159 + </td>
153 160 <td class="muted">@line.Rarity</td>
154 161 <td class="chance">@Percent(line.Intact)</td>
155 162 <td class="chance">@Percent(line.Radiant)</td>

Blog/Components/Pages/Warframe.razor.cs +89 -0

@@ -1,4 +1,5 @@
1 1 using System.Globalization;
2 +using System.Text.RegularExpressions;
2 3 using Blog.Models;
3 4 using Blog.Services;
4 5 using Microsoft.AspNetCore.Components;
@@ -98,6 +99,94 @@ public partial class Warframe : ComponentBase
98 99 };
99 100
100 101 /// <summary>
102 + /// Components a non-prime item is built from. Only used to trim a part name back to the thing
103 + /// the wiki has a page for, so it needs the common ones rather than all of them.
104 + /// </summary>
105 + private static readonly string[] Components =
106 + [
107 + "Chassis", "Systems", "Neuroptics", "Harness", "Wings", "Barrel", "Receiver", "Stock",
108 + "Blade", "Blades", "Handle", "Hilt", "Grip", "String", "Limb", "Link", "Head", "Guard",
109 + "Ornament", "Boot", "Buckle", "Pouch", "Gauntlet", "Bracket", "Carapace", "Cerebrum",
110 + "Fuselage", "Engines", "Avionics", "Nose Cone", "Wing"
111 + ];
112 +
113 + /// <summary>
114 + /// A leading amount the wiki page's title does not carry: <c>100X Oxium</c>, <c>900 Endo</c>,
115 + /// <c>3 Day Affinity Booster</c>. A bare number only counts as one in front of Endo or Credits,
116 + /// because most of the time it is part of the name.
117 + /// </summary>
118 + [GeneratedRegex(@"^(?:[0-9][0-9,]*X |[0-9][0-9,]* Day |[0-9][0-9,]* (?=Endo|Credits))")]
119 + private static partial Regex AmountPrefix { get; }
120 +
121 + /// <summary>
122 + /// A relic handed out already refined: <c>Axi S20 Relic (Radiant)</c> is a reward in its own
123 + /// right, but the wiki documents the relic, not the state it arrives in.
124 + /// </summary>
125 + [GeneratedRegex(@" \((?:Intact|Exceptional|Flawless|Radiant)\)$")]
126 + private static partial Regex Refinement { get; }
127 +
128 + /// <summary>
129 + /// The mark on a Railjack component. DE write <c>Lavan Plating Mk Ii</c>; the wiki documents
130 + /// every mark of it on one page, at <c>Lavan Plating</c>.
131 + /// </summary>
132 + [GeneratedRegex(@" Mk [IVXivx]+$")]
133 + private static partial Regex ComponentMark { get; }
134 +
135 + /// <summary>
136 + /// The wiki page for an item, through MediaWiki's "go" search rather than a direct link.
137 + /// </summary>
138 + /// <remarks>
139 + /// The wiki documents the thing, not the piece: there is a Gyre Prime page but no
140 + /// "Gyre Prime Neuroptics Blueprint" one, and linking straight to the item name 404s. So the
141 + /// name is trimmed back to what is likely to be a title, and handed to the search rather than
142 + /// to /w/. An exact match redirects to the page, and anything the trimming gets wrong lands on
143 + /// search results for it, which is still an answer. A direct link would be a dead end.
144 + /// </remarks>
145 + private static string WikiLink(string name) =>
146 + "https://wiki.warframe.com/index.php?search=" + Uri.EscapeDataString(WikiTitle(name)) + "&go=Go";
147 +
148 + private static string WikiTitle(string name)
149 + {
150 + var title = name;
151 + title = AmountPrefix.Replace(title, "");
152 + title = Refinement.Replace(title, "");
153 + title = ComponentMark.Replace(title, "").Trim();
154 +
155 + // Every part of a prime lives on the prime's own page, so "Akstiletto Prime Barrel" and
156 + // "Nikana Prime Blueprint" both want everything up to and including the word Prime.
157 + var prime = title.IndexOf(" Prime", StringComparison.Ordinal);
158 + if (prime >= 0) return title[..(prime + " Prime".Length)];
159 +
160 + if (title.EndsWith(" Relic", StringComparison.Ordinal))
161 + {
162 + return title[..^" Relic".Length];
163 + }
164 +
165 + // "2,000 Credits Cache" is the reward; Credits is the page.
166 + if (title.EndsWith(" Cache", StringComparison.Ordinal))
167 + {
168 + return title[..^" Cache".Length];
169 + }
170 +
171 + if (title.EndsWith(" Blueprint", StringComparison.Ordinal))
172 + {
173 + title = title[..^" Blueprint".Length];
174 +
175 + // "Gara Chassis Blueprint" is documented under Gara, but "Stahlta Blueprint" is already
176 + // the title, so only a trailing component comes off.
177 + foreach (var component in Components)
178 + {
179 + if (title.EndsWith(" " + component, StringComparison.Ordinal))
180 + {
181 + return title[..^(component.Length + 1)];
182 + }
183 + }
184 + }
185 +
186 + return title;
187 + }
188 +
189 + /// <summary>
101 190 /// The size of the index. Invariant rather than the request's culture: the sentence around it
102 191 /// is English, and "3.481 items" reads as a different number.
103 192 /// </summary>

Blog/wwwroot/app.css +15 -0

@@ -770,6 +770,21 @@ progress {
770 770 margin-block: var(--s-2) 0;
771 771 }
772 772
773 +/* An offsite link sitting beside a name that already links within the site.
774 + Dimmed and smaller so the two read as different kinds of thing, and it does
775 + not compete with the name it follows. */
776 +.wiki {
777 + font-size: 0.8em;
778 + font-weight: 400;
779 + opacity: 50%;
780 + white-space: nowrap;
781 +}
782 +
783 +.wiki:hover,
784 +.wiki:focus {
785 + opacity: 100%;
786 +}
787 +
773 788 /* Each relic is a heading and its own small table. */
774 789 .relic + .relic {
775 790 margin-block-start: var(--s0);

CLAUDE.md +9 -0

@@ -145,6 +145,15 @@ is keyed on the file's last write time, so `dotnet watch` picks up a refresh wit
145 145
146 146 Configuration lives under `Warframe` (`WarframeOptions`): `FilePath`.
147 147
148 +Every item and relic name carries a `[wiki]` link beside it. It goes through MediaWiki's *go*
149 +search (`wiki.warframe.com/index.php?search=...&go=Go`), **not** a direct `/w/Title` link: the wiki
150 +documents the thing rather than the piece, so there is a `Gyre Prime` page but no
151 +`Gyre Prime Neuroptics Blueprint` one, and a direct link to the item name 404s. `WikiTitle` trims a
152 +name back to a likely title (everything up to `Prime`, minus relic/cache suffixes, amounts, and
153 +Railjack marks) and the go-search does the rest: an exact match redirects to the page, anything the
154 +trimming gets wrong lands on search results for it. That is about 96% direct hits on a sample of the
155 +real names, and the remainder degrade to a search rather than a dead end.
156 +
148 157 The page is server-rendered and driven by the query string (`?q=` search, `?item=` selection), so
149 158 results are linkable and work without JS. `Warframe.razor.js` only fills the search box's
150 159 `<datalist>` from `/api/warframe/names` (mapped in `Program.cs`). It does *not* import