b站直播徽章切换增强

展示全部徽章,展示更多信息,更方便切换,可以自动切换徽章

Installer ce script?
Script suggéré par l'auteur

Vous aimerez aussi b站自动续牌.

Installer ce script
  1. // ==UserScript==
  2. // @name b站直播徽章切换增强
  3. // @version 1.2.9
  4. // @description 展示全部徽章,展示更多信息,更方便切换,可以自动切换徽章
  5. // @author Pronax
  6. // @include /https:\/\/live\.bilibili\.com\/(blanc\/)?\d+/
  7. // @icon http://bilibili.com/favicon.ico
  8. // @grant GM_addStyle
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js
  12. // @require https://greasyfork.org/scripts/439903-blive-room-info-api/code/blive_room_info_api.js?version=1037039
  13. // @namespace http://tampermonkey.net/
  14. // ==/UserScript==
  15.  
  16. // ! csrf过期后调用原生徽章按钮不会刷新状态
  17. // ! bug: 有一个最近获得的粉丝牌置顶,然后获取了新粉丝牌,且新粉丝牌为当前房间(最新获得被当前房间顶掉)
  18.  
  19. function main() {
  20. 'use strict';
  21.  
  22. // 设置是否支持使用拼音查找粉丝牌/ID
  23. // 为false时关闭此功能
  24. let pinyinSwitch = true;
  25.  
  26.  
  27.  
  28. if (document.querySelector(".medal-section:not(.scripted)")) {
  29. let controlPanelCtnrBox = document.querySelector(".medal-section:not(.scripted)");
  30. let template = document.createElement("div");
  31. template.className = "medal-section scripted";
  32. template.innerHTML = `<span id="medal-selector" class="dp-i-block medal"><span class="action-item medal get-medal"></span></span>`;
  33. for (let key in controlPanelCtnrBox.dataset) {
  34. template.dataset[key] = controlPanelCtnrBox.dataset[key];
  35. }
  36. controlPanelCtnrBox.after(template);
  37. // controlPanelCtnrBox.classList.add("origin"); // 加了没用,这个元素每次聚焦都会消失
  38. controlPanelCtnrBox.style.display = "none";
  39. }
  40.  
  41. // 活动直播间的CSS调整
  42. setTimeout(() => {
  43. let svgIconList = document.querySelectorAll('.svg-icon');
  44. for (let index = 0; index < svgIconList.length && index < 10; index++) {
  45. const element = svgIconList[index];
  46. if (element) {
  47. let computedStyle = getComputedStyle(element);
  48. let backgroundImage = computedStyle.getPropertyValue('background-image');
  49. // 普通页面用base64覆盖了http的,活动页面还是http
  50. if (backgroundImage && backgroundImage.includes("http")) {
  51. GM_addStyle(".des>.svg-icon{background-position:0 -6em !important}.des>.svg-icon.checkbox-selected{background-position:0 -7em !important}");
  52. }
  53. break;
  54. }
  55. }
  56. }, 3000);
  57. // 加载动画
  58. GM_addStyle(".medal-loading{height:30px;color:#bbb;font-size:13px;display:flex;align-items:center;justify-content:center}.medal-loading>i.icon-link-world{font-size:12px;margin-left:5px;animation:medal-loading-rotate 2s infinite}.medal-loading>i.icon-info{margin-right:5px}@keyframes medal-loading-rotate{from{transform:rotate(45deg)}to{transform:rotate(405deg)}}");
  59. // body内的条目css
  60. GM_addStyle(".medal-list-move{transition:transform .5s!important}.medal-wear-body{height:335px;margin-top:5px;padding-right:2px;overflow:auto;scrollbar-width:thin}.medal-wear-body::-webkit-scrollbar{width:6px}.medal-wear-body::-webkit-scrollbar-thumb{background-color:#aaa}.medal-item-content{display:flex;justify-content:space-between}.medal-wear-body .medal-item{padding:5px 5px 3px;background:0;border:1px solid transparent;border-radius:5px;width:calc(100% - 12px);text-align:left;transition:border,background .2s}.medal-wear-body .medal-item:hover{border:1px solid #d7d7d7;background-color:#f5f5f5}.medal-item .face,.medal-item .search-user-avatar{width:auto;height:35px;margin-right:5px;padding:1px;position:relative;transition:filter .3s}.medal-item .face:hover,.medal-item .search-user-avatar:hover{filter:drop-shadow(0px 0 3px #fb7299);cursor:alias}.medal-item .face>img{height:35px;border-radius:50%}.medal-wear-body .medal-item .name{color:#666;position:relative;max-width:calc(100% - 78px);font-size:14px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.medal-wear-body .medal-item .name:hover{color:#00aeec}.medal-wear-body .medal-item .living-gif{background-image:url(//s1.hdslb.com/bfs/static/blive/live-fansmedal-wall/static/img/icon-online.fd4254c1.gif);background-size:cover;width:16px;height:16px;transform:rotateY(180deg)}.medal-item .wear-icon{background-color:#fb7299;padding:0 2px;color:#fff;height:16px;border:1px solid #fb7299;border-radius:4px;line-height:16px;font-size:14px}.medal-item .room-icon{padding:0 2px;color:#fea249;height:16px;border:1px solid #fea249;border-radius:4px;line-height:16px;font-size:14px}.medal-item .content-icon{padding:0 2px;color:#40bf55;height:16px;border:1px solid #40bf55;border-radius:4px;line-height:16px;font-size:14px}.medal-wear-body .medal-item .text{color:#888;position:relative;line-height:18px;font-size:14px}.medal-wear-body .medal-item .left{color:#2cbce7;line-height:18px;font-size:14px;margin-right:5px}.medal-item-content .medal-content-head{height:18px}.medal-item-content .medal-content-footer{height:18px;padding-top:1px;width:100%}.medal-wear-body .medal-item .progress-level-div{margin-top:3px;width:100%;text-align:center;display:flex;justify-content:space-between;font-size:13px}.medal-wear-body .medal-item .progress-level-div .level-span-left{text-align:right!important}.medal-wear-body .medal-item .progress-level-div .level-span{width:33px;color:#999;padding-top:1px}.medal-wear-body .medal-item .progress-level-div .progress-div{line-height:16px;height:14px;width:70%;background-color:#e2e8ec;border-radius:2px;margin:0 2px;position:relative;overflow:hidden}.medal-wear-body .medal-item .progress-level-div .progress-div-cover{position:absolute;left:0;top:0;overflow:hidden;background-color:#23ade5}.medal-wear-body .medal-item .progress-level-div .progress-div .progress-num-span{color:#23ade5}.medal-wear-body .medal-item .progress-level-div .progress-div-cover .progress-num-span-cover{width:174px;position:relative;z-index:1000;color:#fff}.medal-item.outdated{opacity:.5;filter:grayscale(0.5);}");
  61. // 面板css
  62. GM_addStyle(".chat-input-ctnr .medal-section{position:static;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0 12px;min-width:70px;height:56px;border-right:1px solid #e9eaec;box-sizing:border-box}.medal-section .action-item.medal.get-medal,.medal-section .action-item.medal.wear-medal{width:41px;height:24px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFYAAAAyCAMAAADx7dyJAAAA51BMVEUAAACampqZmZmampqZmZmampqampqZmZmdnZ2oqKiamprU1NSZmZmZmZmZmZmvr6+ampqampqamprIyMiZmZmampqampqZmZmZmZmampqenp6cnJyfn5+ampqbm5uampqenp7Q0NDOzs6ampqamprPz8/Ozs6ampqbm5vMzMyZmZmZmZnNzc2bm5ubm5uZmZmampqcnJyampqampqampqampqamprNzc2amprU1NTU1NTU1NSamprV1dXS0tLR0dHT09PU1NTT09PW1tbS0tLMzMzt7e2ZmZnj4+PPz8/V1dXo6OjZ2dk1f/nUAAAARXRSTlMA49Zr8zV1SzkGgKukj4UL+q5iA97uw1mpiSAbDupeQRX6eLPNPBnIT+bm0rI9JsFlLdmWm41G+buqn35xMBHr2r5WRCgCpQL/AAADqUlEQVRYw7TS226qQBiG4Q8cUAg7hbHIRo1Y0epCa2vbA0/Jf/93tBhApYqJK3G9yT+QQJ4ME/D/et2s8qe12rxW6ix/crNyr/n3bIGntZh952K/G6E/s1m+AbDKF3hqi3wFIM9xlariqoGCOx19iEK/i8kUIiG2spKE32lxmhmNXiBy7aKExKplpE75PPo3tkudHnnyKbIgGtO5TkA6vpzEbWOVXlUc9+o0lBnOtEd/cIpVbDAumpNYM8Giw5Q2ltF1AUQ2S3DLupqoT+UloFGxhprbwh7VKs7Vuqj+2hfBGm9lcs0a1JL1+NnaHimC3ZnmnJumVbOTTjNKy4vyOKtTxQIw5cbZhvq5CLyPqkfZgDntrELG4SDJh0NCIfZDYGJMb9mweyqOz7ch4DujitUZc4ix9MIugyDtB8GuYIcyoFMLu6OWdrBJfanYkXfcxsdEvrCMc8fhnBWsz4GfNW7ZbFA1lzmX54OqDLBcwU5IGQ3FIagNVgG2BrCkUOzUZeb9s+1SV5KWjoJLgl1ScMt6+z1j+71EoXhnQsu7bMC3kCRs1/ZvtkP2NRtNJuu0Gg3vNPh0pvdYLXXeBZuxxP3F6gxnlluof1xvXYxc/q6xwfu4x5o0gGCxpE+3yX54F1YaQWR5nsOq6QCGQ1932MigT1QsLErsBttPL6xnoouyYVKOaEweEGotrOKR4Z5YjGgeXFjJr1ldxvojIgWR7/uxVMxbcRNhRx+A7rnXrKs64smZhUVsd2LfaVyysS79YNvPKELUbxQFjGIbvoxr1qS/tdZvS8JQFMfxHy2SFNxqJJUau6ggCdnSxDacf093d/P9v56Gd7Gcyx2ofZ8Jh88D72Ecy8BPFk+XspmyV9I5sFa/NcN0NOzqR+sPk0d+dwBnZBnWg/08PWHdvotjFm6rnbLjPmB2kj9B/764Aczbluw9wja6l3XnTRqoy7mccT81mr2vIyllMZdDoG31Dgg6zTt38gpgIsf2GbZWw1EvhoOOXutrHGojycF3JnR6ETL2fyOq6Pyo6Fiq6LSr5hCt8GzOWgiKPnOFRA3kahCF+bmIxAI6tvoXNyhWPwC2G+C0Dalilesq2hRthiIVFqhMN1SkCvd0sCSK4rzKc+OIaDlAYbYv0tnstRiunhW+jd8K1sksS9VuNrsOcC6fVJypPDdW5KOkFe0zlefuaYWydoriTOW4MakdSvNIaZXrKvJQnilIq1yXhAlGW6E8sPOU2IKXjeL4w19GXEgqF0Zf/gAAAABJRU5ErkJggg==)}.medal-section .action-item.medal{background-size:cover;border:0}.medal-section .action-item{display:inline-block;margin:0 2px;font-size:12px;color:#fff;line-height:14px;text-align:center;border-radius:2px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.dialog-ctnr.medal{z-index:999;padding:10px 14px 10px 16px;position:absolute}.medal-ctnr{width:268px}.medal-wear-component>.title{font-weight:400;font-size:18px;margin:0;color:#23ade5;line-height:20px}.medal-search{width:160px;margin-left:10px;position:relative;line-height:20px;top:-1px;font-size:14px;font-weight:100;border:0;padding:0;color:var(--Pi4)}.medal-search::placeholder{color:#dcdcdc}.des{cursor:pointer;color:#666;height:20px;display:flex;align-items:center}.des>.svg-icon{width:14px;height:14px;font-size:14px;background-position:0-8em;margin-right:5px}.des>span.pointer{line-height:14px}.des>.svg-icon.checkbox-selected{background-position:0-9em}.qs-icon{width:14px;height:14px;background-size:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAAXNSR0IArs4c6QAAA79JREFUSA29lk1IVFEYhpsZf0AwDDc5RlJBYepK+rHQFhUutaCdiyL8w5JWkijlIjNISBrTUYha2MZAjBZRmEERGa1CSwqysrSVCw2DGNGe93LP5TreOzOL8MC55zvf+37fe/7uuTewJYXS0NBQvLa2Vk0th54fCATCCqM/TzNH/xV1NBqNTsmfqAQSgfX19VUk7YRTRMIp7KfYs9gSkqCEd9KvxC7G/oDdNjAw8Ei4V/EUrKurU5IhkhwmaIDaMzg4+MUrgfERswf7ErWe2Alia4iZNbhpNwiyfEchj1CnIZ1PJmQSmdYWvotoIfU0y/zaYGrXCdpiz/HfQ7AZsZib3NLSkr24uKhZl5Asd3V19XMoFJro7+//5OYhmk4/Aucs9bhb1BG0l/EdxBH2oNGdQDb7WcEgHmDuoC6T6Df97bSr9O9kZ2e3dnd3L2M7hZxR8FPwDpjlDRoUYAj7o2ZmfKZtbGw8gX8cznwwGCwJh8NbGVReVlbWNjg3qU1LS0u3Dd/VXiRu2s5tua0Z2qfxIZ7C+D0bHh4OjY2NvRc7MzOzNBKJ/LUiXQ9mEqF7IS0t7WBfX59WySlgOkwSPaPTa82QUXTiiMaLKWp8fFzvXhEz6/ISE4cyqAd7ekituyincktD/iAjKKFVwh430dgkKZJNO2F88W1OTs43+Ui6Kx5T385dpAskiHoVvklO2owXmVMo/w327bsXLh8nd59aBD05yo2OLo7qNB7ldHSDeBbITwBUfQs52gBjGRkZj/1IcKRRrj3Mp264EfwC4/0cuOv4qhl0d29v79d43PTBNft8LWmYat2NBky1Zf+7GHkr8RFebs3St0hDWs576Mv0ARC7CnSZ2slxbybZmg91nVt7qNlZn5t1SIIOy1gGfIXYWxz79gRUB4IblpZmOIdR4CCpGbXELEFNSUwpbY057aE+npWp6TisI1hvmN0fx5PEkIa0JDiKejH35e4kMW74J523bkciW7mlIS2Lx55MUr0u30R5UsaUWxoKMKe0nRE02Bdt0kRwA0lJNkE5lZvrzXptnEBG8BJA37aT7E3ML2FTU9PeWCz2AnyG6+5YR0eHYjwLYuks4zPAEK9OhUhmhjpFNYD6LUi4tCsrK6XE6TUqW1hYyFUSv2Ln2q/chuPMUA7ziwHhPt2LXjO1R10L/oNRe96dNkcDP4eo9y8GoFU29SfKiDLCzftNNKJqOUj6Vl5jifUOWT/CtLr1fwmn5IEV4LN+hMXB165fCQv1eKzbQw/cculLTeL/8qv/D4FH4W+V11VGAAAAAElFTkSuQmCC);cursor:pointer;position:relative;top:1px}.link-radio-button-ctnr{display:inline-block;cursor:default;vertical-align:middle;font-size:0}.footer-line{position:relative;left:-16px;width:300px;border-top:1px solid #f0f0f0;margin-top:3px}.medal-wear-footer{margin-top:10px;font-size:14px;color:#23ade5;justify-content:space-between}.medal-wear-footer>*{cursor:pointer}.medal-wear-footer a{color:#23ade5}.medal-wear-footer .right-span{float:right}.medal-wear-footer .arrow-box{width:10px;height:10px;font-size:10px;position:relative;top:2px}");
  63. // 直播中 头像动画
  64. GM_addStyle(".search-user-avatar.avatar-small .avatar-wrap{transform:scale(.8)}.search-user-avatar .avatar-wrap{width:100%;height:35px}.medal-item .bili-avatar{display:block;position:relative;background-image:url(data:image/gif;base64,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);background-size:cover;border-radius:50%;margin:0;padding:0;width:35px;height:35px}.medal-item .bili-avatar .bili-avatar-img{border:1px solid var(--line_light)}.medal-item .bili-avatar-img-radius{border-radius:50%}.medal-item .bili-avatar-img{border:0;display:block;-o-object-fit:cover;object-fit:cover;image-rendering:-webkit-optimize-contrast}.medal-item .bili-avatar-face{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:100%;height:100%}.medal-item .bili-avatar *{margin:0;padding:0}.medal-item .bili-avatar-right-icon{width:27.5%;height:27.5%;position:absolute;z-index:2;right:0;bottom:-1px;background-size:cover;image-rendering:-webkit-optimize-contrast;background-image:url(data:image/png;base64,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)}.search-user-avatar .avatar-wrap.live-ani .a-cycle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:35px;height:35px;border:1px solid #f69;border-radius:50%;z-index:1;opacity:0;animation:scaleUpCircle 1.5s linear;animation-iteration-count:infinite}.search-user-avatar .avatar-wrap.live-ani .a-cycle-1{animation-delay:0s}.search-user-avatar .avatar-wrap.live-ani .a-cycle-2{animation-delay:.5s}.search-user-avatar .avatar-wrap.live-ani .a-cycle-3{animation-delay:1s}@keyframes scaleUpCircle{0%{transform:translate(-50%,-50%) scale(1);opacity:1}100%{transform:translate(-50%,-50%) scale(1.5);opacity:0}}");
  65. // 旧的粉丝牌样式
  66. GM_addStyle(".old-style.fans-medal-item{border:1px solid #fff;border-radius:2px;padding:0;height:16px}.old-style .fans-medal-label{height:100%;border-radius:0;padding:0 3px}.old-style .fans-medal-label .fans-medal-content{transform:none}.old-style .fans-medal-level{width:17px;height:14px;border-radius:0}");
  67. // 调用原始按钮更新粉丝牌,暂时隐藏弹窗的css
  68. GM_addStyle(".panel-hide .medalAb{display:none}");
  69.  
  70. // 公用对象
  71. let pinyinPro = undefined;
  72. let my_id = document.cookie.match(/DedeUserID=(\d*); /)[1];
  73. let originMedalSelectorDebounce = null;
  74. let pinyin = pinyinSwitch;
  75. // 默认粉丝牌UID 如果拥有该用户的粉丝牌会在退出直播间时切换到这个粉丝牌
  76. // 废弃功能,不要用
  77. let defaultMedalUid = false;
  78.  
  79. new Vue({
  80. el: '#medel_switch_box',
  81. async created() {
  82. let json = await this.getFansMedalInfo();
  83. let assignMedal = true;
  84. if (this.autoSwitch && json.has_fans_medal) {
  85. assignMedal = false;
  86. this.switchBadge(json.my_fans_medal.medal_id);
  87. }
  88. this.refreshMedalList(1, assignMedal);
  89. if (pinyin) {
  90. fetch("https://unpkg.com/pinyin-pro@3.13.0/dist/index.js")
  91. .then(res => res.text(), err => { })
  92. .then(js => {
  93. if (!js) { console.warn("徽章切换增强-启动拼音组件失败"); return; }
  94. try {
  95. js = js.replace("pinyinPro", "biliSwitchBoostPinyinPro");
  96. eval(js);
  97. pinyinPro = window.biliSwitchBoostPinyinPro;
  98. Reflect.deleteProperty(window, 'biliSwitchBoostPinyinPro');
  99. this.backUpMedalWall.forEach(item => {
  100. // 用户名
  101. item.anchor_info.nick_pinyin = pinyinPro.pinyin(item.anchor_info.nick_name, { toneType: 'none', nonZh: 'consecutive', v: true }).replaceAll(" ", "").toLowerCase();
  102. // 粉丝牌
  103. item.medal.medal_pinyin = pinyinPro.pinyin(item.medal.medal_name, { toneType: 'none', nonZh: 'consecutive', v: true }).replaceAll(" ", "").toLowerCase();
  104. });
  105. } catch (error) {
  106. console.warn("徽章切换增强-启动拼音组件错误", error);
  107. }
  108. });
  109. }
  110. },
  111. mounted: function () {
  112. document.querySelector("#medal-selector").onclick = () => {
  113. this.togglePanel();
  114. };
  115. // 如果有默认粉丝牌,则退出直播间时换上默认粉丝牌
  116. if (defaultMedalUid) {
  117. this.getFansMedalInfo(defaultMedalUid, (json, context) => {
  118. if (json.has_fans_medal == false) { return; }
  119. let index = this.medalWallIndex.indexOf(json.my_fans_medal.medal_id);
  120. window.addEventListener('beforeunload', () => {
  121. context.switchBadge(json.my_fans_medal.medal_id, index);
  122. });
  123. });
  124. }
  125. window.addEventListener('click', e => {
  126. if (e.target.closest(".medal") == null && this.panelStatus) {
  127. this.panelStatus = false;
  128. document.querySelector(".medal-wear-body").scrollTop = 0;
  129. }
  130. });
  131. window.addEventListener('blur', () => { this.isTabBlur = true; });
  132. window.addEventListener('focus', this.refreshScriptInfo);
  133. // // 鼠标移动到礼物栏、弹幕输入区域时进行自动切换
  134. // let initMouseEventDeadLine = Date.now() + 15000;
  135. // (function initMouseEvent(vueInstance) {
  136. // let giftDom = document.querySelector("#gift-control-vm .gift-panel");
  137. // let inputDom = document.querySelector("#control-panel-ctnr-box");
  138. // if (giftDom && inputDom) {
  139. // giftDom.onmouseenter = inputDom.onmouseenter = () => {
  140. // // console.log("徽章更换检测1", vueInstance.isTabBlur);
  141. // if (vueInstance.isTabBlur) {
  142. // vueInstance.refreshScriptInfo();
  143. // }
  144. // // console.log("徽章更换检测2", vueInstance.isTabBlur);
  145. // let cRoomMedal = vueInstance.fansMedalInfo.my_fans_medal.medal_id;
  146. // if (vueInstance.autoSwitch && vueInstance.needSwitch && cRoomMedal != 0) {
  147. // // console.log("徽章触发更换");
  148. // vueInstance.switchBadge(cRoomMedal, vueInstance.medalWallIndex.indexOf(cRoomMedal));
  149. // vueInstance.needSwitch = false;
  150. // }
  151. // };
  152. // } else if (Date.now() < initMouseEventDeadLine) {
  153. // requestIdleCallback(() => {
  154. // initMouseEvent();
  155. // }, { timeout: 3000 });
  156. // } else {
  157. // console.log("切换模块初始化失败");
  158. // }
  159. // })(this);
  160. this.$refs.medalList.$el.addEventListener('scroll', () => {
  161. let medalList = this.$refs.medalList.$el;
  162. let baseline = medalList.scrollHeight - medalList.offsetHeight;
  163. // 滚动条到最后10%的时候开始加载下一页
  164. if (this.pageInfo.isLastPage == false && this.pageInfo.loading == false && medalList.scrollTop > baseline - baseline * .1) {
  165. this.pageInfo.loading = true;
  166. this.refreshMedalList(this.pageInfo.cPage + 1);
  167. }
  168. }, false);
  169. },
  170. computed: {
  171. medalWallIndex: function () {
  172. let indexList = [];
  173. this.medalWall.forEach(item => {
  174. indexList.push(item.medal.medal_id);
  175. });
  176. return indexList;
  177. },
  178. backUpMedalWallIndex: function () {
  179. let indexList = [];
  180. this.backUpMedalWall.forEach(item => {
  181. indexList.push(item.medal.medal_id);
  182. });
  183. return indexList;
  184. }
  185. },
  186. data() {
  187. return {
  188. name: Date.now().toString(16) + "-" + btoa(location.host),
  189. fansMedalInfo: {
  190. "has_fans_medal": false,
  191. "my_fans_medal": {
  192. "target_id": 0,
  193. "medal_id": 0
  194. }
  195. },
  196. currentlyWearing: {
  197. medal: {
  198. medal_id: 0
  199. }
  200. },
  201. recentAward: {
  202. medal: {
  203. medal_id: 0
  204. }
  205. },
  206. autoSwitch: false,
  207. // b站自己做了自动切换功能,所以我就不做了,这里改一下默认值防止有些人开着然后永远关不了了
  208. // autoSwitch: GM_getValue(`autoSwitch-${my_id}`, false),
  209. needSwitch: false,
  210. panelStatus: false,
  211. pageInfo: {
  212. loading: false,
  213. cPage: 1,
  214. isLastPage: true
  215. },
  216. /*
  217. 本来是用于展示的,但是牌子多加载需要翻页的情况显示的全是脏数据
  218. 现在仅用于缓存存在的牌子提速自动换牌的速度
  219. */
  220. backUpMedalWall: GM_getValue(`medalWall-${my_id}`, []),
  221. medalWall: [],
  222. debounce: {},
  223. search: "",
  224. label: "",
  225. isTabBlur: false, // 标识网页是否失焦
  226. }
  227. },
  228. watch: {
  229. currentlyWearing: {
  230. handler(val, oldVal) {
  231. // 持久化用于从其他tab取出信息
  232. GM_setValue(`currentlyWearing-${my_id}`, val);
  233. clearTimeout(originMedalSelectorDebounce);
  234. this.refreshMedal();
  235. /*
  236. 新旧ID相同的情况下也刷新牌子显示,因为牌子的数据可能会有变化
  237. 但是牌子相同的情况下不需要调用网页刷新
  238. */
  239. if (oldVal && val.medal.medal_id == oldVal.medal.medal_id) {
  240. return;
  241. }
  242. // 借用原始徽章按钮来刷新当前页面上的徽章缓存
  243. // 用于其他原版消息发送时展示最新的牌子(比如发表情弹幕)
  244. originMedalSelectorDebounce = setTimeout(() => {
  245. let originMedelBtn = document.querySelector(".medal-section:not(.scripted)>span");
  246. let medelPanelParent = document.querySelector(".control-panel-ctnr-new");
  247. if (!originMedelBtn || !medelPanelParent) {
  248. console.log("徽章刷新失败-找不到原版按钮");
  249. return;
  250. }
  251. originMedelBtn.click();
  252. medelPanelParent.classList.add("panel-hide");
  253. // 检测窗口
  254. let deadLine = Date.now() + 1000;
  255. let interval = setInterval(() => {
  256. if (Date.now() > deadLine) {
  257. clearInterval(interval);
  258. medelPanelParent.classList.remove("panel-hide");
  259. console.log("徽章刷新失败-无法抓取到原版徽章页面");
  260. }
  261. // 弹出后,点击展示设置
  262. let originMedelBox = document.querySelector(".medalAb");
  263. if (originMedelBox) {
  264. clearInterval(interval);
  265. originMedelBox.dispatchEvent(new Event('mouseleave'));
  266. // 立刻执行会闪一下,观感很不好
  267. setTimeout(() => { medelPanelParent.classList.remove("panel-hide"); }, 500);
  268. }
  269. }, 100);
  270. }, 1000);
  271. },
  272. immediate: false
  273. },
  274. autoSwitch(val) {
  275. GM_setValue(`autoSwitch-${my_id}`, val);
  276. if (val) {
  277. let cRoomMedal = this.fansMedalInfo.my_fans_medal.medal_id;
  278. if (cRoomMedal != 0) {
  279. this.switchBadge(cRoomMedal, this.medalWallIndex.indexOf(cRoomMedal));
  280. this.needSwitch = false;
  281. }
  282. }
  283. },
  284. search(val) {
  285. clearTimeout(this.debounce["search"]);
  286. let vm = this;
  287. this.debounce["search"] = setTimeout(function () {
  288. if (val) {
  289. val = val.toLowerCase().trim();
  290. vm.medalWall = vm.backUpMedalWall.filter((item) => {
  291. if (item.medal.medal_name.toLowerCase().includes(val) || item.anchor_info.nick_name.toLowerCase().includes(val)) {
  292. item.score = 2;
  293. return true;
  294. }
  295. if (!pinyinPro) { return false; }
  296. if (item.medal.medal_pinyin.includes(val) || item.anchor_info.nick_pinyin.includes(val)) {
  297. item.score = 1;
  298. return true;
  299. }
  300. return false;
  301. });
  302. vm.medalWall.sort(vm.sort);
  303. vm.pageInfo.isLastPage = true;
  304. } else {
  305. vm.medalWall = vm.backUpMedalWall.slice(0, 50);
  306. vm.pageInfo.isLastPage = vm.backUpMedalWall.length < 50;
  307. }
  308. }, 200);
  309. }
  310. },
  311. methods: {
  312. /* 将10进制数字转为16进制字符串,不足6位时自动补充 */
  313. padToHex(str) {
  314. return str.toString(16).padStart(6, 0);
  315. },
  316. async sleep(ms) {
  317. return new Promise(r => {
  318. setTimeout(() => {
  319. r(true);
  320. }, ms);
  321. });
  322. },
  323. async getCurrentWear() { // 获取当前佩戴粉丝牌
  324. let res = await fetch(`https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel?page=1&page_size=1`, { credentials: 'include', });
  325. let json = await res.json();
  326. if (json.code == json.message) {
  327. for (const item of json.data.special_list) {
  328. if (item.superscript == null) {
  329. this.currentlyWearing = item;
  330. break;
  331. }
  332. }
  333. return;
  334. }
  335. warn("获取当前佩戴失败:", json.message);
  336. },
  337. async getFansMedalInfo(uid, callback) { // 用来获取是否拥有指定用户的粉丝牌
  338. let muid = undefined;
  339. if (!uid) {
  340. muid = uid = await ROOM_INFO_API.getUid();
  341. }
  342. let res = await fetch(`https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/fans_medal_info?target_id=${uid}`, { credentials: 'include', });
  343. let json = await res.json();
  344. if (json.code == json.message) {
  345. // 仅在获取当前房间信息时赋值
  346. if (muid == uid) {
  347. this.fansMedalInfo = json.data;
  348. }
  349. if (callback) {
  350. // 存在回调的情况下异步执行
  351. (async () => {
  352. callback(json.data, this);
  353. })();
  354. }
  355. return json.data;
  356. }
  357. alert("徽章初始化失败:", json.message);
  358. },
  359. async refreshMedalList(page = 1, assignMedal = true) {
  360. let uid = await ROOM_INFO_API.getUid();
  361. return new Promise((resolve, reject) => {
  362. fetch(`https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel?page=${page}&page_size=50&target_id=${uid}`, { credentials: 'include', })
  363. .then(res => res.json())
  364. .then(json => {
  365. if (json.code == json.message) {
  366. /*
  367. 刷新当前佩戴的徽章
  368. special_list的内容不会超过3条,所以两次循环无所谓
  369. */
  370. if (page == 1 && assignMedal) { // 只有第一页special_list才会有值
  371. // 防止在其他地方取消牌子后插件无反应
  372. this.currentlyWearing = { medal: { medal_id: 0 } };
  373. }
  374. for (let item of json.data.special_list) {
  375. // 抓取当前佩戴
  376. // 2023年7月26日 item.medal.wearing_status不总是准确的
  377. if (assignMedal && item.medal.wearing_status) {
  378. this.currentlyWearing = item;
  379. continue;
  380. }
  381. // 抓取最近获取
  382. if (item.superscript && item.superscript.type == 2) {
  383. this.recentAward = item;
  384. continue;
  385. }
  386. }
  387.  
  388. if (page == 1) {
  389. this.label = Math.floor(Math.random() * 2 ** 32);
  390. }
  391. // 合并列表并排序
  392. let list = [].concat(json.data.list, json.data.special_list);
  393. list.forEach((item) => {
  394. // 添加标识
  395. item.label = this.label;
  396. // 解析拼音
  397. if (pinyinPro) {
  398. // 用户名
  399. item.anchor_info.nick_pinyin = pinyinPro.pinyin(item.anchor_info.nick_name, { toneType: 'none', nonZh: 'consecutive', v: true }).replaceAll(" ", "").toLowerCase();
  400. // 粉丝牌
  401. item.medal.medal_pinyin = pinyinPro.pinyin(item.medal.medal_name, { toneType: 'none', nonZh: 'consecutive', v: true }).replaceAll(" ", "").toLowerCase();
  402. }
  403. let index = this.medalWallIndex.indexOf(item.medal.medal_id);
  404. if (index >= 0) {
  405. this.$set(this.medalWall, index, item);
  406. } else {
  407. this.medalWall.push(item);
  408. }
  409. // backUpMedalWall
  410. index = this.backUpMedalWallIndex.indexOf(item.medal.medal_id);
  411. item.superscript = null; // 防止搜索出现脏数据
  412. if (index >= 0) {
  413. this.$set(this.backUpMedalWall, index, item);
  414. } else {
  415. this.backUpMedalWall.push(item);
  416. }
  417. });
  418. this.medalWall.sort(this.sort);
  419. this.backUpMedalWall.sort(this.sort);
  420. // 保存页面数据
  421. this.pageInfo.loading = false;
  422. this.pageInfo.cPage = +json.data.page_info.current_page;
  423. this.pageInfo.isLastPage = page >= json.data.page_info.total_page;
  424. // 获取完整列表后替换旧列表
  425. if (this.pageInfo.isLastPage) {
  426. this.backUpMedalWall = this.medalWall;
  427. }
  428. GM_setValue(`medalWall-${my_id}`, this.backUpMedalWall);
  429. // 返回是否有下一页
  430. resolve(json.data.page_info.has_more && page < json.data.page_info.total_page);
  431. } else {
  432. reject(false);
  433. }
  434. })
  435. .catch(err => {
  436. reject();
  437. });
  438. });
  439. },
  440. async switchBadge(badgeId, index) {
  441. let jct = document.cookie.match(/bili_jct=(\w*); /)[1]
  442. let params = new URLSearchParams();
  443. params.set("medal_id", badgeId);
  444. params.set("csrf_token", jct);
  445. params.set("csrf", jct);
  446. fetch("https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/wear", {
  447. credentials: 'include',
  448. method: 'POST',
  449. body: params
  450. });
  451. // .then(res => res.json())
  452. // .then(json => {
  453. // if (json.code == 0) {
  454. // }
  455. // });
  456. if (index >= 0) {
  457. this.currentlyWearing = this.medalWall[index];
  458. } else {
  459. let result = this.backUpMedalWall.find(item => {
  460. return badgeId == item.medal.medal_id;
  461. });
  462. if (result) {
  463. this.currentlyWearing = result;
  464. } else {
  465. console.warn("徽章列表内找不到对应的徽章");
  466. await this.getCurrentWear();
  467. }
  468. }
  469. // 佩戴时更新为最新状态
  470. // todo 现有接口不能更新 头像、直播状态
  471. // if (this.currentlyWearing.label != this.label) {
  472. // this.getFansMedalInfo(this.currentlyWearing.medal.target_id, (data) => {
  473. // this.medalWall[index].label = this.label;
  474. // this.$set(this.medalWall[index], "medal", data.my_fans_medal);
  475. // });
  476. // }
  477. // 仅主动切换才保存操作人
  478. GM_setValue(`operator-${my_id}`, this.name);
  479. },
  480. takeOff() {
  481. this.currentlyWearing = { medal: { medal_id: 0 } };
  482. let jct = document.cookie.match(/bili_jct=(\w*); /)[1]
  483. let params = new URLSearchParams();
  484. params.set("visit_id", '');
  485. params.set("csrf_token", jct);
  486. params.set("csrf", jct);
  487. fetch("https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/take_off", {
  488. "method": "POST",
  489. "credentials": "include",
  490. "body": params,
  491. });
  492. // 仅主动切换才保存操作人
  493. GM_setValue(`operator-${my_id}`, this.name);
  494. },
  495. openConfig() {
  496. // 点击原版
  497. let originMedelBtn = document.querySelector(".medal-section:not(.scripted)>span");
  498. if (!originMedelBtn) {
  499. console.log("展示设置打开失败-找不到原版按钮");
  500. return;
  501. }
  502. originMedelBtn.click();
  503. // 检测窗口
  504. let deadLine = Date.now() + 1000;
  505. let interval = setInterval(() => {
  506. if (Date.now() > deadLine) {
  507. clearInterval(interval);
  508. console.log("展示设置打开失败-无法抓取到原版徽章页面");
  509. }
  510. // 弹出后,点击展示设置
  511. let originMedelBox = document.querySelector(".medalAb");
  512. if (originMedelBox) {
  513. clearInterval(interval);
  514. let configBtn = document.querySelector(".medalAb .cancel-wear");
  515. if (configBtn) {
  516. configBtn.click();
  517. } else {
  518. console.log("展示设置打开失败-找不到设置按钮");
  519. }
  520. }
  521. }, 100);
  522. },
  523. openSpace: (uid) => {
  524. window.open(`//space.bilibili.com/${uid}`);
  525. },
  526. openRoom: (rid) => {
  527. window.open(`//live.bilibili.com/${rid}`);
  528. },
  529. togglePanel() {
  530. clearTimeout(this.debounce["panel"]);
  531. this.panelStatus = !this.panelStatus;
  532. if (!this.panelStatus) {
  533. this.debounce["panel"] = setTimeout(() => {
  534. this.debounce["panel"] = null;
  535. // 临时处理,防止第二次打开后未翻页脏数据
  536. this.medalWall = this.backUpMedalWall.slice(0, 50);
  537. this.search = "";
  538. }, 2000);
  539. } else {
  540. if (this.debounce["panel"]) { return; }
  541. // 刷新本房间粉丝牌状态
  542. if (this.fansMedalInfo.has_fans_medal == false) {
  543. this.getFansMedalInfo();
  544. }
  545. this.$nextTick(() => {
  546. // 只能在nexttick里面不然元素处于display:none时无法起作用
  547. document.querySelector(".medal-wear-body").scrollTop = 0;
  548. this.refreshMedalList();
  549. });
  550. }
  551. },
  552. refreshMedal() {
  553. let selector = document.querySelector("#medal-selector");
  554. if (this.currentlyWearing.medal.medal_id != 0) {
  555. selector.innerHTML = `
  556. <div class="v-middle fans-medal-item none-select old-style medal-item-margin"
  557. style="border-color:#${this.padToHex(this.currentlyWearing.medal.medal_color_border)}">
  558. <div class="fans-medal-label"
  559. style="background-image:linear-gradient(45deg,#${this.padToHex(this.currentlyWearing.medal.medal_color_start)},#${this.padToHex(this.currentlyWearing.medal.medal_color_end)})">
  560. <span class="fans-medal-content">${this.currentlyWearing.medal.medal_name}</span>
  561. </div>
  562. <div class="fans-medal-level" style="color:#${this.padToHex(this.currentlyWearing.medal.medal_color_start)}">${this.currentlyWearing.medal.level}</div>
  563. </div>
  564. `;
  565. } else {
  566. selector.innerHTML = `<span class="action-item medal get-medal"></span>`;
  567. }
  568. },
  569. sort(a, b) {
  570. // 搜索匹配度
  571. // if (this.search && (a.score || b.score) && a.score != b.score) {
  572. // return a.score > b.score ? -1 : 1;
  573. // }
  574. // 当前房间
  575. if (a.medal.target_id == this.fansMedalInfo.my_fans_medal.target_id) {
  576. return -1;
  577. } else if (b.medal.target_id == this.fansMedalInfo.my_fans_medal.target_id) {
  578. return 1;
  579. }
  580. // 当前佩戴
  581. if (a.medal.medal_id == this.currentlyWearing.medal.medal_id) {
  582. return -1;
  583. } else if (b.medal.medal_id == this.currentlyWearing.medal.medal_id) {
  584. return 1;
  585. }
  586. // 最近获得、其他特殊情况
  587. if (a.medal.medal_id == this.recentAward.medal.medal_id) {
  588. return -1;
  589. } else if (b.medal.medal_id == this.recentAward.medal.medal_id) {
  590. return 1;
  591. }
  592. // 如果不是此次获取的牌子,向后靠
  593. if (a.label != this.label) {
  594. return 1;
  595. } else if (b.label != this.label) {
  596. return -1;
  597. }
  598. // 灰色牌子
  599. if (a.medal.is_lighted == 0) {
  600. return 1;
  601. } else if (b.medal.is_lighted == 0) {
  602. return -1;
  603. }
  604. // 等级排序
  605. if (a.medal.level != b.medal.level) {
  606. return b.medal.level - a.medal.level;
  607. }
  608. // 经验排序
  609. return b.medal.intimacy - a.medal.intimacy;
  610. },
  611. refreshScriptInfo(event) {
  612. // console.log("插件状态更新", this.isTabBlur);
  613. this.isTabBlur = false;
  614. // 获取最新状态
  615. // this.autoSwitch = GM_getValue(`autoSwitch-${my_id}`, false);
  616. // 如果当前页面展示的粉丝牌不是实际佩戴的粉丝牌,那么更新显示
  617. let wearing = GM_getValue(`currentlyWearing-${my_id}`);
  618. if (wearing && this.currentlyWearing.medal.medal_id != wearing.medal.medal_id) {
  619. this.currentlyWearing = wearing;
  620. }
  621. if (wearing && this.name != GM_getValue(`operator-${my_id}`) && this.fansMedalInfo.my_fans_medal.medal_id != wearing.medal.medal_id) {
  622. this.needSwitch = true;
  623. } else {
  624. this.needSwitch = false;
  625. }
  626. }
  627. },
  628. template: `
  629. <div class="border-box dialog-ctnr common-popup-wrap medal a-scale-in" v-show="panelStatus" @mouseleave="togglePanel">
  630. <div class="medal-ctnr none-select">
  631. <div class="medal-wear-component">
  632. <h1 class="dp-i-block title">
  633. 粉丝牌
  634. </h1>
  635. <a href="http://link.bilibili.com/p/help/index#/audience-fans-medal" target="_blank"
  636. class="dp-i-block qs-icon"></a>
  637. <input class="medal-search" placeholder="搜索粉丝牌" v-model="search">
  638. <div class="dp-i-block des f-right" @click="autoSwitch = !autoSwitch" style="display:none">
  639. <span class="cb-icon svg-icon v-middle" :class="{'checkbox-selected':autoSwitch}"></span>
  640. <span class="pointer v-middle">自动更换</span>
  641. </div>
  642. <transition-group name="medal-list" tag="div" class="medal-wear-body" ref="medalList">
  643. <div class="medal-item" v-for="(item,index) in medalWall" :class="{ outdated: item.label != label }"
  644. :key="item.medal.medal_id" :data-uid="item.medal.target_id" :data-rid="item.room_info.room_id"
  645. :data-uname="item.anchor_info.nick_name.toLowerCase()" :data-mname="item.medal.medal_name.toLowerCase()"
  646. @click="currentlyWearing.medal.medal_id == item.medal.medal_id ? takeOff() : switchBadge(item.medal.medal_id,index)">
  647. <div class="medal-item-content">
  648. <template v-if="item.room_info.living_status == 1">
  649. <a :href="'//live.bilibili.com/' + item.room_info.room_id" target="blank" @click.stop="" class="search-user-avatar p_relative avatar-small mr_md cs_pointer">
  650. <div class="avatar-wrap p_relative live-ani">
  651. <div class="avatar-inner">
  652. <div class="bili-avatar" style="width: 35px;height:35px;">
  653. <img class="bili-avatar-img bili-avatar-face bili-avatar-img-radius"
  654. :src="item.anchor_info.avatar">
  655. <span class="bili-avatar-right-icon"
  656. v-if="item.anchor_info.verify == 0"></span>
  657. </div>
  658. </div>
  659. <div class="a-cycle a-cycle-1"></div>
  660. <div class="a-cycle a-cycle-2"></div>
  661. <div class="a-cycle a-cycle-3"></div>
  662. </div>
  663. </a>
  664. </template>
  665. <template v-else>
  666. <a :href="'//live.bilibili.com/' + item.room_info.room_id" target="blank" class="face" @click.stop="">
  667. <img :src="item.anchor_info.avatar">
  668. <span class="bili-avatar-right-icon" v-if="item.anchor_info.verify == 0"></span>
  669. </a>
  670. </template>
  671. <div class="dp-i-block v-bottom w-100 p-relative">
  672. <div class="medal-content-head">
  673. <div class="fans-medal-item none-select old-style f-right"
  674. :style="'border-color:#'+(padToHex(item.medal.medal_color_border))">
  675. <div class="fans-medal-label"
  676. :style="'background-image:linear-gradient(45deg,#'+(padToHex(item.medal.medal_color_start))+',#'+(padToHex(item.medal.medal_color_end))+')'">
  677. <span class="fans-medal-content">{{item.medal.medal_name}}</span>
  678. </div>
  679. <div class="fans-medal-level"
  680. :style="'color:#'+(padToHex(item.medal.medal_color_start))">
  681. {{item.medal.level}}
  682. </div>
  683. </div>
  684. <a :href="'//space.bilibili.com/' + item.medal.target_id" target="blank" @click.stop="" class="name dp-i-block">{{item.anchor_info.nick_name}}</a>
  685. </div>
  686. <div class="medal-content-footer">
  687. <transition enter-active-class="a-scale-in" leave-active-class="a-scale-out"
  688. mode="out-in">
  689. <div class="wear-icon dp-i-block" :key="'wear'"
  690. v-if="item.medal.medal_id == currentlyWearing.medal.medal_id">
  691. 佩戴中
  692. </div>
  693. <div class="room-icon dp-i-block" :key="'room'"
  694. v-else-if="item.medal.medal_id == fansMedalInfo.my_fans_medal.medal_id">
  695. 当前房间
  696. </div>
  697. <div class="content-icon dp-i-block" :key="'content'"
  698. v-else-if="item.medal.medal_id == recentAward.medal.medal_id">
  699. 最近获得
  700. <!-- {{item.superscript.content}} -->
  701. </div>
  702. </transition>
  703. <span class="text f-right dp-i-block">{{item.medal.today_feed}}/{{item.medal.day_limit}}</span>
  704. <span v-if="false" class="left f-right dp-i-block">{{item.medal.next_intimacy - item.medal.intimacy}}</span>
  705. </div>
  706. </div>
  707. </div>
  708. <div class="progress-level-div">
  709. <span class="dp-i-block level-span">Lv.{{item.medal.level}}</span>
  710. <div class="dp-i-block progress-div">
  711. <span
  712. class="dp-i-block progress-num-span">{{item.medal.intimacy}}/{{item.medal.next_intimacy}}</span>
  713. <div class="dp-i-block progress-div-cover"
  714. :style="'width:'+(item.medal.intimacy / item.medal.next_intimacy * 100) + '%'">
  715. <span class="dp-i-block progress-num-span-cover">
  716. {{item.medal.intimacy}}/{{item.medal.next_intimacy}}
  717. </span>
  718. </div>
  719. </div>
  720. <span class="dp-i-block level-span">Lv.{{item.medal.level + 1}}</span>
  721. </div>
  722. </div>
  723. <div class="medal-loading" key="medal-loading">
  724. <template v-if="!pageInfo.isLastPage">
  725. 正在加载<i class="v-middle icon-font icon-link-world"></i>
  726. </template>
  727. <template v-else>
  728. <i class="v-middle icon-font icon-info"></i>没有了
  729. </template>
  730. </div>
  731. </transition-group>
  732. <div class="footer-line"></div>
  733. <div class="dp-flex medal-wear-footer">
  734. <span class="dp-i-block cancel-wear" @click="takeOff">
  735. 不佩戴勋章
  736. </span>
  737. <span class="dp-i-block display-config" @click="openConfig">
  738. 展示设置
  739. </span>
  740. <a href="https://link.bilibili.com/p/center/index#/user-center/wearing-center/my-medal" target="_blank"
  741. class="dp-i-block right-span">
  742. 装扮中心
  743. <span class="dp-i-block icon-font icon-arrow-right arrow-box"></span>
  744. </a>
  745. </div>
  746. </div>
  747. </div>
  748. </div>
  749. `,
  750. });
  751.  
  752. }
  753.  
  754. function spinInit(loadHook, timeout) {
  755. return new Promise((resolve, reject) => {
  756. let startTime = Date.now();
  757. let checkInterval = setInterval(() => {
  758. if (Date.now() - startTime >= timeout) {
  759. clearInterval(checkInterval);
  760. reject("徽章切换增强-无法找到加载点");
  761. }
  762. // console.log(`徽章切换增强-正在寻找加载点`);
  763. let hooked = false;
  764. for (const prop in loadHook) {
  765. // console.log(`徽章切换增强-加载点[${prop}]`);
  766. if (typeof loadHook[prop] === 'function') {
  767. hooked = loadHook[prop]();
  768. if (hooked) {
  769. console.log(`徽章切换增强-加载成功[${prop}]`);
  770. clearInterval(checkInterval);
  771. resolve();
  772. break;
  773. }
  774. }
  775. }
  776. }, 300);
  777. });
  778. }
  779.  
  780. if (!document.cookie.match(/bili_jct=(\w*); /)) { return; } // 未登录就撤
  781.  
  782. spinInit({
  783. ">0.0.1": function () { // 旧版锚点
  784. let bottomBox = document.querySelector(".bottom-actions");
  785. if (bottomBox) {
  786. // 列表元素
  787. let tempElement = document.createElement("div");
  788. tempElement.id = "medel_switch_box";
  789. bottomBox.after(tempElement);
  790. // 旧版适用的插件粉丝牌框框CSS
  791. GM_addStyle(".dialog-ctnr.medal{bottom:100px;left:-1px}");
  792. return true;
  793. }
  794. return false;
  795. },
  796. ">1.2.8": function () { // B站新版前端(SC和点赞独立大按钮)锚点
  797. let bottomBox = document.querySelector(".icon-left-part-new");
  798. if (bottomBox) {
  799. // 列表元素
  800. let tempElement = document.createElement("div");
  801. tempElement.id = "medel_switch_box";
  802. bottomBox.after(tempElement);
  803. // 原版的按钮CSS调整
  804. GM_addStyle(".medalAb .close{display:none}.medal-section:not(.scripted){display:none !important;}.chat-input-ctnr-new .medal-section{padding: 5px !important;}.chat-input-focus .medal-section{display:none !important}");
  805. GM_addStyle(".dialog-ctnr.medal{bottom:-20px;left:-8px}"); // 插件粉丝牌框框CSS
  806. return true;
  807. }
  808. return false;
  809. }
  810. }, 7000)
  811. .then((result) => main());