main.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. class Home {
  2. static start() {
  3. this.cache = {
  4. items: undefined,
  5. item: new Map(),
  6. };
  7. this.itemQuery = { ImageTypes: "Backdrop", EnableImageTypes: "Logo,Backdrop", IncludeItemTypes: "Movie,Series", SortBy: "ProductionYear, PremiereDate, SortName", Recursive: true, ImageTypeLimit: 1, Limit: 10, Fields: "ProductionYear", SortOrder: "Descending", EnableUserData: false, EnableTotalRecordCount: false };
  8. this.coverOptions = { type: "Backdrop", maxWidth: 3000 };
  9. this.logoOptions = { type: "Logo", maxWidth: 3000 };
  10. setInterval(() => {
  11. if (window.location.href.indexOf("!/home") != -1) {
  12. if ($(".view:not(.hide) .misty-banner").length == 0 && $(".misty-loading").length == 0) {
  13. this.initLoading();
  14. }
  15. if ($(".hide .misty-banner").length != 0) {
  16. $(".hide .misty-banner").remove();
  17. this.init();
  18. }
  19. if ($(".section0 .card").length != 0 && $(".view:not(.hide) .misty-banner").length == 0) {
  20. this.init();
  21. }
  22. }
  23. }, 100);
  24. }
  25. static async init() {
  26. // Beta
  27. $(".view:not(.hide)").attr("data-type", "home");
  28. await this.initBanner();
  29. this.initEvent();
  30. }
  31. /* 插入Loading */
  32. static initLoading() {
  33. const load = `
  34. <div class="misty-loading">
  35. <h1>MISTY MEDIA</h1>
  36. <div class="mdl-spinner"><div class="mdl-spinner__layer mdl-spinner__layer-1"><div class="mdl-spinner__circle-clipper mdl-spinner__left"><div class="mdl-spinner__circle mdl-spinner__circleLeft"></div></div><div class="mdl-spinner__circle-clipper mdl-spinner__right"><div class="mdl-spinner__circle mdl-spinner__circleRight"></div></div></div></div>
  37. </div>
  38. `;
  39. $("body").append(load);
  40. }
  41. static injectCode(code) {
  42. let hash = md5(code + Math.random().toString());
  43. return new Promise((resolve, reject) => {
  44. const channel = new BroadcastChannel(hash);
  45. channel.addEventListener("message", (event) => resolve(event.data));
  46. const script = `
  47. <script class="I${hash}">
  48. setTimeout(async ()=> {
  49. async function R${hash}(){${code}};
  50. const channel = new BroadcastChannel("${hash}");
  51. channel.postMessage(await R${hash}());
  52. document.querySelector("script.I${hash}").remove()
  53. }, 16)
  54. </script>
  55. `;
  56. $(document.head || document.documentElement).append(script);
  57. });
  58. }
  59. static injectCall(func, arg) {
  60. const script = `
  61. // const client = (await window.require(["ApiClient"]))[0];
  62. const client = await new Promise((resolve, reject) => {
  63. setInterval(() => {
  64. if (window.ApiClient != undefined) resolve(window.ApiClient);
  65. }, 16);
  66. });
  67. return await client.${func}(${arg})
  68. `;
  69. return this.injectCode(script);
  70. }
  71. static getItems(query) {
  72. if (this.cache.items == undefined) {
  73. this.cache.items = this.injectCall("getItems", "client.getCurrentUserId(), " + JSON.stringify(query));
  74. }
  75. return this.cache.items;
  76. }
  77. static getItem(itemId) {
  78. if (!this.cache.item.has(itemId)) {
  79. this.cache.item.set(itemId, this.injectCall("getItem", `client.getCurrentUserId(), "${itemId}"`));
  80. }
  81. return this.cache.item.get(itemId);
  82. }
  83. static getImageUrl(itemId, options) {
  84. return this.injectCall("getImageUrl", itemId + ", " + JSON.stringify(options));
  85. }
  86. /* 插入Banner */
  87. static async initBanner() {
  88. const banner = `
  89. <div class="misty-banner">
  90. <div class="misty-banner-body">
  91. </div>
  92. <div class="misty-banner-library">
  93. <div class="misty-banner-logos"></div>
  94. </div>
  95. </div>
  96. `;
  97. $(".view:not(.hide) .homeSectionsContainer").prepend(banner);
  98. $(".view:not(.hide) .section0").detach().appendTo(".view:not(.hide) .misty-banner-library");
  99. // 插入数据
  100. const data = await this.getItems(this.itemQuery);
  101. console.log(data);
  102. data.Items.forEach(async (item) => {
  103. const detail = await this.getItem(item.Id),
  104. itemHtml = `
  105. <div class="misty-banner-item" id="${detail.Id}">
  106. <img draggable="false" loading="eager" decoding="async" class="misty-banner-cover" src="${await this.getImageUrl(detail.Id, this.coverOptions)}" alt="Backdrop" style="">
  107. <div class="misty-banner-info padded-left padded-right">
  108. <h1>${detail.Name}</h1>
  109. <div><p>${detail.Overview}</p></div>
  110. <div><button onclick="appRouter.showItem('${detail.Id}')">MORE</button></div>
  111. </div>
  112. </div>
  113. `,
  114. logoHtml = `
  115. <img id="${detail.Id}" draggable="false" loading="auto" decoding="lazy" class="misty-banner-logo" data-banner="img-title" alt="Logo" src="${await this.getImageUrl(detail.Id, this.logoOptions)}">
  116. `;
  117. if (detail.ImageTags && detail.ImageTags.Logo) {
  118. $(".misty-banner-logos").append(logoHtml);
  119. }
  120. $(".misty-banner-body").append(itemHtml);
  121. console.log(item.Id, detail);
  122. });
  123. let complete = 0;
  124. let loading = setInterval(async () => {
  125. // 判断图片加载完毕
  126. $(".misty-banner-cover:not(.complete)").each((i, dom) => {
  127. if (dom.complete) {
  128. dom.classList.add("complete");
  129. complete++;
  130. }
  131. });
  132. if (complete == $(".misty-banner-item").length && $(".misty-banner-item").length != 0) {
  133. clearInterval(loading);
  134. $(".misty-loading").fadeOut(500, () => $(".misty-loading").remove());
  135. await CommonUtils.sleep(150);
  136. // 置入场动画
  137. let delay = 80; // 动媒体库画间隔
  138. let id = $(".misty-banner-item").eq(0).addClass("active").attr("id"); // 初次信息动画
  139. $(`.misty-banner-logo[id=${id}]`).addClass("active");
  140. await CommonUtils.sleep(200); // 间隔动画
  141. $(".section0 > div").addClass("misty-banner-library-overflow"); // 关闭overflow 防止媒体库动画溢出
  142. $(".misty-banner .card").each((i, dom) => setTimeout(() => $(dom).addClass("misty-banner-library-show"), i * delay)); // 媒体库动画
  143. await CommonUtils.sleep(delay * 8 + 1000); // 等待媒体库动画完毕
  144. $(".section0 > div").removeClass("misty-banner-library-overflow"); // 开启overflow 防止无法滚动
  145. // 滚屏逻辑
  146. var index = 0;
  147. clearInterval(this.bannerInterval);
  148. this.bannerInterval = setInterval(async () => {
  149. // 背景切换
  150. index += index + 1 == $(".misty-banner-item").length ? -index : 1;
  151. $(".misty-banner-body").css("left", -(index * 100).toString() + "%");
  152. // 信息切换
  153. $(".misty-banner-item.active").removeClass("active");
  154. let id = $(".misty-banner-item").eq(index).addClass("active").attr("id");
  155. // LOGO切换
  156. $(".misty-banner-logo.active").removeClass("active");
  157. $(`.misty-banner-logo[id=${id}]`).addClass("active");
  158. }, 8000);
  159. }
  160. }, 16);
  161. }
  162. /* 初始事件 */
  163. static initEvent() {
  164. // 通过注入方式, 方可调用appRouter函数, 以解决Content-Script window对象不同步问题
  165. const script = `
  166. // 挂载appRouter
  167. if (!window.appRouter) window.appRouter = (await window.require(["appRouter"]))[0];
  168. // 获取带有 is 属性为 emby-itemscontainer 的元素
  169. const element = document.querySelector('.section0 div[is="emby-itemscontainer"]');
  170. // 创建新元素,并将旧元素的所有子节点添加到其中
  171. const newElement = document.createElement("div");
  172. while (element.firstChild) {
  173. newElement.appendChild(element.firstChild);
  174. }
  175. // 移除 is 属性以隔离原事件
  176. newElement.removeAttribute("is");
  177. // 复制原始元素的 class 属性到新元素中
  178. Array.from(element.classList).forEach(cls => {
  179. newElement.classList.add(cls);
  180. });
  181. // 将新元素插入到原始元素的位置上
  182. element.parentNode.insertBefore(newElement, element);
  183. // 重新挂载媒体库事件
  184. const librarys = document.querySelectorAll(".view:not(.hide) .section0 .card");
  185. librarys.forEach(library => {
  186. library.addEventListener("click", () => {
  187. const dataId = library.getAttribute("data-id");
  188. appRouter.showItem(dataId)
  189. });
  190. });
  191. `;
  192. this.injectCode(script);
  193. }
  194. }
  195. // 运行
  196. if ($("meta[name=application-name]").attr("content") == "Emby" || $(".accent-emby") != undefined) {
  197. Home.start();
  198. }