import { tryParseJson } from "@oh-my-pi/pi-utils";
import type { RenderResult, SpecialHandler } from "./types";
import { buildResult, formatIsoDate, formatNumber, loadPage } from "./types";
interface NuGetODataEntry {
Id: string;
Version: string;
Title?: string;
Description?: string;
Summary?: string;
Authors?: string;
ProjectUrl?: string;
PackageSourceUrl?: string;
Tags?: string;
DownloadCount?: number;
VersionDownloadCount?: number;
Published?: string;
LicenseUrl?: string;
ReleaseNotes?: string;
Dependencies?: string;
}
interface NuGetODataResponse {
d?: {
results?: NuGetODataEntry[];
};
}
function extractXmlField(xml: string, fieldName: string): string | null {
const pattern = new RegExp(`]*>([\\s\\S]*?)`, "i");
const match = xml.match(pattern);
if (!match) return null;
return match[1].trim();
}
/**
* Handle Chocolatey package URLs via NuGet v2 OData API
*/
export const handleChocolatey: SpecialHandler = async (
url: string,
timeout: number,
signal?: AbortSignal,
): Promise => {
try {
const parsed = new URL(url);
if (!parsed.hostname.includes("chocolatey.org")) return null;
// Extract package name from /packages/{name} or /packages/{name}/{version}
const match = parsed.pathname.match(/^\/packages\/([^/]+)(?:\/([^/]+))?/);
if (!match) return null;
const packageName = decodeURIComponent(match[1]);
const specificVersion = match[2] ? decodeURIComponent(match[2]) : null;
const fetchedAt = new Date().toISOString();
// Build OData query - filter by Id and optionally version
let apiUrl = `https://community.chocolatey.org/api/v2/Packages()?$filter=Id%20eq%20'${encodeURIComponent(packageName)}'`;
if (specificVersion) {
apiUrl += `%20and%20Version%20eq%20'${encodeURIComponent(specificVersion)}'`;
} else {
// Get latest version by ordering and taking first
apiUrl += "&$orderby=Version%20desc&$top=1";
}
const result = await loadPage(apiUrl, {
timeout,
signal,
headers: {
Accept: "application/atom+xml, application/xml",
},
});
if (!result.ok) {
const fallback = `# ${packageName}\n\nChocolatey package metadata is currently unavailable.\n\n---\n**Install:** \`choco install ${packageName}\`\n`;
return buildResult(fallback, {
url,
method: "chocolatey",
fetchedAt,
notes: ["Chocolatey API request failed"],
});
}
let pkg = (() => {
const data = tryParseJson(result.content);
return data?.d?.results?.[0] ?? null;
})();
if (!pkg) {
const xmlId = extractXmlField(result.content, "Id");
if (!xmlId) {
const fallback = `# ${packageName}\n\nChocolatey package metadata could not be parsed.\n\n---\n**Install:** \`choco install ${packageName}\`\n`;
return buildResult(fallback, {
url,
method: "chocolatey",
fetchedAt,
notes: ["Chocolatey API response parsing failed"],
});
}
pkg = {
Id: xmlId,
Version: extractXmlField(result.content, "Version") || "",
Title: extractXmlField(result.content, "Title") || undefined,
Description: extractXmlField(result.content, "Description") || undefined,
Summary: extractXmlField(result.content, "Summary") || undefined,
Authors: extractXmlField(result.content, "Authors") || undefined,
ProjectUrl: extractXmlField(result.content, "ProjectUrl") || undefined,
PackageSourceUrl: extractXmlField(result.content, "PackageSourceUrl") || undefined,
Tags: extractXmlField(result.content, "Tags") || undefined,
DownloadCount: (() => {
const value = extractXmlField(result.content, "DownloadCount");
return value ? Number.parseInt(value, 10) : undefined;
})(),
VersionDownloadCount: (() => {
const value = extractXmlField(result.content, "VersionDownloadCount");
return value ? Number.parseInt(value, 10) : undefined;
})(),
Published: extractXmlField(result.content, "Published") || undefined,
LicenseUrl: extractXmlField(result.content, "LicenseUrl") || undefined,
ReleaseNotes: extractXmlField(result.content, "ReleaseNotes") || undefined,
Dependencies: extractXmlField(result.content, "Dependencies") || undefined,
};
}
// Build markdown output
let md = `# ${pkg.Title || pkg.Id}\n\n`;
if (pkg.Summary) {
md += `${pkg.Summary}\n\n`;
} else if (pkg.Description) {
// Use first paragraph of description as summary
const firstPara = pkg.Description.split(/\n\n/)[0];
md += `${firstPara}\n\n`;
}
md += `**Version:** ${pkg.Version}`;
if (pkg.Authors) md += ` · **Authors:** ${pkg.Authors}`;
md += "\n";
if (pkg.DownloadCount !== undefined) {
md += `**Total Downloads:** ${formatNumber(pkg.DownloadCount)}`;
if (pkg.VersionDownloadCount !== undefined) {
md += ` · **Version Downloads:** ${formatNumber(pkg.VersionDownloadCount)}`;
}
md += "\n";
}
if (pkg.Published) {
const published = formatIsoDate(pkg.Published);
if (published) md += `**Published:** ${published}\n`;
}
md += "\n";
if (pkg.ProjectUrl) md += `**Project URL:** ${pkg.ProjectUrl}\n`;
if (pkg.PackageSourceUrl) md += `**Source:** ${pkg.PackageSourceUrl}\n`;
if (pkg.LicenseUrl) md += `**License:** ${pkg.LicenseUrl}\n`;
if (pkg.Tags) {
const tags = pkg.Tags.split(/\s+/).filter(t => t.length > 0);
if (tags.length > 0) {
md += `**Tags:** ${tags.join(", ")}\n`;
}
}
// Full description if different from summary
if (pkg.Description && pkg.Description !== pkg.Summary) {
md += `\n## Description\n\n${pkg.Description}\n`;
}
if (pkg.ReleaseNotes) {
md += `\n## Release Notes\n\n${pkg.ReleaseNotes}\n`;
}
if (pkg.Dependencies) {
// Dependencies format: "id:version|id:version"
const deps = pkg.Dependencies.split("|").filter(d => d.trim().length > 0);
if (deps.length > 0) {
md += `\n## Dependencies\n\n`;
for (const dep of deps) {
const [depId, depVersion] = dep.split(":");
if (depId) {
md += `- ${depId}${depVersion ? `: ${depVersion}` : ""}\n`;
}
}
}
}
md += `\n---\n**Install:** \`choco install ${packageName}\`\n`;
return buildResult(md, { url, method: "chocolatey", fetchedAt, notes: ["Fetched via Chocolatey NuGet API"] });
} catch {}
return null;
};