Accept YouTube /live/ and /shorts/ URLs in extractVideoId
The video-id regex only matched /watch?v=, youtu.be, /embed/, and /v/ forms, so youtube.com/live/<id> and youtube.com/shorts/<id> links were rejected with "Invalid YouTube URL". Add both forms to the server and frontend extractors (kept in sync) and cover them with tests. Ship as 0.2.159.
This commit is contained in:
+1
-1
@@ -2714,7 +2714,7 @@
|
|||||||
|
|
||||||
function extractVideoId(url) {
|
function extractVideoId(url) {
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/,
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/live\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
|
||||||
/^([a-zA-Z0-9_-]{11})$/,
|
/^([a-zA-Z0-9_-]{11})$/,
|
||||||
];
|
];
|
||||||
for (const p of patterns) {
|
for (const p of patterns) {
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ describe("extractVideoId", () => {
|
|||||||
assert.equal(extractVideoId("https://www.youtube.com/embed/dQw4w9WgXcQ"), "dQw4w9WgXcQ");
|
assert.equal(extractVideoId("https://www.youtube.com/embed/dQw4w9WgXcQ"), "dQw4w9WgXcQ");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("extracts from /live/ URL (with ?si tracking param)", () => {
|
||||||
|
assert.equal(
|
||||||
|
extractVideoId("https://www.youtube.com/live/QEq1Fa-Br0U?si=CqlsUBpyTs_ksqi3"),
|
||||||
|
"QEq1Fa-Br0U"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("extracts from /shorts/ URL", () => {
|
||||||
|
assert.equal(extractVideoId("https://www.youtube.com/shorts/dQw4w9WgXcQ"), "dQw4w9WgXcQ");
|
||||||
|
});
|
||||||
|
|
||||||
test("accepts a bare 11-character id", () => {
|
test("accepts a bare 11-character id", () => {
|
||||||
assert.equal(extractVideoId("dQw4w9WgXcQ"), "dQw4w9WgXcQ");
|
assert.equal(extractVideoId("dQw4w9WgXcQ"), "dQw4w9WgXcQ");
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-3
@@ -14,12 +14,12 @@ export function sendEvent(res, event, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── YouTube video-id extraction ─────────────────────────────────────────────
|
// ── YouTube video-id extraction ─────────────────────────────────────────────
|
||||||
// Accepts watch URLs, youtu.be, /embed/, /v/, or a bare 11-char id.
|
// Accepts watch URLs, youtu.be, /embed/, /v/, /live/, /shorts/, or a bare
|
||||||
// Returns null when no id can be extracted.
|
// 11-char id. Returns null when no id can be extracted.
|
||||||
export function extractVideoId(url) {
|
export function extractVideoId(url) {
|
||||||
if (!url) return null;
|
if (!url) return null;
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/,
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/live\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
|
||||||
/^([a-zA-Z0-9_-]{11})$/,
|
/^([a-zA-Z0-9_-]{11})$/,
|
||||||
];
|
];
|
||||||
for (const p of patterns) {
|
for (const p of patterns) {
|
||||||
|
|||||||
@@ -177,8 +177,9 @@ import { v_0_2_155 } from './v0.2.155'
|
|||||||
import { v_0_2_156 } from './v0.2.156'
|
import { v_0_2_156 } from './v0.2.156'
|
||||||
import { v_0_2_157 } from './v0.2.157'
|
import { v_0_2_157 } from './v0.2.157'
|
||||||
import { v_0_2_158 } from './v0.2.158'
|
import { v_0_2_158 } from './v0.2.158'
|
||||||
|
import { v_0_2_159 } from './v0.2.159'
|
||||||
|
|
||||||
export const versionGraph = VersionGraph.of({
|
export const versionGraph = VersionGraph.of({
|
||||||
current: v_0_2_158,
|
current: v_0_2_159,
|
||||||
other: [v_0_2_157, v_0_2_156, v_0_2_155, v_0_2_154, v_0_2_153, v_0_2_152, v_0_2_151, v_0_2_150, v_0_2_149, v_0_2_148, v_0_2_147, v_0_2_146, v_0_2_145, v_0_2_144, v_0_2_143, v_0_2_142, v_0_2_141, v_0_2_140, v_0_2_139, v_0_2_138, v_0_2_137, v_0_2_136, v_0_2_135, v_0_2_134, v_0_2_133, v_0_2_132, v_0_2_131, v_0_2_130, v_0_2_129, v_0_2_128, v_0_2_127, v_0_2_126, v_0_2_125, v_0_2_124, v_0_2_123, v_0_2_122, v_0_2_121, v_0_2_120, v_0_2_119, v_0_2_118, v_0_2_117, v_0_2_116, v_0_2_115, v_0_2_114, v_0_2_113, v_0_2_112, v_0_2_111, v_0_2_110, v_0_2_109, v_0_2_108, v_0_2_107, v_0_2_106, v_0_2_105, v_0_2_104, v_0_2_103, v_0_2_102, v_0_2_101, v_0_2_100, v_0_2_99, v_0_2_98, v_0_2_97, v_0_2_96, v_0_2_95, v_0_2_94, v_0_2_93, v_0_2_92, v_0_2_91, v_0_2_90, v_0_2_89, v_0_2_88, v_0_2_87, v_0_2_86, v_0_2_85, v_0_2_84, v_0_2_83, v_0_2_82, v_0_2_81, v_0_2_80, v_0_2_79, v_0_2_78, v_0_2_77, v_0_2_76, v_0_2_75, v_0_2_74, v_0_2_73, v_0_2_72, v_0_2_71, v_0_2_70, v_0_2_69, v_0_2_68, v_0_2_67, v_0_2_66, v_0_2_65, v_0_2_64, v_0_2_63, v_0_2_62, v_0_2_61, v_0_2_60, v_0_2_59, v_0_2_58, v_0_2_57, v_0_2_56, v_0_2_55, v_0_2_54, v_0_2_53, v_0_2_52, v_0_2_51, v_0_2_50, v_0_2_49, v_0_2_48, v_0_2_47, v_0_2_46, v_0_2_45, v_0_2_44, v_0_2_43, v_0_2_42, v_0_2_41, v_0_2_40, v_0_2_39, v_0_2_38, v_0_2_37, v_0_2_36, v_0_2_35, v_0_2_34, v_0_2_33, v_0_2_32, v_0_2_31, v_0_2_30, v_0_2_29, v_0_2_28, v_0_2_27, v_0_2_26, v_0_2_25, v_0_2_24, v_0_2_23, v_0_2_22, v_0_2_21, v_0_2_20, v_0_2_19, v_0_2_18, v_0_2_17, v_0_2_16, v_0_2_15, v_0_2_14, v_0_2_13, v_0_2_12, v_0_2_11, v_0_2_10, v_0_2_9, v_0_2_8, v_0_2_7, v_0_2_6, v_0_2_5, v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
|
other: [v_0_2_158, v_0_2_157, v_0_2_156, v_0_2_155, v_0_2_154, v_0_2_153, v_0_2_152, v_0_2_151, v_0_2_150, v_0_2_149, v_0_2_148, v_0_2_147, v_0_2_146, v_0_2_145, v_0_2_144, v_0_2_143, v_0_2_142, v_0_2_141, v_0_2_140, v_0_2_139, v_0_2_138, v_0_2_137, v_0_2_136, v_0_2_135, v_0_2_134, v_0_2_133, v_0_2_132, v_0_2_131, v_0_2_130, v_0_2_129, v_0_2_128, v_0_2_127, v_0_2_126, v_0_2_125, v_0_2_124, v_0_2_123, v_0_2_122, v_0_2_121, v_0_2_120, v_0_2_119, v_0_2_118, v_0_2_117, v_0_2_116, v_0_2_115, v_0_2_114, v_0_2_113, v_0_2_112, v_0_2_111, v_0_2_110, v_0_2_109, v_0_2_108, v_0_2_107, v_0_2_106, v_0_2_105, v_0_2_104, v_0_2_103, v_0_2_102, v_0_2_101, v_0_2_100, v_0_2_99, v_0_2_98, v_0_2_97, v_0_2_96, v_0_2_95, v_0_2_94, v_0_2_93, v_0_2_92, v_0_2_91, v_0_2_90, v_0_2_89, v_0_2_88, v_0_2_87, v_0_2_86, v_0_2_85, v_0_2_84, v_0_2_83, v_0_2_82, v_0_2_81, v_0_2_80, v_0_2_79, v_0_2_78, v_0_2_77, v_0_2_76, v_0_2_75, v_0_2_74, v_0_2_73, v_0_2_72, v_0_2_71, v_0_2_70, v_0_2_69, v_0_2_68, v_0_2_67, v_0_2_66, v_0_2_65, v_0_2_64, v_0_2_63, v_0_2_62, v_0_2_61, v_0_2_60, v_0_2_59, v_0_2_58, v_0_2_57, v_0_2_56, v_0_2_55, v_0_2_54, v_0_2_53, v_0_2_52, v_0_2_51, v_0_2_50, v_0_2_49, v_0_2_48, v_0_2_47, v_0_2_46, v_0_2_45, v_0_2_44, v_0_2_43, v_0_2_42, v_0_2_41, v_0_2_40, v_0_2_39, v_0_2_38, v_0_2_37, v_0_2_36, v_0_2_35, v_0_2_34, v_0_2_33, v_0_2_32, v_0_2_31, v_0_2_30, v_0_2_29, v_0_2_28, v_0_2_27, v_0_2_26, v_0_2_25, v_0_2_24, v_0_2_23, v_0_2_22, v_0_2_21, v_0_2_20, v_0_2_19, v_0_2_18, v_0_2_17, v_0_2_16, v_0_2_15, v_0_2_14, v_0_2_13, v_0_2_12, v_0_2_11, v_0_2_10, v_0_2_9, v_0_2_8, v_0_2_7, v_0_2_6, v_0_2_5, v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { VersionInfo } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
|
export const v_0_2_159 = VersionInfo.of({
|
||||||
|
version: '0.2.159:0',
|
||||||
|
releaseNotes: {
|
||||||
|
en_US:
|
||||||
|
'Fix: YouTube "live" and "shorts" links (youtube.com/live/… and youtube.com/shorts/…) are now accepted — previously they were rejected as "Invalid YouTube URL".',
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
up: async ({ effects }) => {},
|
||||||
|
down: async ({ effects }) => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user