您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bangumi 打上贴贴,让贴贴有趣起来
// ==UserScript== // @name Bangumi 打上贴贴 // @namespace b38.dev // @version 1.0.0 // @author 神戸小鳥 @vickscarlet // @description Bangumi 打上贴贴,让贴贴有趣起来 // @license MIT // @icon https://bgm.tv/img/favicon.ico // @homepage https://github.com/bangumi/scripts/blob/master/vickscarlet/scripts/likes_firework // @match *://bgm.tv/* // @match *://chii.in/* // @match *://bangumi.tv/* // ==/UserScript== (function () { 'use strict'; (() => { chiiLib.ukagaka.addGeneralConfig({ title: "打上贴贴", name: "myCustomSetting", type: "radio", defaultValue: "default", getCurrentValue: () => localStorage.getItem("likes-firework-autoplay") || "no", onChange: (value) => localStorage.setItem("likes-firework-autoplay", value), options: [ { value: "yes", label: "自动播放" }, { value: "no", label: "不自动播放" } ] }); const likes = Array.from( document.querySelectorAll("#columnInSubjectA > .clearit .likes_grid a.item") ).map((e) => { const url = e.querySelector(".emoji").style.backgroundImage.split('"')[1]; const count = Number(e.querySelector(".num").innerText); return [url, count]; }); if (!likes.length) return; const rand = (min, max) => ~~(Math.random() * (max - min + 1) + min); const displacement = (v0, a, t) => v0 * t + a * t * t / 2; class Launch { start; target; current; hue; end; xy; total; hitX = false; hitY = false; speed = 2; lineWidth = 1; brightness = rand(50, 80); alpha = rand(50, 100) / 100; targetRadius = 1; acceleration = 4 / 100; time = Date.now(); constructor({ start, target, hue, end }) { this.start = Array.from(start); this.target = Array.from(target); this.current = Array.from(this.start); this.hue = hue; this.end = end; const angle = Math.atan2(target[1] - start[1], target[0] - start[0]); this.xy = [Math.cos(angle), Math.sin(angle)]; this.total = Math.sqrt((target[0] - start[0]) ** 2 + (target[1] - start[1]) ** 2); } update(ctx, _) { const dt = (Date.now() - this.time) / 4; const s = displacement(this.speed, this.acceleration, dt); if (s > this.total) return this.end(); const last = this.current; this.current = [this.start[0] + s * this.xy[0], this.start[1] + s * this.xy[1]]; ctx.beginPath(); ctx.moveTo(Math.round(last[0]), Math.round(last[1])); ctx.lineTo(Math.round(this.current[0]), Math.round(this.current[1])); ctx.closePath(); ctx.strokeStyle = "hsla(" + this.hue + ", 100%, " + this.brightness + "%, " + this.alpha + ")"; ctx.stroke(); } } class Explosion { center; hue; url; endCallback; particles = /* @__PURE__ */ new Set(); like; total = rand(30, 45); _done = false; _dp = false; _dl = false; constructor({ center, hue, url, end }) { this.center = Array.from(center); this.hue = hue; this.url = url; this.endCallback = end; let count = this.total; while (count--) { const particle = new Particle({ center, hue, end: () => { this.particles.delete(particle); if (!this.particles.size) { this._dp = true; if (this._dl) this.end(); } } }); this.particles.add(particle); } this.like = new Like({ url: this.url, center: this.center, end: () => { this._dl = true; if (this._dp) this.end(); } }); } update(ctx, dt) { if (this._done) return this; for (const particle of this.particles) { particle.update(ctx, dt); } this.like.update(ctx, dt); } end() { this._done = true; this.endCallback(); } } class Like { center; url; end; size = 21; pixel = 0.5; gap = 1; alpha = 0.5; constructor({ center, url, end }) { this.center = Array.from(center); this.url = url; this.end = end; } update(ctx, dt) { this.gap += dt / 10; this.pixel += dt / 40; this.alpha -= 0.01; if (this.alpha < 0.05 || this.gap > 3) { return this.end(); } const data = Like.get(this.url); const [cx, cy] = this.center; for (let h = 0; h < this.size; h++) { for (let w = 0; w < this.size; w++) { ctx.beginPath(); ctx.arc( cx + (w - 11) * this.gap, cy + (h - 11) * this.gap, this.pixel, 0, Math.PI * 2, false ); const [r, g, b, a] = data[h * this.size + w]; const alpha = a * this.alpha; ctx.closePath(); ctx.fillStyle = "rgba(" + r + "," + g + "," + b + "," + alpha + ")"; ctx.fill(); } } } static data = /* @__PURE__ */ new Map(); static async init(urls) { for (const url of urls) { if (this.data.has(url)) continue; const data = await this.loading(url); this.data.set(url, data); } } static async loading(url) { return new Promise((reslove) => { const img = new Image(); img.src = url; img.onload = function() { let imgWidth = 21; let imgHeight = 21; const c = document.createElement("canvas"); c.width = 21; c.height = 21; const ctx = c.getContext("2d"); ctx.drawImage(img, 0, 0, imgWidth, imgHeight); let imgData = ctx.getImageData(0, 0, imgWidth, imgHeight); const datas = []; for (let h = 0; h < imgHeight; h += 1) { for (let w = 0; w < imgWidth; w += 1) { let position = (imgWidth * h + w) * 4; let r = imgData.data[position], g = imgData.data[position + 1], b = imgData.data[position + 2], a = imgData.data[position + 3]; datas.push([r, g, b, a]); } } reslove(datas); }; }); } static get(url) { return this.data.get(url); } } class Particle { x; y; hue; end; angle = rand(0, 360); speed = rand(1, 15); brightness = rand(50, 80); alpha = rand(40, 100) / 100; decay = rand(10, 50) / 1e3; wind = rand(-100, 100) / 100; partSpeedVariance = 10; gravity = 1 / 2; friction = 1 - 5 / 100; hueVariance = 30; lineWidth = 1; flickerDensity = 20; constructor({ center, hue, end }) { [this.x, this.y] = center; this.hue = rand(hue - this.hueVariance, hue + this.hueVariance); this.end = end; } update(ctx, dt) { let radians = this.angle * Math.PI / 180; let vx = Math.cos(radians) * this.speed; let vy = Math.sin(radians) * this.speed + this.gravity; this.speed *= this.friction; const { x, y } = this; this.x += vx * dt; this.y += vy * dt; this.angle += this.wind; this.alpha -= this.decay; if (this.alpha < 0.05) { this.end(); return; } ctx.beginPath(); ctx.moveTo(Math.round(x), Math.round(y)); ctx.lineTo(Math.round(this.x), Math.round(this.y)); ctx.closePath(); ctx.strokeStyle = "hsla(" + this.hue + ", 100%, " + this.brightness + "%, " + this.alpha + ")"; ctx.stroke(); let inverseDensity = 50 - this.flickerDensity; if (rand(0, inverseDensity) === inverseDensity) { ctx.beginPath(); ctx.arc( Math.round(this.x), Math.round(this.y), rand(this.lineWidth, this.lineWidth + 3) / 2, 0, Math.PI * 2, false ); ctx.closePath(); let randAlpha = rand(50, 100) / 100; ctx.fillStyle = "hsla(" + this.hue + ", 100%, " + this.brightness + "%, " + randAlpha + ")"; ctx.fill(); } } } class Firework { url; start; target; endCallback; state = { type: "idle" }; hue = rand(0, 360); constructor({ url, start, target, end }) { this.url = url; this.start = Array.from(start); this.target = Array.from(target); this.endCallback = end; } update(ctx, dt) { switch (this.state.type) { case "launch": case "explosion": this.state.item.update(ctx, dt); break; } return this; } launch() { this.state = { type: "launch", item: new Launch({ start: this.start, target: this.target, hue: this.hue, end: () => this.explosion() }) }; return this; } explosion() { this.state = { type: "explosion", item: new Explosion({ url: this.url, center: this.target, hue: this.hue, end: () => this.end() }) }; return this; } end() { this.state = { type: "end" }; this.endCallback(); return this; } } class Fireworks { cw = window.innerWidth; ch = window.innerHeight; canvas = document.createElement("canvas"); ctx = this.canvas.getContext("2d"); fireworks = /* @__PURE__ */ new Set(); _loop = false; time = Date.now(); timeout = 0; constructor() { const actions = document.querySelector( "#columnInSubjectA > .clearit .topic_actions .post_actions" ); const btn = document.createElement("div"); btn.classList.add("action"); const a = document.createElement("a"); a.classList.add("icon"); a.href = "javascript:void(0)"; const span = document.createElement("span"); span.classList.add("title"); span.appendChild(document.createTextNode("🎇 打上贴贴")); a.append(span); btn.append(a); actions?.prepend(btn); btn.addEventListener("click", () => this.init(likes)); this.canvas.style.position = "fixed"; this.canvas.style.top = "0"; this.canvas.style.left = "0"; this.canvas.style.width = "100vw"; this.canvas.style.height = "100vh"; this.canvas.style.pointerEvents = "none"; this.canvas.style.zIndex = "999999"; this.canvas.width = this.cw; this.canvas.height = this.ch; this.ctx.lineCap = "round"; this.ctx.lineJoin = "round"; document.body.append(this.canvas); } async init(likes2) { await Like.init(likes2.map(([url]) => url)); likes2.map(([url, count]) => new Array(count).fill(url)).flat().sort(() => rand(-10, 10)).forEach((url, i) => { setTimeout(() => { this.launch(url); }, Math.min(rand(20, 100) * i, 15e3)); }); } launch(url) { const target = [rand(50, this.cw - 50), rand(50, this.ch / 2) - 50]; const dx = rand(30, 200); const firework = new Firework({ url, start: [target[0] > this.cw / 2 ? target[0] - dx : target[0] + dx, this.ch], target, end: () => { this.fireworks.delete(firework); if (!this.fireworks.size) { clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.canvas.style.display = "none"; this._loop = false; }, 5e3); } } }); this.fireworks.add(firework); firework.launch(); if (this.fireworks.size == 1) { this.canvas.style.display = "block"; this._loop = true; this.loop(); } } loop() { if (!this._loop) return; requestAnimationFrame(() => this.loop()); this.ctx.globalCompositeOperation = "destination-out"; this.ctx.fillStyle = "rgba(0,0,0,.25)"; this.ctx.fillRect(0, 0, this.cw, this.ch); this.ctx.globalCompositeOperation = "lighter"; let now = Date.now(); let dt = (now - this.time) / 16; dt = dt > 5 ? 5 : dt; this.time = now; for (const firework of this.fireworks) firework.update(this.ctx, dt); } } const fireworks = new Fireworks(); if (localStorage.getItem("likes-firework-autoplay") != "yes") return; fireworks.init(likes); })(); })();