ButtEOS

2/21/2022, 9:26:31 PM

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
}