Replace Ugly Avatars

🔃 Replace specified user's avatar (profile photo) and username (nickname)

目前為 2023-08-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name                 Replace Ugly Avatars
// @name:zh-CN           赐你个头像吧
// @namespace            https://github.com/utags/replace-ugly-avatars
// @homepageURL          https://github.com/utags/replace-ugly-avatars#readme
// @supportURL           https://github.com/utags/replace-ugly-avatars/issues
// @version              0.0.5
// @description          🔃 Replace specified user's avatar (profile photo) and username (nickname)
// @description:zh-CN    🔃 换掉别人的头像与昵称
// @icon                 data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%230d6efd' class='bi bi-arrow-repeat' viewBox='0 0 16 16'%3E %3Cpath d='M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z'/%3E %3Cpath fill-rule='evenodd' d='M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z'/%3E %3C/svg%3E
// @author               Pipecraft
// @license              MIT
// @match                https://*.v2ex.com/*
// @match                https://v2hot.pipecraft.net/*
// @run-at               document-start
// @grant                GM_addElement
// @grant                GM.getValue
// @grant                GM.setValue
// @grant                GM_addValueChangeListener
// @grant                GM_removeValueChangeListener
// ==/UserScript==
//
;(() => {
  "use strict"
  var doc = document
  if (typeof String.prototype.replaceAll !== "function") {
    String.prototype.replaceAll = String.prototype.replace
  }
  var $ = (selectors, element) => (element || doc).querySelector(selectors)
  var $$ = (selectors, element) => [
    ...(element || doc).querySelectorAll(selectors),
  ]
  var getRootElement = (type) =>
    type === 1
      ? doc.head || doc.body || doc.documentElement
      : type === 2
      ? doc.body || doc.documentElement
      : doc.documentElement
  var createElement = (tagName, attributes) =>
    setAttributes(doc.createElement(tagName), attributes)
  var addElement = (parentNode, tagName, attributes) => {
    if (typeof parentNode === "string") {
      return addElement(null, parentNode, tagName)
    }
    if (!tagName) {
      return
    }
    if (!parentNode) {
      parentNode = /^(script|link|style|meta)$/.test(tagName)
        ? getRootElement(1)
        : getRootElement(2)
    }
    if (typeof tagName === "string") {
      const element = createElement(tagName, attributes)
      parentNode.append(element)
      return element
    }
    setAttributes(tagName, attributes)
    parentNode.append(tagName)
    return tagName
  }
  var addEventListener = (element, type, listener, options) => {
    if (!element) {
      return
    }
    if (typeof type === "object") {
      for (const type1 in type) {
        if (Object.hasOwn(type, type1)) {
          element.addEventListener(type1, type[type1])
        }
      }
    } else if (typeof type === "string" && typeof listener === "function") {
      element.addEventListener(type, listener, options)
    }
  }
  var removeEventListener = (element, type, listener, options) => {
    if (!element) {
      return
    }
    if (typeof type === "object") {
      for (const type1 in type) {
        if (Object.hasOwn(type, type1)) {
          element.removeEventListener(type1, type[type1])
        }
      }
    } else if (typeof type === "string" && typeof listener === "function") {
      element.removeEventListener(type, listener, options)
    }
  }
  var setAttribute = (element, name, value) =>
    element ? element.setAttribute(name, value) : void 0
  var setAttributes = (element, attributes) => {
    if (element && attributes) {
      for (const name in attributes) {
        if (Object.hasOwn(attributes, name)) {
          const value = attributes[name]
          if (value === void 0) {
            continue
          }
          if (/^(value|textContent|innerText)$/.test(name)) {
            element[name] = value
          } else if (/^(innerHTML)$/.test(name)) {
            element[name] = createHTML(value)
          } else if (name === "style") {
            setStyle(element, value, true)
          } else if (/on\w+/.test(name)) {
            const type = name.slice(2)
            addEventListener(element, type, value)
          } else {
            setAttribute(element, name, value)
          }
        }
      }
    }
    return element
  }
  var addClass = (element, className) => {
    if (!element || !element.classList) {
      return
    }
    element.classList.add(className)
  }
  var removeClass = (element, className) => {
    if (!element || !element.classList) {
      return
    }
    element.classList.remove(className)
  }
  var setStyle = (element, values, overwrite) => {
    if (!element) {
      return
    }
    const style = element.style
    if (typeof values === "string") {
      style.cssText = overwrite ? values : style.cssText + ";" + values
      return
    }
    if (overwrite) {
      style.cssText = ""
    }
    for (const key in values) {
      if (Object.hasOwn(values, key)) {
        style[key] = values[key].replace("!important", "")
      }
    }
  }
  var throttle = (func, interval) => {
    let timeoutId = null
    let next = false
    const handler = (...args) => {
      if (timeoutId) {
        next = true
      } else {
        func.apply(void 0, args)
        timeoutId = setTimeout(() => {
          timeoutId = null
          if (next) {
            next = false
            handler()
          }
        }, interval)
      }
    }
    return handler
  }
  if (typeof Object.hasOwn !== "function") {
    Object.hasOwn = (instance, prop) =>
      Object.prototype.hasOwnProperty.call(instance, prop)
  }
  var getOffsetPosition = (element, referElement) => {
    const position = { top: 0, left: 0 }
    referElement = referElement || doc.body
    while (element && element !== referElement) {
      position.top += element.offsetTop
      position.left += element.offsetLeft
      element = element.offsetParent
    }
    return position
  }
  var headFuncArray = []
  var bodyFuncArray = []
  var headBodyObserver
  var startObserveHeadBodyExists = () => {
    if (headBodyObserver) {
      return
    }
    headBodyObserver = new MutationObserver(() => {
      if (doc.head && doc.body) {
        headBodyObserver.disconnect()
      }
      if (doc.head && headFuncArray.length > 0) {
        for (const func of headFuncArray) {
          func()
        }
        headFuncArray.length = 0
      }
      if (doc.body && bodyFuncArray.length > 0) {
        for (const func of bodyFuncArray) {
          func()
        }
        bodyFuncArray.length = 0
      }
    })
    headBodyObserver.observe(doc, {
      childList: true,
      subtree: true,
    })
  }
  var runWhenHeadExists = (func) => {
    if (!doc.head) {
      headFuncArray.push(func)
      startObserveHeadBodyExists()
      return
    }
    func()
  }
  var escapeHTMLPolicy =
    typeof trustedTypes !== "undefined" &&
    typeof trustedTypes.createPolicy === "function"
      ? trustedTypes.createPolicy("beuEscapePolicy", {
          createHTML: (string) => string,
        })
      : void 0
  var createHTML = (html) => {
    return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html
  }
  var addElement2 =
    typeof GM_addElement === "function"
      ? (parentNode, tagName, attributes) => {
          if (typeof parentNode === "string") {
            return addElement2(null, parentNode, tagName)
          }
          if (!tagName) {
            return
          }
          if (!parentNode) {
            parentNode = /^(script|link|style|meta)$/.test(tagName)
              ? getRootElement(1)
              : getRootElement(2)
          }
          if (typeof tagName === "string") {
            let attributes2
            if (attributes) {
              const entries1 = []
              const entries2 = []
              for (const entry of Object.entries(attributes)) {
                if (/^(on\w+|innerHTML)$/.test(entry[0])) {
                  entries2.push(entry)
                } else {
                  entries1.push(entry)
                }
              }
              attributes = Object.fromEntries(entries1)
              attributes2 = Object.fromEntries(entries2)
            }
            const element = GM_addElement(null, tagName, attributes)
            setAttributes(element, attributes2)
            parentNode.append(element)
            return element
          }
          setAttributes(tagName, attributes)
          parentNode.append(tagName)
          return tagName
        }
      : addElement
  var content_default =
    '#rua_container .change_button{position:absolute;box-sizing:border-box;width:20px;height:20px;padding:1px;border:1px solid;cursor:pointer;color:#0d6efd}#rua_container .change_button.advanced{color:#00008b;display:none}#rua_container .change_button.hide{display:none}#rua_container .change_button:active,#rua_container .change_button.active{opacity:50%;transition:all .2s}#rua_container:hover .change_button{display:block}img.rua_fadeout{box-sizing:border-box;padding:20px;transition:all 2s ease-out}#Main .header .fr a img{width:73px;height:73px}td[width="48"] img{width:48px;height:48px}'
  var styles = [
    "adventurer",
    "adventurer-neutral",
    "avataaars",
    "avataaars-neutral",
    "big-ears",
    "big-ears-neutral",
    "big-smile",
    "bottts",
    "bottts-neutral",
    "croodles",
    "croodles-neutral",
    "fun-emoji",
    "icons",
    "identicon",
    "initials",
    "lorelei",
    "lorelei-neutral",
    "micah",
    "miniavs",
    "notionists",
    "notionists-neutral",
    "open-peeps",
    "personas",
    "pixel-art",
    "pixel-art-neutral",
    "shapes",
    "thumbs",
  ]
  function getRandomInt(min, max) {
    min = Math.ceil(min)
    max = Math.floor(max)
    return Math.floor(Math.random() * (max - min)) + min
  }
  function getRandomAvatar(prefix) {
    const randomStyle = styles[getRandomInt(0, styles.length)]
    return "https://api.dicebear.com/6.x/"
      .concat(randomStyle, "/svg?seed=")
      .concat(prefix, ".")
      .concat(Date.now())
  }
  var changeIcon =
    '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-repeat" viewBox="0 0 16 16">\n<path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/>\n<path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/>\n</svg>'
  var listeners = {}
  var getValue = async (key) => {
    const value = await GM.getValue(key)
    return value && value !== "undefined" ? JSON.parse(value) : void 0
  }
  var setValue = async (key, value) => {
    if (value !== void 0) {
      const newValue = JSON.stringify(value)
      if (listeners[key]) {
        const oldValue = await GM.getValue(key)
        await GM.setValue(key, newValue)
        if (newValue !== oldValue) {
          for (const func of listeners[key]) {
            func(key, oldValue, newValue)
          }
        }
      } else {
        await GM.setValue(key, newValue)
      }
    }
  }
  var _addValueChangeListener = (key, func) => {
    listeners[key] = listeners[key] || []
    listeners[key].push(func)
    return () => {
      if (listeners[key] && listeners[key].length > 0) {
        for (let i = listeners[key].length - 1; i >= 0; i--) {
          if (listeners[key][i] === func) {
            listeners[key].splice(i, 1)
          }
        }
      }
    }
  }
  var addValueChangeListener = (key, func) => {
    if (typeof GM_addValueChangeListener !== "function") {
      console.warn("Do not support GM_addValueChangeListener!")
      return _addValueChangeListener(key, func)
    }
    const listenerId = GM_addValueChangeListener(key, func)
    return () => {
      GM_removeValueChangeListener(listenerId)
    }
  }
  var host = location.host
  var storageKey = "avatar:v2ex.com"
  async function saveAvatar(userName, src) {
    const values = (await getValue(storageKey)) || {}
    values[userName] = src
    await setValue(storageKey, values)
  }
  var cachedValues = {}
  async function reloadCachedValues() {
    cachedValues = (await getValue(storageKey)) || {}
  }
  function getChangedAavatar(userName) {
    return cachedValues[userName]
  }
  async function initStorage(options) {
    addValueChangeListener(storageKey, async () => {
      await reloadCachedValues()
      if (options && typeof options.avatarValueChangeListener === "function") {
        options.avatarValueChangeListener()
      }
    })
    await reloadCachedValues()
  }
  function isAvatar(element) {
    if (!element || element.tagName !== "IMG") {
      return false
    }
    if (element.dataset.ruaUserName) {
      return true
    }
    return false
  }
  var currentTarget
  function addChangeButton(element) {
    currentTarget = element
    const container =
      $("#rua_container") ||
      addElement2(doc.body, "div", {
        id: "rua_container",
      })
    const changeButton =
      $(".change_button.quick", container) ||
      addElement2(container, "button", {
        innerHTML: changeIcon,
        class: "change_button quick",
        async onclick() {
          addClass(changeButton, "active")
          setTimeout(() => {
            removeClass(changeButton, "active")
          }, 200)
          const userName = currentTarget.dataset.ruaUserName || "noname"
          const avatarUrl = getRandomAvatar(userName)
          changeAvatar(currentTarget, avatarUrl, true)
          await saveAvatar(userName, avatarUrl)
        },
      })
    const changeButton2 =
      $(".change_button.advanced", container) ||
      addElement2(container, "button", {
        innerHTML: changeIcon,
        class: "change_button advanced",
        async onclick() {
          addClass(changeButton2, "active")
          setTimeout(() => {
            removeClass(changeButton2, "active")
          }, 200)
          const userName = currentTarget.dataset.ruaUserName || "noname"
          const avatarUrl = prompt(
            "\u8BF7\u8F93\u5165\u5934\u50CF\u94FE\u63A5",
            ""
          )
          if (avatarUrl) {
            changeAvatar(currentTarget, avatarUrl, true)
            await saveAvatar(userName, avatarUrl)
          }
        },
      })
    removeClass(changeButton, "hide")
    removeClass(changeButton2, "hide")
    const pos = getOffsetPosition(element)
    changeButton.style.top = pos.top + "px"
    changeButton.style.left =
      pos.left + element.clientWidth - changeButton.clientWidth + "px"
    changeButton2.style.top = pos.top + changeButton.clientHeight + "px"
    changeButton2.style.left =
      pos.left + element.clientWidth - changeButton.clientWidth + "px"
    const mouseoutHandler = () => {
      addClass(changeButton, "hide")
      addClass(changeButton2, "hide")
      removeEventListener(element, "mouseout", mouseoutHandler)
    }
    addEventListener(element, "mouseout", mouseoutHandler)
  }
  function getUserName(element) {
    if (!element) {
      return
    }
    const userNameElement = $('a[href*="/member/"]', element)
    if (userNameElement) {
      const userName = (/member\/(\w+)/.exec(userNameElement.href) || [])[1]
      if (userName) {
        return userName.toLowerCase()
      }
      return
    }
    return getUserName(element.parentElement)
  }
  function changeAvatar(element, src, animation = false) {
    if (element.ruaLoading) {
      return
    }
    if (!element.dataset.orgSrc) {
      const orgSrc = element.dataset.src || element.src
      element.dataset.orgSrc = orgSrc
    }
    element.ruaLoading = true
    const imgOnloadHandler = () => {
      element.ruaLoading = false
      removeClass(element, "rua_fadeout")
      removeEventListener(element, "load", imgOnloadHandler)
      removeEventListener(element, "error", imgOnloadHandler)
    }
    addEventListener(element, "load", imgOnloadHandler)
    addEventListener(element, "error", imgOnloadHandler)
    const width = element.clientWidth
    const height = element.clientHeight
    if (width > 1) {
      element.style.width = width + "px"
    }
    if (height > 1) {
      element.style.height = height + "px"
    }
    if (animation) {
      addClass(element, "rua_fadeout")
    } else {
      element.src =
        "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
    }
    setTimeout(() => {
      element.src = src
    })
    if (element.dataset.src) {
      element.dataset.src = src
    }
  }
  function scanAvatars() {
    const avatars = $$('.avatar,a[href*="/member/"] img')
    for (const avatar of avatars) {
      let userName = avatar.dataset.ruaUserName
      if (!userName) {
        userName = getUserName(avatar)
        if (!userName) {
          console.error("Can't get username", avatar, userName)
          continue
        }
        avatar.dataset.ruaUserName = userName
      }
      const newAvatarSrc = getChangedAavatar(userName)
      if (newAvatarSrc && avatar.src !== newAvatarSrc) {
        changeAvatar(avatar, newAvatarSrc)
      }
    }
  }
  async function main() {
    if ($("#rua_tyle")) {
      return
    }
    runWhenHeadExists(() => {
      addElement2("style", {
        textContent: content_default,
        id: "rua_tyle",
      })
    })
    addEventListener(doc, "mouseover", (event) => {
      const target = event.target
      if (!isAvatar(target)) {
        return
      }
      addChangeButton(target)
    })
    await initStorage({
      avatarValueChangeListener() {
        scanAvatars()
      },
    })
    scanAvatars()
    const observer = new MutationObserver(
      throttle(async () => {
        scanAvatars()
      }, 500)
    )
    observer.observe(doc, {
      childList: true,
      subtree: true,
    })
  }
  main()
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址