

























@@ -2,7 +2,7 @@ import { z } from "zod";
22import { normalizeOptionalString } from "./string-coerce.ts";
3344const TWEAKCN_HOSTS = new Set(["tweakcn.com", "www.tweakcn.com"]);
5-const THEME_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;
5+const THEME_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
66const CUSTOM_THEME_STYLE_ID = "openclaw-custom-theme";
77const MAX_TWEAKCN_THEME_BYTES = 200_000;
88const MAX_CSS_TOKEN_LENGTH = 240;
@@ -165,7 +165,7 @@ function requireThemeId(value: string) {
165165}
166166}
167167168-function normalizeThemeIdFromPath(pathname: string): string {
168+function normalizeThemeIdFromPath(pathname: string): string | null {
169169const segments = pathname.split("/").filter(Boolean);
170170if (segments.length === 2 && segments[0] === "themes") {
171171requireThemeId(segments[1]);
@@ -175,6 +175,43 @@ function normalizeThemeIdFromPath(pathname: string): string {
175175requireThemeId(segments[2]);
176176return segments[2];
177177}
178+return null;
179+}
180+181+function normalizePastedThemeInput(input: string): string {
182+const normalized = normalizeOptionalString(input);
183+if (!normalized) {
184+throw new Error("Paste a tweakcn theme link to import.");
185+}
186+const inputValue = normalized.replace(/[.,;:]+$/, "");
187+if (THEME_ID_PATTERN.test(inputValue)) {
188+return `https://tweakcn.com/themes/${inputValue}`;
189+}
190+if (inputValue.startsWith("/themes/") || inputValue.startsWith("/r/themes/")) {
191+return `https://tweakcn.com${inputValue}`;
192+}
193+if (/^(?:www\.)?tweakcn\.com\//i.test(inputValue)) {
194+return `https://${inputValue}`;
195+}
196+const embeddedUrl = inputValue
197+.match(/https?:\/\/(?:www\.)?tweakcn\.com\/[^\s<>"')]+/i)?.[0]
198+?.replace(/[.,;:]+$/, "");
199+return embeddedUrl ?? inputValue;
200+}
201+202+function normalizeThemeIdFromUrl(parsed: URL): string {
203+const pathThemeId = normalizeThemeIdFromPath(parsed.pathname);
204+if (pathThemeId) {
205+return pathThemeId;
206+}
207+const queryThemeId =
208+parsed.searchParams.get("theme") ??
209+parsed.searchParams.get("themeId") ??
210+parsed.searchParams.get("id");
211+if (queryThemeId) {
212+requireThemeId(queryThemeId);
213+return queryThemeId;
214+}
178215throw new Error("Unsupported tweakcn link. Expected a theme share URL.");
179216}
180217@@ -384,10 +421,7 @@ function describeThemeLabel(value: string | undefined) {
384421}
385422386423export function normalizeTweakcnThemeUrl(input: string): TweakcnThemeResolution {
387-const normalized = normalizeOptionalString(input);
388-if (!normalized) {
389-throw new Error("Paste a tweakcn theme link to import.");
390-}
424+const normalized = normalizePastedThemeInput(input);
391425let parsed: URL;
392426try {
393427parsed = new URL(normalized);
@@ -397,7 +431,7 @@ export function normalizeTweakcnThemeUrl(input: string): TweakcnThemeResolution
397431if (!TWEAKCN_HOSTS.has(parsed.hostname)) {
398432throw new Error("Only tweakcn.com theme links are supported.");
399433}
400-const themeId = normalizeThemeIdFromPath(parsed.pathname);
434+const themeId = normalizeThemeIdFromUrl(parsed);
401435return {
402436 themeId,
403437sourceUrl: `https://tweakcn.com/themes/${themeId}`,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。