o2o-infinith-demo/supabase/functions/_shared/normalizeHandles.ts

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 };
}