赐你个头像吧

🔃 换掉别人的头像与昵称

当前为 2023-08-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Replace Ugly Avatars
  3. // @name:zh-CN 赐你个头像吧
  4. // @namespace https://github.com/utags/replace-ugly-avatars
  5. // @homepageURL https://github.com/utags/replace-ugly-avatars#readme
  6. // @supportURL https://github.com/utags/replace-ugly-avatars/issues
  7. // @version 0.2.0
  8. // @description 🔃 Replace specified user's avatar (profile photo) and username (nickname)
  9. // @description:zh-CN 🔃 换掉别人的头像与昵称
  10. // @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
  11. // @author Pipecraft
  12. // @license MIT
  13. // @match https://*.v2ex.com/*
  14. // @match https://v2hot.pipecraft.net/*
  15. // @run-at document-start
  16. // @grant GM.getValue
  17. // @grant GM.setValue
  18. // @grant GM.deleteValue
  19. // @grant GM_addValueChangeListener
  20. // @grant GM_removeValueChangeListener
  21. // @grant GM_addElement
  22. // @grant GM.registerMenuCommand
  23. // ==/UserScript==
  24. //
  25. ;(() => {
  26. "use strict"
  27. var listeners = {}
  28. var getValue = async (key) => {
  29. const value = await GM.getValue(key)
  30. return value && value !== "undefined" ? JSON.parse(value) : void 0
  31. }
  32. var setValue = async (key, value) => {
  33. if (value !== void 0) {
  34. const newValue = JSON.stringify(value)
  35. if (listeners[key]) {
  36. const oldValue = await GM.getValue(key)
  37. await GM.setValue(key, newValue)
  38. if (newValue !== oldValue) {
  39. for (const func of listeners[key]) {
  40. func(key, oldValue, newValue)
  41. }
  42. }
  43. } else {
  44. await GM.setValue(key, newValue)
  45. }
  46. }
  47. }
  48. var deleteValue = async (key) => GM.deleteValue(key)
  49. var _addValueChangeListener = (key, func) => {
  50. listeners[key] = listeners[key] || []
  51. listeners[key].push(func)
  52. return () => {
  53. if (listeners[key] && listeners[key].length > 0) {
  54. for (let i = listeners[key].length - 1; i >= 0; i--) {
  55. if (listeners[key][i] === func) {
  56. listeners[key].splice(i, 1)
  57. }
  58. }
  59. }
  60. }
  61. }
  62. var addValueChangeListener = (key, func) => {
  63. if (typeof GM_addValueChangeListener !== "function") {
  64. console.warn("Do not support GM_addValueChangeListener!")
  65. return _addValueChangeListener(key, func)
  66. }
  67. const listenerId = GM_addValueChangeListener(key, func)
  68. return () => {
  69. GM_removeValueChangeListener(listenerId)
  70. }
  71. }
  72. var doc = document
  73. if (typeof String.prototype.replaceAll !== "function") {
  74. String.prototype.replaceAll = String.prototype.replace
  75. }
  76. var $ = (selectors, element) => (element || doc).querySelector(selectors)
  77. var $$ = (selectors, element) => [
  78. ...(element || doc).querySelectorAll(selectors),
  79. ]
  80. var getRootElement = (type) =>
  81. type === 1
  82. ? doc.head || doc.body || doc.documentElement
  83. : type === 2
  84. ? doc.body || doc.documentElement
  85. : doc.documentElement
  86. var createElement = (tagName, attributes) =>
  87. setAttributes(doc.createElement(tagName), attributes)
  88. var addElement = (parentNode, tagName, attributes) => {
  89. if (typeof parentNode === "string") {
  90. return addElement(null, parentNode, tagName)
  91. }
  92. if (!tagName) {
  93. return
  94. }
  95. if (!parentNode) {
  96. parentNode = /^(script|link|style|meta)$/.test(tagName)
  97. ? getRootElement(1)
  98. : getRootElement(2)
  99. }
  100. if (typeof tagName === "string") {
  101. const element = createElement(tagName, attributes)
  102. parentNode.append(element)
  103. return element
  104. }
  105. setAttributes(tagName, attributes)
  106. parentNode.append(tagName)
  107. return tagName
  108. }
  109. var addEventListener = (element, type, listener, options) => {
  110. if (!element) {
  111. return
  112. }
  113. if (typeof type === "object") {
  114. for (const type1 in type) {
  115. if (Object.hasOwn(type, type1)) {
  116. element.addEventListener(type1, type[type1])
  117. }
  118. }
  119. } else if (typeof type === "string" && typeof listener === "function") {
  120. element.addEventListener(type, listener, options)
  121. }
  122. }
  123. var removeEventListener = (element, type, listener, options) => {
  124. if (!element) {
  125. return
  126. }
  127. if (typeof type === "object") {
  128. for (const type1 in type) {
  129. if (Object.hasOwn(type, type1)) {
  130. element.removeEventListener(type1, type[type1])
  131. }
  132. }
  133. } else if (typeof type === "string" && typeof listener === "function") {
  134. element.removeEventListener(type, listener, options)
  135. }
  136. }
  137. var setAttribute = (element, name, value) =>
  138. element ? element.setAttribute(name, value) : void 0
  139. var setAttributes = (element, attributes) => {
  140. if (element && attributes) {
  141. for (const name in attributes) {
  142. if (Object.hasOwn(attributes, name)) {
  143. const value = attributes[name]
  144. if (value === void 0) {
  145. continue
  146. }
  147. if (/^(value|textContent|innerText)$/.test(name)) {
  148. element[name] = value
  149. } else if (/^(innerHTML)$/.test(name)) {
  150. element[name] = createHTML(value)
  151. } else if (name === "style") {
  152. setStyle(element, value, true)
  153. } else if (/on\w+/.test(name)) {
  154. const type = name.slice(2)
  155. addEventListener(element, type, value)
  156. } else {
  157. setAttribute(element, name, value)
  158. }
  159. }
  160. }
  161. }
  162. return element
  163. }
  164. var addClass = (element, className) => {
  165. if (!element || !element.classList) {
  166. return
  167. }
  168. element.classList.add(className)
  169. }
  170. var removeClass = (element, className) => {
  171. if (!element || !element.classList) {
  172. return
  173. }
  174. element.classList.remove(className)
  175. }
  176. var setStyle = (element, values, overwrite) => {
  177. if (!element) {
  178. return
  179. }
  180. const style = element.style
  181. if (typeof values === "string") {
  182. style.cssText = overwrite ? values : style.cssText + ";" + values
  183. return
  184. }
  185. if (overwrite) {
  186. style.cssText = ""
  187. }
  188. for (const key in values) {
  189. if (Object.hasOwn(values, key)) {
  190. style[key] = values[key].replace("!important", "")
  191. }
  192. }
  193. }
  194. var throttle = (func, interval) => {
  195. let timeoutId = null
  196. let next = false
  197. const handler = (...args) => {
  198. if (timeoutId) {
  199. next = true
  200. } else {
  201. func.apply(void 0, args)
  202. timeoutId = setTimeout(() => {
  203. timeoutId = null
  204. if (next) {
  205. next = false
  206. handler()
  207. }
  208. }, interval)
  209. }
  210. }
  211. return handler
  212. }
  213. if (typeof Object.hasOwn !== "function") {
  214. Object.hasOwn = (instance, prop) =>
  215. Object.prototype.hasOwnProperty.call(instance, prop)
  216. }
  217. var getOffsetPosition = (element, referElement) => {
  218. const position = { top: 0, left: 0 }
  219. referElement = referElement || doc.body
  220. while (element && element !== referElement) {
  221. position.top += element.offsetTop
  222. position.left += element.offsetLeft
  223. element = element.offsetParent
  224. }
  225. return position
  226. }
  227. var runOnceCache = {}
  228. var runOnce = async (key, func) => {
  229. if (Object.hasOwn(runOnceCache, key)) {
  230. return runOnceCache[key]
  231. }
  232. const result = await func()
  233. if (key) {
  234. runOnceCache[key] = result
  235. }
  236. return result
  237. }
  238. var parseInt10 = (number, defaultValue) => {
  239. if (typeof number === "number" && !Number.isNaN(number)) {
  240. return number
  241. }
  242. if (typeof defaultValue !== "number") {
  243. defaultValue = Number.NaN
  244. }
  245. if (!number) {
  246. return defaultValue
  247. }
  248. const result = Number.parseInt(number, 10)
  249. return Number.isNaN(result) ? defaultValue : result
  250. }
  251. var headFuncArray = []
  252. var bodyFuncArray = []
  253. var headBodyObserver
  254. var startObserveHeadBodyExists = () => {
  255. if (headBodyObserver) {
  256. return
  257. }
  258. headBodyObserver = new MutationObserver(() => {
  259. if (doc.head && doc.body) {
  260. headBodyObserver.disconnect()
  261. }
  262. if (doc.head && headFuncArray.length > 0) {
  263. for (const func of headFuncArray) {
  264. func()
  265. }
  266. headFuncArray.length = 0
  267. }
  268. if (doc.body && bodyFuncArray.length > 0) {
  269. for (const func of bodyFuncArray) {
  270. func()
  271. }
  272. bodyFuncArray.length = 0
  273. }
  274. })
  275. headBodyObserver.observe(doc, {
  276. childList: true,
  277. subtree: true,
  278. })
  279. }
  280. var runWhenHeadExists = (func) => {
  281. if (!doc.head) {
  282. headFuncArray.push(func)
  283. startObserveHeadBodyExists()
  284. return
  285. }
  286. func()
  287. }
  288. var runWhenBodyExists = (func) => {
  289. if (!doc.body) {
  290. bodyFuncArray.push(func)
  291. startObserveHeadBodyExists()
  292. return
  293. }
  294. func()
  295. }
  296. var escapeHTMLPolicy =
  297. typeof trustedTypes !== "undefined" &&
  298. typeof trustedTypes.createPolicy === "function"
  299. ? trustedTypes.createPolicy("beuEscapePolicy", {
  300. createHTML: (string) => string,
  301. })
  302. : void 0
  303. var createHTML = (html) => {
  304. return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html
  305. }
  306. var addElement2 =
  307. typeof GM_addElement === "function"
  308. ? (parentNode, tagName, attributes) => {
  309. if (typeof parentNode === "string") {
  310. return addElement2(null, parentNode, tagName)
  311. }
  312. if (!tagName) {
  313. return
  314. }
  315. if (!parentNode) {
  316. parentNode = /^(script|link|style|meta)$/.test(tagName)
  317. ? getRootElement(1)
  318. : getRootElement(2)
  319. }
  320. if (typeof tagName === "string") {
  321. let attributes2
  322. if (attributes) {
  323. const entries1 = []
  324. const entries2 = []
  325. for (const entry of Object.entries(attributes)) {
  326. if (/^(on\w+|innerHTML)$/.test(entry[0])) {
  327. entries2.push(entry)
  328. } else {
  329. entries1.push(entry)
  330. }
  331. }
  332. attributes = Object.fromEntries(entries1)
  333. attributes2 = Object.fromEntries(entries2)
  334. }
  335. const element = GM_addElement(null, tagName, attributes)
  336. setAttributes(element, attributes2)
  337. parentNode.append(element)
  338. return element
  339. }
  340. setAttributes(tagName, attributes)
  341. parentNode.append(tagName)
  342. return tagName
  343. }
  344. : addElement
  345. var addStyle = (styleText) =>
  346. addElement2(null, "style", { textContent: styleText })
  347. var registerMenuCommand = (name, callback, accessKey) => {
  348. if (window !== top) {
  349. return
  350. }
  351. if (typeof GM.registerMenuCommand !== "function") {
  352. console.warn("Do not support GM.registerMenuCommand!")
  353. return
  354. }
  355. GM.registerMenuCommand(name, callback, accessKey)
  356. }
  357. var style_default =
  358. '#browser_extension_settings_container{--browser-extension-settings-background-color: #f2f2f7;--browser-extension-settings-text-color: #444444;--browser-extension-settings-link-color: #217dfc;--sb-track-color: #00000000;--sb-thumb-color: #33334480;--sb-size: 2px;position:fixed;top:10px;right:30px;max-height:90%;height:600px;overflow:hidden;display:none;z-index:100000;border-radius:5px;-webkit-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);-moz-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);box-shadow:0px 10px 39px 10px rgba(62,66,66,.22) !important}#browser_extension_settings_container .browser_extension_settings_wrapper{display:flex;height:100%;overflow:hidden;background-color:var(--browser-extension-settings-background-color)}#browser_extension_settings_container .browser_extension_settings_wrapper h1{font-size:26px;font-weight:800;border:none}#browser_extension_settings_container .browser_extension_settings_wrapper h2{font-size:18px;font-weight:600;border:none}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container{overflow-x:auto;box-sizing:border-box;padding:10px 15px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div{background-color:#fff;font-size:14px;border-top:1px solid #ccc;padding:6px 15px 6px 15px}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited{display:flex;justify-content:space-between;align-items:center;cursor:pointer;text-decoration:none;color:var(--browser-extension-settings-text-color)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited:hover{text-decoration:none;color:var(--browser-extension-settings-text-color)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited span{margin-right:10px;line-height:24px}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div.active,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div.active,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:hover{background-color:#e4e4e6}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div.active a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div.active a{cursor:default}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:first-of-type,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:first-of-type{border-top:none;border-top-right-radius:10px;border-top-left-radius:10px}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:last-of-type,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:last-of-type{border-bottom-right-radius:10px;border-bottom-left-radius:10px}#browser_extension_settings_container .thin_scrollbar{scrollbar-color:var(--sb-thumb-color) var(--sb-track-color);scrollbar-width:thin}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar{width:var(--sb-size)}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar-track{background:var(--sb-track-color);border-radius:10px}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar-thumb{background:var(--sb-thumb-color);border-radius:10px}#browser_extension_settings_main{min-width:250px;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:10px 15px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color)}#browser_extension_settings_main h2{text-align:center;margin:5px 0 0;font-size:18px;font-weight:600;border:none}#browser_extension_settings_main footer{display:flex;justify-content:center;flex-direction:column;font-size:11px;margin:10px auto 0px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color)}#browser_extension_settings_main footer a{color:var(--browser-extension-settings-link-color) !important;text-decoration:none;padding:0}#browser_extension_settings_main footer p{text-align:center;padding:0;margin:2px;line-height:13px}#browser_extension_settings_main a.navigation_go_previous{color:var(--browser-extension-settings-link-color);cursor:pointer;display:none}#browser_extension_settings_main a.navigation_go_previous::before{content:"< "}#browser_extension_settings_main .option_groups{background-color:#fff;padding:6px 15px 6px 15px;border-radius:10px;display:flex;flex-direction:column;margin:10px 0 0}#browser_extension_settings_main .option_groups .action{font-size:14px;padding:6px 0 6px 0;color:var(--browser-extension-settings-link-color);cursor:pointer}#browser_extension_settings_main .bes_external_link{font-size:14px;padding:6px 0 6px 0}#browser_extension_settings_main .bes_external_link a,#browser_extension_settings_main .bes_external_link a:visited,#browser_extension_settings_main .bes_external_link a:hover{color:var(--browser-extension-settings-link-color);text-decoration:none;cursor:pointer}#browser_extension_settings_main .option_groups textarea{font-size:12px;margin:10px 0 10px 0;height:100px;width:100%;border:1px solid #a9a9a9;border-radius:4px;box-sizing:border-box}#browser_extension_settings_main .switch_option{display:flex;justify-content:space-between;align-items:center;padding:6px 0 6px 0;font-size:14px}#browser_extension_settings_main .option_groups>*{border-top:1px solid #ccc}#browser_extension_settings_main .option_groups>*:first-child{border-top:none}#browser_extension_settings_main .switch_option>img{width:24px;height:24px;margin-right:10px}#browser_extension_settings_main .switch_option>span{margin-right:10px;flex-grow:1}#browser_extension_settings_main .option_groups .bes_tip{position:relative;margin:0;padding:0 15px 0 0;border:none;max-width:none;font-size:14px}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_anchor{cursor:help;text-decoration:underline}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_content{position:absolute;bottom:15px;left:0;background-color:#fff;color:var(--browser-extension-settings-text-color);text-align:left;padding:10px;display:none;border-radius:5px;-webkit-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);-moz-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);box-shadow:0px 10px 39px 10px rgba(62,66,66,.22) !important}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_anchor:hover+.bes_tip_content,#browser_extension_settings_main .option_groups .bes_tip .bes_tip_content:hover{display:block}#browser_extension_settings_main .option_groups .bes_tip p,#browser_extension_settings_main .option_groups .bes_tip pre{margin:revert;padding:revert}#browser_extension_settings_main .option_groups .bes_tip pre{font-family:Consolas,panic sans,bitstream vera sans mono,Menlo,microsoft yahei,monospace;font-size:13px;letter-spacing:.015em;line-height:120%;white-space:pre;overflow:auto;background-color:#f5f5f5;word-break:normal;overflow-wrap:normal;padding:.5em;border:none}#browser_extension_settings_main .container{--button-width: 51px;--button-height: 24px;--toggle-diameter: 20px;--color-off: #e9e9eb;--color-on: #34c759;width:var(--button-width);height:var(--button-height);position:relative;padding:0;margin:0;flex:none;user-select:none}#browser_extension_settings_main input[type=checkbox]{opacity:0;width:0;height:0;position:absolute}#browser_extension_settings_main .switch{width:100%;height:100%;display:block;background-color:var(--color-off);border-radius:calc(var(--button-height)/2);border:none;cursor:pointer;transition:all .2s ease-out}#browser_extension_settings_main .switch::before{display:none}#browser_extension_settings_main .slider{width:var(--toggle-diameter);height:var(--toggle-diameter);position:absolute;left:2px;top:calc(50% - var(--toggle-diameter)/2);border-radius:50%;background:#fff;box-shadow:0px 3px 8px rgba(0,0,0,.15),0px 3px 1px rgba(0,0,0,.06);transition:all .2s ease-out;cursor:pointer}#browser_extension_settings_main input[type=checkbox]:checked+.switch{background-color:var(--color-on)}#browser_extension_settings_main input[type=checkbox]:checked+.switch .slider{left:calc(var(--button-width) - var(--toggle-diameter) - 2px)}#browser_extension_side_menu{min-height:80px;width:30px;opacity:0;position:fixed;top:80px;right:0;padding-top:20px;z-index:10000}#browser_extension_side_menu:hover{opacity:1}#browser_extension_side_menu button{cursor:pointer;width:24px;height:24px;padding:0;border:none;background-color:rgba(0,0,0,0);background-image:none}#browser_extension_side_menu button svg{width:24px;height:24px}#browser_extension_side_menu button:hover{opacity:70%}#browser_extension_side_menu button:active{opacity:100%}@media(max-width: 500px){#browser_extension_settings_container{right:10px}#browser_extension_settings_container .extension_list_container{display:none}#browser_extension_settings_container .extension_list_container.bes_active{display:block}#browser_extension_settings_container .extension_list_container.bes_active+div{display:none}#browser_extension_settings_main a.navigation_go_previous{display:block}}'
  359. function createSwitch(options = {}) {
  360. const container = createElement("label", { class: "container" })
  361. const checkbox = createElement(
  362. "input",
  363. options.checked ? { type: "checkbox", checked: "" } : { type: "checkbox" }
  364. )
  365. addElement2(container, checkbox)
  366. const switchElm = createElement("span", { class: "switch" })
  367. addElement2(switchElm, "span", { class: "slider" })
  368. addElement2(container, switchElm)
  369. if (options.onchange) {
  370. addEventListener(checkbox, "change", options.onchange)
  371. }
  372. return container
  373. }
  374. function createSwitchOption(icon, text, options) {
  375. if (typeof text !== "string") {
  376. return createSwitchOption(void 0, icon, text)
  377. }
  378. const div = createElement("div", { class: "switch_option" })
  379. if (icon) {
  380. addElement2(div, "img", { src: icon })
  381. }
  382. addElement2(div, "span", { textContent: text })
  383. div.append(createSwitch(options))
  384. return div
  385. }
  386. var besVersion = 30
  387. var openButton =
  388. '<svg viewBox="0 0 60.2601318359375 84.8134765625" version="1.1" xmlns="http://www.w3.org/2000/svg" class=" glyph-box" style="height: 9.62969px; width: 6.84191px;"><g transform="matrix(1 0 0 1 -6.194965820312518 77.63671875)"><path d="M66.4551-35.2539C66.4551-36.4746 65.9668-37.5977 65.0391-38.4766L26.3672-76.3672C25.4883-77.1973 24.4141-77.6367 23.1445-77.6367C20.6543-77.6367 18.7012-75.7324 18.7012-73.1934C18.7012-71.9727 19.1895-70.8496 19.9707-70.0195L55.5176-35.2539L19.9707-0.488281C19.1895 0.341797 18.7012 1.41602 18.7012 2.68555C18.7012 5.22461 20.6543 7.12891 23.1445 7.12891C24.4141 7.12891 25.4883 6.68945 26.3672 5.81055L65.0391-32.0312C65.9668-32.959 66.4551-34.0332 66.4551-35.2539Z"></path></g></svg>'
  389. var openInNewTabButton =
  390. '<svg viewBox="0 0 72.127685546875 72.2177734375" version="1.1" xmlns="http://www.w3.org/2000/svg" class=" glyph-box" style="height: 8.19958px; width: 8.18935px;"><g transform="matrix(1 0 0 1 -12.451127929687573 71.3388671875)"><path d="M84.5703-17.334L84.5215-66.4551C84.5215-69.2383 82.7148-71.1914 79.7852-71.1914L30.6641-71.1914C27.9297-71.1914 26.0742-69.0918 26.0742-66.748C26.0742-64.4043 28.1738-62.4023 30.4688-62.4023L47.4609-62.4023L71.2891-63.1836L62.207-55.2246L13.8184-6.73828C12.9395-5.85938 12.4512-4.73633 12.4512-3.66211C12.4512-1.31836 14.5508 0.878906 16.9922 0.878906C18.1152 0.878906 19.1895 0.488281 20.0684-0.439453L68.5547-48.877L76.6113-58.0078L75.7324-35.2051L75.7324-17.1387C75.7324-14.8438 77.7344-12.6953 80.127-12.6953C82.4707-12.6953 84.5703-14.6973 84.5703-17.334Z"></path></g></svg>'
  391. var settingButton =
  392. '<svg viewBox="0 0 16 16" version="1.1">\n<path d="M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"></path>\n</svg>'
  393. var relatedExtensions = [
  394. {
  395. id: "utags",
  396. title: "\u{1F3F7}\uFE0F UTags - Add usertags to links",
  397. url: "https://greasyfork.org/zh-CN/scripts/460718-utags-add-usertags-to-links",
  398. },
  399. {
  400. id: "links-helper",
  401. title: "\u{1F517} \u94FE\u63A5\u52A9\u624B",
  402. description:
  403. "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00\u7B2C\u4E09\u65B9\u7F51\u7AD9\u94FE\u63A5\uFF0C\u56FE\u7247\u94FE\u63A5\u8F6C\u56FE\u7247\u6807\u7B7E\u7B49",
  404. url: "https://greasyfork.org/zh-CN/scripts/464541-links-helper",
  405. },
  406. {
  407. id: "v2ex.rep",
  408. title:
  409. "V2EX.REP - \u4E13\u6CE8\u63D0\u5347 V2EX \u4E3B\u9898\u56DE\u590D\u6D4F\u89C8\u4F53\u9A8C",
  410. url: "https://greasyfork.org/zh-CN/scripts/466589-v2ex-rep-%E4%B8%93%E6%B3%A8%E6%8F%90%E5%8D%87-v2ex-%E4%B8%BB%E9%A2%98%E5%9B%9E%E5%A4%8D%E6%B5%8F%E8%A7%88%E4%BD%93%E9%AA%8C",
  411. },
  412. {
  413. id: "v2ex.min",
  414. title: "v2ex.min - V2EX \u6781\u7B80\u98CE\u683C",
  415. url: "https://greasyfork.org/zh-CN/scripts/463552-v2ex-min-v2ex-%E6%9E%81%E7%AE%80%E9%A3%8E%E6%A0%BC",
  416. },
  417. {
  418. id: "replace-ugly-avatars",
  419. title: "\u8D50\u4F60\u4E2A\u5934\u50CF\u5427",
  420. url: "https://greasyfork.org/zh-CN/scripts/472616-replace-ugly-avatars",
  421. },
  422. {
  423. id: "more-by-pipecraft",
  424. title: "\u66F4\u591A\u6709\u8DA3\u7684\u811A\u672C",
  425. url: "https://greasyfork.org/zh-CN/users/1030884-pipecraft",
  426. },
  427. ]
  428. var getInstalledExtesionList = () => {
  429. return $(".extension_list_container .installed_extension_list")
  430. }
  431. var getRelatedExtesionList = () => {
  432. return $(".extension_list_container .related_extension_list")
  433. }
  434. var isInstalledExtension = (id) => {
  435. const list = getInstalledExtesionList()
  436. if (!list) {
  437. return false
  438. }
  439. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  440. return Boolean(installed)
  441. }
  442. var addCurrentExtension = (extension) => {
  443. const list = getInstalledExtesionList()
  444. if (!list) {
  445. return
  446. }
  447. if (isInstalledExtension(extension.id)) {
  448. return
  449. }
  450. const element = createInstalledExtension(extension)
  451. list.append(element)
  452. const list2 = getRelatedExtesionList()
  453. if (list2) {
  454. updateRelatedExtensions(list2)
  455. }
  456. }
  457. var activeExtension = (id) => {
  458. const list = getInstalledExtesionList()
  459. if (!list) {
  460. return false
  461. }
  462. for (const element of $$(".active", list)) {
  463. removeClass(element, "active")
  464. }
  465. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  466. if (installed) {
  467. addClass(installed, "active")
  468. }
  469. }
  470. var activeExtensionList = () => {
  471. const extensionListContainer = $(".extension_list_container")
  472. if (extensionListContainer) {
  473. addClass(extensionListContainer, "bes_active")
  474. }
  475. }
  476. var deactiveExtensionList = () => {
  477. const extensionListContainer = $(".extension_list_container")
  478. if (extensionListContainer) {
  479. removeClass(extensionListContainer, "bes_active")
  480. }
  481. }
  482. var createInstalledExtension = (installedExtension) => {
  483. const div = createElement("div", {
  484. class: "installed_extension",
  485. "data-extension-id": installedExtension.id,
  486. })
  487. const a = addElement2(div, "a", {
  488. onclick: installedExtension.onclick,
  489. })
  490. addElement2(a, "span", {
  491. textContent: installedExtension.title,
  492. })
  493. const svg = addElement2(a, "svg")
  494. svg.outerHTML = createHTML(openButton)
  495. return div
  496. }
  497. var updateRelatedExtensions = (container) => {
  498. const relatedExtensionElements = $$("[data-extension-id]", container)
  499. if (relatedExtensionElements.length > 0) {
  500. for (const relatedExtensionElement of relatedExtensionElements) {
  501. if (
  502. isInstalledExtension(
  503. relatedExtensionElement.dataset.extensionId || "noid"
  504. )
  505. ) {
  506. relatedExtensionElement.remove()
  507. }
  508. }
  509. } else {
  510. container.innerHTML = createHTML("")
  511. }
  512. for (const relatedExtension of relatedExtensions) {
  513. if (
  514. isInstalledExtension(relatedExtension.id) ||
  515. $('[data-extension-id="'.concat(relatedExtension.id, '"]'), container)
  516. ) {
  517. continue
  518. }
  519. if ($$("[data-extension-id]", container).length >= 4) {
  520. return
  521. }
  522. const div4 = addElement2(container, "div", {
  523. class: "related_extension",
  524. "data-extension-id": relatedExtension.id,
  525. })
  526. const a = addElement2(div4, "a", {
  527. href: relatedExtension.url,
  528. target: "_blank",
  529. })
  530. addElement2(a, "span", {
  531. textContent: relatedExtension.title,
  532. })
  533. const svg = addElement2(a, "svg")
  534. svg.outerHTML = createHTML(openInNewTabButton)
  535. }
  536. }
  537. function createExtensionList(installedExtensions) {
  538. const div = createElement("div", {
  539. class: "extension_list_container thin_scrollbar",
  540. })
  541. addElement2(div, "h1", { textContent: "Settings" })
  542. const div2 = addElement2(div, "div", {
  543. class: "installed_extension_list",
  544. })
  545. for (const installedExtension of installedExtensions) {
  546. if (isInstalledExtension(installedExtension.id)) {
  547. continue
  548. }
  549. const element = createInstalledExtension(installedExtension)
  550. div2.append(element)
  551. }
  552. addElement2(div, "h2", { textContent: "Other Extensions" })
  553. const div3 = addElement2(div, "div", {
  554. class: "related_extension_list",
  555. })
  556. updateRelatedExtensions(div3)
  557. return div
  558. }
  559. var prefix = "browser_extension_settings_"
  560. var randomId = String(Math.round(Math.random() * 1e4))
  561. var settingsContainerId = prefix + "container_" + randomId
  562. var settingsElementId = prefix + "main_" + randomId
  563. var getSettingsElement = () => $("#" + settingsElementId)
  564. var getSettingsStyle = () =>
  565. style_default
  566. .replaceAll(/browser_extension_settings_container/gm, settingsContainerId)
  567. .replaceAll(/browser_extension_settings_main/gm, settingsElementId)
  568. var storageKey = "settings"
  569. var settingsOptions
  570. var settingsTable = {}
  571. var settings = {}
  572. async function getSettings() {
  573. var _a
  574. return (_a = await getValue(storageKey)) != null ? _a : {}
  575. }
  576. async function saveSettingsValue(key, value) {
  577. const settings2 = await getSettings()
  578. settings2[key] =
  579. settingsTable[key] && settingsTable[key].defaultValue === value
  580. ? void 0
  581. : value
  582. await setValue(storageKey, settings2)
  583. }
  584. async function saveSettingsValues(values) {
  585. const settings2 = await getSettings()
  586. for (const key in values) {
  587. if (Object.hasOwn(values, key)) {
  588. const value = values[key]
  589. settings2[key] =
  590. settingsTable[key] && settingsTable[key].defaultValue === value
  591. ? void 0
  592. : value
  593. }
  594. }
  595. await setValue(storageKey, settings2)
  596. }
  597. function getSettingsValue(key) {
  598. var _a
  599. return Object.hasOwn(settings, key)
  600. ? settings[key]
  601. : (_a = settingsTable[key]) == null
  602. ? void 0
  603. : _a.defaultValue
  604. }
  605. var closeModal = () => {
  606. const settingsContainer = getSettingsContainer()
  607. if (settingsContainer) {
  608. settingsContainer.style.display = "none"
  609. }
  610. removeEventListener(document, "click", onDocumentClick, true)
  611. removeEventListener(document, "keydown", onDocumentKeyDown, true)
  612. }
  613. var onDocumentClick = (event) => {
  614. const target = event.target
  615. if (
  616. target == null ? void 0 : target.closest(".".concat(prefix, "container"))
  617. ) {
  618. return
  619. }
  620. closeModal()
  621. }
  622. var onDocumentKeyDown = (event) => {
  623. if (event.defaultPrevented) {
  624. return
  625. }
  626. if (event.key === "Escape") {
  627. closeModal()
  628. event.preventDefault()
  629. }
  630. }
  631. async function updateOptions() {
  632. if (!getSettingsElement()) {
  633. return
  634. }
  635. for (const key in settingsTable) {
  636. if (Object.hasOwn(settingsTable, key)) {
  637. const item = settingsTable[key]
  638. const type = item.type || "switch"
  639. switch (type) {
  640. case "switch": {
  641. const checkbox = $(
  642. "#"
  643. .concat(
  644. settingsElementId,
  645. ' .option_groups .switch_option[data-key="'
  646. )
  647. .concat(key, '"] input')
  648. )
  649. if (checkbox) {
  650. checkbox.checked = getSettingsValue(key)
  651. }
  652. break
  653. }
  654. case "textarea": {
  655. const textArea = $(
  656. "#"
  657. .concat(
  658. settingsElementId,
  659. ' .option_groups textarea[data-key="'
  660. )
  661. .concat(key, '"]')
  662. )
  663. if (textArea) {
  664. textArea.value = getSettingsValue(key)
  665. }
  666. break
  667. }
  668. default: {
  669. break
  670. }
  671. }
  672. }
  673. }
  674. if (typeof settingsOptions.onViewUpdate === "function") {
  675. const settingsMain = createSettingsElement()
  676. settingsOptions.onViewUpdate(settingsMain)
  677. }
  678. }
  679. function getSettingsContainer() {
  680. const container = $(".".concat(prefix, "container"))
  681. if (container) {
  682. const theVersion = parseInt10(container.dataset.besVersion, 0)
  683. if (theVersion < besVersion) {
  684. container.id = settingsContainerId
  685. container.dataset.besVersion = String(besVersion)
  686. }
  687. return container
  688. }
  689. return addElement2(doc.body, "div", {
  690. id: settingsContainerId,
  691. class: "".concat(prefix, "container"),
  692. "data-bes-version": besVersion,
  693. style: "display: none;",
  694. })
  695. }
  696. function getSettingsWrapper() {
  697. const container = getSettingsContainer()
  698. return (
  699. $(".".concat(prefix, "wrapper"), container) ||
  700. addElement2(container, "div", {
  701. class: "".concat(prefix, "wrapper"),
  702. })
  703. )
  704. }
  705. function initExtensionList() {
  706. const wrapper = getSettingsWrapper()
  707. if (!$(".extension_list_container", wrapper)) {
  708. const list = createExtensionList([])
  709. wrapper.append(list)
  710. }
  711. addCurrentExtension({
  712. id: settingsOptions.id,
  713. title: settingsOptions.title,
  714. onclick: showSettings,
  715. })
  716. }
  717. function createSettingsElement() {
  718. let settingsMain = getSettingsElement()
  719. if (!settingsMain) {
  720. const wrapper = getSettingsWrapper()
  721. for (const element of $$(".".concat(prefix, "main"))) {
  722. element.remove()
  723. }
  724. settingsMain = addElement2(wrapper, "div", {
  725. id: settingsElementId,
  726. class: "".concat(prefix, "main thin_scrollbar"),
  727. })
  728. addElement2(settingsMain, "a", {
  729. textContent: "Settings",
  730. class: "navigation_go_previous",
  731. onclick() {
  732. activeExtensionList()
  733. },
  734. })
  735. if (settingsOptions.title) {
  736. addElement2(settingsMain, "h2", { textContent: settingsOptions.title })
  737. }
  738. const optionGroups = []
  739. const getOptionGroup = (index) => {
  740. if (index > optionGroups.length) {
  741. for (let i = optionGroups.length; i < index; i++) {
  742. optionGroups.push(
  743. addElement2(settingsMain, "div", {
  744. class: "option_groups",
  745. })
  746. )
  747. }
  748. }
  749. return optionGroups[index - 1]
  750. }
  751. for (const key in settingsTable) {
  752. if (Object.hasOwn(settingsTable, key)) {
  753. const item = settingsTable[key]
  754. const type = item.type || "switch"
  755. const group = item.group || 1
  756. const optionGroup = getOptionGroup(group)
  757. switch (type) {
  758. case "switch": {
  759. const switchOption = createSwitchOption(item.icon, item.title, {
  760. async onchange(event) {
  761. const checkbox = event.target
  762. if (checkbox) {
  763. await saveSettingsValue(key, checkbox.checked)
  764. }
  765. },
  766. })
  767. switchOption.dataset.key = key
  768. addElement2(optionGroup, switchOption)
  769. break
  770. }
  771. case "textarea": {
  772. let timeoutId
  773. const div = addElement2(optionGroup, "div", {
  774. class: "bes_textarea",
  775. })
  776. addElement2(div, "textarea", {
  777. "data-key": key,
  778. placeholder: item.placeholder || "",
  779. onkeyup(event) {
  780. const textArea = event.target
  781. if (timeoutId) {
  782. clearTimeout(timeoutId)
  783. timeoutId = void 0
  784. }
  785. timeoutId = setTimeout(async () => {
  786. if (textArea) {
  787. await saveSettingsValue(key, textArea.value.trim())
  788. }
  789. }, 100)
  790. },
  791. })
  792. break
  793. }
  794. case "action": {
  795. addElement2(optionGroup, "a", {
  796. class: "action",
  797. textContent: item.title,
  798. onclick: item.onclick,
  799. })
  800. break
  801. }
  802. case "externalLink": {
  803. const div4 = addElement2(optionGroup, "div", {
  804. class: "bes_external_link",
  805. })
  806. addElement2(div4, "a", {
  807. textContent: item.title,
  808. href: item.url,
  809. target: "_blank",
  810. })
  811. break
  812. }
  813. case "tip": {
  814. const tip = addElement2(optionGroup, "div", {
  815. class: "bes_tip",
  816. })
  817. addElement2(tip, "a", {
  818. class: "bes_tip_anchor",
  819. textContent: item.title,
  820. })
  821. const tipContent = addElement2(tip, "div", {
  822. class: "bes_tip_content",
  823. innerHTML: createHTML(item.tipContent),
  824. })
  825. break
  826. }
  827. }
  828. }
  829. }
  830. if (settingsOptions.footer) {
  831. const footer = addElement2(settingsMain, "footer")
  832. footer.innerHTML = createHTML(
  833. typeof settingsOptions.footer === "string"
  834. ? settingsOptions.footer
  835. : '<p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>'
  836. )
  837. }
  838. }
  839. return settingsMain
  840. }
  841. function addSideMenu() {
  842. if (!getSettingsValue("displaySettingsButtonInSideMenu")) {
  843. return
  844. }
  845. const menu =
  846. $("#browser_extension_side_menu") ||
  847. addElement2(doc.body, "div", {
  848. id: "browser_extension_side_menu",
  849. "data-bes-version": besVersion,
  850. })
  851. const button = $("button[data-bes-version]", menu)
  852. if (button) {
  853. const theVersion = parseInt10(button.dataset.besVersion, 0)
  854. if (theVersion >= besVersion) {
  855. return
  856. }
  857. button.remove()
  858. }
  859. addElement2(menu, "button", {
  860. type: "button",
  861. "data-bes-version": besVersion,
  862. title: "\u8BBE\u7F6E",
  863. onclick() {
  864. setTimeout(showSettings, 1)
  865. },
  866. innerHTML: settingButton,
  867. })
  868. }
  869. function addCommonSettings(settingsTable3) {
  870. let maxGroup = 0
  871. for (const key in settingsTable3) {
  872. if (Object.hasOwn(settingsTable3, key)) {
  873. const item = settingsTable3[key]
  874. const group = item.group || 1
  875. if (group > maxGroup) {
  876. maxGroup = group
  877. }
  878. }
  879. }
  880. settingsTable3.displaySettingsButtonInSideMenu = {
  881. title: "Display Settings Button in Side Menu",
  882. defaultValue: !(
  883. typeof GM === "object" && typeof GM.registerMenuCommand === "function"
  884. ),
  885. group: maxGroup + 1,
  886. }
  887. }
  888. function handleShowSettingsUrl() {
  889. if (location.hash === "#bes-show-settings") {
  890. setTimeout(showSettings, 100)
  891. }
  892. }
  893. async function showSettings() {
  894. const settingsContainer = getSettingsContainer()
  895. const settingsMain = createSettingsElement()
  896. await updateOptions()
  897. settingsContainer.style.display = "block"
  898. addEventListener(document, "click", onDocumentClick, true)
  899. addEventListener(document, "keydown", onDocumentKeyDown, true)
  900. activeExtension(settingsOptions.id)
  901. deactiveExtensionList()
  902. }
  903. var initSettings = async (options) => {
  904. settingsOptions = options
  905. settingsTable = options.settingsTable || {}
  906. addCommonSettings(settingsTable)
  907. addValueChangeListener(storageKey, async () => {
  908. settings = await getSettings()
  909. await updateOptions()
  910. addSideMenu()
  911. if (typeof options.onValueChange === "function") {
  912. options.onValueChange()
  913. }
  914. })
  915. settings = await getSettings()
  916. runWhenHeadExists(() => {
  917. addStyle(getSettingsStyle())
  918. })
  919. runWhenBodyExists(() => {
  920. initExtensionList()
  921. addSideMenu()
  922. })
  923. handleShowSettingsUrl()
  924. }
  925. var content_default =
  926. '#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:45%;transition:all 1s ease-out}#Main .header .fr a img{width:73px;height:73px}td[width="48"] img{width:48px;height:48px}'
  927. var styles = [
  928. "adventurer",
  929. "adventurer-neutral",
  930. "avataaars",
  931. "avataaars-neutral",
  932. "big-ears",
  933. "big-ears-neutral",
  934. "big-smile",
  935. "bottts",
  936. "bottts-neutral",
  937. "croodles",
  938. "croodles-neutral",
  939. "fun-emoji",
  940. "icons",
  941. "identicon",
  942. "initials",
  943. "lorelei",
  944. "lorelei-neutral",
  945. "micah",
  946. "miniavs",
  947. "notionists",
  948. "notionists-neutral",
  949. "open-peeps",
  950. "personas",
  951. "pixel-art",
  952. "pixel-art-neutral",
  953. "shapes",
  954. "thumbs",
  955. ]
  956. var allAvatarStyleList = styles
  957. function getRandomInt(min, max) {
  958. min = Math.ceil(min)
  959. max = Math.floor(max)
  960. return Math.floor(Math.random() * (max - min)) + min
  961. }
  962. function getRandomFlipParameter(style) {
  963. if (style === "initials" || style === "identicon") {
  964. return ""
  965. }
  966. const values = [false, false, false, false, true]
  967. const value = values[getRandomInt(0, values.length)]
  968. return value ? "&flip=true" : ""
  969. }
  970. function getRandomRadiusParameter(style) {
  971. const values = [0, 0, 0, 10, 10, 10, 20, 20, 30, 50]
  972. const value = values[getRandomInt(0, values.length)]
  973. return value ? "&radius=" + value : ""
  974. }
  975. function getRandomBackgroundColorParameter(style) {
  976. const values = [
  977. "",
  978. "",
  979. "",
  980. "",
  981. "",
  982. "",
  983. "",
  984. "",
  985. "",
  986. "ffffff",
  987. "b6e3f4",
  988. "c0aede",
  989. "d1d4f9",
  990. "ffd5dc",
  991. "ffdfbf",
  992. ]
  993. const value = values[getRandomInt(0, values.length)]
  994. return value ? "&backgroundColor=" + value : ""
  995. }
  996. function getRandomAvatar(prefix2, styleList) {
  997. const styles2 =
  998. !styleList || styleList.length === 0 ? allAvatarStyleList : styleList
  999. const randomStyle = styles2[getRandomInt(0, styles2.length)]
  1000. return (
  1001. "https://api.dicebear.com/6.x/"
  1002. .concat(randomStyle, "/svg?seed=")
  1003. .concat(prefix2, ".")
  1004. .concat(Date.now()) +
  1005. getRandomFlipParameter(randomStyle) +
  1006. getRandomRadiusParameter(randomStyle) +
  1007. getRandomBackgroundColorParameter(randomStyle)
  1008. )
  1009. }
  1010. var changeIcon =
  1011. '<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>'
  1012. var host = location.host
  1013. var storageKey2 = "avatar:v2ex.com"
  1014. async function saveAvatar(userName, src) {
  1015. const values = (await getValue(storageKey2)) || {}
  1016. values[userName] = src
  1017. await setValue(storageKey2, values)
  1018. }
  1019. async function saveAvatars(newValues) {
  1020. let values = (await getValue(storageKey2)) || {}
  1021. values = Object.assign(values, newValues)
  1022. await setValue(storageKey2, values)
  1023. }
  1024. async function clearAvatarData() {
  1025. await deleteValue(storageKey2)
  1026. }
  1027. var cachedValues = {}
  1028. async function reloadCachedValues() {
  1029. cachedValues = (await getValue(storageKey2)) || {}
  1030. }
  1031. function getChangedAavatar(userName) {
  1032. return cachedValues[userName]
  1033. }
  1034. async function initStorage(options) {
  1035. addValueChangeListener(storageKey2, async () => {
  1036. await reloadCachedValues()
  1037. if (options && typeof options.avatarValueChangeListener === "function") {
  1038. options.avatarValueChangeListener()
  1039. }
  1040. })
  1041. await reloadCachedValues()
  1042. }
  1043. var host2 = location.host
  1044. var isEnabledByDefault = () => {
  1045. if (host2.includes("xxxxxxxx")) {
  1046. return false
  1047. }
  1048. return true
  1049. }
  1050. var settingsTable2 = {
  1051. ["enableCurrentSite_".concat(host2)]: {
  1052. title: "Enable current site",
  1053. defaultValue: isEnabledByDefault(),
  1054. },
  1055. "style-adventurer": {
  1056. title: "Adventurer",
  1057. icon: "https://api.dicebear.com/6.x/adventurer/svg?seed=JD",
  1058. defaultValue: true,
  1059. group: 2,
  1060. },
  1061. "style-adventurer-neutral": {
  1062. title: "Adventurer Neutral",
  1063. icon: "https://api.dicebear.com/6.x/adventurer-neutral/svg?seed=JD",
  1064. defaultValue: true,
  1065. group: 2,
  1066. },
  1067. "style-avataaars": {
  1068. title: "Avataaars",
  1069. icon: "https://api.dicebear.com/6.x/avataaars/svg?seed=JD",
  1070. defaultValue: true,
  1071. group: 2,
  1072. },
  1073. "style-avataaars-neutral": {
  1074. title: "Avataaars Neutral",
  1075. icon: "https://api.dicebear.com/6.x/avataaars-neutral/svg?seed=JD",
  1076. defaultValue: true,
  1077. group: 2,
  1078. },
  1079. "style-big-ears": {
  1080. title: "Big Ears",
  1081. icon: "https://api.dicebear.com/6.x/big-ears/svg?seed=JD",
  1082. defaultValue: true,
  1083. group: 2,
  1084. },
  1085. "style-big-ears-neutral": {
  1086. title: "Big Ears Neutral",
  1087. icon: "https://api.dicebear.com/6.x/big-ears-neutral/svg?seed=JD",
  1088. defaultValue: true,
  1089. group: 2,
  1090. },
  1091. "style-big-smile": {
  1092. title: "Big Smile",
  1093. icon: "https://api.dicebear.com/6.x/big-smile/svg?seed=JD",
  1094. defaultValue: true,
  1095. group: 2,
  1096. },
  1097. "style-bottts": {
  1098. title: "Bottts",
  1099. icon: "https://api.dicebear.com/6.x/bottts/svg?seed=JD",
  1100. defaultValue: true,
  1101. group: 2,
  1102. },
  1103. "style-bottts-neutral": {
  1104. title: "Bottts Neutral",
  1105. icon: "https://api.dicebear.com/6.x/bottts-neutral/svg?seed=JD",
  1106. defaultValue: true,
  1107. group: 2,
  1108. },
  1109. "style-croodles": {
  1110. title: "Croodles",
  1111. icon: "https://api.dicebear.com/6.x/croodles/svg?seed=JD",
  1112. defaultValue: true,
  1113. group: 2,
  1114. },
  1115. "style-croodles-neutral": {
  1116. title: "Croodles Neutral",
  1117. icon: "https://api.dicebear.com/6.x/croodles-neutral/svg?seed=JD",
  1118. defaultValue: true,
  1119. group: 2,
  1120. },
  1121. "style-fun-emoji": {
  1122. title: "Fun Emoji",
  1123. icon: "https://api.dicebear.com/6.x/fun-emoji/svg?seed=JD",
  1124. defaultValue: true,
  1125. group: 2,
  1126. },
  1127. "style-icons": {
  1128. title: "Icons",
  1129. icon: "https://api.dicebear.com/6.x/icons/svg?seed=JD",
  1130. defaultValue: true,
  1131. group: 2,
  1132. },
  1133. "style-identicon": {
  1134. title: "Identicon",
  1135. icon: "https://api.dicebear.com/6.x/identicon/svg?seed=JD",
  1136. defaultValue: true,
  1137. group: 2,
  1138. },
  1139. "style-initials": {
  1140. title: "Initials",
  1141. icon: "https://api.dicebear.com/6.x/initials/svg?seed=JD",
  1142. defaultValue: true,
  1143. group: 2,
  1144. },
  1145. "style-lorelei": {
  1146. title: "Lorelei",
  1147. icon: "https://api.dicebear.com/6.x/lorelei/svg?seed=JD",
  1148. defaultValue: true,
  1149. group: 2,
  1150. },
  1151. "style-lorelei-neutral": {
  1152. title: "Lorelei Neutral",
  1153. icon: "https://api.dicebear.com/6.x/lorelei-neutral/svg?seed=JD",
  1154. defaultValue: true,
  1155. group: 2,
  1156. },
  1157. "style-micah": {
  1158. title: "Micah",
  1159. icon: "https://api.dicebear.com/6.x/micah/svg?seed=JD",
  1160. defaultValue: true,
  1161. group: 2,
  1162. },
  1163. "style-miniavs": {
  1164. title: "Miniavs",
  1165. icon: "https://api.dicebear.com/6.x/miniavs/svg?seed=JD",
  1166. defaultValue: true,
  1167. group: 2,
  1168. },
  1169. "style-notionists": {
  1170. title: "Notionists",
  1171. icon: "https://api.dicebear.com/6.x/notionists/svg?seed=JD",
  1172. defaultValue: true,
  1173. group: 2,
  1174. },
  1175. "style-notionists-neutral": {
  1176. title: "Notionists Neutral",
  1177. icon: "https://api.dicebear.com/6.x/notionists-neutral/svg?seed=JD",
  1178. defaultValue: true,
  1179. group: 2,
  1180. },
  1181. "style-open-peeps": {
  1182. title: "Open Peeps",
  1183. icon: "https://api.dicebear.com/6.x/open-peeps/svg?seed=JD",
  1184. defaultValue: true,
  1185. group: 2,
  1186. },
  1187. "style-personas": {
  1188. title: "Personas",
  1189. icon: "https://api.dicebear.com/6.x/personas/svg?seed=JD",
  1190. defaultValue: true,
  1191. group: 2,
  1192. },
  1193. "style-pixel-art": {
  1194. title: "Pixel Art",
  1195. icon: "https://api.dicebear.com/6.x/pixel-art/svg?seed=JD",
  1196. defaultValue: true,
  1197. group: 2,
  1198. },
  1199. "style-pixel-art-neutral": {
  1200. title: "Pixel Art Neutral",
  1201. icon: "https://api.dicebear.com/6.x/pixel-art-neutral/svg?seed=JD",
  1202. defaultValue: true,
  1203. group: 2,
  1204. },
  1205. "style-shapes": {
  1206. title: "Shapes",
  1207. icon: "https://api.dicebear.com/6.x/shapes/svg?seed=JD",
  1208. defaultValue: true,
  1209. group: 2,
  1210. },
  1211. "style-thumbs": {
  1212. title: "Thumbs",
  1213. icon: "https://api.dicebear.com/6.x/thumbs/svg?seed=JD",
  1214. defaultValue: true,
  1215. group: 2,
  1216. },
  1217. autoReplaceAll: {
  1218. title: "\u81EA\u52A8\u66FF\u6362\u5168\u90E8\u5934\u50CF",
  1219. defaultValue: false,
  1220. group: 3,
  1221. },
  1222. clearData: {
  1223. title: "\u6E05\u7A7A\u88AB\u66FF\u6362\u7684\u5934\u50CF\u6570\u636E",
  1224. type: "action",
  1225. async onclick() {
  1226. if (
  1227. confirm(
  1228. "\u786E\u5B9A\u8981\u5220\u9664\u6240\u6709\u88AB\u66FF\u6362\u7684\u5934\u50CF\u6570\u636E\u5417\uFF1F"
  1229. )
  1230. ) {
  1231. await clearAvatarData()
  1232. setTimeout(() => {
  1233. alert("\u5220\u9664\u5B8C\u6BD5!")
  1234. })
  1235. }
  1236. },
  1237. group: 4,
  1238. },
  1239. }
  1240. var avatarStyleList = []
  1241. function updateAvatarStyleList() {
  1242. avatarStyleList = allAvatarStyleList.filter((style) =>
  1243. getSettingsValue("style-".concat(style))
  1244. )
  1245. if (avatarStyleList.length === 0 && !doc.hidden) {
  1246. setTimeout(async () => {
  1247. alert(
  1248. "\u81F3\u5C11\u9700\u8981\u542F\u7528\u4E00\u79CD\u5934\u50CF\u98CE\u683C"
  1249. )
  1250. await saveSettingsValues({
  1251. "style-adventurer": true,
  1252. })
  1253. const firstStyleOption = $(
  1254. '.browser_extension_settings_container [data-key="style-adventurer"]'
  1255. )
  1256. if (firstStyleOption) {
  1257. firstStyleOption.scrollIntoView({ block: "nearest" })
  1258. }
  1259. }, 200)
  1260. }
  1261. }
  1262. var lastValueOfEnableCurrentSite = true
  1263. var lastValueOfAutoReplaceAll = false
  1264. async function onSettingsChange() {
  1265. if (getSettingsValue("enableCurrentSite_".concat(host2))) {
  1266. if (!lastValueOfEnableCurrentSite) {
  1267. if ($("#rua_tyle")) {
  1268. scanAvatars()
  1269. } else {
  1270. await main()
  1271. }
  1272. }
  1273. } else if (lastValueOfEnableCurrentSite) {
  1274. for (const element of $$("img[data-rua-org-src]")) {
  1275. if (
  1276. element.dataset.ruaOrgSrc &&
  1277. element.src !== element.dataset.ruaOrgSrc
  1278. ) {
  1279. element.src = element.dataset.ruaOrgSrc
  1280. }
  1281. }
  1282. }
  1283. lastValueOfEnableCurrentSite = getSettingsValue(
  1284. "enableCurrentSite_".concat(host2)
  1285. )
  1286. if (
  1287. getSettingsValue("autoReplaceAll") &&
  1288. !lastValueOfAutoReplaceAll &&
  1289. !doc.hidden
  1290. ) {
  1291. if (
  1292. confirm(
  1293. "\u786E\u5B9A\u8981\u81EA\u52A8\u66FF\u6362\u5168\u90E8\u5934\u50CF\u5417\uFF1F"
  1294. )
  1295. ) {
  1296. lastValueOfAutoReplaceAll = getSettingsValue("autoReplaceAll")
  1297. scanAvatars()
  1298. } else {
  1299. await saveSettingsValues({
  1300. autoReplaceAll: false,
  1301. })
  1302. }
  1303. }
  1304. lastValueOfAutoReplaceAll = getSettingsValue("autoReplaceAll")
  1305. updateAvatarStyleList()
  1306. }
  1307. function isAvatar(element) {
  1308. if (!element || element.tagName !== "IMG") {
  1309. return false
  1310. }
  1311. if (element.dataset.ruaUserName) {
  1312. return true
  1313. }
  1314. return false
  1315. }
  1316. var currentTarget
  1317. function addChangeButton(element) {
  1318. currentTarget = element
  1319. const container =
  1320. $("#rua_container") ||
  1321. addElement2(doc.body, "div", {
  1322. id: "rua_container",
  1323. })
  1324. const changeButton =
  1325. $(".change_button.quick", container) ||
  1326. addElement2(container, "button", {
  1327. innerHTML: changeIcon,
  1328. class: "change_button quick",
  1329. async onclick() {
  1330. addClass(changeButton, "active")
  1331. setTimeout(() => {
  1332. removeClass(changeButton, "active")
  1333. }, 200)
  1334. const userName = currentTarget.dataset.ruaUserName || "noname"
  1335. const avatarUrl = getRandomAvatar(userName, avatarStyleList)
  1336. changeAvatar(currentTarget, avatarUrl, true)
  1337. await saveAvatar(userName, avatarUrl)
  1338. },
  1339. })
  1340. const changeButton2 =
  1341. $(".change_button.advanced", container) ||
  1342. addElement2(container, "button", {
  1343. innerHTML: changeIcon,
  1344. class: "change_button advanced",
  1345. async onclick() {
  1346. addClass(changeButton2, "active")
  1347. setTimeout(() => {
  1348. removeClass(changeButton2, "active")
  1349. }, 200)
  1350. const userName = currentTarget.dataset.ruaUserName || "noname"
  1351. const avatarUrl = prompt(
  1352. "\u8BF7\u8F93\u5165\u5934\u50CF\u94FE\u63A5",
  1353. ""
  1354. )
  1355. if (avatarUrl) {
  1356. changeAvatar(currentTarget, avatarUrl, true)
  1357. await saveAvatar(userName, avatarUrl)
  1358. }
  1359. },
  1360. })
  1361. removeClass(changeButton, "hide")
  1362. removeClass(changeButton2, "hide")
  1363. const pos = getOffsetPosition(element)
  1364. const leftOffset =
  1365. element.clientWidth - changeButton.clientWidth > 20
  1366. ? element.clientWidth - changeButton.clientWidth
  1367. : element.clientWidth - 1
  1368. changeButton.style.top = pos.top + "px"
  1369. changeButton.style.left = pos.left + leftOffset + "px"
  1370. changeButton2.style.top = pos.top + changeButton.clientHeight + "px"
  1371. changeButton2.style.left = pos.left + leftOffset + "px"
  1372. const mouseoutHandler = () => {
  1373. addClass(changeButton, "hide")
  1374. addClass(changeButton2, "hide")
  1375. removeEventListener(element, "mouseout", mouseoutHandler)
  1376. }
  1377. addEventListener(element, "mouseout", mouseoutHandler)
  1378. }
  1379. function getUserName(element) {
  1380. if (!element) {
  1381. return
  1382. }
  1383. const userNameElement = $('a[href*="/member/"]', element)
  1384. if (userNameElement) {
  1385. const userName = (/member\/(\w+)/.exec(userNameElement.href) || [])[1]
  1386. if (userName) {
  1387. return userName.toLowerCase()
  1388. }
  1389. return
  1390. }
  1391. return getUserName(element.parentElement)
  1392. }
  1393. function changeAvatar(element, src, animation = false) {
  1394. if (element.ruaLoading) {
  1395. return
  1396. }
  1397. if (!element.dataset.ruaOrgSrc) {
  1398. const orgSrc = element.dataset.src || element.src
  1399. element.dataset.ruaOrgSrc = orgSrc
  1400. }
  1401. element.ruaLoading = true
  1402. const imgOnloadHandler = () => {
  1403. element.ruaLoading = false
  1404. removeClass(element, "rua_fadeout")
  1405. removeEventListener(element, "load", imgOnloadHandler)
  1406. removeEventListener(element, "error", imgOnloadHandler)
  1407. }
  1408. addEventListener(element, "load", imgOnloadHandler)
  1409. addEventListener(element, "error", imgOnloadHandler)
  1410. const width = element.clientWidth
  1411. const height = element.clientHeight
  1412. if (width > 1) {
  1413. element.style.width = width + "px"
  1414. }
  1415. if (height > 1) {
  1416. element.style.height = height + "px"
  1417. }
  1418. if (animation) {
  1419. addClass(element, "rua_fadeout")
  1420. } else {
  1421. element.src =
  1422. "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
  1423. }
  1424. setTimeout(() => {
  1425. element.src = src
  1426. }, 100)
  1427. if (element.dataset.src) {
  1428. element.dataset.src = src
  1429. }
  1430. }
  1431. var scanAvatars = throttle(async () => {
  1432. if (doc.hidden || !getSettingsValue("enableCurrentSite_".concat(host2))) {
  1433. return
  1434. }
  1435. const newValues = {}
  1436. const avatars = $$('.avatar,a[href*="/member/"] img')
  1437. for (const avatar of avatars) {
  1438. let userName = avatar.dataset.ruaUserName
  1439. if (!userName) {
  1440. userName = getUserName(avatar)
  1441. if (!userName) {
  1442. console.error("Can't get username", avatar, userName)
  1443. continue
  1444. }
  1445. avatar.dataset.ruaUserName = userName
  1446. }
  1447. setAttributes(avatar, {
  1448. loading: "lazy",
  1449. referrerpolicy: "no-referrer",
  1450. rel: "noreferrer",
  1451. })
  1452. const newAvatarSrc = getChangedAavatar(userName)
  1453. if (newAvatarSrc && avatar.src !== newAvatarSrc) {
  1454. changeAvatar(avatar, newAvatarSrc)
  1455. } else if (!newAvatarSrc) {
  1456. if (
  1457. avatar.dataset.ruaOrgSrc &&
  1458. avatar.src !== avatar.dataset.ruaOrgSrc
  1459. ) {
  1460. avatar.src = avatar.dataset.ruaOrgSrc
  1461. }
  1462. if (lastValueOfAutoReplaceAll && Object.entries(newValues).length < 3) {
  1463. const avatarUrl = getRandomAvatar(userName, avatarStyleList)
  1464. newValues[userName] = avatarUrl
  1465. }
  1466. }
  1467. }
  1468. if (lastValueOfAutoReplaceAll) {
  1469. await saveAvatars(newValues)
  1470. }
  1471. }, 100)
  1472. async function main() {
  1473. if ($("#rua_tyle")) {
  1474. return
  1475. }
  1476. await runOnce("main", async () => {
  1477. await initSettings({
  1478. id: "replace-ugly-avatars",
  1479. title: "\u8D50\u4F60\u4E2A\u5934\u50CF\u5427",
  1480. footer:
  1481. '\n <p>After change settings, reload the page to take effect</p>\n <p>\n <a href="https://github.com/utags/replace-ugly-avatars/issues" target="_blank">\n Report and Issue...\n </a></p>\n <p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>',
  1482. settingsTable: settingsTable2,
  1483. async onValueChange() {
  1484. await onSettingsChange()
  1485. },
  1486. })
  1487. registerMenuCommand("\u2699\uFE0F \u8BBE\u7F6E", showSettings, "o")
  1488. })
  1489. lastValueOfEnableCurrentSite = getSettingsValue(
  1490. "enableCurrentSite_".concat(host2)
  1491. )
  1492. lastValueOfAutoReplaceAll = getSettingsValue("autoReplaceAll")
  1493. if (!getSettingsValue("enableCurrentSite_".concat(host2))) {
  1494. return
  1495. }
  1496. updateAvatarStyleList()
  1497. runWhenHeadExists(() => {
  1498. addElement2("style", {
  1499. textContent: content_default,
  1500. id: "rua_tyle",
  1501. })
  1502. })
  1503. addEventListener(doc, "mouseover", (event) => {
  1504. const target = event.target
  1505. if (!isAvatar(target)) {
  1506. return
  1507. }
  1508. addChangeButton(target)
  1509. })
  1510. addEventListener(doc, "visibilitychange", () => {
  1511. if (!doc.hidden) {
  1512. scanAvatars()
  1513. }
  1514. })
  1515. await initStorage({
  1516. avatarValueChangeListener() {
  1517. scanAvatars()
  1518. },
  1519. })
  1520. if ($("img")) {
  1521. scanAvatars()
  1522. }
  1523. const observer = new MutationObserver(() => {
  1524. scanAvatars()
  1525. })
  1526. observer.observe(doc, {
  1527. childList: true,
  1528. subtree: true,
  1529. })
  1530. }
  1531. main()
  1532. })()