|
10 | 10 | // @grant none
|
11 | 11 | // ==/UserScript==
|
12 | 12 | (() => {
|
13 |
| - // Utilities. |
14 |
| - const sel = document.querySelector.bind(document); |
15 |
| - const selIn = (el, selector) => el.querySelector(selector); |
16 |
| - const dom = (tag, attrs, ...children) => { |
17 |
| - const el = document.createElement(tag); |
18 |
| - if (attrs) |
19 |
| - Object.keys(attrs).forEach((attr) => el.setAttribute(attr.toString(), attrs[attr] ?? "")); |
20 |
| - children |
21 |
| - .map((obj) => typeof obj === "string" ? document.createTextNode(obj) : obj) |
22 |
| - .forEach((node) => el.appendChild(node)); |
23 |
| - return el; |
24 |
| - }; |
25 |
| - const counter = () => { |
26 |
| - let i = 0; |
27 |
| - return () => i++; |
28 |
| - }; |
29 |
| - const onFullLoad = (cb) => /complete/.test(document.readyState) |
30 |
| - ? setTimeout(cb, 0) |
31 |
| - : window.addEventListener("load", cb, { once: true }); |
32 |
| - const checkValue = (value) => value === undefined ? "" : value; |
33 |
| - const input = (name, value) => dom("input", { |
34 |
| - name: name, |
35 |
| - value: checkValue(value?.toString()), |
36 |
| - type: "text", |
| 13 | + // Utilities. |
| 14 | + const sel = document.querySelector.bind(document); |
| 15 | + const selIn = (el, selector) => el.querySelector(selector); |
| 16 | + const dom = (tag, attrs, ...children) => { |
| 17 | + const el = document.createElement(tag); |
| 18 | + if (attrs) |
| 19 | + Object.keys(attrs).forEach((attr) => |
| 20 | + el.setAttribute(attr.toString(), attrs[attr] ?? ""), |
| 21 | + ); |
| 22 | + children |
| 23 | + .map((obj) => |
| 24 | + typeof obj === "string" ? document.createTextNode(obj) : obj, |
| 25 | + ) |
| 26 | + .forEach((node) => el.appendChild(node)); |
| 27 | + return el; |
| 28 | + }; |
| 29 | + const counter = () => { |
| 30 | + let i = 0; |
| 31 | + return () => i++; |
| 32 | + }; |
| 33 | + const onFullLoad = (cb) => |
| 34 | + /complete/.test(document.readyState) |
| 35 | + ? setTimeout(cb, 0) |
| 36 | + : window.addEventListener("load", cb, { once: true }); |
| 37 | + const checkValue = (value) => (value === undefined ? "" : value); |
| 38 | + const input = (name, value) => |
| 39 | + dom("input", { |
| 40 | + name: name, |
| 41 | + value: checkValue(value?.toString()), |
| 42 | + type: "text", |
37 | 43 | });
|
38 |
| - const onChanged = (el, cb) => { |
39 |
| - const observer = new MutationObserver(cb); |
40 |
| - observer.observe(el, { childList: true, subtree: true }); |
41 |
| - return observer.disconnect.bind(observer); |
42 |
| - }; |
43 |
| - onFullLoad(async () => { |
44 |
| - // Add elements to DOM. |
45 |
| - const button = dom("button", { |
46 |
| - class: "btn btn-outline-secondary pull-right", |
47 |
| - id: "musicbrainz-button", |
48 |
| - }, "Add to MusicBrainz"); |
49 |
| - const form = dom("form", { |
50 |
| - name: "musicbrainz-submit", |
51 |
| - action: "https://musicbrainz.org/release/add", |
52 |
| - method: "post", |
53 |
| - "accept-charset": "utf-8", |
54 |
| - style: "display: none", |
55 |
| - }); |
56 |
| - const container = sel(".card-header"); |
57 |
| - container?.insertAdjacentElement("afterbegin", form); |
58 |
| - container?.insertAdjacentElement("afterbegin", button); |
59 |
| - button.addEventListener("click", (e) => { |
60 |
| - form.submit(); |
61 |
| - e.preventDefault(); |
62 |
| - }); |
63 |
| - sel("head")?.append(dom("style", {}, ` |
| 44 | + const onChanged = (el, cb) => { |
| 45 | + const observer = new MutationObserver(cb); |
| 46 | + observer.observe(el, { childList: true, subtree: true }); |
| 47 | + return observer.disconnect.bind(observer); |
| 48 | + }; |
| 49 | + onFullLoad(async () => { |
| 50 | + // Add elements to DOM. |
| 51 | + const button = dom( |
| 52 | + "button", |
| 53 | + { |
| 54 | + class: "btn btn-outline-secondary pull-right", |
| 55 | + id: "musicbrainz-button", |
| 56 | + }, |
| 57 | + "Add to MusicBrainz", |
| 58 | + ); |
| 59 | + const form = dom("form", { |
| 60 | + name: "musicbrainz-submit", |
| 61 | + action: "https://musicbrainz.org/release/add", |
| 62 | + method: "post", |
| 63 | + "accept-charset": "utf-8", |
| 64 | + style: "display: none", |
| 65 | + }); |
| 66 | + const container = sel(".card-header"); |
| 67 | + container?.insertAdjacentElement("afterbegin", form); |
| 68 | + container?.insertAdjacentElement("afterbegin", button); |
| 69 | + button.addEventListener("click", (e) => { |
| 70 | + form.submit(); |
| 71 | + e.preventDefault(); |
| 72 | + }); |
| 73 | + sel("head")?.append( |
| 74 | + dom( |
| 75 | + "style", |
| 76 | + {}, |
| 77 | + ` |
64 | 78 | #musicbrainz-button {
|
65 | 79 | float: right
|
66 |
| - }`)); |
67 |
| - const table = sel(".table"); |
68 |
| - const songsTable = sel("#songsTable"); |
69 |
| - if (!table || !songsTable) { |
70 |
| - return; |
71 |
| - } |
72 |
| - const updateForm = () => { |
73 |
| - // Get values. |
74 |
| - const values = Array.from(table.querySelectorAll("tr") ?? []).reduce((r, el) => { |
75 |
| - const label = selIn(el, "th")?.textContent?.trim() ?? ""; |
76 |
| - const value = selIn(el, "td")?.textContent?.trim() ?? ""; |
77 |
| - console.log({ el, label, value }); |
78 |
| - if (/表演者/.test(label)) |
79 |
| - r.artist = value; |
80 |
| - else if (/樂團名稱/.test(label)) |
81 |
| - r.artist = value; |
82 |
| - else if (/專輯名稱/.test(label)) |
83 |
| - r.title = value; |
84 |
| - else if (/發行公司/.test(label)) |
85 |
| - r.label = value; |
86 |
| - else if (/產品編碼/.test(label)) |
87 |
| - r.cat = value; |
88 |
| - else if (/EAN\/UPC碼/.test(label)) |
89 |
| - r.barcode = value; |
90 |
| - else if (/發行日期/.test(label)) |
91 |
| - r.date = value.split("."); |
92 |
| - return r; |
93 |
| - }, {}); |
94 |
| - values.tracks = Array.from(songsTable.querySelectorAll("tr") ?? []).map((el) => { |
95 |
| - const [hours, minutes, seconds] = el |
96 |
| - .querySelector("td.text-right") |
97 |
| - ?.textContent?.trim() |
98 |
| - .split(".") |
99 |
| - .map(Number) ?? []; |
100 |
| - const paddedSeconds = seconds?.toString().padStart(2, "0"); |
101 |
| - return { |
102 |
| - title: el |
103 |
| - .querySelector("a") |
104 |
| - ?.textContent?.trim() |
105 |
| - .replace(/^\[\S+\] \d+[.](.*)$/, "$1") ?? "", |
106 |
| - length: hours && minutes |
107 |
| - ? `${hours * 60 + minutes}:${paddedSeconds}` |
108 |
| - : "0:00", |
109 |
| - }; |
110 |
| - }); |
111 |
| - console.log({ values }); |
112 |
| - // Create form inputs. |
113 |
| - const baseInputs = [ |
114 |
| - input("name", values.title), |
115 |
| - input("artist_credit.names.0.name", values.artist), |
116 |
| - input("labels.0.name", values.label), |
117 |
| - input("labels.0.catalog_number", values.cat), |
118 |
| - input("events.0.date.year", values.date?.[0]), |
119 |
| - input("events.0.date.month", values.date?.[1]), |
120 |
| - input("events.0.country", "TW"), |
121 |
| - input("barcode", values.barcode), |
122 |
| - input("urls.0.url", window.location.href), |
123 |
| - input("urls.0.link_type", "82"), |
124 |
| - input("language", "cmn"), |
125 |
| - input("script", "Hant"), |
126 |
| - input("status", "official"), |
127 |
| - input("mediums.0.format", "cd"), |
128 |
| - input("edit_note", "From Taiwan ISRC: " + window.location.href), |
129 |
| - ]; |
130 |
| - const trackCount = counter(); |
131 |
| - const trackInputs = values.tracks.flatMap(({ title, length }) => { |
132 |
| - const i = trackCount(); |
133 |
| - return [ |
134 |
| - input(`mediums.0.track.${i}.name`, title), |
135 |
| - input(`mediums.0.track.${i}.length`, length), |
136 |
| - input(`mediums.0.track.${i}.number`, i + 1), |
137 |
| - ]; |
138 |
| - }); |
139 |
| - // Replace form contents with new data. |
140 |
| - form.textContent = ""; |
141 |
| - form.append(...baseInputs, ...trackInputs); |
142 |
| - }; |
143 |
| - updateForm(); |
144 |
| - // Listen to changes in data. |
145 |
| - onChanged(table, updateForm); |
146 |
| - onChanged(songsTable, updateForm); |
147 |
| - }); |
| 80 | + }`, |
| 81 | + ), |
| 82 | + ); |
| 83 | + const table = sel(".table"); |
| 84 | + const songsTable = sel("#songsTable"); |
| 85 | + if (!table || !songsTable) { |
| 86 | + return; |
| 87 | + } |
| 88 | + const updateForm = () => { |
| 89 | + // Get values. |
| 90 | + const values = Array.from(table.querySelectorAll("tr") ?? []).reduce( |
| 91 | + (r, el) => { |
| 92 | + const label = selIn(el, "th")?.textContent?.trim() ?? ""; |
| 93 | + const value = selIn(el, "td")?.textContent?.trim() ?? ""; |
| 94 | + console.log({ el, label, value }); |
| 95 | + if (/表演者/.test(label)) r.artist = value; |
| 96 | + else if (/樂團名稱/.test(label)) r.artist = value; |
| 97 | + else if (/專輯名稱/.test(label)) r.title = value; |
| 98 | + else if (/發行公司/.test(label)) r.label = value; |
| 99 | + else if (/產品編碼/.test(label)) r.cat = value; |
| 100 | + else if (/EAN\/UPC碼/.test(label)) r.barcode = value; |
| 101 | + else if (/發行日期/.test(label)) r.date = value.split("."); |
| 102 | + return r; |
| 103 | + }, |
| 104 | + {}, |
| 105 | + ); |
| 106 | + values.tracks = Array.from(songsTable.querySelectorAll("tr") ?? []).map( |
| 107 | + (el) => { |
| 108 | + const [hours, minutes, seconds] = |
| 109 | + el |
| 110 | + .querySelector("td.text-right") |
| 111 | + ?.textContent?.trim() |
| 112 | + .split(".") |
| 113 | + .map(Number) ?? []; |
| 114 | + const paddedSeconds = seconds?.toString().padStart(2, "0"); |
| 115 | + return { |
| 116 | + title: |
| 117 | + el |
| 118 | + .querySelector("a") |
| 119 | + ?.textContent?.trim() |
| 120 | + .replace(/^\[\S+\] \d+[.](.*)$/, "$1") ?? "", |
| 121 | + length: |
| 122 | + hours && minutes |
| 123 | + ? `${hours * 60 + minutes}:${paddedSeconds}` |
| 124 | + : "0:00", |
| 125 | + }; |
| 126 | + }, |
| 127 | + ); |
| 128 | + console.log({ values }); |
| 129 | + // Create form inputs. |
| 130 | + const baseInputs = [ |
| 131 | + input("name", values.title), |
| 132 | + input("artist_credit.names.0.name", values.artist), |
| 133 | + input("labels.0.name", values.label), |
| 134 | + input("labels.0.catalog_number", values.cat), |
| 135 | + input("events.0.date.year", values.date?.[0]), |
| 136 | + input("events.0.date.month", values.date?.[1]), |
| 137 | + input("events.0.country", "TW"), |
| 138 | + input("barcode", values.barcode), |
| 139 | + input("urls.0.url", window.location.href), |
| 140 | + input("urls.0.link_type", "82"), |
| 141 | + input("language", "cmn"), |
| 142 | + input("script", "Hant"), |
| 143 | + input("status", "official"), |
| 144 | + input("mediums.0.format", "cd"), |
| 145 | + input("edit_note", "From Taiwan ISRC: " + window.location.href), |
| 146 | + ]; |
| 147 | + const trackCount = counter(); |
| 148 | + const trackInputs = values.tracks.flatMap(({ title, length }) => { |
| 149 | + const i = trackCount(); |
| 150 | + return [ |
| 151 | + input(`mediums.0.track.${i}.name`, title), |
| 152 | + input(`mediums.0.track.${i}.length`, length), |
| 153 | + input(`mediums.0.track.${i}.number`, i + 1), |
| 154 | + ]; |
| 155 | + }); |
| 156 | + // Replace form contents with new data. |
| 157 | + form.textContent = ""; |
| 158 | + form.append(...baseInputs, ...trackInputs); |
| 159 | + }; |
| 160 | + updateForm(); |
| 161 | + // Listen to changes in data. |
| 162 | + onChanged(table, updateForm); |
| 163 | + onChanged(songsTable, updateForm); |
| 164 | + }); |
148 | 165 | })();
|
0 commit comments