diff --git a/src/main/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoId.java b/src/main/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoId.java new file mode 100644 index 0000000..3ec98fa --- /dev/null +++ b/src/main/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoId.java @@ -0,0 +1,21 @@ +package com.hlab.yanalyst.domain.shortform; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** 유튜브 URL에서 11자 videoId를 뽑는다. youtube.com / youtu.be 도메인만 인정. */ +public final class YoutubeVideoId { + + private static final Pattern PATTERN = Pattern.compile( + "^https?://(?:www\\.|m\\.)?(?:youtube\\.com/(?:watch\\?[^#]*\\bv=|shorts/|embed/|live/)|youtu\\.be/)" + + "([A-Za-z0-9_-]{11})(?:[?&#/].*)?$"); + + private YoutubeVideoId() {} + + public static Optional from(String url) { + if (url == null || url.isBlank()) return Optional.empty(); + Matcher m = PATTERN.matcher(url.trim()); + return m.matches() ? Optional.of(m.group(1)) : Optional.empty(); + } +} diff --git a/src/test/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoIdTest.java b/src/test/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoIdTest.java new file mode 100644 index 0000000..448f915 --- /dev/null +++ b/src/test/java/com/hlab/yanalyst/domain/shortform/YoutubeVideoIdTest.java @@ -0,0 +1,27 @@ +package com.hlab.yanalyst.domain.shortform; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class YoutubeVideoIdTest { + + @Test + void 다양한_유튜브_url_형식에서_videoId를_추출한다() { + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://www.youtube.com/watch?v=JW5mwZ8RsG8").orElseThrow()); + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://www.youtube.com/watch?v=JW5mwZ8RsG8&t=10s").orElseThrow()); + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://youtu.be/JW5mwZ8RsG8").orElseThrow()); + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://www.youtube.com/shorts/JW5mwZ8RsG8").orElseThrow()); + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://www.youtube.com/embed/JW5mwZ8RsG8").orElseThrow()); + assertEquals("JW5mwZ8RsG8", YoutubeVideoId.from("https://www.youtube.com/live/JW5mwZ8RsG8?feature=share").orElseThrow()); + } + + @Test + void 유튜브가_아니거나_id가_없으면_empty() { + assertTrue(YoutubeVideoId.from(null).isEmpty()); + assertTrue(YoutubeVideoId.from("").isEmpty()); + assertTrue(YoutubeVideoId.from("https://example.com/watch?v=JW5mwZ8RsG8").isEmpty()); + assertTrue(YoutubeVideoId.from("https://www.youtube.com/watch").isEmpty()); + assertTrue(YoutubeVideoId.from("https://www.youtube.com/watch?v=short").isEmpty()); + } +}