Skip to content

Commit 80753fd

Browse files
committed
✨ Scrapbox-Parserの外部リンク記法解析結果をさらに細かく分ける函数を作った
1 parent fd9c54b commit 80753fd

15 files changed

+628
-0
lines changed

deps/testing.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "https://deno.land/[email protected]/testing/asserts.ts";
2+
export * from "https://deno.land/[email protected]/testing/snapshot.ts";

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./rest/mod.ts";
22
export * from "./browser/mod.ts";
33
export * from "./title.ts";
4+
export * from "./parseAbsoluteLink.ts";

parseAbsoluteLink.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type { LinkNode } from "./deps/scrapbox.ts";
2+
import { parseYoutube } from "./parser/youtube.ts";
3+
import { parseVimeo } from "./parser/vimeo.ts";
4+
import { parseSpotify } from "./parser/spotify.ts";
5+
import { parseAnchorFM } from "./parser/anchor-fm.ts";
6+
7+
export type { LinkNode };
8+
9+
export interface AbsoluteLinkNode {
10+
type: "absoluteLink";
11+
content: string;
12+
href: string;
13+
raw: string;
14+
}
15+
16+
/** Youtube埋め込み */
17+
export interface YoutubeNode {
18+
type: "youtube";
19+
videoId: string;
20+
pathType: "com" | "dotbe" | "short";
21+
params: URLSearchParams;
22+
href: string;
23+
raw: string;
24+
}
25+
26+
/** Youtube List埋め込み */
27+
export interface YoutubeListNode {
28+
type: "youtube";
29+
listId: string;
30+
pathType: "list";
31+
params: URLSearchParams;
32+
href: string;
33+
raw: string;
34+
}
35+
36+
/** Vimeo埋め込み */
37+
export interface VimeoNode {
38+
type: "vimeo";
39+
videoId: string;
40+
href: string;
41+
raw: string;
42+
}
43+
44+
/** Spotify埋め込み */
45+
export interface SpotifyNode {
46+
type: "spotify";
47+
videoId: string;
48+
pathType: "track" | "artist" | "playlist" | "album" | "episode" | "show";
49+
href: string;
50+
raw: string;
51+
}
52+
53+
/** Anchor FM埋め込み */
54+
export interface AnchorFMNode {
55+
type: "anchor-fm";
56+
videoId: string;
57+
href: string;
58+
raw: string;
59+
}
60+
61+
/** 動画埋め込み */
62+
export interface VideoNode {
63+
type: "video";
64+
href: VideoURL;
65+
raw: string;
66+
}
67+
68+
/** 音声埋め込み */
69+
export interface AudioNode {
70+
type: "audio";
71+
content: string;
72+
href: AudioURL;
73+
raw: string;
74+
}
75+
76+
/** scrapbox-parserで解析した外部リンク記法を、埋め込み形式ごとに細かく解析する
77+
*
78+
* @param link scrapbox-parserで解析した外部リンク記法のobject
79+
* @return 解析した記法のobject
80+
*/
81+
export const parseAbsoluteLink = (
82+
link: LinkNode & { pathType: "absolute" },
83+
):
84+
| AbsoluteLinkNode
85+
| VideoNode
86+
| AudioNode
87+
| YoutubeNode
88+
| YoutubeListNode
89+
| VimeoNode
90+
| SpotifyNode
91+
| AnchorFMNode => {
92+
const { type: _, pathType: __, content, href, ...baseLink } = link;
93+
if (content === "") {
94+
const youtube = parseYoutube(href);
95+
if (youtube) return { type: "youtube", href, ...youtube, ...baseLink };
96+
97+
const vimeoId = parseVimeo(href);
98+
if (vimeoId) return { type: "vimeo", videoId: vimeoId, href, ...baseLink };
99+
100+
const spotify = parseSpotify(href);
101+
if (spotify) return { type: "spotify", href, ...spotify, ...baseLink };
102+
103+
const anchorFMId = parseAnchorFM(href);
104+
if (anchorFMId) {
105+
return { type: "anchor-fm", videoId: anchorFMId, href, ...baseLink };
106+
}
107+
108+
if (isVideoURL(href)) return { type: "video", href, ...baseLink };
109+
}
110+
if (isAudioURL(href)) return { type: "audio", content, href, ...baseLink };
111+
112+
return { type: "absoluteLink", content, href, ...baseLink };
113+
};
114+
115+
type AudioURL = `${string}.${"mp3" | "ogg" | "wav" | "acc"}`;
116+
const isAudioURL = (url: string): url is AudioURL =>
117+
/\.(?:mp3|ogg|wav|aac)$/.test(url);
118+
119+
type VideoURL = `${string}.${"mp4" | "webm"}`;
120+
const isVideoURL = (url: string): url is VideoURL =>
121+
/\.(?:mp4|webm)$/.test(url);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const snapshot = {};
2+
3+
snapshot[`spotify links > is 1`] = `"1-FM-e1gh6a7/a-a7m2veg"`;
4+
5+
snapshot[`spotify links > is not 1`] = `undefined`;
6+
7+
snapshot[`spotify links > is not 2`] = `undefined`;
8+
9+
snapshot[`spotify links > is not 3`] = `undefined`;
10+
11+
snapshot[`spotify links > is not 4`] = `undefined`;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export const snapshot = {};
2+
3+
snapshot[`spotify links > is 1`] = `
4+
{
5+
pathType: "track",
6+
videoId: "0rlYL6IQIwLZwYIguyy3l0",
7+
}
8+
`;
9+
10+
snapshot[`spotify links > is 2`] = `
11+
{
12+
pathType: "album",
13+
videoId: "1bgUOjg3V0a7tvEfF1N6Kk",
14+
}
15+
`;
16+
17+
snapshot[`spotify links > is 3`] = `
18+
{
19+
pathType: "episode",
20+
videoId: "0JtPGoprZK2WlYMjhFF2xD",
21+
}
22+
`;
23+
24+
snapshot[`spotify links > is 4`] = `
25+
{
26+
pathType: "playlist",
27+
videoId: "2uOyQytSjDq9GF5z1RJj5w",
28+
}
29+
`;
30+
31+
snapshot[`spotify links > is not 1`] = `undefined`;
32+
33+
snapshot[`spotify links > is not 2`] = `undefined`;
34+
35+
snapshot[`spotify links > is not 3`] = `undefined`;
36+
37+
snapshot[`spotify links > is not 4`] = `undefined`;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const snapshot = {};
2+
3+
snapshot[`vimeo links > is 1`] = `"121284607"`;
4+
5+
snapshot[`vimeo links > is not 1`] = `undefined`;
6+
7+
snapshot[`vimeo links > is not 2`] = `undefined`;
8+
9+
snapshot[`vimeo links > is not 3`] = `undefined`;
10+
11+
snapshot[`vimeo links > is not 4`] = `undefined`;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
export const snapshot = {};
2+
3+
snapshot[`youtube links > is 1`] = `
4+
{
5+
params: URLSearchParams {
6+
[Symbol("[[webidl.brand]]")]: Symbol("[[webidl.brand]]"),
7+
[Symbol(list)]: [
8+
[
9+
"v",
10+
"LSvaOcaUQ3Y",
11+
],
12+
],
13+
[Symbol("url object")]: URL {
14+
hash: "",
15+
host: "www.youtube.com",
16+
hostname: "www.youtube.com",
17+
href: "https://www.youtube.com/watch?v=LSvaOcaUQ3Y",
18+
origin: "https://www.youtube.com",
19+
password: "",
20+
pathname: "/watch",
21+
port: "",
22+
protocol: "https:",
23+
search: "?v=LSvaOcaUQ3Y",
24+
username: "",
25+
},
26+
},
27+
pathType: "com",
28+
videoId: "LSvaOcaUQ3Y",
29+
}
30+
`;
31+
32+
snapshot[`youtube links > is 2`] = `
33+
{
34+
listId: "PLmoRDY8IgE2Okxy4WWdP95RHXOTGzJfQs",
35+
params: URLSearchParams {
36+
[Symbol("[[webidl.brand]]")]: Symbol("[[webidl.brand]]"),
37+
[Symbol(list)]: [
38+
[
39+
"list",
40+
"PLmoRDY8IgE2Okxy4WWdP95RHXOTGzJfQs",
41+
],
42+
],
43+
[Symbol("url object")]: null,
44+
},
45+
pathType: "list",
46+
}
47+
`;
48+
49+
snapshot[`youtube links > is 3`] = `
50+
{
51+
params: URLSearchParams {
52+
[Symbol("[[webidl.brand]]")]: Symbol("[[webidl.brand]]"),
53+
[Symbol(list)]: [
54+
[
55+
"v",
56+
"57rdbK4vmKE",
57+
],
58+
[
59+
"list",
60+
"PLmoRDY8IgE2Okxy4WWdP95RHXOTGzJfQs",
61+
],
62+
],
63+
[Symbol("url object")]: URL {
64+
hash: "",
65+
host: "www.youtube.com",
66+
hostname: "www.youtube.com",
67+
href: "https://www.youtube.com/watch?v=57rdbK4vmKE&list=PLmoRDY8IgE2Okxy4WWdP95RHXOTGzJfQs",
68+
origin: "https://www.youtube.com",
69+
password: "",
70+
pathname: "/watch",
71+
port: "",
72+
protocol: "https:",
73+
search: "?v=57rdbK4vmKE&list=PLmoRDY8IgE2Okxy4WWdP95RHXOTGzJfQs",
74+
username: "",
75+
},
76+
},
77+
pathType: "com",
78+
videoId: "57rdbK4vmKE",
79+
}
80+
`;
81+
82+
snapshot[`youtube links > is 4`] = `
83+
{
84+
params: URLSearchParams {
85+
[Symbol("[[webidl.brand]]")]: Symbol("[[webidl.brand]]"),
86+
[Symbol(list)]: [
87+
[
88+
"v",
89+
"nj1cre2e6t0",
90+
],
91+
],
92+
[Symbol("url object")]: URL {
93+
hash: "",
94+
host: "music.youtube.com",
95+
hostname: "music.youtube.com",
96+
href: "https://music.youtube.com/watch?v=nj1cre2e6t0",
97+
origin: "https://music.youtube.com",
98+
password: "",
99+
pathname: "/watch",
100+
port: "",
101+
protocol: "https:",
102+
search: "?v=nj1cre2e6t0",
103+
username: "",
104+
},
105+
},
106+
pathType: "com",
107+
videoId: "nj1cre2e6t0",
108+
}
109+
`;
110+
111+
snapshot[`youtube links > is 5`] = `
112+
{
113+
params: URLSearchParams {
114+
[Symbol("[[webidl.brand]]")]: Symbol("[[webidl.brand]]"),
115+
[Symbol(list)]: [
116+
],
117+
[Symbol("url object")]: null,
118+
},
119+
pathType: "dotbe",
120+
videoId: "nj1cre2e6t0",
121+
}
122+
`;
123+
124+
snapshot[`youtube links > is not 1`] = `undefined`;
125+
126+
snapshot[`youtube links > is not 2`] = `undefined`;
127+
128+
snapshot[`youtube links > is not 3`] = `undefined`;
129+
130+
snapshot[`youtube links > is not 4`] = `undefined`;

parser/anchor-fm.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { parseAnchorFM } from "./anchor-fm.ts";
2+
import { assertSnapshot } from "../deps/testing.ts";
3+
4+
Deno.test("spotify links", async (t) => {
5+
await t.step("is", async (t) => {
6+
await assertSnapshot(
7+
t,
8+
parseAnchorFM(
9+
"https://anchor.fm/notainc/episodes/1-FM-e1gh6a7/a-a7m2veg",
10+
),
11+
);
12+
});
13+
14+
await t.step("is not", async (t) => {
15+
await assertSnapshot(
16+
t,
17+
parseAnchorFM(
18+
"https://gyazo.com/da78df293f9e83a74b5402411e2f2e01",
19+
),
20+
);
21+
await assertSnapshot(
22+
t,
23+
parseAnchorFM(
24+
"ほげほげ",
25+
),
26+
);
27+
await assertSnapshot(
28+
t,
29+
parseAnchorFM(
30+
"https://yourtube.com/watch?v=rafere",
31+
),
32+
);
33+
await assertSnapshot(
34+
t,
35+
parseAnchorFM(
36+
"https://example.com",
37+
),
38+
);
39+
});
40+
});

parser/anchor-fm.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const AnchorFMRegExp =
2+
/https?:\/\/anchor\.fm\/[a-zA-Z\d_-]+\/episodes\/([a-zA-Z\d_-]+(?:\/[a-zA-Z\d_-]+)?)(?:\?[^\s]{0,100}|)/;
3+
4+
/** anchorFMのURLからIDを取得する
5+
*
6+
* @param url
7+
* @return ID anchorFMのURLでなければ`undefined`を返す
8+
*/
9+
export const parseAnchorFM = (url: string): string | undefined => {
10+
const matches = url.match(AnchorFMRegExp);
11+
if (!matches) return undefined;
12+
13+
const [, videoId] = matches;
14+
return videoId;
15+
};

0 commit comments

Comments
 (0)