Greasy Fork is available in English.

#相亲相爱一嘉人#

在哔站右下角添加嘉然小姐的live2d模型

  1. // ==UserScript==
  2. // @name #相亲相爱一嘉人#
  3. // @description 在哔站右下角添加嘉然小姐的live2d模型
  4. // @version 1.0.1
  5. // @namespace https://github.com/journey-ad
  6. // @author journey-ad
  7. // @include /^https:\/\/(www|live|space|t)\.bilibili\.com\/.*$/
  8. // @icon https://www.google.com/s2/favicons?domain=bilibili.com
  9. // @license GPL v2
  10. // @run-at document-end
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (async function () {
  15. 'use strict';
  16.  
  17. if (inIframe()) {
  18. console.log('iframe中不加载');
  19. return false;
  20. }
  21.  
  22. const 引流 = [
  23. "https://space.bilibili.com/672328094",
  24. "https://www.bilibili.com/video/BV1FZ4y1F7HH",
  25. "https://www.bilibili.com/video/BV1FX4y1g7u8",
  26. "https://www.bilibili.com/video/BV1aK4y1P7Cg",
  27. "https://www.bilibili.com/video/BV17A411V7Uh",
  28. "https://www.bilibili.com/video/BV1JV411b7Pc",
  29. "https://www.bilibili.com/video/BV1AV411v7er",
  30. "https://www.bilibili.com/video/BV1564y1173Q",
  31.  
  32. "https://www.bilibili.com/video/BV1MX4y1N75X",
  33. "https://www.bilibili.com/video/BV17h411U71w",
  34. "https://www.bilibili.com/video/BV1ry4y1Y71t",
  35. "https://www.bilibili.com/video/BV1Sy4y1n7c4",
  36. "https://www.bilibili.com/video/BV15y4y177uk",
  37. "https://www.bilibili.com/video/BV1PN411X7QW",
  38. "https://www.bilibili.com/video/BV1Dp4y1H7iB",
  39. "https://www.bilibili.com/video/BV1bi4y1P7Eh",
  40. "https://www.bilibili.com/video/BV1vQ4y1Z7C2",
  41. "https://www.bilibili.com/video/BV1oU4y1h7Sc",
  42. ]
  43.  
  44. const CUSTOM_CSS = `#pio-container {
  45. display: block !important;
  46. bottom: -0.3rem;
  47. z-index: 22637261;
  48. transition: transform 0.3s;
  49. cursor: grab;
  50. }
  51.  
  52. #pio-container:hover {
  53. transform: translateY(-0.3rem);
  54. }
  55.  
  56. #pio-container:active {
  57. cursor: grabbing;
  58. }
  59.  
  60. #pio-container .pio-dialog {
  61. right: 10%;
  62. line-height: 1.5;
  63. background: rgba(255, 255, 255, 0.9);
  64. }
  65.  
  66. #pio {
  67. height: 240px;
  68. }
  69.  
  70. .pio-action .pio-home {
  71. display: none;
  72. }
  73.  
  74. .pio-action span {
  75. background: none;
  76. background-size: 100%;
  77. border: 1px solid #fdcf7b;
  78. border: 0;
  79. width: 2em;
  80. height: 2em;
  81. margin-bottom: 0.6em;
  82. }
  83.  
  84. .pio-action .pio-skin {
  85. background: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 512c0 282.774 229.226 512 512 512s512-229.226 512-512S794.774 0 512 0 0 229.226 0 512z' fill='%23FEC43C'/%3E%3Cpath d='M1013.76 408.576C965.632 175.104 759.808 0 512 0 229.376 0 0 229.376 0 512c0 123.904 44.032 236.544 116.736 324.608 87.04 48.128 186.368 74.752 292.864 74.752 301.056 0 550.912-217.088 604.16-502.784z' fill='%23FFD73A'/%3E%3Cpath d='M233.456 460.383a93.759 93.759 0 1 0 187.526 0c0-51.783-41.984-93.76-93.767-93.76s-93.759 41.977-93.759 93.76zm458.39 0c0 51.782 41.976 93.759 93.759 93.759s93.759-41.984 93.759-93.76c0-51.782-41.984-93.758-93.76-93.758-51.782 0-93.758 41.976-93.758 93.759z' fill='%23873A18'/%3E%3Cpath d='M556.41 689.577H410.561c-17.707 0-31.256-13.548-31.256-31.255 0-17.715 13.549-31.256 31.256-31.256h145.85c17.714 0 31.255 13.548 31.255 31.256s-13.549 31.255-31.256 31.255zM320.97 429.127H156.357c-14.588 0-27.089-13.548-27.089-31.256s12.5-31.247 27.097-31.247H320.96c14.58 0 27.089 13.54 27.089 31.247 0 17.715-12.509 31.256-27.097 31.256zm454.215 0H618.92c-17.715 0-31.255-13.548-31.255-31.256s13.548-31.247 31.255-31.247h156.263c17.715 0 31.255 13.54 31.255 31.247 0 17.715-13.548 31.256-31.255 31.256z' fill='%23873A18'/%3E%3Cpath d='M102.4 327.68C46.08 327.68 0 281.6 0 225.28 0 133.12 102.4 0 102.4 0s102.4 133.12 102.4 225.28c0 56.32-46.08 102.4-102.4 102.4z' fill='%2361A3E0'/%3E%3C/svg%3E");
  86. }
  87.  
  88. .pio-action .pio-info {
  89. background: url("data:image/svg+xml,%3Csvg viewBox='0 0 500 500' xmlns='http://www.w3.org/2000/svg'%3E%3Crect transform='rotate(45.001 238.211 363.575)' x='29.285' y='22.411' width='273.903' height='505.038' rx='70' ry='70' fill='%23dcdcdc'/%3E%3Cpath d='M218.543 249.999l-47.186 47.186c-8.987 8.988-8.987 22.47 0 31.457 8.988 8.988 22.47 8.988 31.457 0L250 281.456l15.728 15.729c17.976 17.976 17.976 46.063 0 64.038l-64.037 64.038c-17.976 17.975-46.063 17.975-64.038 0l-64.038-64.038c-17.975-17.975-17.975-46.062 0-64.038l64.038-64.037c17.975-17.976 46.062-17.976 64.038 0l16.852 16.851z' fill='%23fff'/%3E%3Cpath d='M281.457 249.999l47.186-47.186c8.988-8.987 8.988-22.469 0-31.457-8.987-8.987-22.469-8.987-31.457 0L250 218.542l-15.729-15.729c-17.975-17.975-17.975-46.062 0-64.037l64.038-64.038c17.975-17.975 46.062-17.975 64.038 0l64.037 64.038c17.977 17.975 17.977 46.062 0 64.037l-64.037 64.038c-17.976 17.976-46.063 17.976-64.038 0l-16.852-16.852z' fill='%2361a3e0'/%3E%3C/svg%3E");
  90. }
  91.  
  92. .pio-action .pio-top {
  93. background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 500 500' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M262.737 70.075c-3.175-2.89-8.439-5.365-12.737-5.365-4.29 0-9.448 2.37-12.632 5.263l-87.814 87.812c-2.921 3.255-5.23 8.518-5.23 12.73 0 4.203 2.196 9.353 5.118 12.617 3.246 2.915 8.621 5.345 12.842 5.345 4.203 0 9.353-2.197 12.617-5.118l75.093-74.848 74.992 74.993c3.175 2.889 8.433 5.359 12.731 5.359 4.29 0 9.448-2.371 12.632-5.263 2.918-3.247 5.329-8.61 5.329-12.827 0-4.204-2.197-9.354-5.118-12.616zm-103.97 233.514v-36.181H19.695v36.181h51.447v131.444h36.178V303.589zm126.788-35.923h-63.85c-8.732.187-18.571 3.868-25.539 10.451-6.579 6.961-10.367 16.85-10.557 25.589v95.488c.179 8.709 3.781 18.668 10.493 25.582 6.913 6.712 16.839 10.334 25.548 10.514h63.849c8.732-.187 18.571-3.868 25.538-10.45 6.581-6.962 10.368-16.852 10.558-25.59v-95.488c-.187-8.733-3.87-18.573-10.452-25.539-6.962-6.581-16.85-10.367-25.588-10.557zm-.14 131.589l.003.105.403.021a20.74 20.74 0 0 0-.322-.013h-.08c.006.172.014.313.021.414l-.027-.414h-.118l.01-.013.107.005-.007-.117-.033.025-.079.1h-63.648l-.106.003-.032.438c.007-.092.015-.243.021-.438-.163.005-.283.012-.365.017l.365-.023.003-.139-.055-.039-.301-.208.356.244.001-.029v-95.493a3.627 3.627 0 0 0-.004-.108l-.417-.028c.106.007.253.014.417.019a10.069 10.069 0 0 0-.023-.42l.031.42.123.004.016-.022.087-.113.036-.047-.137.182.044.001h63.551l.096.074.064.05-.001.049zm184.441-121.032c-6.963-6.58-16.852-10.367-25.59-10.557h-88.627V435.29h36.181v-68.165h52.39c8.732-.187 18.572-3.87 25.54-10.452 6.579-6.961 10.366-16.851 10.556-25.588v-27.323c-.187-8.733-3.868-18.572-10.45-25.539zm-25.471 52.609l.003.105.437.032a10.682 10.682 0 0 0-.437-.021c.007.211.017.355.023.436l-.033-.436a79.554 79.554 0 0 0-.142-.003l-.038.054-.112.166-.119.175.262-.396H391.82v-27.099h52.451l.112-.004.025-.405a14.96 14.96 0 0 0-.018.405c.171-.006.313-.015.416-.023l-.416.031-.004.122-.01-.008.007-.113-.119.008.041.054.081.062-.001.045z' fill='%234c4c4c'/%3E%3C/svg%3E");
  94. }
  95.  
  96. .pio-action .pio-close {
  97. background: url("data:image/svg+xml,%3Csvg viewBox='0 0 500 500' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M249.999 198.668L352.665 96c14.667-14.666 36.668-14.666 51.335 0 14.666 14.667 14.666 36.668 0 51.334L301.333 250 404 352.668c14.666 14.667 14.666 36.666 0 51.332-14.667 14.667-36.667 14.667-51.334 0L249.999 301.334 147.333 404c-14.668 14.667-36.666 14.667-51.334 0-14.666-14.666-14.666-36.665 0-51.332L198.666 250 95.999 147.334c-14.666-14.666-14.666-36.667 0-51.334 14.668-14.666 36.665-14.666 51.333 0l102.667 102.668z' fill='%23873a18'/%3E%3C/svg%3E");
  98. }
  99. `
  100.  
  101. // 用到的库
  102. const LIBS = [
  103. 'https://cdn.jsdelivr.net/gh/journey-ad/blog-img@94eb7e2/live2d/lib/pio.css',
  104. 'https://cdn.jsdelivr.net/npm/greensock@1.20.2/dist/TweenLite.js',
  105. 'https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js',
  106. 'https://cdn.jsdelivr.net/npm/pixi.js@5.3.6/dist/pixi.min.js',
  107. 'https://cdn.jsdelivr.net/npm/pixi-live2d-display@0.3.1/dist/cubism4.min.js',
  108. 'https://cdn.jsdelivr.net/gh/journey-ad/blog-img@94eb7e2/live2d/lib/pio_sdk4.js',
  109. 'https://cdn.jsdelivr.net/gh/journey-ad/blog-img@94eb7e2/live2d/lib/pio.js'
  110. ]
  111.  
  112. const reqArr = LIBS.map(src => loadSource(src))
  113.  
  114. // 创建顺序加载队列
  115. const doTask = reqArr.reduce((prev, next) => prev.then(() => next()), Promise.resolve());
  116.  
  117. // 队列执行完毕后
  118. doTask.then(() => {
  119. // 移除自带看板娘
  120. const haruna = document.getElementById('my-dear-haruna-vm')
  121. haruna && haruna.remove()
  122.  
  123. // 初始化pio
  124. _pio_initialize_pixi()
  125.  
  126. // 添加自定义样式
  127. addStyle(CUSTOM_CSS)
  128.  
  129. 加载圣·嘉然()
  130.  
  131. console.log("all done.")
  132. });
  133.  
  134. // 初始化设定
  135. const initConfig = {
  136. mode: "fixed",
  137. hidden: true,
  138. content: {
  139. link: 引流[Math.floor(Math.random() * 引流.length)], // 引流链接
  140. referer: "Hi!", // 存在访问来源时的欢迎文本
  141. welcome: ["Hi!"], // 未开启时间问好时的欢迎文本
  142. skin: ["诶,想看看其他团员吗?", "替换后入场文本"], // 0更换模型提示文案 1更换完毕入场文案
  143. custom: [
  144. // 鼠标移上去提示元素
  145. { "selector": ".most-viewed-panel .most-viewed-item, .live-up-list .live-detail, .card .user-name, .user .name, .post-content .content-full a, .tag-list .content, .title, h2 a[title]", "type": "link" }
  146. ],
  147. },
  148. model: [
  149. // 待加载的模型列表
  150. "https://cdn.jsdelivr.net/gh/journey-ad/blog-img/live2d/Diana/Diana.model3.json",
  151. "https://cdn.jsdelivr.net/gh/journey-ad/blog-img/live2d/Ava/Ava.model3.json",
  152. ],
  153. tips: true, // 时间问好
  154. onModelLoad: onModelLoad // 模型加载完成回调
  155. }
  156.  
  157. let pio_reference // pio实例
  158.  
  159. function 加载圣·嘉然() {
  160. pio_reference = new Paul_Pio(initConfig)
  161.  
  162. pio_alignment = "right" // 右下角
  163.  
  164. const closeBtn = document.querySelector(".pio-container .pio-action .pio-close")
  165. closeBtn.insertAdjacentHTML('beforebegin', '<span class="pio-top"></span>')
  166. const topBtn = document.querySelector(".pio-container .pio-action .pio-top")
  167. // 返回顶部
  168. topBtn.onclick = function () {
  169. window.scrollTo({ top: 0, behavior: 'smooth' });
  170. };
  171. topBtn.onmouseover = function () {
  172. pio_reference.modules.render("想回到页面顶部吗?");
  173. };
  174.  
  175. // Then apply style
  176. pio_refresh_style()
  177. }
  178.  
  179. // 模型加载完成回调
  180. function onModelLoad(model) {
  181. const canvas = document.getElementById("pio")
  182. const modelNmae = model.internalModel.settings.name
  183. const coreModel = model.internalModel.coreModel
  184. const motionManager = model.internalModel.motionManager
  185.  
  186. let touchList = [
  187. {
  188. text: "点击展示文本1",
  189. motion: "Idle"
  190. },
  191. {
  192. text: "点击展示文本2",
  193. motion: "Idle"
  194. }
  195. ]
  196.  
  197. // 播放动作
  198. function playAction(action) {
  199. action.text && pio_reference.modules.render(action.text) // 展示文案
  200. action.motion && pio_reference.model.motion(action.motion) // 播放动作
  201.  
  202. if (action.from && action.to) {
  203. // 指定部件渐入渐出
  204. Object.keys(action.from).forEach(id => {
  205. const hidePartIndex = coreModel._partIds.indexOf(id)
  206. TweenLite.to(coreModel._partOpacities, 0.6, { [hidePartIndex]: action.from[id] });
  207. // coreModel._partOpacities[hidePartIndex] = action.from[id]
  208. })
  209.  
  210. motionManager.once("motionFinish", (data) => {
  211. Object.keys(action.to).forEach(id => {
  212. const hidePartIndex = coreModel._partIds.indexOf(id)
  213. TweenLite.to(coreModel._partOpacities, 0.6, { [hidePartIndex]: action.to[id] });
  214. // coreModel._partOpacities[hidePartIndex] = action.to[id]
  215. })
  216. })
  217. }
  218. }
  219.  
  220. canvas.onclick = function () {
  221. // 除闲置动作外不打断
  222. if (motionManager.state.currentGroup !== "Idle") return
  223.  
  224. // 随机选择并播放动作
  225. const action = pio_reference.modules.rand(touchList)
  226. playAction(action)
  227. }
  228.  
  229. if (modelNmae === "Diana") {
  230. // 嘉然小姐
  231.  
  232. // 入场动作及文案
  233. initConfig.content.skin[1] = ["我是吃货担当 嘉然 Diana~", "嘉心糖们 想然然了没有呀~", "有人在吗?"]
  234. playAction({ motion: "Tap抱阿草-左手" })
  235.  
  236. // 点击动作及文案,不区分区域
  237. touchList = [
  238. {
  239. text: "嘉心糖屁用没有",
  240. motion: "Tap生气 -领结"
  241. },
  242. {
  243. text: "有人急了,但我不说是谁~",
  244. motion: "Tap= = 左蝴蝶结"
  245. },
  246. {
  247. text: "呜呜...呜呜呜....",
  248. motion: "Tap哭 -眼角"
  249. },
  250. {
  251. text: "想然然了没有呀~",
  252. motion: "Tap害羞-中间刘海"
  253. },
  254. {
  255. text: "阿草好软呀~",
  256. motion: "Tap抱阿草-左手"
  257. },
  258. {
  259. text: "不要再戳啦!好痒!",
  260. motion: "Tap摇头- 身体"
  261. },
  262. {
  263. text: "嗷呜~~~",
  264. motion: "Tap耳朵-发卡"
  265. },
  266. {
  267. text: "zzZ。。。",
  268. motion: "Leave"
  269. },
  270. {
  271. text: "哇!好吃的!",
  272. motion: "Tap右头发"
  273. },
  274. ]
  275.  
  276. } else if (modelNmae === "Ava") {
  277. initConfig.content.skin[1] = ["我是<s>拉胯</s>Gamer担当 向晚 AvA~", "怎么推流辣!", "AAAAAAAAAAvvvvAAA 向晚!"]
  278. playAction({
  279. motion: "Tap左眼",
  280. from: {
  281. "Part15": 1
  282. },
  283. to: {
  284. "Part15": 0
  285. }
  286. })
  287.  
  288. touchList = [
  289. {
  290. text: "水母 水母~ 只是普通的生物",
  291. motion: "Tap右手"
  292. },
  293. {
  294. text: "可爱的鸽子鸽子~我喜欢你~",
  295. motion: "Tap胸口项链",
  296. from: {
  297. "Part12": 1
  298. },
  299. to: {
  300. "Part12": 0
  301. }
  302. },
  303. {
  304. text: "好...好兄弟之间喜欢很正常啦",
  305. motion: "Tap中间刘海",
  306. from: {
  307. "Part12": 1
  308. },
  309. to: {
  310. "Part12": 0
  311. }
  312. },
  313. {
  314. text: "啊啊啊!怎么推流辣",
  315. motion: "Tap右眼",
  316. from: {
  317. "Part16": 1
  318. },
  319. to: {
  320. "Part16": 0
  321. }
  322. },
  323. {
  324. text: "你怎么老摸我,我的身体是不是可有魅力",
  325. motion: "Tap嘴"
  326. },
  327. {
  328. text: "AAAAAAAAAAvvvvAAA 向晚!",
  329. motion: "Tap左眼",
  330. from: {
  331. "Part15": 1
  332. },
  333. to: {
  334. "Part15": 0
  335. }
  336. }
  337. ]
  338.  
  339. // 钻头比较大,宽度*1.2倍,模型位移也要重新计算
  340. canvas.width = model.width * 1.2
  341. model.x = canvas.width - model.width
  342.  
  343. // 模型问题,手动隐藏指定部件
  344. const hideParts = [
  345. "Part5", // 晕
  346. "neko", // 喵喵拳
  347. "game", // 左手游戏手柄
  348. "Part15", // 墨镜
  349. "Part21", // 右手小臂
  350. "Part22", // 左手垂下
  351. "Part", // 双手抱拳
  352. "Part16", // 惊讶特效
  353. "Part12" // 小心心
  354. ]
  355. const hidePartsIndex = hideParts.map(id => coreModel._partIds.indexOf(id))
  356. hidePartsIndex.forEach(idx => {
  357. coreModel._partOpacities[idx] = 0
  358. })
  359. }
  360. }
  361.  
  362. // 检测是否处于iframe内嵌环境
  363. function inIframe() {
  364. try {
  365. return window.self !== window.top;
  366. } catch (e) {
  367. return true;
  368. }
  369. }
  370.  
  371. // 加载js或css,返回函数包裹的promise实例,用于顺序加载队列
  372. function loadSource(src) {
  373. return () => {
  374. return new Promise(function (resolve, reject) {
  375. const TYPE = src.split('.').pop()
  376. let s = null;
  377. let r = false;
  378. if (TYPE === 'js') {
  379. s = document.createElement('script');
  380. s.type = 'text/javascript';
  381. s.src = src;
  382. s.async = true;
  383.  
  384. } else if (TYPE === 'css') {
  385. s = document.createElement('link');
  386. s.rel = 'stylesheet';
  387. s.type = 'text/css';
  388. s.href = src;
  389.  
  390. }
  391. s.onerror = function (err) {
  392. reject(err, s);
  393. };
  394. s.onload = s.onreadystatechange = function () {
  395. // console.log(this.readyState); // uncomment this line to see which ready states are called.
  396. if (!r && (!this.readyState || this.readyState == 'complete')) {
  397. r = true;
  398. console.log(src)
  399. resolve();
  400. }
  401. };
  402. const t = document.getElementsByTagName('script')[0];
  403. t.parentElement.insertBefore(s, t);
  404. });
  405. }
  406. }
  407.  
  408. // 添加css
  409. function addStyle(css) {
  410. if (typeof GM_addStyle != "undefined") {
  411. GM_addStyle(css);
  412. } else if (typeof PRO_addStyle != "undefined") {
  413. PRO_addStyle(css);
  414. } else {
  415. const node = document.createElement("style");
  416. node.type = "text/css";
  417. node.appendChild(document.createTextNode(css));
  418. const heads = document.getElementsByTagName("head");
  419.  
  420. if (heads.length > 0) {
  421. heads[0].appendChild(node);
  422. } else {
  423. // no head yet, stick it whereever
  424. document.documentElement.appendChild(node);
  425. }
  426. }
  427. }
  428.  
  429. })();