embyDouban

emby 里展示: 豆瓣 Bangumi bgm.tv 评分 链接 (豆瓣评论可关)

  1. // ==UserScript==
  2. // @name embyDouban
  3. // @name:zh-CN embyDouban
  4. // @name:en embyDouban
  5. // @namespace https://github.com/kjtsune/embyToLocalPlayer/tree/main/embyDouban
  6. // @version 2025.03.10
  7. // @description emby 里展示: 豆瓣 Bangumi bgm.tv 评分 链接 (豆瓣评论可关)
  8. // @description:zh-CN emby 里展示: 豆瓣 Bangumi bgm.tv 评分 链接 (豆瓣评论可关)
  9. // @description:en show douban Bangumi ratings in emby
  10. // @author Kjtsune
  11. // @match *://*/web/index.html*
  12. // @match *://*/*/web/index.html*
  13. // @match https://app.emby.media/*
  14. // @icon https://www.google.com/s2/favicons?sz=64&domain=emby.media
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // @connect api.bgm.tv
  19. // @connect api.douban.com
  20. // @connect movie.douban.com
  21. // @license MIT
  22. // ==/UserScript==
  23. 'use strict';
  24.  
  25. setModeSwitchMenu('enableDoubanComment', '豆瓣评论已经', '', '开启')
  26. let enableDoubanComment = (localStorage.getItem('enableDoubanComment') === 'false') ? false : true;
  27.  
  28. let config = {
  29. logLevel: 2,
  30. };
  31. let logger = {
  32. error: function (...args) {
  33. if (config.logLevel >= 1) {
  34. console.log('%cerror', 'color: yellow; font-style: italic; background-color: blue;', ...args);
  35. }
  36. },
  37. info: function (...args) {
  38. if (config.logLevel >= 2) {
  39. console.log('%cinfo', 'color: yellow; font-style: italic; background-color: blue;', ...args);
  40. }
  41. },
  42. debug: function (...args) {
  43. if (config.logLevel >= 3) {
  44. console.log('%cdebug', 'color: yellow; font-style: italic; background-color: blue;', ...args);
  45. }
  46. },
  47. }
  48.  
  49. async function sleep(ms) {
  50. return new Promise(resolve => setTimeout(resolve, ms));
  51. }
  52.  
  53. function switchLocalStorage(key, defaultValue = 'false', trueValue = 'true', falseValue = 'false') {
  54. if (key in localStorage) {
  55. let value = (localStorage.getItem(key) === trueValue) ? falseValue : trueValue;
  56. localStorage.setItem(key, value);
  57. } else {
  58. localStorage.setItem(key, defaultValue)
  59. }
  60. console.log('switchLocalStorage ', key, ' to ', localStorage.getItem(key))
  61. }
  62.  
  63. function setModeSwitchMenu(storageKey, menuStart = '', menuEnd = '', defaultValue = '关闭', trueValue = '开启', falseValue = '关闭') {
  64. let switchNameMap = { 'true': trueValue, 'false': falseValue, null: defaultValue };
  65. let menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
  66.  
  67. function clickMenu() {
  68. GM_unregisterMenuCommand(menuId);
  69. switchLocalStorage(storageKey)
  70. menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
  71. }
  72.  
  73. }
  74.  
  75. function isHidden(el) {
  76. return (el.offsetParent === null);
  77. }
  78.  
  79. function isEmpty(s) {
  80. return !s || s === 'N/A' || s === 'undefined';
  81. }
  82.  
  83. function getVisibleElement(elList) {
  84. if (!elList) { return; }
  85. if (NodeList.prototype.isPrototypeOf(elList)) {
  86. for (let i = 0; i < elList.length; i++) {
  87. if (!isHidden(elList[i])) {
  88. return elList[i];
  89. }
  90. }
  91. } else {
  92. console.log('%c%s', 'color: orange;', 'return raw ', elList);
  93. return elList;
  94. }
  95.  
  96. }
  97.  
  98. function cleanLocalStorage() {
  99. let count = 0
  100. for (i in localStorage) {
  101. if (i.search(/^tt/) != -1 || i.search(/^\d{7}/) != -1) {
  102. console.log(i);
  103. count++;
  104. localStorage.removeItem(i);
  105. }
  106. }
  107. console.log(`remove done, count=${count}`)
  108. }
  109.  
  110. function getURL_GM(url, data = null) {
  111. let method = (data) ? 'POST' : 'GET'
  112. return new Promise(resolve => GM.xmlHttpRequest({
  113. method: method,
  114. url: url,
  115. data: data,
  116. onload: function (response) {
  117. if (response.status >= 200 && response.status < 400) {
  118. resolve(response.responseText);
  119. } else {
  120. console.error(`Error ${method} ${url}:`, response.status, response.statusText, response.responseText);
  121. resolve();
  122. }
  123. },
  124. onerror: function (response) {
  125. console.error(`Error during GM.xmlHttpRequest to ${url}:`, response.statusText);
  126. resolve();
  127. }
  128. }));
  129. }
  130.  
  131. async function getJSON_GM(url, data = null) {
  132. const res = await getURL_GM(url, data);
  133. if (res) {
  134. return JSON.parse(res);
  135. }
  136. }
  137.  
  138. // async function getJSONP_GM(url) {
  139. // const data = await getURL_GM(url);
  140. // if (data) {
  141. // const end = data.lastIndexOf(')');
  142. // const [, json] = data.substring(0, end).split('(', 2);
  143. // return JSON.parse(json);
  144. // }
  145. // }
  146.  
  147. async function getJSON(url) {
  148. try {
  149. const response = await fetch(url);
  150. if (response.status >= 200 && response.status < 400)
  151. return await response.json();
  152. console.error(`Error fetching ${url}:`, response.status, response.statusText, await response.text());
  153. }
  154. catch (e) {
  155. console.error(`Error fetching ${url}:`, e);
  156. }
  157. }
  158.  
  159. function textSimilarity(text1, text2) {
  160. const len1 = text1.length;
  161. const len2 = text2.length;
  162. let count = 0;
  163. for (let i = 0; i < len1; i++) {
  164. if (text2.indexOf(text1[i]) != -1) {
  165. count++;
  166. }
  167. }
  168. const similarity = count / Math.min(len1, len2);
  169. return similarity;
  170. }
  171.  
  172. function getEmbyTitle() {
  173. let container = getVisibleElement(document.querySelectorAll('.itemPrimaryNameContainer'));
  174. if (!container) return '';
  175. let textTitle = container.querySelector('.itemName-primary');
  176. if (textTitle && textTitle.textContent) {
  177. return textTitle.textContent.trim();
  178. }
  179. let imgTitle = container.querySelector('.itemName-primary-logo img');
  180. if (imgTitle) {
  181. return imgTitle.getAttribute('alt')?.trim() || '';
  182. }
  183. return '';
  184. }
  185.  
  186. async function getDoubanInfo(imdbId) {
  187. if (!imdbId) {
  188. return;
  189. }
  190.  
  191. let embyTitle = getEmbyTitle();
  192. // const search = await getJSON_GM(`https://movie.douban.com/j/subject_suggest?q=${id}`);
  193. const search = await getJSON_GM(`https://movie.douban.com/j/subject_suggest?q=${embyTitle}`);
  194. if (search && search.length > 0 && search[0].id) {
  195. let doubanId = search[0].id;
  196. let doubanTitle = search[0].title;
  197. let doubanSubTitle = search[0].sub_title;
  198. if (textSimilarity(embyTitle, doubanTitle) < 0.4 && textSimilarity(embyTitle, doubanSubTitle) < 0.4) {
  199. logger.info(`douban title not match emby:${embyTitle} douban:${doubanTitle} ${doubanSubTitle}`);
  200. return;
  201. }
  202. const abstract = await getJSON_GM(`https://movie.douban.com/j/subject_abstract?subject_id=${doubanId}`);
  203. const average = abstract && abstract.subject && abstract.subject.rate ? abstract.subject.rate : '?';
  204. const comment = abstract && abstract.subject && abstract.subject.short_comment && abstract.subject.short_comment.content;
  205. return {
  206. id: doubanId,
  207. comment: comment,
  208. // url: `https://movie.douban.com/subject/${doubanId}/`,
  209. rating: { numRaters: '', max: 10, average },
  210. title: search[0].title,
  211. sub_title: search[0].sub_title,
  212. };
  213. }
  214. }
  215.  
  216. function insertDoubanComment(doubanId, doubanComment) {
  217. console.log('%c%o%s', 'color:orange;', 'start add comment ', doubanId)
  218. if (!enableDoubanComment) { return; }
  219. let commentKey = `${doubanId}Comment`;
  220. doubanComment = doubanComment || localStorage.getItem(commentKey);
  221. let el = getVisibleElement(document.querySelectorAll('div#doubanComment'));
  222. if (el || isEmpty(doubanComment)) {
  223. console.log('%c%s', 'color: orange', 'skip add doubanComment', el, doubanComment);
  224. return;
  225. }
  226. let embyComment = getVisibleElement(document.querySelectorAll('div.overview-text'));
  227. if (embyComment) {
  228. let parentNode = (ApiClient._serverVersion.startsWith('4.6')
  229. ) ? embyComment.parentNode : embyComment.parentNode.parentNode
  230. parentNode.insertAdjacentHTML('afterend', `<div id="doubanComment"><li>douban comment
  231. </li>${doubanComment}</li></div>`);
  232. console.log('%c%s', 'color: orange;', 'insert doubanComment ', doubanId, doubanComment);
  233. }
  234. }
  235.  
  236. function insertDoubanScore(doubanId, rating, socreIconHrefClass) {
  237. rating = rating || localStorage.getItem(doubanId);
  238. console.log('%c%s', 'color: orange;', 'start ', doubanId, rating);
  239. let el = getVisibleElement(document.querySelectorAll('a#doubanScore'));
  240. if (el || !rating) {
  241. console.log('%c%s', 'color: orange', 'skip add score', el, rating);
  242. return;
  243. }
  244. let yearDiv = getVisibleElement(document.querySelectorAll('div[class="mediaInfoItem"]'));
  245. if (yearDiv) {
  246. let doubanIco = '<img style="width:16px;" src="data:image/x-icon;base64,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AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP+RwIn////////////4+/j/GnwK/xF3AP8RdwD/EXcA/xF3AP8afAr/+Pv4////////////kcCJ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Xn0v///////////77auf8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP++2rn////////////V59L/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8rhhz/////////////////erNw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/3qzcP////////////////8rhhz/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/26sZP////////////////81jCf/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/NYwn/////////////////26sZP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/vdm4////////////5PDi/y+IIP8viCD/L4gg/y+IIP8viCD/L4gg/y+IIP8viCD/5PDi////////////vdm4/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ/////////////////////////////////////////////////////////////////////////////////////////////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD/////////////////////////////////////////////////////////////////////////////////////////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P///////////9vq2P+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP/b6tj////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ////////////tdWw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/7XVsP///////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD///////////+11bD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/tdWw////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P///////////7XVsP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP+11bD////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ////////////tdWw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/7XVsP///////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD////////////b6tj/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/2+rY////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P/////////////////////////////////////////////////////////////////////////////////////////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ/////////////////////////////////////////////////////////////////////////////////////////////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/+Lu4P//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4u7g/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/4u7g///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////i7uD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/i7uD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+Lu4P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AOYRdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwDmEXcASRF3AOYRdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA5hF3AEkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">'
  247. yearDiv.insertAdjacentHTML('beforebegin', `<div class="starRatingContainer mediaInfoItem douban">${doubanIco}<a id="doubanScore"
  248. href="https://movie.douban.com/subject/${doubanId}/" ${socreIconHrefClass}>${rating}</a></div>`);
  249. console.log('%c%s', 'color: orange;', 'insert score ', doubanId, rating);
  250. }
  251. console.log('%c%s', 'color: orange;', 'finish ', doubanId, rating);
  252. }
  253.  
  254. function imdbIconLinkAdder(imdbHref, socreIconHrefClass) {
  255. let imdbDiv = getVisibleElement(document.querySelectorAll('div[class="starRatingContainer mediaInfoItem"]'));
  256. if (isEmpty(imdbDiv)) { return; }
  257. if (imdbDiv.querySelector('#imdbScoreLink')) { return; }
  258. let imdbScore = imdbDiv.textContent.match(/[0-9.]+/);
  259. imdbDiv.lastChild.remove();
  260. imdbDiv.insertAdjacentHTML('beforeend', `<a id="imdbScoreLink"
  261. href="${imdbHref}" ${socreIconHrefClass}>${imdbScore}</a>`)
  262. }
  263.  
  264. async function insertDoubanMain(linkZone) {
  265. if (isEmpty(linkZone)) { return; }
  266. let doubanButton = linkZone.querySelector('a[href*="douban.com"]');
  267. let imdbButton = linkZone.querySelector('a[href^="https://www.imdb"]');
  268. if (doubanButton || !imdbButton) { return; }
  269. let imdbId = imdbButton.href.match(/tt\d+/);
  270. if (!imdbId) {
  271. return;
  272. }
  273. let socreIconHrefClass = 'class="button-link button-link-color-inherit emby-button" style="font-weight:inherit;" target="_blank"';
  274. imdbIconLinkAdder(imdbButton.href, socreIconHrefClass);
  275.  
  276. if (imdbId in localStorage) {
  277. var doubanId = localStorage.getItem(imdbId);
  278. if (!doubanId) { return; }
  279. } else {
  280. await getDoubanInfo(imdbId).then(function (data) {
  281. if (!isEmpty(data)) {
  282. let doubanId = data.id;
  283. localStorage.setItem(imdbId, doubanId);
  284. if (data.rating && !isEmpty(data.rating.average)) {
  285. insertDoubanScore(doubanId, data.rating.average, socreIconHrefClass);
  286. localStorage.setItem(doubanId, data.rating.average);
  287. localStorage.setItem(doubanId + 'Info', JSON.stringify(data));
  288. }
  289. if (enableDoubanComment) {
  290. insertDoubanComment(doubanId, data.comment);
  291. localStorage.setItem(doubanId + 'Comment', data.comment);
  292. }
  293. }
  294. console.log('%c%o%s', 'background:yellow;', data, ' result and send a requests')
  295. });
  296. var doubanId = localStorage.getItem(imdbId);
  297. }
  298. console.log('%c%o%s', 'color:orange;', 'douban id ', doubanId, String(imdbId));
  299. if (!doubanId) {
  300. localStorage.setItem(imdbId, '');
  301. return;
  302. }
  303. let buttonClass = imdbButton.className;
  304. let doubanString = `<a is="emby-linkbutton" class="${buttonClass}"
  305. href="https://movie.douban.com/subject/${doubanId}/" target="_blank">
  306. <i class="md-icon button-icon button-icon-left">link</i>Douban</a>`;
  307. imdbButton.insertAdjacentHTML('beforebegin', doubanString);
  308. insertDoubanScore(doubanId, undefined, socreIconHrefClass);
  309. insertDoubanComment(doubanId);
  310. }
  311.  
  312. function insertBangumiByPath(idNode) {
  313. let el = getVisibleElement(document.querySelectorAll('a#bangumibutton'));
  314. if (el) { return; }
  315. let id = idNode.textContent.match(/(?<=bgm\=)\d+/);
  316. let bgmHtml = `<a id="bangumibutton" is="emby-linkbutton" class="raised item-tag-button nobackdropfilter emby-button" href="https://bgm.tv/subject/${id}" target="_blank"><i class="md-icon button-icon button-icon-left">link</i>Bangumi</a>`
  317. idNode.insertAdjacentHTML('beforebegin', bgmHtml);
  318. }
  319.  
  320. function insertBangumiScore(bgmObj, infoTable, linkZone) {
  321. if (!bgmObj) return;
  322. let bgmRate = infoTable.querySelector('a#bgmScore');
  323. if (bgmRate) return;
  324.  
  325. let yearDiv = infoTable.querySelector('div[class="mediaInfoItem"]');
  326. let bgmHref = `https://bgm.tv/subject/${bgmObj.id}`;
  327. if (yearDiv && bgmObj.trust) {
  328. let socreIconHrefClass = 'class="button-link button-link-color-inherit emby-button" style="font-weight:inherit;" target="_blank"';
  329. let bgmIco = '<img style="width:16px;" src="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJu+f//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn//////7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvnAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn/sm75////////////sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+cCwaPn/sGj5/9iz/P///////////////////////////////////////////////////////////9iz/P+waPn/rF/6/6xf+v//////////////////////////////////////////////////////////////////////rF/6/6lW+/+pVvv/////////////////////////////////zXn2/////////////////////////////////6lW+/+lTfz/pU38///////Nefb/zXn2/8159v//////zXn2///////Nefb//////8159v/Nefb/zXn2//////+lTfz/okT8/6JE/P//////////////////////2bb8/8159v/Nefb/zXn2/9m2/P//////////////////////okT8/546/f+eOv3//////8159v/Nefb/zXn2////////////////////////////zXn2/8159v/Nefb//////546/f+bMf7/mzH+//////////////////////////////////////////////////////////////////////+bMf7/lyj+wJco/v/Mk/7////////////////////////////////////////////////////////////Mk///lyj+wJQf/xCUH//AlB///5Qf//+UH///lB///5Qf//+aP///mj///5o///+UH///lB///5Qf//+UH///lB//wJQf/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/5o////Nefb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAM159v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNefb/AAAAAAAAAAAAAAAA+f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/j8AAP3fAAD77wAA9/cAAA==">'
  330. yearDiv.insertAdjacentHTML('beforebegin', `<div class="starRatingContainer mediaInfoItem bgm">${bgmIco}
  331. <a id="bgmScore" href="${bgmHref}" ${socreIconHrefClass}>${bgmObj.score}</a></div>`);
  332. console.log('%c%s', 'color: orange;', 'insert bgmScore ', bgmObj.score);
  333. }
  334. let tmdbButton = linkZone.querySelector('a[href^="https://www.themovie"]');
  335. let bgmButton = linkZone.querySelector('a[href^="https://bgm.tv"]');
  336. if (bgmButton) return;
  337. let buttonClass = tmdbButton.className;
  338. let bgmString = `<a is="emby-linkbutton" class="${buttonClass}" href="${bgmHref}" target="_blank"><i class="md-icon button-icon button-icon-left">link</i>Bangumi</a>`;
  339. tmdbButton.insertAdjacentHTML('beforebegin', bgmString);
  340. }
  341.  
  342. function checkIsExpire(key, expireDay = 1) {
  343. let timestamp = localStorage.getItem(key);
  344. if (!timestamp) return true;
  345. let expireMs = expireDay * 864E5;
  346. if (Number(timestamp) + expireMs < Date.now()) {
  347. localStorage.removeItem(key)
  348. logger.info(key, 'IsExpire, old', timestamp, 'now', Date.now());
  349. return true;
  350. } else {
  351. return false;
  352. }
  353.  
  354. }
  355.  
  356. async function insertBangumiMain(infoTable, linkZone) {
  357. if (!infoTable || !linkZone) return;
  358. let mediaInfoItems = infoTable.querySelectorAll('div[class="mediaInfoItem"] > a');
  359. let isAnime = 0;
  360. mediaInfoItems.forEach(tagItem => {
  361. if (tagItem.textContent && tagItem.textContent.search(/动画|Anim/) != -1) { isAnime++ }
  362. });
  363. if (isAnime == 0) {
  364. if (mediaInfoItems.length > 2) return;
  365. let itemGenres = getVisibleElement(document.querySelectorAll('div[class*="itemGenres"]'));
  366. if (!itemGenres) return;
  367. itemGenres = itemGenres.querySelectorAll('a')
  368. itemGenres.forEach(tagItem => {
  369. if (tagItem.textContent && tagItem.textContent.search(/动画|Anim/) != -1) { isAnime++ }
  370. });
  371. if (isAnime == 0) return;
  372. };
  373.  
  374. let bgmRate = infoTable.querySelector('a#bgmScore');
  375. if (bgmRate) return;
  376.  
  377. let tmdbButton = linkZone.querySelector('a[href^="https://www.themovie"]');
  378. if (!tmdbButton) return;
  379. let tmdbId = tmdbButton.href.match(/...\d+/);
  380.  
  381. let tmdbExpireKey = tmdbId + 'expire'
  382. let year = infoTable.querySelector('div[class="mediaInfoItem"]').textContent.match(/^\d{4}/);
  383. let expireDay = (Number(year) < new Date().getFullYear() && new Date().getMonth() + 1 != 1) ? 30 : 3
  384. let needUpdate = false;
  385. if (tmdbExpireKey in localStorage) {
  386. if (checkIsExpire(tmdbExpireKey, expireDay)) {
  387. needUpdate = true;
  388. localStorage.setItem(tmdbExpireKey, JSON.stringify(Date.now()));
  389. }
  390. } else {
  391. localStorage.setItem(tmdbExpireKey, JSON.stringify(Date.now()));
  392. }
  393.  
  394. let tmdbBgmKey = tmdbId + 'bgm';
  395. let bgmObj = localStorage.getItem(tmdbBgmKey);
  396. if (bgmObj && !needUpdate) {
  397. bgmObj = JSON.parse(bgmObj)
  398. insertBangumiScore(bgmObj, infoTable, linkZone);
  399. return;
  400. }
  401.  
  402. let tmdbNotBgmKey = tmdbId + 'NotBgm';
  403. if (!checkIsExpire(tmdbNotBgmKey)) {
  404. return;
  405. }
  406. let userId = ApiClient._serverInfo.UserId;
  407. let itemId = /\?id=(\d*)/.exec(window.location.hash)[1];
  408. let itemInfo = await ApiClient.getItems(userId, {
  409. 'Ids': itemId,
  410. 'Fields': 'OriginalTitle,PremiereDate'
  411. })
  412. itemInfo = itemInfo['Items'][0]
  413. let title = itemInfo.Name;
  414. let originalTitle = itemInfo.OriginalTitle;
  415.  
  416. let splitRe = /[/\/]/;
  417. if (splitRe.test(originalTitle)) { //纸片人
  418. logger.info(originalTitle);
  419. let zprTitle = originalTitle.split(splitRe);
  420. for (let _i in zprTitle) {
  421. let _t = zprTitle[_i];
  422. if (/[あいうえおかきくけこさしすせそたちつてとなにぬねのひふへほまみむめもやゆよらりるれろわをんー]/.test(_t)) {
  423. originalTitle = _t;
  424. break
  425. } else {
  426. originalTitle = zprTitle[0];
  427. }
  428. }
  429. }
  430.  
  431. let premiereDate = new Date(itemInfo.PremiereDate);
  432. premiereDate.setDate(premiereDate.getDate() - 2);
  433. let startDate = premiereDate.toISOString().slice(0, 10);
  434. premiereDate.setDate(premiereDate.getDate() + 4);
  435. let endDate = premiereDate.toISOString().slice(0, 10);
  436.  
  437. logger.info('bgm ->', originalTitle, startDate, endDate);
  438. let bgmInfo = await getJSON_GM('https://api.bgm.tv/v0/search/subjects?limit=10', JSON.stringify({
  439. 'keyword': originalTitle,
  440. // "keyword": 'titletitletitletitletitletitletitle',
  441. 'filter': {
  442. 'type': [
  443. 2
  444. ],
  445. 'air_date': [
  446. `>=${startDate}`,
  447. `<${endDate}`
  448. ],
  449. 'nsfw': true
  450. }
  451. }))
  452. logger.info('bgmInfo', bgmInfo['data'])
  453. bgmInfo = (bgmInfo['data']) ? bgmInfo['data'][0] : null;
  454. if (!bgmInfo) {
  455. localStorage.setItem(tmdbNotBgmKey, JSON.stringify(Date.now()));
  456. logger.error('getJSON_GM not bgmInfo return');
  457. return;
  458. };
  459.  
  460. let trust = false;
  461. if (textSimilarity(originalTitle, bgmInfo['name']) < 0.4 && (textSimilarity(title, bgmInfo['name_cn'])) < 0.4
  462. && (textSimilarity(title, bgmInfo['name'])) < 0.4) {
  463. localStorage.setItem(tmdbNotBgmKey, JSON.stringify(Date.now()));
  464. logger.error('not bgmObj and title not Similarity, skip');
  465. } else {
  466. trust = true
  467. }
  468. let score = bgmInfo.score ? bgmInfo.score : bgmInfo.rating?.score;
  469. logger.info(bgmInfo)
  470. bgmObj = {
  471. id: bgmInfo['id'],
  472. score: score,
  473. name: bgmInfo['name'],
  474. name_cn: bgmInfo['name_cn'],
  475. trust: trust,
  476. }
  477. localStorage.setItem(tmdbBgmKey, JSON.stringify(bgmObj));
  478. insertBangumiScore(bgmObj, infoTable, linkZone);
  479. }
  480.  
  481. function cleanDoubanError() {
  482. let expireKey = 'doubanErrorExpireKey';
  483. let needClean = false;
  484. if (expireKey in localStorage) {
  485. if (checkIsExpire(expireKey, 3)) {
  486. needClean = true
  487. localStorage.setItem(expireKey, JSON.stringify(Date.now()));
  488. }
  489. } else {
  490. localStorage.setItem(expireKey, JSON.stringify(Date.now()));
  491. }
  492. if (!needClean) return;
  493.  
  494. let count = 0
  495. for (let i in localStorage) {
  496. if (i.search(/^tt\d+/) != -1 && localStorage.getItem(i) === '') {
  497. console.log(i);
  498. count++;
  499. localStorage.removeItem(i);
  500. }
  501. }
  502. logger.info(`cleanDoubanError done, count=${count}`);
  503. }
  504.  
  505. var runLimit = 50;
  506.  
  507. async function main() {
  508. let linkZone = getVisibleElement(document.querySelectorAll('div[class*="linksSection"]'));
  509. let infoTable = getVisibleElement(document.querySelectorAll('div[class*="flex-grow detailTextContainer"]'));
  510. if (infoTable && linkZone) {
  511. if (!infoTable.querySelector('h3.itemName-secondary')) { // not eps page
  512. insertDoubanMain(linkZone);
  513. await insertBangumiMain(infoTable, linkZone)
  514. } else {
  515. let bgmIdNode = document.evaluate('//div[contains(text(), "[bgm=")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  516. if (bgmIdNode) { insertBangumiByPath(bgmIdNode) };
  517. }
  518. }
  519. if (runLimit > 50) {
  520. cleanDoubanError();
  521. runLimit = 0
  522. }
  523. }
  524.  
  525. (function loop() {
  526. setTimeout(async function () {
  527. // if (runLimit > 5) return;
  528. await main();
  529. loop();
  530. runLimit += 1
  531. }, 700);
  532. })();

QingJ © 2025

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