108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
/**
|
|
* Normalize an Instagram handle from various input formats to a pure username.
|
|
*
|
|
* Handles these formats:
|
|
* - "https://www.instagram.com/banobagi_ps/" → "banobagi_ps"
|
|
* - "https://instagram.com/banobagi_ps?hl=en" → "banobagi_ps"
|
|
* - "http://instagram.com/banobagi_ps" → "banobagi_ps"
|
|
* - "instagram.com/banobagi_ps" → "banobagi_ps"
|
|
* - "@banobagi_ps" → "banobagi_ps"
|
|
* - "banobagi_ps" → "banobagi_ps"
|
|
* - null / undefined / "" → null
|
|
*/
|
|
export function normalizeInstagramHandle(
|
|
raw: string | null | undefined,
|
|
): string | null {
|
|
if (!raw || typeof raw !== "string") return null;
|
|
|
|
let handle = raw.trim();
|
|
if (!handle) return null;
|
|
|
|
// If it contains "instagram.com", extract the first path segment
|
|
if (handle.includes("instagram.com")) {
|
|
try {
|
|
// Add protocol if missing so URL constructor works
|
|
const urlStr = handle.startsWith("http")
|
|
? handle
|
|
: `https://${handle}`;
|
|
const url = new URL(urlStr);
|
|
// pathname is like "/banobagi_ps/" or "/banobagi_ps"
|
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
handle = segments[0] || "";
|
|
} catch {
|
|
// URL parsing failed — try regex fallback
|
|
const match = handle.match(/instagram\.com\/([^/?#]+)/);
|
|
handle = match?.[1] || "";
|
|
}
|
|
}
|
|
|
|
// Strip leading @
|
|
if (handle.startsWith("@")) {
|
|
handle = handle.slice(1);
|
|
}
|
|
|
|
// Strip trailing slash
|
|
handle = handle.replace(/\/+$/, "");
|
|
|
|
return handle || null;
|
|
}
|
|
|
|
/**
|
|
* Normalize a YouTube channel identifier from various URL formats.
|
|
*
|
|
* Returns an object with the best identifier type for API lookup:
|
|
* - "https://www.youtube.com/@banobagips" → { type: 'handle', value: 'banobagips' }
|
|
* - "https://youtube.com/c/banobagips" → { type: 'username', value: 'banobagips' }
|
|
* - "https://youtube.com/user/banobagi" → { type: 'username', value: 'banobagi' }
|
|
* - "https://youtube.com/channel/UCxxxx" → { type: 'channelId', value: 'UCxxxx' }
|
|
* - "@banobagips" → { type: 'handle', value: 'banobagips' }
|
|
* - "UCxxxx" → { type: 'channelId', value: 'UCxxxx' }
|
|
* - "banobagips" → { type: 'username', value: 'banobagips' }
|
|
*/
|
|
export function normalizeYouTubeChannel(
|
|
raw: string | null | undefined,
|
|
): { type: 'handle' | 'username' | 'channelId'; value: string } | null {
|
|
if (!raw || typeof raw !== "string") return null;
|
|
|
|
let input = raw.trim();
|
|
if (!input) return null;
|
|
|
|
// Parse YouTube URLs
|
|
if (input.includes("youtube.com") || input.includes("youtu.be")) {
|
|
try {
|
|
const urlStr = input.startsWith("http") ? input : `https://${input}`;
|
|
const url = new URL(urlStr);
|
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
|
|
if (segments[0] === "channel" && segments[1]?.startsWith("UC")) {
|
|
return { type: "channelId", value: segments[1] };
|
|
}
|
|
if (segments[0] === "c" && segments[1]) {
|
|
return { type: "username", value: segments[1] };
|
|
}
|
|
if (segments[0] === "user" && segments[1]) {
|
|
return { type: "username", value: segments[1] };
|
|
}
|
|
if (segments[0]?.startsWith("@")) {
|
|
return { type: "handle", value: segments[0].slice(1) };
|
|
}
|
|
// Fallback: first path segment
|
|
if (segments[0]) {
|
|
return { type: "username", value: segments[0] };
|
|
}
|
|
} catch {
|
|
// URL parsing failed
|
|
}
|
|
}
|
|
|
|
// Non-URL formats
|
|
if (input.startsWith("@")) {
|
|
return { type: "handle", value: input.slice(1) };
|
|
}
|
|
if (input.startsWith("UC") && input.length > 20) {
|
|
return { type: "channelId", value: input };
|
|
}
|
|
|
|
return { type: "username", value: input };
|
|
}
|