您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
2/21/2022, 9:26:31 PM
// ==UserScript== // @name ButtEOS // @namespace Violentmonkey Scripts // @grant none // @match *://milovana.com/webteases/showtease.php // @match *://milovana.com/eos/editor/* // @match *://eosscript.com/* // @license BSD // @version 1.1 // @author cfs6t08p // @description 2/21/2022, 9:26:31 PM // ==/UserScript== /* jshint esversion: 8 */ function mod(a, b) { return ((a % b) + b) % b; } function actionIndex(pattern, time) { for(let a = 0; a < pattern.numActions; a++) { if(pattern.actions[a].at > time) { return a; } } } function positionAt(pattern, time, index) { if(pattern.actions[index].at > time) { let a1 = pattern.actions[mod((index - 1), pattern.numActions)]; let a2 = pattern.actions[index]; let a1Wrap = mod(a1.at, pattern.patternLength); let dp = a2.pos - a1.pos; let dt = a2.at - a1Wrap; let alpha = (time - a1Wrap) / dt; return 99 - (a1.pos + alpha * dp); } } function vibe(level) { window.parent.postMessage({buttEOS: true, vib: {level: level}}, "https://milovana.com/webteases/*"); } function linear(position, duration) { window.parent.postMessage({buttEOS: true, linear: {position: position, duration: duration }}, "https://milovana.com/webteases/*"); } if(document.getElementById("eosContainer")) { let eos = document.getElementById("eosContainer"); let bod = document.body; let div = document.createElement("div"); div.style = "position: absolute; left: 20px; top: 40px; width: 160px; z-index: 100000"; bod.append(div); let bar = document.createElement("div"); let fill = document.createElement("div"); let arrow = document.createElement("div"); let line = document.createElement("div"); let text = document.createElement("div"); bar.style = "position: absolute; left: 20px; height: 80%; bottom: 10%; width: 50px; border-top-left-radius: 25px; border-top-right-radius: 25px; background-color: #ffffff20; visibility: hidden;"; fill.style = "position: absolute; left: 7px; width: 36px; bottom: 0px; background-color: #bb55bbcc;"; arrow.style = "position: absolute; left: 7px; width: 36px; height: 18px; border-top-left-radius: 18px; border-top-right-radius: 18px; background-color: #bb55bbcc;"; line.style = "position: absolute; width: 100%; height: 4px; bottom: 15%; background-color: #ffff00cc;"; text.style = "position: absolute; width: 100%; height: 10%; bottom: 0px; color: white; padding-top: 5px;"; bar.append(fill); bar.append(arrow); bar.append(line); div.append(bar); div.append(text); let currentPattern = {}; let lastPatternName; let lastBPM; let bpmPattern; let patternStart; let prevActionIndex; let newPatterns = 0; let vibeLevel; let patterns = {}; setInterval(() => { let xpath = ".//p[contains(text(),'Load pattern:')]"; let result = document.evaluate(xpath, eos, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); if(result.snapshotLength == 0) { newPatterns = 0; } for(let i = 0; i < result.snapshotLength; i++) { let node = result.snapshotItem(i); let name = node.textContent.slice(13).trim(); let data = node.parentNode.childNodes; if(data.length >= 3) { let text = data[1].textContent; let funscript = ""; for(let l = 2; l < data.length; l++) { funscript = funscript + data[l].textContent; } if(patterns[name] === undefined) { patterns[name] = {}; try { let pattern = JSON.parse(funscript); pattern.valid = true; pattern.text = text; pattern.numActions = pattern.actions.length; pattern.patternLength = pattern.actions[pattern.numActions - 1].at; let time = 0; for(let a = 0; a < pattern.numActions; a++) { let at = pattern.actions[a].at; pattern.actions[a].dur = at - time; time = at; } patterns[name] = pattern; newPatterns++; console.log(pattern); } catch(error) { console.error("Failed to load pattern \"" + name + "\""); console.error(error); } } } } if(newPatterns > 0) { text.innerText = "Loaded " + newPatterns + " pattern(s)"; } }, 100); setInterval(() => { let now = Date.now(); let h = (eos.clientHeight - 40) * 0.7; div.style.height = h + "px"; let vibePath = ".//div[contains(text(),'Vibrator:')]"; let vibeNotification = document.evaluate(vibePath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; let newVibe = vibeLevel; if(vibeNotification) { newVibe = parseInt(vibeNotification.textContent.slice(9)); } else { newVibe = 0; } if((newVibe != vibeLevel) && !(Number.isNaN(newVibe) && Number.isNaN(vibeLevel))) { vibeLevel = newVibe; if(Number.isNaN(vibeLevel) || vibeLevel > 100 || vibeLevel < 0) { console.error("Invalid vibrator level: \"" + vibeLevel + "\""); vibe(0); } else { vibe(vibeLevel); } } let pattern; let patternPath = ".//div[contains(text(),'Pattern:')]"; let patternNotification = document.evaluate(patternPath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(patternNotification) { let name = patternNotification.textContent.slice(8).trim(); pattern = patterns[name]; if(lastPatternName != name) { lastPatternName = name; if(!pattern) { console.error("Pattern \"" + name + "\" not found"); } } } let bpmPath = ".//div[contains(text(),'BPM:')]"; let bpmNotification = document.evaluate(bpmPath, eos, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(bpmNotification) { let bpm = parseInt(bpmNotification.textContent.slice(4)); if((bpm != lastBPM) && !(Number.isNaN(bpm) && Number.isNaN(lastBPM))) { lastBPM = bpm; if(Number.isNaN(bpm) || bpm <= 0 || bpm > 600) { console.error("Invalid BPM: \"" + bpm + "\""); bpmPattern = undefined; } else { let period = (60 * 1000) / bpm; pattern = {valid: true, numActions: 2, patternLength: period, text: "", actions: [{at: period / 2, pos: 0, dur: period / 2},{at: period, pos: 100, dur: period / 2}]}; bpmPattern = pattern; } } else { pattern = bpmPattern; } } if(pattern != currentPattern) { currentPattern = pattern; patternStart = now; prevActionIndex = -1; if(pattern) { text.innerText = pattern.text; bar.style.visibility = "visible"; } else { text.innerText = ""; bar.style.visibility = "hidden"; } newPatterns = 0; } if(currentPattern !== undefined && currentPattern.valid) { let patternTime = mod(now - patternStart, currentPattern.patternLength); let index = actionIndex(currentPattern, patternTime); if(index != prevActionIndex) { linear(currentPattern.actions[index].pos, currentPattern.actions[index].dur); prevActionIndex = index; } let fillHeight = ((bar.clientHeight - 25) * positionAt(currentPattern, patternTime, index)) / 100; fill.style.height = fillHeight + "px"; arrow.style.bottom = fillHeight + "px"; } }, 10); } if(document.querySelector(".eosTopBody")) { window.addEventListener("message", (event) => { if(event.data.buttEOS) { if(window.buttplug_devices) { if(event.data.vib) { window.buttplug_devices.forEach((device) => { if(device.messageAttributes(Buttplug.ButtplugDeviceMessageType.VibrateCmd)) { device.vibrate(event.data.vib.level / 100); } }); } if(event.data.linear) { window.buttplug_devices.forEach((device) => { if(device.messageAttributes(Buttplug.ButtplugDeviceMessageType.LinearCmd)) { device.linear(event.data.linear.position / 100, event.data.linear.duration); } }); } } event.stopImmediatePropagation(); } }); let bpscript= document.createElement("script"); bpscript.src = "https://cdn.jsdelivr.net/npm/[email protected]/dist/web/buttplug.min.js"; document.body.append(bpscript); window.addEventListener("load", function (e) { let style = document.createElement("style"); style.innerHTML = ` #buttplug-top-container h3, li { font-family:Arial; font-size:15px; } #buttplug-top-container ul { list-style-type: none; column-count: 2; } .buttplug-button { box-shadow:inset 0px 1px 3px 0px #91b8b3; background:linear-gradient(to bottom, #768d87 5%, #6c7c7c 100%); background-color:#768d87; border-radius:5px; border:1px solid #566963; display:inline-block; cursor:pointer; color:#ffffff; font-family:Arial; font-size:15px; font-weight:bold; padding:11px 23px; text-decoration:none; text-shadow:0px -1px 0px #2b665e; margin: 5px; } .buttplug-button:hover { background:linear-gradient(to bottom, #6c7c7c 5%, #768d87 100%); background-color:#6c7c7c; } .buttplug-button:active { position:relative; top:1px; } #buttplug-top-container { position: fixed; top: 0; right: 0; width: 100%; height: 100%; overflow: hidden; background: rgba(0, 0, 0, 0.7); display: none; } #buttplug-dialog { width: 50%; min-height: 200px; position: absolute; top: 10%; left: 0; left: 0; right: 0; margin: auto; background: #888888cc; border-radius: 5px; padding: 20px; } .close { background: #000; cursor: pointer; width: 20px; height: 20px; border-radius: 2px; text-align: center; color: white; } #close-bottom-right { position: absolute; bottom: 0; right: 0; } body { width: 100%; height: 100%; } .open { width: 50px; height: 50px; background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 290.56 293.08'%3E%3Cdefs%3E%3Cstyle%3E.cls-1,.cls-3%7Bfill:none;%7D.cls-1%7Bstroke:%23fff;stroke-miterlimit:10;%7D.cls-2%7Bfill:%23fff;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Ebuttplug-logo-1%3C/title%3E%3Crect x='0.5' y='0.5' width='289.56' height='292.08' rx='32' ry='32'/%3E%3Crect class='cls-1' x='0.5' y='0.5' width='289.56' height='292.08' rx='32' ry='32'/%3E%3Crect class='cls-2' x='10.63' y='10.72' width='269.29' height='271.63' rx='25' ry='25'/%3E%3Crect class='cls-1' x='10.63' y='10.72' width='269.29' height='271.63' rx='25' ry='25'/%3E%3Crect x='17.37' y='17.51' width='255.83' height='258.05' rx='20' ry='20'/%3E%3Crect class='cls-1' x='17.37' y='17.51' width='255.83' height='258.05' rx='20' ry='20'/%3E%3Cline class='cls-3' x1='156.1' y1='152.66' x2='142.44' y2='162.32'/%3E%3Cpath class='cls-2' d='M325.32,383.36a3.07,3.07,0,0,1-1.71-5.64,107.76,107.76,0,0,1,14.2-9.47l2.32-1.36c2.57-1.54,5.24-3,7.83-4.36a95,95,0,0,0,13.73-8.38c1.9-1.49,2.33-6.94,2.59-10.2v-.12c.86-10.76,1-22.09-7.83-32-9.93-11.24-8.63-25.63-6.06-38.22,3-14.72,5.94-29.72,8.78-44.22,3.34-17.09,6.8-34.76,10.41-52.11,1.82-8.76,6.31-14.55,12.3-15.88a20.85,20.85,0,0,1,6.58,0c6,1.33,10.48,7.12,12.3,15.88,3.61,17.35,7.07,35,10.41,52.12,2.83,14.5,5.77,29.49,8.78,44.21,2.58,12.59,3.87,27-6.06,38.22-8.79,10-8.69,21.29-7.83,32v.12c.26,3.26.69,8.71,2.6,10.2a95.08,95.08,0,0,0,13.73,8.38c2.58,1.39,5.26,2.82,7.83,4.36l2.32,1.36a108,108,0,0,1,14.2,9.47,3.07,3.07,0,0,1-1.81,5.64H325.32Zm2.69-4H442.34a109.85,109.85,0,0,0-11.81-7.65l-2.37-1.39c-2.48-1.49-5.11-2.9-7.66-4.26a98.21,98.21,0,0,1-14.31-8.76c-3.28-2.57-3.75-8.37-4.12-13v-.12c-.93-11.61-1-23.88,8.83-35,8.74-9.9,7.63-22.56,5.14-34.77-3-14.73-5.95-29.74-8.79-44.25-3.34-17.08-6.79-34.75-10.4-52.07-1.49-7.15-4.86-11.81-9.25-12.79a9.39,9.39,0,0,0-2.27-.17H385a9.32,9.32,0,0,0-2.27.17c-4.39,1-7.76,5.64-9.25,12.79-3.61,17.32-7.06,35-10.4,52.06-2.84,14.51-5.77,29.52-8.79,44.25-2.5,12.21-3.61,24.87,5.14,34.77,9.83,11.13,9.75,23.4,8.82,35v.12c-.37,4.66-.83,10.46-4.12,13a98.14,98.14,0,0,1-14.31,8.76c-2.54,1.36-5.17,2.77-7.66,4.26l-2.37,1.39A109.88,109.88,0,0,0,328,379.35Z' transform='translate(-239.9 -125.68)'/%3E%3C/svg%3E%0A"); display: none; z-index:999; } #open-bottom-right { position: fixed; bottom: 0; right: 0; display: block; } `; document.body.append(style); let open_element = document.createElement('div'); open_element.id = `open-bottom-right`; open_element.className = "open"; document.body.append(open_element); let container_div = document.createElement('div'); container_div.innerHTML = ` <div id="buttplug-dialog"> <div id="close-bottom-right" class="close">V</div> <div id="buttplug-container" style="margin: 10px; display: flex;"> <div id="buttplug-connector" style="display: block;"> <a href="#" class="buttplug-button" id="buttplug-connect-browser">Connect in Browser</a> <br/> <a href="#" class="buttplug-button" id="buttplug-connect-intiface">Connect to Intiface Desktop</a> <br/> </div> <div id="buttplug-enumeration" style="display: none;"> <a href="#" class="buttplug-button" id="buttplug-scanning">Start Scanning</a> <a href="#" class="buttplug-button" id="buttplug-disconnect">Disconnect</a> <br/> <h3>Devices</h3> <ul id="buttplug-device-list"> <li> </li> </ul> </div> </div> </div>`; container_div.id = "buttplug-top-container"; document.body.append(container_div); // We need the buttplug_devices to be global, so that tampermonkey user // scripts can work with it. Hang it off window. window.buttplug_devices = []; setTimeout(() => (async function () { // Set up Buttplug await Buttplug.buttplugInit(); const buttplug_client = new Buttplug.ButtplugClient("ButtEOS Client"); const dialog_div = document.getElementById("buttplug-dialog"); const connector_div = document.getElementById("buttplug-connector"); const enumeration_div = document.getElementById("buttplug-enumeration"); const scanning_button = document.getElementById("buttplug-scanning"); const connect_browser_button = document.getElementById("buttplug-connect-browser"); const connect_intiface_button = document.getElementById("buttplug-connect-intiface"); const disconnect_button = document.getElementById("buttplug-disconnect"); const device_list = document.getElementById("buttplug-device-list"); buttplug_client.addListener('deviceadded', async (device) => { const element_id = `buttplug-device-${device.Index}`; const input = document.createElement("li"); input.id = element_id; const checkbox = document.createElement("input"); const checkbox_id = `${element_id}-checkbox`; checkbox.type = "checkbox"; checkbox.id = checkbox_id; input.addEventListener("click", async (event) => { const index = window.buttplug_devices.indexOf(device); if (index > -1) { await device.stop(); window.buttplug_devices.splice(index, 1); checkbox.checked = false; } else { window.buttplug_devices.push(device); checkbox.checked = true; } }); let label = document.createElement("label"); label.for = `${element_id}-checkbox`; label.innerHTML = device.Name; input.appendChild(checkbox); input.appendChild(label); device_list.appendChild(input); }); buttplug_client.addListener('deviceremoved', async (device) => { const element_id = `buttplug-device-${device.Index}`; var element = document.getElementById(element_id); element.parentNode.removeChild(element); }); connect_browser_button.addEventListener("click", async (event) => { const connector = new Buttplug.ButtplugEmbeddedConnectorOptions(); await buttplug_client.connect(connector); connector_div.style.display = "none"; enumeration_div.style.display = "block"; }, false); connect_intiface_button.addEventListener("click", async (event) => { const connector = new Buttplug.ButtplugWebsocketConnectorOptions("ws://localhost:12345/"); await buttplug_client.connect(connector); connector_div.style.display = "none"; enumeration_div.style.display = "block"; }, false); disconnect_button.addEventListener("click", async (event) => { await buttplug_client.disconnect(); enumeration_div.style.display = "none"; connector_div.style.display = "block"; }, false); scanning_button.addEventListener('click', async () => { await buttplug_client.startScanning(); }); let container = document.querySelector("#buttplug-top-container"); let close = document.getElementById(`close-bottom-right`); let open = document.getElementById(`open-bottom-right`); close.addEventListener("click", () => { container.style.display = "none"; open.style.display = "block"; }, false); container_div.addEventListener("click", () => { container.style.display = "none"; open.style.display = "block"; }, false); dialog_div.addEventListener("click", (ev) => { ev.stopPropagation(); }, false); open.addEventListener("click", () => { open.style.display = "none"; container.style.display = "block"; }, false); })(), 0); }, false); }