Minyami

Get Minyami download commands for supported sites

  1. // ==UserScript==
  2. // @name Minyami
  3. // @description Get Minyami download commands for supported sites
  4. // @namespace Violentmonkey Scripts
  5. // @include *
  6. // @exclude *://*.paypal.com/*
  7. // @grant GM_notification
  8. // @grant GM_registerMenuCommand
  9. // @grant GM_unregisterMenuCommand
  10. // @grant GM_setClipboard
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @version 0.0.1.20250618200853
  14. // ==/UserScript==
  15.  
  16. (async () => {
  17. let stream = {};
  18. let command = "";
  19. let completed = false;
  20.  
  21. if (! (GM_getValue("server", "") === "none" || /^https?:\/\//g.test(GM_getValue("server", "")))) {
  22. await GM_setValue("server", prompt("Enter your download Server URL (to disable this feature input: none)", ""))
  23. };
  24. const server = GM_getValue("server", "");
  25.  
  26. if (sessionStorage.getItem("streams") === null) {
  27. sessionStorage.setItem("streams", "[]");
  28. }
  29.  
  30. function createCommand(stream) {
  31. return `minyami -d "${stream.url}" --output "${stream.title}.ts" --key "${stream.key}" --live --threads 50`
  32. }
  33.  
  34. function sendToServer(stream) {
  35. if (server !== "none") {
  36. fetch(server, {
  37. method: "POST",
  38. body: createCommand(stream),
  39. headers: { "Content-type": "text/plain; charset=UTF-8" }
  40. });
  41. }
  42. }
  43.  
  44. function getDate(offset) {
  45. const date = new Date();
  46.  
  47. // Subtract the offset in hours
  48. date.setHours(date.getHours() - offset);
  49.  
  50. const year = date.getFullYear();
  51. const month = String(date.getMonth() + 1).padStart(2, '0'); // Ensures two-digit format
  52. const day = String(date.getDate()).padStart(2, '0'); // Ensures two-digit format
  53.  
  54. return `${year}-${month}-${day}`;
  55. }
  56.  
  57. function registerDownloadAllMenu() {
  58. let streams = JSON.parse(sessionStorage.getItem("streams"));
  59. if (streams.length > 1) {
  60. let commands = ""
  61. streams.forEach((stream, i) => {
  62. commands += createCommand(stream) + (i < streams.length - 1 ? "\n" : "");
  63. });
  64. GM_registerMenuCommand("📋🔄️ Copy and clear all Minyami commands", () => {
  65. GM_setClipboard(commands);
  66. GM_notification("All Minyami commands copied!", "Minyami");
  67. sessionStorage.setItem("streams", "[]");
  68. GM_unregisterMenuCommand(`📋 ${stream.title}`);
  69. GM_unregisterMenuCommand("📋🔄️ Copy and clear all Minyami commands");
  70. }, {
  71. title: `${streams.length} Minyami commands`
  72. });
  73. }
  74. }
  75.  
  76. registerDownloadAllMenu();
  77.  
  78. const notify = function(object) {
  79. if (completed === false) {
  80. switch (object.type) {
  81. case "chunklist":
  82. let timestamp = document.querySelectorAll("div#root div#video-page-wrapper span.MuiTypography-root.MuiTypography-caption")[0].innerHTML
  83. if (/^20[0-9]{2}\/[0-9]{2}\/[0-9]{2}$/g.test(timestamp)) {
  84. stream.title = timestamp.replaceAll("\/", "-") + " - " + object.title;
  85. } else if (/^[0-9]日前/g.test(timestamp)) {
  86. stream.title = getDate((timestamp.replace("日前",""))*24) + " - " + object.title;
  87. } else if (/.*時間前/g.test(timestamp)) {
  88. stream.title = getDate(timestamp.replace("時間前","")) + " - " + object.title;
  89. } else {
  90. stream.title = object.title;
  91. };
  92.  
  93. stream.url = object.url;
  94. stream.m3u8 = new URL(object.url).pathname.split('/').pop();
  95. break;
  96. case "key":
  97. stream.key = object.key;
  98. completed = true;
  99. break;
  100. };
  101.  
  102. if (completed === true) {
  103. let streams = JSON.parse(sessionStorage.getItem("streams"));
  104. streams = streams.filter(obj => obj.m3u8 !== stream.m3u8);
  105. streams.push(stream);
  106. sessionStorage.setItem("streams", JSON.stringify(streams));
  107. registerDownloadAllMenu();
  108.  
  109. GM_registerMenuCommand(`📋 ${stream.title}`, () => {
  110. GM_setClipboard(createCommand(stream));
  111. GM_notification("Minyami command copied!", "Minyami");
  112. });
  113.  
  114. sendToServer(stream);
  115. };
  116. };
  117. };
  118.  
  119. // ========================================== unmodified Code below (from https://raw.githubusercontent.com/Last-Order/Minyami-chrome-extension/refs/heads/master/assets/scripts/inject_core.js) ==========================================
  120.  
  121. window.addEventListener("unload", notify({ type: "page_url", url: window.location.href }), false);
  122. const escapeFilename = (filename) => {
  123. return filename.replace(/[\/\*\\\:|\?<>"!]/gi, "_");
  124. };
  125. let key = "";
  126. if (window.fetch) {
  127. const _fetch = fetch;
  128. fetch = (url, ...fargs) => {
  129. return new Promise((resolve, reject) => {
  130. _fetch(url, ...fargs)
  131. .then(async (res) => {
  132. resolve(res);
  133. return res.clone();
  134. })
  135. .then(async (r) => {
  136. if (r.url.match(/\.m3u8(\?|$)/)) {
  137. const responseText = await r.text();
  138. let title = escapeFilename(document.title);
  139. let streamName;
  140. switch (location.host) {
  141. case "abema.tv": {
  142. if (document.querySelector(".c-tv-SwitchAngleButton-current-angle-name")) {
  143. streamName = document.querySelector(
  144. ".c-tv-SwitchAngleButton-current-angle-name"
  145. ).innerText;
  146. }
  147. }
  148. }
  149. if (responseText.match(/#EXT-X-STREAM-INF/) !== null) {
  150. notify({
  151. type: "playlist",
  152. content: responseText,
  153. url: r.url,
  154. title,
  155. streamName
  156. });
  157. } else {
  158. const keyUrlMatch = responseText.match(/#EXT-X-KEY:.*URI="(.*)"/);
  159. notify({
  160. type: "chunklist",
  161. content: responseText,
  162. url: r.url,
  163. title,
  164. ...(keyUrlMatch && { keyUrl: keyUrlMatch[1] })
  165. });
  166. }
  167. switch (location.host) {
  168. case "spwn.jp": {
  169. spwn();
  170. }
  171. }
  172. }
  173. })
  174. .catch((e) => {
  175. reject(e);
  176. });
  177. });
  178. };
  179. }
  180. XMLHttpRequest.prototype._open = XMLHttpRequest.prototype.open;
  181. Object.defineProperty(XMLHttpRequest.prototype, "open", {
  182. get: function() {
  183. return this._open;
  184. },
  185. set: function(f) {
  186. this._open = new Proxy(f, {
  187. apply: function(f, instance, fargs) {
  188. listen.call(instance, ...fargs);
  189. return f.call(instance, ...fargs);
  190. }
  191. });
  192. }
  193. });
  194. XMLHttpRequest.prototype.open = XMLHttpRequest.prototype.open;
  195. const listen = function() {
  196. this.addEventListener("load", function() {
  197. if (this.readyState === 4 && new URL(this.responseURL).pathname.endsWith("m3u8")) {
  198. let title;
  199. switch (location.host) {
  200. case "nogidoga.com": {
  201. title = escapeFilename(document.querySelector(".EpisodePage__Title").innerText);
  202. break;
  203. }
  204. case "www.dmm.co.jp": {
  205. title = escapeFilename(document.querySelector(".title")?.innerText);
  206. break;
  207. }
  208. }
  209. if (this.responseText.match(/#EXT-X-STREAM-INF/) !== null) {
  210. notify({
  211. type: "playlist",
  212. content: this.responseText,
  213. url: this.responseURL,
  214. title: title || escapeFilename(document.title)
  215. });
  216. } else {
  217. const keyUrlMatch = this.responseText.match(/#EXT-X-KEY:.*URI="(.*)"/);
  218. notify({
  219. type: "chunklist",
  220. content: this.responseText,
  221. url: this.responseURL,
  222. title: title || escapeFilename(document.title),
  223. ...(keyUrlMatch && { keyUrl: keyUrlMatch[1] })
  224. });
  225. }
  226. // Execute after m3u8 loads
  227. switch (location.host) {
  228. case "live2.nicovideo.jp":
  229. case "live.nicovideo.jp": {
  230. nico(this);
  231. break;
  232. }
  233. case "www.dmm.com":
  234. case "www.dmm.co.jp": {
  235. dmm(this);
  236. break;
  237. }
  238. case "spwn.jp": {
  239. spwn(this);
  240. break;
  241. }
  242. }
  243. }
  244. // Execute when first AJAX request finished
  245. switch (location.host) {
  246. case "www.dmm.com":
  247. case "www.dmm.co.jp": {
  248. dmm(this);
  249. break;
  250. }
  251. case "www.360ch.tv": {
  252. ch360(this);
  253. break;
  254. }
  255. case "hibiki-radio.jp": {
  256. matchurl(this, "datakey");
  257. break;
  258. }
  259. case "www.onsen.ag": {
  260. matchurl(this, "key.m3u8key");
  261. break;
  262. }
  263. case "www.showroom-live.com": {
  264. showroom(this);
  265. break;
  266. }
  267. case "nicochannel.jp":
  268. case "gs-ch.com":
  269. case "qlover.jp":
  270. case "pizzaradio.jp":
  271. case "kemomimirefle.net":
  272. case "tenshi-nano.com":
  273. case "ado-dokidokihimitsukichi-daigakuimo.com":
  274. case "canan8181.com":
  275. case "keisuke-ueda.jp":
  276. case "p-jinriki-fc.com":
  277. case "rnqq.jp":
  278. case "ryogomatsumaru.com":
  279. case "takahashifumiya.com":
  280. case "yamingfc.net": {
  281. matchurl(this, "https://hls-auth.cloud.stream.co.jp/key");
  282. break;
  283. }
  284. }
  285. });
  286. };
  287. /**
  288. * Site Scripts
  289. */
  290. /**
  291. * Get key for Abema!
  292. */
  293. const abema = () => {
  294. Object.defineProperty(__CLIENT_REGION__, "isAllowed", {
  295. get: () => true
  296. });
  297. Object.defineProperty(__CLIENT_REGION__, "status", {
  298. get: () => false
  299. });
  300. const _Uint8Array = Uint8Array;
  301. Uint8Array = class extends _Uint8Array {
  302. constructor(...args) {
  303. super(...args);
  304. if (this.length === 16) {
  305. const key = Array.from(new _Uint8Array(this))
  306. .map((i) => (i.toString(16).length === 1 ? "0" + i.toString(16) : i.toString(16)))
  307. .join("");
  308. if (key !== "00000000000000000000000000000000") {
  309. notify({
  310. type: "key",
  311. key: key
  312. });
  313. }
  314. }
  315. return this;
  316. }
  317. };
  318. };
  319.  
  320. const matchurl = (xhr, keyword) => {
  321. if (xhr.readyState === 4 && xhr.responseURL.includes(keyword)) {
  322. const key = Array.from(new Uint8Array(xhr.response))
  323. .map((i) => (i.toString(16).length === 1 ? "0" + i.toString(16) : i.toString(16)))
  324. .join("");
  325. notify({
  326. type: "key",
  327. key: key,
  328. url: xhr.responseURL
  329. });
  330. }
  331. };
  332.  
  333. const nico = (xhr) => {
  334. try {
  335. const liveData = JSON.parse(document.querySelector("#embedded-data").getAttribute("data-props"));
  336. const websocketUrl = liveData.site.relive.webSocketUrl;
  337. if (websocketUrl.match(/audience_token=(.+)/)[1]) {
  338. key = websocketUrl.match(/audience_token=(.+)/)[1];
  339. }
  340. if (liveData.program?.stream?.maxQuality) {
  341. key += `,${liveData.program.stream.maxQuality}`;
  342. }
  343. notify({
  344. type: "key",
  345. key: key,
  346. });
  347. } catch {}
  348. };
  349.  
  350. const spwn = () => {
  351. notify({
  352. type: "cookies",
  353. cookies:
  354. "CloudFront-Policy=" +
  355. document.cookie.match(/CloudFront-Policy\=(.+?)(;|$)/)[1] +
  356. "; " +
  357. "CloudFront-Signature=" +
  358. document.cookie.match(/CloudFront-Signature\=(.+?)(;|$)/)[1] +
  359. "; " +
  360. "CloudFront-Key-Pair-Id=" +
  361. document.cookie.match(/CloudFront-Key-Pair-Id\=(.+?)(;|$)/)[1] +
  362. "; "
  363. });
  364. };
  365.  
  366. const dmm = (xhr) => {
  367. if (
  368. xhr.readyState === 4 &&
  369. (xhr.responseURL.match(new RegExp("https://www.dmm.(com|co.jp)/service/-/drm_iphone")) ||
  370. xhr.responseURL.startsWith("https://mlic.dmm.co.jp/drm/hlsaes/key/"))
  371. ) {
  372. const key = Array.from(new Uint8Array(xhr.response))
  373. .map((i) => (i.toString(16).length === 1 ? "0" + i.toString(16) : i.toString(16)))
  374. .join("");
  375. notify({
  376. type: "cookies",
  377. cookies: "licenseUID=" + document.cookie.match(/licenseUID\=(.+?)(;|$)/)[1]
  378. });
  379. notify({
  380. type: "key",
  381. key: key,
  382. url: xhr.responseURL
  383. });
  384. }
  385. };
  386.  
  387. const ch360 = (xhr) => {
  388. notify({
  389. type: "cookies",
  390. cookies: "ch360pt=" + document.cookie.match(/ch360pt\=(.+?)(;|$)/)[1]
  391. });
  392. };
  393.  
  394. const twicas = async () => {
  395. const userName = location.href.match(/https:\/\/twitcasting.tv\/(.+?)(\/|$)/);
  396. if (userName && !location.href.includes("/movie/")) {
  397. await fetch(`https://twitcasting.tv/${userName[1]}/metastream.m3u8`);
  398. }
  399. if (location.href.includes("/movie/")) {
  400. const playlistInfo = document.querySelector("video").getAttribute("data-movie-playlist");
  401. if (playlistInfo) {
  402. const parsedPlaylistInfo = JSON.parse(playlistInfo)[2][0];
  403. const url = parsedPlaylistInfo.source.url;
  404. const title = escapeFilename(document.querySelector("#movie_title_content").innerText);
  405. notify({
  406. type: "playlist_chunklist",
  407. content: "",
  408. url,
  409. title,
  410. chunkLists: [
  411. {
  412. type: "video",
  413. resolution: {
  414. x: "Unknown",
  415. y: "Unknown"
  416. },
  417. url
  418. }
  419. ]
  420. });
  421. }
  422. }
  423. };
  424.  
  425. const youtube = async () => {
  426. const playerResponse = ytplayer.config.args.raw_player_response;
  427. if (playerResponse) {
  428. const HlsManifestUrl = playerResponse.streamingData.hlsManifestUrl;
  429. await fetch(HlsManifestUrl);
  430. }
  431. notify({
  432. type: "cookies",
  433. cookies: "PREF=" + document.cookie.match(/PREF\=(.+?)(;|$)/)[1]
  434. });
  435. };
  436.  
  437. const showroom = (xhr) => {
  438. if (xhr.responseURL.includes("api/live/streaming_url")) {
  439. const response = JSON.parse(xhr.responseText);
  440. if (response.streaming_url_list.some((i) => i.url.endsWith("chunklist.m3u8"))) {
  441. notify({
  442. type: "playlist_chunklist",
  443. content: xhr.responseText,
  444. url: xhr.responseURL,
  445. title: escapeFilename(document.title),
  446. chunkLists: response.streaming_url_list
  447. .filter((i) => i.url.endsWith("chunklist.m3u8"))
  448. .map((i) => {
  449. return {
  450. type: "video",
  451. bandwidth: i.quality * 1024,
  452. resolution: {
  453. x: "Unknown",
  454. y: "Unknown"
  455. },
  456. url: i.url
  457. };
  458. })
  459. });
  460. }
  461. }
  462. };
  463.  
  464. const bilibili = async () => {
  465. if (!location.href.match(/live\.bilibili\.com\/(?:blanc\/)*(\d+)/)) {
  466. return;
  467. }
  468. const roomId = location.href.match(/live\.bilibili\.com\/(?:blanc\/)*(\d+)/)[1];
  469. const api = `https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id=${roomId}&protocol=0,1&format=0,1,2&codec=0,1,2&qn=10000&platform=web&ptype=16`;
  470. const roomLiveInfo = await (await fetch(api)).json();
  471. if (!roomLiveInfo?.data?.playurl_info?.playurl?.stream) {
  472. return;
  473. }
  474. const hlsInfo = roomLiveInfo.data.playurl_info.playurl.stream.find((i) => i.protocol_name === "http_hls");
  475. const streamFormats = hlsInfo.format;
  476. const chunkLists = [];
  477. for (const format of streamFormats) {
  478. const codec = format.codec[0];
  479. if (codec.url_info[0].host && codec.base_url) {
  480. const chunkListUrl = codec.url_info[0].host + codec.base_url + (codec.url_info[0].extra || "");
  481. fetch(chunkListUrl);
  482. }
  483. }
  484. };
  485.  
  486. const asobistore = () => {
  487. const url = document.querySelector("#embed_placeholder source").getAttribute("src");
  488. fetch(url);
  489. };
  490.  
  491. // Execute when load
  492. switch (location.host) {
  493. case "abema.tv": {
  494. abema();
  495. break;
  496. }
  497. case "twitcasting.tv": {
  498. twicas();
  499. break;
  500. }
  501. case "www.youtube.com": {
  502. youtube();
  503. break;
  504. }
  505. case "live.bilibili.com": {
  506. bilibili();
  507. break;
  508. }
  509. case "playervspf.channel.or.jp": {
  510. asobistore();
  511. break;
  512. }
  513. }
  514. })();

QingJ © 2025

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