Show KRW on Gateio

Display KRW approximation next to USD on Gateio

  1. // ==UserScript==
  2. // @license Unlicense
  3. // @name Show KRW on Gateio
  4. // @namespace https://greatest.deepsurf.us/
  5. // @version 202402030832
  6. // @description Display KRW approximation next to USD on Gateio
  7. // @author lee101570
  8. // @match https://www.gate.io/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=gate.io
  10. // @connect m.stock.naver.com
  11. // @connect api.upbit.com
  12. // @grant GM.xmlHttpRequest
  13. // @noframes
  14. // ==/UserScript==
  15. /* jshint esversion: 8 */
  16. /* global WeakRef */
  17. /**
  18. * This source code was created by modifying the original code by the following developer.
  19. *
  20. * Developer: funyaba
  21. * Original Source Code: https://update.greatest.deepsurf.us/scripts/520945/Show%20KRW%20on%20Bitget.user.js
  22. */
  23. (async function() {
  24. 'use strict';
  25.  
  26. // Your code here...
  27. async function resolveAllObject(obj) {
  28. const promises = Object.values(obj).map(fn => fn());
  29. const results = await Promise.all(promises);
  30. return Object
  31. .fromEntries(Object.keys(obj).map((key, i) => [key, results[i]]));
  32. }
  33.  
  34. class ExchangeService {
  35. static async getInstanceAsync() {
  36. const cache = new Map();
  37.  
  38. const apis = {
  39. ['USDKRW']: {
  40. fetcher: async () => {
  41. const { response } = await GM.xmlHttpRequest({
  42. method: 'GET',
  43. url: 'https://m.stock.naver.com' +
  44. '/front-api/marketIndex/productDetail' +
  45. '?category=exchange&reutersCode=FX_USDKRW',
  46. responseType: 'json',
  47. });
  48.  
  49. const { result } = response;
  50. return parseFloat(result.calcPrice);
  51. },
  52. interval: 1000 * 60 * 60,
  53. },
  54. ['USDTKRW']: {
  55. fetcher: async () => {
  56. const { response: [{ trade_price: tradePrice }] } =
  57. await GM.xmlHttpRequest({
  58. method: 'GET',
  59. url: 'https://api.upbit.com/v1/ticker?markets=KRW-USDT',
  60. responseType: 'json',
  61. });
  62.  
  63. return tradePrice;
  64. },
  65. interval: 1000,
  66. },
  67. };
  68. const fetchers = Object.fromEntries(Object.entries(apis).map(([k, v]) => [k, v.fetcher]));
  69. const resolved = await resolveAllObject(fetchers);
  70.  
  71. for (const [key, value] of Object.entries(resolved)) {
  72. cache.set(key, value);
  73. }
  74.  
  75. for (const [key, value] of Object.entries(apis)) {
  76. setInterval(async () => {
  77. cache.set(key, await value.fetcher());
  78. }, value.interval);
  79. }
  80.  
  81. return {
  82. get(key) {
  83. return cache.get(key);
  84. },
  85. };
  86. }
  87. }
  88. const exchangeService = await ExchangeService.getInstanceAsync();
  89.  
  90. const numberFormatter = new Intl.NumberFormat('en', {
  91. maximumFractionDigits: 0,
  92. });
  93. const numberRegex = /(-?(?:[0-9]{1,3},)*[0-9]{1,3}(?:\.?[0-9]*))/g;
  94.  
  95. const replaceValue = (targetRef) => {
  96. let target = targetRef.deref();
  97. try {
  98. if (!target) return;
  99.  
  100. let text = target.nodeValue.trim();
  101. if (text.includes('KRW') || text.includes('₩')) return;
  102.  
  103. let matches = null;
  104. if (text === 'USD' || text === 'USDT') {
  105. if (!target.parentElement || !target.parentElement.parentElement) return;
  106.  
  107. const parentText = target.parentElement.parentElement.innerText;
  108. if (!parentText) return;
  109.  
  110. matches = parentText.match(numberRegex);
  111. } else {
  112. matches = text.match(numberRegex);
  113. }
  114. if (!matches) return;
  115.  
  116. let price = NaN;
  117. switch (true) {
  118. case text.endsWith('USD'):
  119. price = exchangeService.get('USDKRW');
  120. break;
  121. case text.endsWith('USDT'):
  122. case text.includes('₮'):
  123. price = exchangeService.get('USDTKRW');
  124. break;
  125. default:
  126. return;
  127. }
  128. if (isNaN(price)) return;
  129.  
  130. const value = matches[0].replace(/,/g, '');
  131. const converted = numberFormatter.format(parseFloat(value) * price);
  132. const formatted = text.includes('₮') ?
  133. `${text} (₩${converted})` :
  134. `${text} (${converted} KRW)`;
  135.  
  136. target.nodeValue = formatted;
  137. } finally {
  138. target = null;
  139. }
  140. };
  141.  
  142. const observerConfig = {
  143. childList: true,
  144. characterData: true,
  145. subtree: true
  146. };
  147.  
  148. let mutationTargetRefs = [];
  149. let isScheduled = false;
  150. const callback = (mutations, observer) => {
  151. mutations.forEach((mutation) => {
  152. if (mutation.target.nodeName.toUpperCase() === 'SVG') return;
  153. mutationTargetRefs.push(new WeakRef(mutation.target));
  154. });
  155.  
  156. if (isScheduled) return;
  157. isScheduled = true;
  158.  
  159. requestAnimationFrame(() => {
  160. observer.disconnect();
  161.  
  162. const nodes = new WeakSet();
  163. for (let i = 0; i < mutationTargetRefs.length; ++i) {
  164. let mutationTarget = mutationTargetRefs[i].deref();
  165. try {
  166. if (!mutationTarget) {
  167. mutationTargetRefs[i] = null;
  168. continue;
  169. }
  170. if (nodes.has(mutationTarget)) continue;
  171. nodes.add(mutationTarget);
  172.  
  173. switch (mutationTarget.nodeType) {
  174. case Node.TEXT_NODE:
  175. replaceValue(new WeakRef(mutationTarget));
  176. nodes.add(mutationTarget);
  177. break;
  178. case Node.ELEMENT_NODE: {
  179. if (!mutationTarget.innerHTML.match(numberRegex)) break;
  180.  
  181. const treeWalker = document.createTreeWalker(
  182. mutationTarget,
  183. NodeFilter.SHOW_TEXT,
  184. );
  185.  
  186. let node;
  187. while (treeWalker.nextNode()) {
  188. node = treeWalker.currentNode;
  189. try {
  190. if (nodes.has(node)) continue;
  191. if (!node.isConnected) continue;
  192.  
  193. if (
  194. !node.parentElement ||
  195. node.parentElement.closest('textarea, [contenteditable="true"]')
  196. ) continue;
  197.  
  198. replaceValue(new WeakRef(node));
  199. } finally {
  200. nodes.add(node);
  201. node = null;
  202. }
  203. }
  204. break;
  205. }
  206. default:
  207. break;
  208. }
  209. } finally {
  210. mutationTarget = null;
  211. }
  212. }
  213. mutationTargetRefs = [];
  214. isScheduled = false;
  215.  
  216. observer.observe(document.body, observerConfig);
  217. });
  218. };
  219. const observer = new MutationObserver(callback);
  220.  
  221. observer.observe(document.body, observerConfig);
  222. })();