91 Plus M

打造行動裝置看91譜的最好體驗。

目前為 2022-11-26 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 91 Plus M
  3. // @namespace https://github.com/DonkeyBear
  4. // @version 0.96.2
  5. // @description 打造行動裝置看91譜的最好體驗。
  6. // @author DonkeyBear
  7. // @match https://www.91pu.com.tw/m/*
  8. // @match https://www.91pu.com.tw/song/*
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. let currentUrl = window.location.href;
  14. if (currentUrl.match(/\/song\//)) {
  15. let sheetId = currentUrl.match(/\/\d*\./)[0].slice(1, -1);
  16. let newUrl = `https://www.91pu.com.tw/m/tone.shtml?id=${sheetId}`;
  17. window.location.replace(newUrl);
  18. }
  19.  
  20. document.querySelector("html").style.backgroundColor = "#f7f7f7";
  21.  
  22. let observerCheckList = {
  23. modifyTitle: false,
  24. modifyHeaderBackground: false,
  25. modifyHeaderFlex: false,
  26. modifyFunctionBarMargin: false,
  27. modifyTransposeButton: false
  28. }
  29.  
  30. const observer = new MutationObserver(() => {
  31. /* 隱藏網頁元素 */
  32. let elementShouldBlock = {
  33. // 需要倒數才能關閉的蓋版廣告
  34. modalAd: document.querySelector("#viptoneWindow.window"),
  35. // 在頁面最底部的廣告
  36. bottomAd: document.querySelector("#bottomad"),
  37. // 最上方提醒升級VIP的廣告
  38. updateVipBar: document.querySelector(".update_vip_bar"),
  39. // 譜上的LOGO和浮水印
  40. overlayLogo: document.querySelector(".wmask"),
  41. // 彈出式頁尾
  42. footer: document.querySelector("footer"),
  43. // 自動滾動頁面捲軸
  44. autoScroll: document.querySelector(".autoscroll"),
  45. // 頁首的返回列
  46. headerBackplace: document.querySelector(".backplace"),
  47. // 頁首的Key選項
  48. keys: document.querySelector(".set .keys"),
  49. // 其餘的Google廣告
  50. adsByGoogle: document.querySelectorAll(".adsbygoogle")
  51. }
  52. for (let selected in elementShouldBlock) {
  53. // 將上述元素隱藏
  54. if (elementShouldBlock[selected]) {
  55. if (elementShouldBlock[selected].length === undefined) {
  56. // Node
  57. elementShouldBlock[selected].style.display = "none";
  58. } else {
  59. // NodeList
  60. for (let elem of elementShouldBlock[selected]) {
  61. elem.style.display = "none";
  62. }
  63. }
  64. }
  65. }
  66. if (document.querySelectorAll(".setint .hr")) {
  67. // 隱藏頁首部分功能鈕
  68. for (let i = 3; i < 6; i++) {
  69. if (document.querySelectorAll(".setint .hr")[i]) {
  70. document.querySelectorAll(".setint .hr")[i].style.display = "none";
  71. }
  72. }
  73. }
  74.  
  75. /* 更改網頁標題 */
  76. if (!observerCheckList.modifyTitle) {
  77. if (document.querySelector("#mtitle")) {
  78. document.title = `${document.querySelector("#mtitle").innerText} | 91+ M`;
  79. observerCheckList.modify = true;
  80. }
  81. }
  82.  
  83. /* 更改頁首背景樣式 */
  84. if (!observerCheckList.modifyHeaderBackground) {
  85. if (document.querySelector("header")) {
  86. document.querySelector("header").style.backdropFilter = "blur(5px) saturate(80%)";
  87. document.querySelector("header").style['-webkit-backdrop-filter'] = "blur(5px) saturate(80%)";
  88. document.querySelector("header").style.backgroundColor = "rgba(25, 20, 90, 0.5)";
  89. observerCheckList.modifyHeaderBackground = true;
  90. }
  91. }
  92.  
  93. /* 更改頁首內容物排列方式 */
  94. if (!observerCheckList.modifyHeaderFlex) {
  95. for (let elem of [
  96. document.querySelector(".setint"),
  97. document.querySelector(".plays .capo")
  98. ]) {
  99. if (elem) {
  100. elem.style.display = "flex";
  101. elem.style.justifyContent = "space-between";
  102. if (elem.classList.contains("setint")) {
  103. elem.style.borderTop = "1px solid rgba(255, 255, 255, 0.2)";
  104. }
  105. observerCheckList.modifyHeaderFlex = true;
  106. }
  107. }
  108. }
  109.  
  110. /* 更改六線譜前奏功能列邊界留白 */
  111. if (!observerCheckList.modifyFunctionBarMargin) {
  112. if (document.querySelector(".tfunc2")) {
  113. observerCheckList.modifyFunctionBarMargin = true;
  114. document.querySelector(".tfunc2").style.margin = "10px";
  115. }
  116. }
  117.  
  118. /* 刪除內建的移調鈕,建立自製的 */
  119. if (!observerCheckList.modifyTransposeButton) {
  120. if (document.querySelector(".capo .select")) {
  121. let stringCapo = document.querySelector(".capo .select").innerText.split(" / ")[0]; // CAPO
  122. let stringKey = document.querySelector(".capo .select").innerText.split(" / ")[1]; // 調
  123. for (let i of document.querySelectorAll(".capo span[play]")) {
  124. i.style.display = "none";
  125. }
  126. // 建立降調鈕
  127. let spanMinus = document.createElement("span");
  128. spanMinus.innerText = "-";
  129. spanMinus.className = "select";
  130. spanMinus.onclick = () => {
  131. spanCapo.innerText = spanCapo.innerText.replace(/-?\d+/, match => {
  132. return Number(match) - 1;
  133. });
  134. spanCapo.innerText = spanCapo.innerText.replace(/\(.+\)/, match => {
  135. return `(${transpose(match.slice(1, -1), 1)})`;
  136. });
  137. for (let i of document.querySelectorAll("#tone_z .tf")) {
  138. i.innerHTML = transpose(i.innerText, 1).replace(/(#|b)/g, "<sup>$&</sup>");
  139. }
  140. }
  141. // 當前調
  142. let spanCapo = document.createElement("span");
  143. spanCapo.innerText = `Capo: ${stringCapo} (${stringKey})`;
  144. // 建立降調鈕
  145. let spanPlus = document.createElement("span");
  146. spanPlus.innerText = "+";
  147. spanPlus.className = "select";
  148. spanPlus.onclick = () => {
  149. spanCapo.innerText = spanCapo.innerText.replace(/-?\d+/, match => {
  150. return Number(match) + 1;
  151. });
  152. spanCapo.innerText = spanCapo.innerText.replace(/\(.+\)/, match => {
  153. return `(${transpose(match.slice(1, -1), -1)})`;
  154. });
  155. for (let i of document.querySelectorAll("#tone_z .tf")) {
  156. i.innerHTML = transpose(i.innerText, -1).replace(/(#|b)/g, "<sup>$&</sup>");
  157. }
  158. }
  159. // 放入功能列
  160. for (let i of [spanMinus, spanCapo, spanPlus]) {
  161. document.querySelector(".plays .capo").appendChild(i);
  162. }
  163. observerCheckList.modifyTransposeButton = true;
  164. }
  165. }
  166. });
  167. observer.observe(document.body, { childList: true, subtree: true });
  168.  
  169. function transpose(chord, transposeValue) {
  170.  
  171. const keys = {
  172. "C": "[I]", "C#": "[I#]",
  173. "D": "[II]", "D#": "[II#]",
  174. "E": "[III]",
  175. "F": "[IV]", "F#": "[IV#]",
  176. "G": "[V]", "G#": "[V#]",
  177. "A": "[VI]", "A#": "[VI#]",
  178. "B": "[VII]"
  179. };
  180.  
  181. const pitchNameFix = {
  182. "#b": "", "b#": "",
  183. "E#": "F", "Fb": "E",
  184. "B#": "C", "Cb": "B",
  185. "C##": "D", "D##": "E",
  186. "F##": "G", "G##": "A",
  187. "A##": "B"
  188. };
  189.  
  190. let resultChord = chord;
  191.  
  192. for (let i = 0; i < 12; i++) {
  193. // first, transpose to Roman number.
  194. resultChord = resultChord.replaceAll(
  195. Object.keys(keys)[i],
  196. Object.values(keys)[i]
  197. );
  198. }
  199.  
  200. for (let i = 0; i < 12; i++) {
  201. // transpose offset
  202. let fixedTransposeValue = (i + transposeValue) % 12;
  203. if (fixedTransposeValue < 0) { fixedTransposeValue += 12 }
  204. // second, transpose to pitch names.
  205. resultChord = resultChord.replaceAll(
  206. Object.values(keys)[i],
  207. Object.keys(keys)[fixedTransposeValue]
  208. );
  209. }
  210.  
  211. for (let i = 0; i < 11; i++) {
  212. // fix illegal pitch names.
  213. resultChord = resultChord.replaceAll(
  214. Object.keys(pitchNameFix)[i],
  215. Object.values(pitchNameFix)[i]
  216. );
  217. }
  218.  
  219. return resultChord;
  220. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址