sorrysoul / Hitched Unhinged - unhinged.ai

// ==UserScript==
// @name        Hitched Unhinged - unhinged.ai
// @namespace   Unhinged AI Scripts
// @match       https://www.unhinged.ai/chat?conversationId=*
// @version     0.1.32
// @author      sorrysoul
// @description Let two bots interact
// @license     MIT
// @grant   GM_getValue
// @grant   GM_setValue
// ==/UserScript==

console.log('Loading Hitched Unhinged v' + GM_info.script.version)

const urlParams = new URLSearchParams(window.location.search)
const conversationId = urlParams.get('conversationId')

const sKey = '_x_b'

let lbk = ''
let buddy = ''
let convData
let theBot = null
let waitUntil = 0

function setNativeValue(element, value) {
//   const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
//   const prototype = Object.getPrototypeOf(element)
//   const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set

//   if (valueSetter && valueSetter !== prototypeValueSetter) {
//     prototypeValueSetter.call(element, value)
//   } else {
//     valueSetter.call(element, value)
//   }
  console.log('do input', element)
  console.dir(element)
  var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set
  nativeInputValueSetter.call(element, value)
  var ev2 = new Event('input', { bubbles: true})
  element.dispatchEvent(ev2)
}

const getActiveBots = () => {
  // yeah, really should use a different way for many reasons, but...
  const cId = convData._id
  let bots = GM_getValue( sKey )
  bots = JSON.parse(bots || '{}')
  let myBot = theBot || bots[cId]
  if (!myBot) {
    myBot = theBot || {id: cId}
  }
  bots[cId] = myBot
  theBot = myBot
  myBot.i = (isNaN(myBot.i) ? -1 : myBot.i)
  myBot.d = Date.now()
  myBot.n = convData.character.name || 'unknown'
  const lbdy = buddy
  bots = Object.values(bots)
    .filter(b => Date.now() < (b.d + (120000)))
    .sort((a, b) => a.n.localeCompare(b.n) || a.id.localeCompare(b.id))
    .reduce((a, v) => {a[v.id] = v; if (cId && v.t === cId) {buddy = v.id}; return a}, {})
  myBot.t = buddy
  GM_setValue(sKey, JSON.stringify(bots))
  if (buddy && buddy !== lbdy) {
    nlbk = ''
  }
  return bots
}

const saveBot = (bot) => {
  bot.d = Date.now()
  let bots = GM_getValue( sKey )
  bots = JSON.parse(bots || '{}')
  bots[bot.id] = bot
  GM_setValue(sKey, JSON.stringify(bots))
  return bots
}

const getConvData = () => {
  const nd = document.querySelector('#__NEXT_DATA__')
  if (nd) {
    const json = nd.innerText
    const convdata = JSON.parse(json)
    console.log('convdata', convdata)
    if (!convdata || !convdata.props || !convdata.props.pageProps || !convdata.props.pageProps.conversation) {
      console.error("Doesn't appear to contain a valid conversation")
      return null
    }
    return convdata.props.pageProps.conversation
  }
}

const doInterval = () => {
  if (!convData) {
    convData = getConvData()
    if (!convData) {
      console.log('No convData')
      return
    }
    console.log('got convData', convData)
  }
  const cId = convData._id
  if (!cId) {
    console.log('No coversation')
    return
  }
  const bots = getActiveBots()
  const myBot = bots[cId]
  if (!myBot) {
    console.error('My bot not there?', myBot, cId, bots)
  }
  const doc = unsafeWindow.document
  const bb = bots[buddy]
  const input = document.querySelector('textarea.message-input')
  const msgs = document.querySelectorAll('.message-bubble-profile-picture+div .message-bubble-content')
  const recv = document.querySelector('.message-bubble-content-container.recipient:not(.clickable)')
  let lastMsg = null
  if (!recv && bb) {
    const hasbb = bb && bb.m && input && (isNaN(myBot.lmi) || bb.i > myBot.lmi)
    if (hasbb) {
      console.log('got nessage', bb)
      input.focus()
      setNativeValue(input, bb.m)
      // input.value = bb.m
      myBot.lmi = bb.i
      setTimeout(() => {
        const sb = document.querySelector('.message-send-button')
        if (sb) {
          // sb.dispatchEvent(new Event('click', { bubbles: true}))
          sb.click()
        } else {
          console.error('No message send button')
        }
      }, 200)
    } else {
      const ma = []
      for (const msg of msgs) {
        const bbl = msg && msg.closest('div[data-component="MessageBubble"]')
        const bbli = bbl && parseInt(bbl.getAttribute('index'))
        ma.push({m: msg, i: bbli})
      }
      ma.sort((a, b) => b.i - a.i)
      const mel = ma[0]
      if (mel && (isNaN(myBot.i) || mel.i > myBot.i)) {
        console.log('set lm', mel, myBot)
        myBot.i = mel.i
        myBot.m = mel.m.innerText
      }

    }
  } else {
    // console.log('incomming message from my AI...', recv)
  }
  saveBot(myBot)
  let xbs = document.getElementById('xb-select')
  if (Object.keys(bots).length < 2) {
    xbs && xbs.remove()
    // console.error('No other bos', bots)
    return
  }
  const nlbk = Object.values(bots).map(b => b.n + ':' + b.id).join(';')
  const changed = lbk !== nlbk
  lbk = nlbk
  if (!xbs || changed) {
    console.log('found new bot convo', bots)
    xbs && xbs.remove()
    xbs = doc.createElement('select')
    const opt = doc.createElement('option')
    opt.value = ''
    opt.innerHTML = 'Select Bot to Talk To'
    xbs.appendChild(opt)
    Object.values(bots).forEach(b => {
      if (b.id === convData._id) return
      const opt = doc.createElement('option')
      opt.value = b.id
      opt.innerHTML = b.n + ' (' + b.id + ')'
      if (b.t === cId) opt.selected = true
      xbs.appendChild(opt)
    })
    xbs.style.display = 'block'
    xbs.style.position = 'fixed'
    xbs.style.top = '60px'
    xbs.style.right = '0px'
    xbs.id = 'xb-select'
    xbs.onchange = (e) => {
      const bots = getActiveBots()
      const myBot = bots[cId]
      buddy = e.target.value
      myBot.t = buddy
      saveBot(myBot)
    }
    document.body.appendChild(xbs)
  }
}

let toTimer = setInterval(doInterval, 1000) // Ugly, yeah, but oh well.
// setTimeout(()=>{clearInterval(toTimer)}, 10000)