ব্লগ / ডেভেলপার গাইড
সোশ্যাল মিডিয়া প্রোফাইল URL কীভাবে পার্স ও নরমালাইজ করবেন (Instagram, TikTok, YouTube, X)
ক্রিয়েটররা প্রোফাইল লিংক পেস্ট করেন যত রকমে ভাবা যায়: www সহ বা ছাড়া, মোবাইল অ্যাপ থেকে ট্র্যাকিং প্যারামিটারসহ, শর্ট লিংক হিসেবে, পুরোনো twitter.com URL হিসেবে, বা প্রোফাইলের বদলে একটি পোস্টের লিংক হিসেবে। আপনার প্রোডাক্ট যদি এই স্ট্রিংগুলো যেমন আসে তেমনই সংরক্ষণ করে, তাহলে একই ক্রিয়েটর তিনবার দেখা যায় আর আপনার join-গুলো চুপচাপ মিস করে। এই গাইডে দেখানো হয়েছে কীভাবে যেকোনো Instagram, TikTok, YouTube বা X প্রোফাইল URL-কে একটি প্ল্যাটফর্ম, একটি হ্যান্ডেল বা চ্যানেল ID আর একটি canonical URL-এ পরিণত করবেন, JavaScript ও Python-এ পরীক্ষিত কোডসহ।
প্রোফাইল URL নরমালাইজ করা কেন জরুরি
প্রোফাইল URL একটি আইডেন্টিফায়ার, আর আইডেন্টিফায়ার তখনই কাজ করে যখন সেটি একভাবেই লেখা হয়। https://www.instagram.com/natgeo/, instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== আর https://m.instagram.com/natgeo সবগুলো একই অ্যাকাউন্ট নির্দেশ করে, কিন্তু স্ট্রিং হিসেবে এরা তিনটি আলাদা মান।
- ডুপ্লিকেট সরানো। একজন ক্রিয়েটর দুবার সাইন আপ করলে, বা দুজন টিম সদস্য একই ইনফ্লুয়েন্সারকে ক্যাম্পেইনে যোগ করলে, আপনি দুটি নয়, একটি রেকর্ড চান।
- Join। অন্য টুলের এক্সপোর্টের সঙ্গে আপনার CRM মেলানো তখনই কাজ করে যখন দুই দিকে একই কি ব্যবহার হয়।
- খারাপ ইনপুট শুরুতেই বাতিল। একটি রিলের লিংক, সার্চ পেজ বা লগইন স্ক্রিন প্রোফাইল নয়। ইনপুটের সময়েই এটি ধরা পরে পরিষ্কার করার চেয়ে সস্তা।
- API কল। অফিশিয়াল API-গুলো হ্যান্ডেল বা ID চায়, ট্র্যাকিং প্যারামিটার জোড়া URL নয়।
লক্ষ্য হলো একটি ছোট, কড়া ফাংশন: ইউজাররা আসলে যা পেস্ট করেন তা নেয়, আসল প্রোফাইল URL-এর জন্য নরমালাইজ করা ফলাফল দেয়, আর বাকি সবকিছুর জন্য null দেয়।
যে URL রূপগুলো আপনি দেখবেন
নিচের রূপগুলো ২০২৬ সালের অক্টোবরে লাইভ সাইটে যাচাই করা হয়েছে। প্ল্যাটফর্মগুলো আগাম না জানিয়ে রাউটিং বদলায়, তাই এটিকে একটি মুহূর্তের ছবি হিসেবে দেখুন আর আপনার টেস্ট কেসগুলো হালনাগাদ রাখুন।
- প্রোফাইল থাকে
https://www.instagram.com/<handle>/-এ। শুধুinstagram.comআরm.instagram.comহোস্টwww.instagram.com-এ রিডাইরেক্ট করে। - অ্যাপ থেকে শেয়ার করা লিংকে প্রায়ই একটি
?igsh=প্যারামিটার থাকে। পুরো কোয়েরি স্ট্রিং আর ফ্র্যাগমেন্ট বাদ দিন। - পোস্ট হলো
/p/<code>/, রিল/reel/<code>/, আর স্টোরি/stories/<handle>/<id>/। পাথে হ্যান্ডেল থাকলেও এগুলোর কোনোটিই প্রোফাইল URL নয়। /explore/,/accounts/আর/direct/-এর মতো অ্যাপ রাউট হ্যান্ডেলের সঙ্গে প্রথম পাথ সেগমেন্ট ভাগ করে, তাই সংরক্ষিত শব্দের একটি তালিকা লাগবে। নিচের কোডের তালিকাটি সম্পূর্ণ নয়।
TikTok
- প্রোফাইল হলো
https://www.tiktok.com/@<handle>। শুধুtiktok.comহোস্টwww-এ রিডাইরেক্ট করে। - ভিডিও হলো
/@<handle>/video/<id>। হ্যান্ডেলটি লেখকের, কিন্তু URL-টি একটি ভিডিও লিংক, তাই নিচের পার্সার এটি বাতিল করে। vm.tiktok.com,vt.tiktok.comআরwww.tiktok.com/t/<code>/-এর শর্ট লিংকে কোনো হ্যান্ডেলই থাকে না। কোথায় নিয়ে যায় দেখতে HTTP রিডাইরেক্ট অনুসরণ করতে হবে, তারপর শেষ URL-টি পার্স করুন।@ছাড়া একটি পাথ (যেমনtiktok.com/khaby.lame) প্রোফাইল URL নয়।
YouTube
- হ্যান্ডেল URL:
https://www.youtube.com/@<handle>। YouTube-এর Help Center বলে হ্যান্ডেল স্বয়ংক্রিয়ভাবে চ্যানেলের URL হয়ে যায়। - চ্যানেল ID URL:
https://www.youtube.com/channel/UC...। এটিই স্থির রূপ। বাস্তবে চ্যানেল ID ২৪ অক্ষরের আরUCদিয়ে শুরু হয়। Google এই ফরম্যাট ডকুমেন্ট করে না, তাই নিচের যাচাইটি একটি যৌক্তিক সীমা, নিয়ম নয়। - পুরোনো রূপ:
/user/<username>আর/c/<customname>। দুটোই আজও কাজ করে, আর Help Center বলে পুরোনো ব্যক্তিগত URL কাজ করতে থাকবে। কোনোটিই খোঁজ ছাড়া চ্যানেল ID বলে না। - শুধু
youtube.com/<name>-ও কিছু চ্যানেলের জন্য এখনো কাজ করে, কিন্তু এটি সাইটের রাউটের সঙ্গে একই নেমস্পেস ভাগ করে, তাই নিচের পার্সার এটি গ্রহণ করে না। /@handle/videosআর/channel/UC.../featured-এর মতো ট্যাব চ্যানেলেরই অংশ, এগুলো গ্রহণ করা নিরাপদ।m.youtube.comরিডাইরেক্ট করেwww.youtube.com-এ। ভিডিও লিংক (/watch?v=,/shorts/,youtu.be/<id>) চ্যানেল নয়।
X (আগে Twitter)
- প্রোফাইল হলো
https://x.com/<handle>।twitter.com,www.twitter.com,www.x.com,mobile.twitter.comআরmobile.x.comসবগুলোx.comবাtwitter.com-এর একটি প্রোফাইলে রিডাইরেক্ট করে, তাই এগুলোকে একটি প্ল্যাটফর্ম হিসেবে ধরুন। - পোস্ট হলো
/<handle>/status/<id>। TikTok ভিডিওর মতো, এগুলো লেখকের নাম দেয় কিন্তু প্রোফাইল URL নয়। /home,/explore,/search,/settings,/notifications,/messagesআর/i/-এর নিচের যেকোনো কিছুর মতো অ্যাপ রাউট হ্যান্ডেল নয়।- শেয়ার করা লিংকে প্রায়ই
?s=আর?t=প্যারামিটার যোগ হয়। এগুলো বাদ দিন।
প্ল্যাটফর্মভেদে হ্যান্ডেলের অক্ষরের নিয়ম
হ্যান্ডেল যাচাই করলে টাইপো আর বেশিরভাগ অ-প্রোফাইল পাথ ধরা পড়ে। প্ল্যাটফর্ম যেখানে নিজের নিয়ম প্রকাশ করে সেখানে সেটি ব্যবহার করুন, আর যেখানে করে না সেখানে যাচাই ঢিলে রাখুন, যাতে কোনো আসল অ্যাকাউন্ট কখনো বাতিল না হয়।
| প্ল্যাটফর্ম | অনুমোদিত অক্ষর | দৈর্ঘ্য | বড়-ছোট হাতের অক্ষর | উৎস |
|---|---|---|---|---|
| অক্ষর, সংখ্যা, পিরিয়ড, আন্ডারস্কোর | ৩০ বহুল উল্লিখিত; Instagram-এর হেল্প পেজে পাওয়া যায়নি | কেস-ইনসেনসিটিভ ধরুন; ছোট হাতের অক্ষরে সংরক্ষণ করুন | Instagram Help Center পিরিয়ড, সংখ্যা আর আন্ডারস্কোরের উল্লেখ করে | |
| TikTok | অক্ষর, সংখ্যা, আন্ডারস্কোর, পিরিয়ড; শেষে পিরিয়ড থাকতে পারবে না | ২ থেকে ২৪ | URL-এ কেস-ইনসেনসিটিভ (@TikTok থেকে tiktok আসে) | TikTok Support, ইউজারনেম বদলানো |
| YouTube | সমর্থিত ভাষার অক্ষর বা সংখ্যা, সঙ্গে আন্ডারস্কোর, হাইফেন, পিরিয়ড আর ল্যাটিন মিডল ডট (প্রথমে বা শেষে নয়) | বেশিরভাগ লিপিতে ৩ থেকে ৩০; Han, Hangul আর আরও কিছু লিপিতে ছোট সীমা | কেস-সেনসিটিভ নয় | YouTube Help, হ্যান্ডেলের নির্দেশিকা |
| X | অক্ষর, সংখ্যা আর আন্ডারস্কোর | সর্বোচ্চ ১৫; নতুন ইউজারনেম ৪-এর বেশি হতে হবে, তবে পুরোনো ছোট হ্যান্ডেলও আছে | কেস-সেনসিটিভ নয় (x.com/JACK থেকে canonical হয় x.com/jack) | X Help Center, ইউজারনেমের নিয়ম |
একটি নরমালাইজেশন অ্যালগরিদম
- Step 1ছেঁটে ফেলুন আর স্কিম যোগ করুন
স্পেস আর অপ্রয়োজনীয় অ্যাঙ্গেল ব্র্যাকেট সরান। ইনপুটে স্কিম না থাকলে শুরুতে
https://জুড়ে দিন, যাতেinstagram.com/natgeoপার্স হয়। - Step 2একটি আসল URL পার্সার দিয়ে পার্স করুন
JavaScript-এ
new URL()বা Python-এurllib.parse.urlsplitব্যবহার করুন, পুরো স্ট্রিংয়ের ওপর regex কখনো নয়। পার্সার পোর্ট, ইউজার ইনফো আর এনকোড করা অক্ষর সামলায়, আরhttps://instagram.com@evil.com/natgeo-কে Instagram হিসেবে পার হতে দেয় না। - Step 3হোস্ট নরমালাইজ করুন
ছোট হাতের অক্ষরে আনুন আর শুরুর একটি
www.,m.বাmobile.সরান। তারপর হোস্টগুলোর একটি হুবহু অনুমোদিত তালিকায় খুঁজুন। কখনোincludes("instagram.com")ব্যবহার করবেন না, যাinstagram.com.evil.com-কেও মেলায়। - Step 4কোয়েরি স্ট্রিং আর ফ্র্যাগমেন্ট বাদ দিন
igsh,s,tআরlang-এর মতো ট্র্যাকিং প্যারামিটার কখনো বদলায় না যে URL কোন প্রোফাইল নির্দেশ করে। - Step 5পাথ ভাগ আর ডিকোড করুন
/দিয়ে ভাগ করুন, ফাঁকা সেগমেন্ট বাদ দিন (এতে শেষের স্ল্যাশ সরে যায়) আর প্রতিটি পার্সেন্ট-ডিকোড করুন। ভুল এনকোডিংকে অবৈধ ইনপুট হিসেবে ধরুন। - Step 6প্ল্যাটফর্ম অনুযায়ী রাউট করুন
প্রতিটি প্ল্যাটফর্মের রূপ প্রয়োগ করুন: Instagram আর X-এ একটি সেগমেন্ট, TikTok-এ একটি
@সেগমেন্ট, আর YouTube-এ@handle,channel/UC...,user/nameবাc/name। সংরক্ষিত শব্দ বাতিল করুন। - Step 7হ্যান্ডেল যাচাই করে ছোট হাতের অক্ষরে আনুন
প্ল্যাটফর্মের অক্ষরের নিয়মের সঙ্গে হ্যান্ডেল মিলিয়ে দেখুন, তারপর ছোট হাতের অক্ষরে আনুন। YouTube চ্যানেল ID ছোট হাতের অক্ষরে আনবেন না: এগুলো কেস-সেনসিটিভ।
- Step 8canonical URL বানান
একটি নির্দিষ্ট হোস্টে অংশগুলো থেকে URL আবার বানান, যাতে প্রতিটি সমতুল্য ইনপুট ঠিক একই স্ট্রিং দেয়।
- Step 9শর্ট লিংক আলাদাভাবে সামলান
TikTok শর্ট লিংক শনাক্ত করুন, একটি HTTP রিকোয়েস্ট দিয়ে রিডাইরেক্ট অনুসরণ করুন, আর শেষ URL আবার পার্সারে দিন। এটি বিশুদ্ধ ফাংশনের বাইরে রাখুন, যাতে পার্সিং দ্রুত আর পরীক্ষাযোগ্য থাকে।
JavaScript ও Python-এ কোড
দুটি ফাংশনই একই ইনপুটে একই অবজেক্ট ফেরত দেয়: হ্যান্ডেল URL-এর জন্য { platform, handle, canonicalUrl }, YouTube চ্যানেল ID-র জন্য { platform, channelId, canonicalUrl }, পুরোনো দুই YouTube রূপের জন্য username বা customName, আর প্রোফাইল URL নয় এমন যেকোনো কিছুর জন্য null। কোনোটিরই কোনো ডিপেনডেন্সি লাগে না।
JavaScript (Node 18+)
const HOSTS = {
"instagram.com": "instagram",
"tiktok.com": "tiktok",
"youtube.com": "youtube",
"x.com": "x",
"twitter.com": "x",
};
const IG_RESERVED = new Set(["p", "reel", "reels", "tv", "stories", "explore", "accounts", "direct"]);
const X_RESERVED = new Set([
"home", "explore", "search", "i", "settings", "notifications", "messages",
"compose", "intent", "hashtag", "login", "signup", "logout", "tos", "privacy",
]);
const IG_HANDLE = /^[a-z0-9._]{1,30}$/;
const TIKTOK_HANDLE = /^[a-z0-9._]{2,24}$/;
const X_HANDLE = /^[a-z0-9_]{1,15}$/;
const YT_HANDLE = /^[\p{L}\p{M}\p{N}._\-·]{1,30}$/u;
const YT_CHANNEL_ID = /^UC[A-Za-z0-9_-]{22}$/;
const YT_LEGACY_NAME = /^[A-Za-z0-9._-]{1,100}$/;
export function parseProfileUrl(input) {
if (typeof input !== "string") return null;
let raw = input.trim().replace(/^<|>$/g, "");
if (!raw) return null;
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) raw = "https://" + raw;
let url;
try {
url = new URL(raw);
} catch {
return null;
}
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
const host = url.hostname.toLowerCase().replace(/^(www|m|mobile)\./, "");
const platform = HOSTS[host];
if (!platform) return null;
let segments;
try {
segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
} catch {
return null; // malformed percent-encoding
}
const [first = "", second] = segments;
if (platform === "instagram") {
const handle = first.toLowerCase();
if (segments.length !== 1 || IG_RESERVED.has(handle) || !IG_HANDLE.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://www.instagram.com/${handle}/` };
}
if (platform === "tiktok") {
if (segments.length !== 1 || !first.startsWith("@")) return null;
const handle = first.slice(1).toLowerCase();
if (!TIKTOK_HANDLE.test(handle) || handle.endsWith(".")) return null;
return { platform, handle, canonicalUrl: `https://www.tiktok.com/@${handle}` };
}
if (platform === "x") {
const handle = first.toLowerCase();
if (segments.length !== 1 || X_RESERVED.has(handle) || !X_HANDLE.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://x.com/${handle}` };
}
// YouTube: /@handle[/tab], /channel/UC...[/tab], /user/name, /c/name
if (first.startsWith("@")) {
const handle = first.slice(1).toLowerCase();
if (!YT_HANDLE.test(handle) || /^[._\-·]|[._\-·]$/.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://www.youtube.com/@${encodeURIComponent(handle)}` };
}
if (first === "channel" && second && YT_CHANNEL_ID.test(second)) {
return { platform, channelId: second, canonicalUrl: `https://www.youtube.com/channel/${second}` };
}
if ((first === "user" || first === "c") && second && segments.length === 2 && YT_LEGACY_NAME.test(second)) {
const key = first === "user" ? "username" : "customName";
return { platform, [key]: second, canonicalUrl: `https://www.youtube.com/${first}/${second}` };
}
return null; // videos, shorts, playlists, search and anything else
}
const SHORT_HOSTS = new Set(["vm.tiktok.com", "vt.tiktok.com"]);
export function isShortLink(input) {
try {
const url = new URL(/^https?:\/\//i.test(input) ? input : "https://" + input);
const host = url.hostname.toLowerCase();
return SHORT_HOSTS.has(host) || (host.replace(/^www\./, "") === "tiktok.com" && url.pathname.startsWith("/t/"));
} catch {
return false;
}
}
export async function resolveShortLink(input) {
const res = await fetch(/^https?:\/\//i.test(input) ? input : "https://" + input, {
redirect: "follow",
signal: AbortSignal.timeout(10_000),
});
await res.body?.cancel();
return res.url; // the final URL after every redirect
}resolveShortLink বিল্ট-ইন fetch ব্যবহার করে, যা রিডাইরেক্ট অনুসরণ করে আর শেষ ঠিকানাটি res.url হিসেবে দেয়। isShortLink(link) true হলে parseProfileUrl(await resolveShortLink(link)) কল করুন। শর্ট লিংক প্রোফাইলের বদলে একটি ভিডিওতেও নিয়ে যেতে পারে, সেক্ষেত্রে পার্সার ঠিকভাবেই null ফেরত দেয়।
Python 3
import re
import unicodedata
import urllib.request
from urllib.parse import quote, unquote, urlsplit
HOSTS = {
"instagram.com": "instagram",
"tiktok.com": "tiktok",
"youtube.com": "youtube",
"x.com": "x",
"twitter.com": "x",
}
IG_RESERVED = {"p", "reel", "reels", "tv", "stories", "explore", "accounts", "direct"}
X_RESERVED = {
"home", "explore", "search", "i", "settings", "notifications", "messages",
"compose", "intent", "hashtag", "login", "signup", "logout", "tos", "privacy",
}
IG_HANDLE = re.compile(r"[a-z0-9._]{1,30}")
TIKTOK_HANDLE = re.compile(r"[a-z0-9._]{2,24}")
X_HANDLE = re.compile(r"[a-z0-9_]{1,15}")
YT_CHANNEL_ID = re.compile(r"UC[A-Za-z0-9_-]{22}")
YT_LEGACY_NAME = re.compile(r"[A-Za-z0-9._-]{1,100}")
YT_PUNCT = "._-·"
def _is_youtube_handle(handle):
if not 1 <= len(handle) <= 30 or handle[0] in YT_PUNCT or handle[-1] in YT_PUNCT:
return False
return all(
ch.isalnum() or ch in YT_PUNCT or unicodedata.category(ch).startswith("M")
for ch in handle
)
def parse_profile_url(value):
if not isinstance(value, str):
return None
raw = value.strip().strip("<>")
if not raw:
return None
if not re.match(r"^[a-z][a-z0-9+.-]*://", raw, re.I):
raw = "https://" + raw
try:
url = urlsplit(raw)
host = (url.hostname or "").lower()
except ValueError:
return None
if url.scheme.lower() not in ("http", "https"):
return None
host = re.sub(r"^(www|m|mobile)\.", "", host)
platform = HOSTS.get(host)
if not platform:
return None
try:
segments = [unquote(s, errors="strict") for s in url.path.split("/") if s]
except UnicodeDecodeError:
return None # malformed percent-encoding
first = segments[0] if segments else ""
second = segments[1] if len(segments) > 1 else None
if platform == "instagram":
handle = first.lower()
if len(segments) != 1 or handle in IG_RESERVED or not IG_HANDLE.fullmatch(handle):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.instagram.com/{handle}/"}
if platform == "tiktok":
if len(segments) != 1 or not first.startswith("@"):
return None
handle = first[1:].lower()
if not TIKTOK_HANDLE.fullmatch(handle) or handle.endswith("."):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.tiktok.com/@{handle}"}
if platform == "x":
handle = first.lower()
if len(segments) != 1 or handle in X_RESERVED or not X_HANDLE.fullmatch(handle):
return None
return {"platform": platform, "handle": handle, "canonicalUrl": f"https://x.com/{handle}"}
# YouTube: /@handle[/tab], /channel/UC...[/tab], /user/name, /c/name
if first.startswith("@"):
handle = first[1:].lower()
if not _is_youtube_handle(handle):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.youtube.com/@{quote(handle, safe='._-')}"}
if first == "channel" and second and YT_CHANNEL_ID.fullmatch(second):
return {"platform": platform, "channelId": second,
"canonicalUrl": f"https://www.youtube.com/channel/{second}"}
if first in ("user", "c") and second and len(segments) == 2 and YT_LEGACY_NAME.fullmatch(second):
key = "username" if first == "user" else "customName"
return {"platform": platform, key: second,
"canonicalUrl": f"https://www.youtube.com/{first}/{second}"}
return None # videos, shorts, playlists, search and anything else
SHORT_HOSTS = {"vm.tiktok.com", "vt.tiktok.com"}
def is_short_link(value):
url = urlsplit(value if re.match(r"^https?://", value, re.I) else "https://" + value)
host = (url.hostname or "").lower()
return host in SHORT_HOSTS or (
re.sub(r"^www\.", "", host) == "tiktok.com" and url.path.startswith("/t/")
)
def resolve_short_link(value, timeout=10):
if not re.match(r"^https?://", value, re.I):
value = "https://" + value
request = urllib.request.Request(value, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.geturl() # the final URL after every redirectPython-এর \w আর str.isalnum() Unicode অক্ষর আর সংখ্যা ধরে, কিন্তু দেবনাগরীর মতো লিপিতে ব্যবহৃত combining mark ধরে না, তাই YouTube যাচাইটি Unicode M ক্যাটাগরির অক্ষরও গ্রহণ করে।
এজ কেস আর টেস্ট
নিচের প্রতিটি সারি টেস্ট স্যুটের অংশ, যা দুটি ইমপ্লিমেন্টেশনই পাস করে।
| ইনপুট | আউটপুট | কেন |
|---|---|---|
instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== | instagram, natgeo, https://www.instagram.com/natgeo/ | স্কিম নেই, মিশ্র কেস, শেয়ার প্যারামিটার |
https://m.instagram.com/nat.geo_/#bio | instagram, nat.geo_ | মোবাইল হোস্ট আর ফ্র্যাগমেন্ট |
https://www.instagram.com/p/C9xYzAbCdEf/ | null | একটি পোস্ট, প্রোফাইল নয় |
https://www.instagram.com/stories/natgeo/1234567890/ | null | একটি স্টোরি, যদিও পাথে হ্যান্ডেল আছে |
tiktok.com/@Khaby.Lame/ | tiktok, khaby.lame, https://www.tiktok.com/@khaby.lame | শেষের স্ল্যাশ আর কেস |
https://www.tiktok.com/@khaby.lame/video/7106594312292453675 | null | একটি ভিডিও URL |
https://vm.tiktok.com/ZMabc123/ | null (আগে রিডাইরেক্ট অনুসরণ করুন) | শর্ট লিংক, এতে কোনো হ্যান্ডেল নেই |
https://m.youtube.com/@GoogleDevelopers/videos?app=desktop | youtube, googledevelopers | মোবাইল হোস্ট, চ্যানেল ট্যাব, কোয়েরি |
https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured | youtube, channelId UC_x5XG1OV2P6uZZ5FSM9Ttw | চ্যানেল ID তার কেস ধরে রাখে |
https://www.youtube.com/c/GoogleDevelopers | youtube, customName GoogleDevelopers | পুরোনো কাস্টম URL; ID-র জন্য খোঁজ লাগবে |
https://www.youtube.com/@%E3%83%86%E3%82%B9%E3%83%88 | youtube, হ্যান্ডেল テスト | পার্সেন্ট-এনকোড করা Unicode হ্যান্ডেল |
https://youtu.be/dQw4w9WgXcQ | null | একটি ভিডিও, চ্যানেল নয় |
https://mobile.twitter.com/jack?s=21&t=abc | x, jack, https://x.com/jack | পুরোনো ডোমেইন আর শেয়ার প্যারামিটার |
https://x.com/i/flow/login | null | অ্যাপ রাউট |
https://x.com/this_handle_is_too_long | null | ১৫ অক্ষরের বেশি |
https://instagram.com.evil.com/natgeo | null | দেখতে একই রকম হোস্ট |
https://instagram.com@evil.com/natgeo | null | ইউজার ইনফোর কৌশল; আসল হোস্ট evil.com |
@natgeo | null | শুধু হ্যান্ডেলের কোনো প্ল্যাটফর্ম নেই |
কেসগুলো আপনার রিপোজিটরিতে রাখুন আর CI-তে চালান। কোনো প্ল্যাটফর্ম নতুন রাউট বা শেয়ার প্যারামিটার যোগ করলে আপনি একটি সারি যোগ করবেন, আর সমাধান স্পষ্ট হয়ে যাবে।
import assert from "node:assert/strict";
import { parseProfileUrl } from "./parse.mjs";
const cases = [
["instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA==", { platform: "instagram", handle: "natgeo", canonicalUrl: "https://www.instagram.com/natgeo/" }],
["https://www.instagram.com/reel/C9xYzAbCdEf/", null],
["https://www.instagram.com/explore/", null],
["tiktok.com/@Khaby.Lame/", { platform: "tiktok", handle: "khaby.lame", canonicalUrl: "https://www.tiktok.com/@khaby.lame" }],
["https://www.tiktok.com/@khaby.lame/video/7106594312292453675", null],
["https://vm.tiktok.com/ZMabc123/", null],
["https://m.youtube.com/@GoogleDevelopers/videos", { platform: "youtube", handle: "googledevelopers", canonicalUrl: "https://www.youtube.com/@googledevelopers" }],
["https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured", { platform: "youtube", channelId: "UC_x5XG1OV2P6uZZ5FSM9Ttw", canonicalUrl: "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw" }],
["https://www.youtube.com/user/GoogleDevelopers", { platform: "youtube", username: "GoogleDevelopers", canonicalUrl: "https://www.youtube.com/user/GoogleDevelopers" }],
["https://youtu.be/dQw4w9WgXcQ", null],
["https://twitter.com/Jack/", { platform: "x", handle: "jack", canonicalUrl: "https://x.com/jack" }],
["https://mobile.twitter.com/jack?s=21", { platform: "x", handle: "jack", canonicalUrl: "https://x.com/jack" }],
["https://x.com/jack/status/20", null],
["https://x.com/i/flow/login", null],
["https://instagram.com.evil.com/natgeo", null],
];
for (const [input, expected] of cases) {
assert.deepEqual(parseProfileUrl(input), expected, input);
}
console.log(`${cases.length} cases passed`);from parse_profile import parse_profile_url
CASES = [
("instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA==", {"platform": "instagram", "handle": "natgeo", "canonicalUrl": "https://www.instagram.com/natgeo/"}),
("https://www.instagram.com/reel/C9xYzAbCdEf/", None),
("tiktok.com/@Khaby.Lame/", {"platform": "tiktok", "handle": "khaby.lame", "canonicalUrl": "https://www.tiktok.com/@khaby.lame"}),
("https://vm.tiktok.com/ZMabc123/", None),
("https://m.youtube.com/@GoogleDevelopers/videos", {"platform": "youtube", "handle": "googledevelopers", "canonicalUrl": "https://www.youtube.com/@googledevelopers"}),
("https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured", {"platform": "youtube", "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", "canonicalUrl": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw"}),
("https://youtu.be/dQw4w9WgXcQ", None),
("https://twitter.com/Jack/", {"platform": "x", "handle": "jack", "canonicalUrl": "https://x.com/jack"}),
("https://x.com/i/flow/login", None),
("https://instagram.com.evil.com/natgeo", None),
]
for value, expected in CASES:
assert parse_profile_url(value) == expected, value
print(f"{len(CASES)} cases passed")আসল ইনপুট আর canonical রূপ দুটোই সংরক্ষণ করুন
নরমালাইজেশন ইচ্ছাকৃতভাবেই কিছু তথ্য হারায়, আর আপনার নিয়ম সময়ের সঙ্গে উন্নত হবে। দুটি মানই সংরক্ষণ করুন:
- `raw_url`: ইউজার বা ইমপোর্ট ঠিক যা দিয়েছে। এতে পরে একটি ভালো পার্সার আবার চালাতে পারবেন, আর ইউজার প্রোফাইল এডিট করার সময় এটিই তাঁকে দেখাবেন।
- `platform` আর `handle` বা `channel_id`: অংশগুলো,
(platform, handle)-এর ওপর একটি unique index সহ। এটিই আপনার ডুপ্লিকেট সরানো আর join-এর কি। - `canonical_url`: আবার বানানো URL, দেখানোর জন্য আর প্ল্যাটফর্মে কল করার জন্য।
- `parsed_at` আর একটি পার্সার ভার্সন: যাতে আপনার নিয়মের পুরোনো ভার্সনে নরমালাইজ করা সারিগুলো খুঁজে পান।
হ্যান্ডেল স্থায়ী নয়। ক্রিয়েটররা প্রতিটি প্ল্যাটফর্মে অ্যাকাউন্টের নাম বদলান, আর গত বছর সংরক্ষণ করা একটি হ্যান্ডেল এখন অন্য কারও হতে পারে। যেখানে কোনো অফিশিয়াল API একটি স্থির ID দেয়, যেমন YouTube চ্যানেল ID, সেটিকে দীর্ঘমেয়াদি কি হিসেবে সংরক্ষণ করুন আর হ্যান্ডেলকে দেখানোর মান হিসেবে ধরুন, যা আপনি রিফ্রেশ করবেন। হ্যান্ডেল থেকে YouTube চ্যানেল ID পাওয়া নিয়ে সঙ্গী গাইডটি দেখায় কীভাবে।
একটি প্রোফাইল থেকে বাকিগুলোতে
একটি প্রোফাইল URL নরমালাইজ হয়ে গেলে, পরের প্রশ্ন সাধারণত হয় একই ক্রিয়েটরের আর কোন অ্যাকাউন্টগুলো আছে। sociallinks.dev একটি API যা একটি Instagram, TikTok, YouTube বা X প্রোফাইল URL নেয় আর অন্য প্ল্যাটফর্মে ক্রিয়েটরের অ্যাকাউন্টগুলো ফেরত দেয়, প্রতিটি মিলের জন্য কনফিডেন্স স্কোর আর প্রমাণসহ।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
সংরক্ষণের আগে কি হ্যান্ডেল ছোট হাতের অক্ষরে আনা উচিত?
খোঁজার কি-এর জন্য হ্যাঁ। YouTube আর X বলে হ্যান্ডেল কেস-সেনসিটিভ নয়, আর TikTok @TikTok থেকে @tiktok-এর একই অ্যাকাউন্টে যায়। Instagram কেসের কোনো নিয়ম ডকুমেন্ট করে না, তবে ইউজারনেম ছোট হাতের অক্ষরে দেখানো হয়, তাই ছোট হাতের অক্ষরে আনাই নিরাপদ ডিফল্ট। দেখাতে চাইলে আসল কেস raw URL-এ রেখে দিন। YouTube চ্যানেল ID ছোট হাতের অক্ষরে আনবেন না, এগুলো কেস-সেনসিটিভ।
পোস্ট বা ভিডিও URL থেকে কি হ্যান্ডেল বের করা যায়?
TikTok ভিডিও (/@handle/video/id) আর X পোস্টে (/handle/status/id) পাথের হ্যান্ডেলটি লেখকের, তাই সেটিই চাইলে বের করতে পারেন। Instagram পোস্ট আর রিল URL (/p/ আর /reel/)-এ হ্যান্ডেল থাকে না। এই গাইডের ফাংশনগুলো এদের সবার জন্য null দেয়, কারণ এগুলো প্রোফাইল লিংক নয়।
vm.tiktok.com লিংক কীভাবে সামলাব?
শর্ট লিংকে শুধু একটি কোড থাকে। একটি HTTP রিকোয়েস্ট করুন, রিডাইরেক্ট অনুসরণ করুন আর শেষ URL পার্স করুন। এটি একটি আলাদা ধাপে টাইমআউট আর ক্যাশসহ করুন, কারণ এটি নেটওয়ার্ক কল আর কোডের মেয়াদ শেষ হতে পারে।
twitter.com কি এখনো বৈধ প্রোফাইল URL?
হ্যাঁ। twitter.com প্রোফাইল URL x.com-এ রিডাইরেক্ট করে, তাই দুটি ডোমেইনকেই X প্ল্যাটফর্ম হিসেবে ধরুন আর x.com রূপটিকে canonical হিসেবে সংরক্ষণ করুন।
একটি বড় regular expression কেন ব্যবহার করব না?
পুরো স্ট্রিংয়ের ওপর একটি regex পড়তে কঠিন, আর দেখতে একই রকম হোস্ট, ইউজার ইনফো বা এনকোড করা অক্ষর দিয়ে সহজেই বোকা বানানো যায়। আগে স্ট্যান্ডার্ড লাইব্রেরি দিয়ে URL পার্স করুন, তারপর শুধু হ্যান্ডেলের ওপর ছোট regex ব্যবহার করুন।
YouTube-এর /c/ বা /user/ URL থেকে চ্যানেল ID কীভাবে পাব?
/user/<name> URL YouTube Data API-র forUsername ফিল্টারের সঙ্গে মেলে। /c/ কাস্টম নামের জন্য কোনো API ফিল্টার নেই। দুটি ক্ষেত্রের জন্যই হ্যান্ডেল থেকে YouTube চ্যানেল ID পাওয়ার গাইডটি দেখুন।