0xWho

Sorry, 0x who?

// ==UserScript==
// @name         0xWho
// @namespace    https://github.com/jack-the-pug/0xwho
// @version      0.1
// @description  Sorry, 0x who?
// @author       JtP
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAS1BMVEUAAAC+Ngv/ckG/NQz/b0O/Ngz/cEO/Ng3/cEOrMAuyNQ+zNhCzNhG7PBW/NgzBQRrDQhvFRBzHRR7OSyLaUyr1aT34aj77bUD/cEOHzN+4AAAACXRSTlMALy+np/Ly9PT89qvQAAAAkklEQVR42uzVQwLDQABA0bodG/e/aB3b/Ou8aHRYUcfzlVJf2O1ySl1/p7QI/HokxZlWAX9JgGs1uCUArQZ+CrADgbFoAsC3+kD+gKwN0A+g2gD/AK4NxA+IYT76l0RIzmqkjeYQcm1qAqfAP+XqAEtAGLE1AAOxWA0AEjUG812iO2h+oDQ/sl4jplIkudodPgAA1tB1J/uFI7EAAAAASUVORK5CYII=
// @grant        none
// @match        https://*/*
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

// ==== THIS IS YOUR ADDRESS BOOK ====
const addressBook = {
//  "address"                                     "name"
    "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045": "vitalik",
}
// ==== /END OF YOUR ADDRESS BOOK ====

class OXWHOInject{
    constructor(){
        this.addressMap = new Map()
        for(const address in addressBook){
            this.addressMap.set(address.toLowerCase(),{
                name:addressBook[address],
                address:address
            })
        }
        this.sourceDoms= new Map()
        this.formatAddressMap = new Map()
        this.addressMap.forEach(({name}, address) => {
            const formatAddress = this.replaceAddressByName(address,name).toLowerCase()
            this.formatAddressMap.set(address,formatAddress)
            this.formatAddressMap.set(formatAddress,address)
        })
        this.tooltipEl = document.createElement('div')
        this.tooltipEl.id = 'OXWHO-tip'
        this.tooltipTemplate = `
          <div>{{NAME}}</div>
        `
        this.init()
    }
    init(){
       this.injectStyle()
       window.addEventListener('load',() => {
        let timer = setTimeout(() => {
         this.changeDom()
         clearTimeout(timer)
        },1000)
       })
       document.addEventListener('selectionchange',() => this.handleSelectionchange())

       document.addEventListener('hashchange',this.changeDom)
       document.addEventListener('popstate',this.changeDom)
       document.addEventListener('pushstate',this.changeDom)

       let timer
       document.addEventListener('scroll',() => {
           if(timer) clearTimeout(timer)
           timer = setTimeout(() => {
               this.changeDom()
               clearTimeout(timer)
           },200)
       })
    }
    replaceAddressByName(address,name){
        const nameLen = name.length
        return nameLen <= 36 ? `0x${name}_${address.substring(nameLen + 3)}` : `0x${name.substring(0,35)}_${address.substring(38)}`
    }
    isAddress(text,strict = true){
        return strict ? /^0x[a-fA-F0-9]{40}$/.test(text) : /0x[a-fA-F0-9]{40}/g.test(text)
    }
     getAddressDoms(){
        const doms = document.querySelectorAll('* :not(script) :not(style) :not(a) :not(img) :not(input) :not(textarea) ')
        const nodeMap = new Map()
        doms.forEach((dom) => {
           let text = dom.textContent?.trim()
           if(!text || dom.childNodes.length !== 1 || dom.firstChild?.nodeName !== '#text') return
           // there can be multiple matches (addresses) in one element
           const textIter = text.matchAll(/0x[a-fA-F0-9]{40}/g)
           for(let matchRes of textIter){
             const address = matchRes[0]
             if(this.addressMap.has(address.toLowerCase())){
                // save the original address, for later revertion of the inline labeling edit
                dom.OXWHO_address = address
                nodeMap.set(dom, dom.textContent)
             }
           }
        })
        return nodeMap
    }
    changeDom(){
        const doms = this.getAddressDoms()
        this.sourceDoms = doms
        doms.forEach((text,node)=> {
            const matchTextAddress = text.matchAll(/0x[a-fA-F0-9]{40}/g)
            for(const matchRes of matchTextAddress){
                const address = matchRes[0]
                if(this.formatAddressMap.has(address.toLowerCase())){
                    const formatAddress = this.replaceAddressByName(node.OXWHO_address,this.addressMap.get(address.toLowerCase())?.name)
                    text= text.replace(address,formatAddress)
                }
            }
            node.textContent =text
        })
    }
    getSelectedMeta(){
        const selection = window.getSelection()
        if(!selection) return null
        let text = selection.toString().trim()
        if(!text && document.getElementById('OXWHO-tip')){
            document.body.removeChild(this.tooltipEl)
            return null
        }
        let el = selection.getRangeAt(0).startContainer
        if(el.nodeName === '#text' && el.nodeType === 3) el = el.parentElement
        return [text,el]
    }
    handleSelectionchange () {
        const selection = this.getSelectedMeta()
        if(!selection) return
        let [text,el] = selection
        if(this.isAddress(text)){
            text = text.toLowerCase()
            if(el.childNodes.length === 0) el = el.parentElement
            const position = this.getPosition(el)
            const profile = this.addressMap.get(text.toLowerCase())
            this.addTooltip(position,profile)
            return
        }

        if(!this.formatAddressMap.has(text.toLowerCase())) return
        const address = this.formatAddressMap.get(text.toLowerCase())

        if (this.isAddress(address)) {
            // @ts-ignore
            // must use textContent to replace
            el.textContent = el.textContent?.replace(text, el.OXWHO_address)
            const selc = window.getSelection()
            selc?.removeAllRanges()
            const range = document.createRange()
            range.selectNode(el)
            selc?.addRange(range)
        }
    }
    addTooltip(position, profile){
        const {x,y,w} = position
        this.tooltipEl.style.left = `${x + w/2}px`
        this.tooltipEl.style.top = `${y}px`
        this.tooltipEl.innerHTML = this.tooltipTemplate.replace('{{NAME}}',profile.name)
        document.body.appendChild(this.tooltipEl)
   }
   getPosition(el){
    const rect = el.getBoundingClientRect()
    const style = window.getComputedStyle(el)
    return {
        y: rect.y + window.scrollY + rect.height - parseFloat(style.paddingBottom),
        x: rect.x + window.scrollX + parseFloat(style.paddingLeft),
        h: rect.height,
        w: rect.width
    }
   }
   injectStyle(){
       const style = document.createElement('style')
       style.textContent = `
        #OXWHO-tip{
            position:absolute;
            z-index: 2147483647;
            background: rgba(253,230,138,0.8);
            padding: 4px 5px;
            color: black;
            border-radius: 4px;
            margin-top: 5px;
            font-weight: 500;
            font-size: 13px;
        }
       `
       document.head.appendChild(style)
   }
}


new OXWHOInject()

})()