Greasy Fork is available in English.

owowify website

owowify website evewywhewe! >w<

  1. // ==UserScript==
  2. // @name owowify website
  3. // @description owowify website evewywhewe! >w<
  4. // @author owowed
  5. // @version 0.0.3
  6. // @namespace fun.owowed.moe
  7. // @license GPL-3.0-or-later
  8. // @match *://*/*
  9. // @grant none
  10. // @run-at document-end
  11. // @copyright All rights reserved. Licensed under GPL-3.0-or-later. View license at https://spdx.org/licenses/GPL-3.0-or-later.html
  12. // ==/UserScript==
  13.  
  14. /**
  15. * wincesed undew GNYU! GPL vewsion 3.0 -w-
  16. */
  17.  
  18. const blocklistTags = "style, script, svg, noscript, iframe, object, code, pre, input";
  19.  
  20. !function () {
  21. const updateSubtreeOwowification = () => {
  22. setTimeout(() => owowifySubtree(document.body), 700);
  23. };
  24. const windowHrefCheck = stateCheck(
  25. () => window.location.href,
  26. () => updateSubtreeOwowification()
  27. );
  28. const documentTitleCheck = stateCheck(
  29. () => owowify(document.title)
  30. );
  31.  
  32. const observer = new MutationObserver((records) => {
  33. if (windowHrefCheck()) return;
  34.  
  35. documentTitleCheck();
  36.  
  37. for (const record of records) {
  38. for (const newNode of record.addedNodes) {
  39. owowifySubtree(newNode);
  40. }
  41. }
  42. });
  43.  
  44. observer.observe(document.body, {
  45. childList: true,
  46. subtree: true,
  47. /* don't use characterData, it'll kill your page */
  48. });
  49.  
  50. owowifySubtree(document.body);
  51. document.title = owowify(document.title);
  52.  
  53. function stateCheck(newValueGetter, callback) {
  54. let value = newValueGetter;
  55.  
  56. return () => {
  57. const newValue = newValueGetter();
  58. if (value != newValue) {
  59. value = newValue;
  60. callback?.();
  61. return true;
  62. }
  63. };
  64. }
  65. }();
  66.  
  67. /** @param {Element} element */
  68. function owowifySubtree(element) {
  69. if (element.nodeType != Node.ELEMENT_NODE) return;
  70.  
  71. const currentElementValid =
  72. isElementValidOwowify(element) &&
  73. isElementValidOwowify(element.parentElement);
  74.  
  75. if (!currentElementValid) {
  76. element.noOwowify = true;
  77. }
  78.  
  79. if (element instanceof HTMLInputElement) {
  80. if (element.readOnly && !["password", "url", "hidden"].includes(element.type)) {
  81. element.value = owowify(element.value);
  82. element.placeholder = owowify(element.placeholder);
  83. }
  84. }
  85.  
  86. if (element.hasAttribute("title")) {
  87. element.setAttribute("title", owowify(element.getAttribute("title")));
  88. }
  89.  
  90. for (const node of element.childNodes) {
  91. if (node.nodeType == Node.ELEMENT_NODE) {
  92. owowifySubtree(node);
  93. }
  94. if (node.nodeType == Node.TEXT_NODE && currentElementValid) {
  95. owowifyTextNode(node);
  96. }
  97. }
  98. }
  99.  
  100. /** @param {Node} node */
  101. function owowifyTextNode(node) {
  102. if (node.nodeType == Node.TEXT_NODE) {
  103. node.nodeValue = owowify(node.nodeValue);
  104. }
  105. }
  106.  
  107. /** @param {Element} element */
  108. function isElementValidOwowify(element) {
  109. if (element == null) return null;
  110. if (element.noOwowify || element.classList?.contains?.("-owo-no-owowify")) return false;
  111. if (element instanceof HTMLInputElement) {
  112. return !element.textContent.includes(element.href);
  113. }
  114. return !(element.matches(blocklistTags) || element.isContentEditable);
  115. }
  116.  
  117. /**
  118. * @param {String} inputText
  119. * @returns {String}
  120. */
  121. function owowify(inputText) {
  122. const endSentencePattern = String.raw`([\w ,.!?]+)?`; // endSentencePattern
  123. // const endSentencePattern1 = String.raw`([\w ,.?]+)?`; // endSentencePattern without "!" sign
  124. // const endSentencePattern2 = String.raw`([\w ,.]+)?`; // endSentencePattern without "!" and "?" sign
  125. const vowel = "[aiueo]";
  126. const vowelNoE = "[aiuo]"; // vowel without e
  127. const vowelNoIE = "[auo]"; // vowel without i and e
  128. const zackqyWord = "[jzckq]";
  129.  
  130. const result = inputText
  131. // OwO emote
  132. .replace(reg`/(i(?:'|)m(?:\s+|\s+so+\s+)bored)${endSentencePattern}/gi`, subOwoEmote("-w-"))
  133. .replace(reg`/(love\s+(?:you|him|her|them))${endSentencePattern}/gi`, subOwoEmote("uwu"))
  134. .replace(reg`/(i\s+don(?:'|)t\s+care|i\s*d\s*c)${endSentencePattern}/gi`, subOwoEmote("0w0"))
  135. // world substitution
  136. .replace(reg`/l${vowel}ve?/gi`, $0 => subSameCase($0, "luv"))
  137. // OwO translation
  138. /* replace all "r" to "w", no exception! */
  139. .replace(/r/gi, $0 => subSameCase($0, "w"))
  140. /* lame -> wame, goal -> goaw, gallery -> gallewy, lol -> lol, null -> null */
  141. .replace(reg`/(?<!([wl]${vowel}+)|l)l(?!(${vowel}?l+)|.${vowel})/gi`, $0 => subSameCase($0, "w"))
  142. /* na -> nya, nu -> nyu, no -> nyo, ne -> nye */
  143. .replace(reg`/n(${vowelNoE}+)/gi`,
  144. ($0, $vowel) => subSameCase($0 + $vowel, `ny${$vowel}`))
  145. /* ma -> mya, mu -> myu, mo -> myo */
  146. .replace(reg`/m(${vowelNoIE}+)(?!w*${zackqyWord})/gi`,
  147. ($0, $vowel) => subSameCase($0 + $vowel, `my${$vowel}`))
  148. /* pa -> pwa, pu -> pwu, po -> pwo */
  149. .replace(reg`/p(${vowelNoIE}+)(?!w*${zackqyWord})/gi`,
  150. ($0, $vowel) => subSameCase($0 + $vowel, `pw${$vowel}`));
  151.  
  152. return result;
  153. }
  154.  
  155. function subOwoEmote(emote) {
  156. const matchEndSpace = /^\s+$/g;
  157.  
  158. return ($0, $setenceBeforeEnd, $endSentence) => {
  159. if ($endSentence == undefined || matchEndSpace.test($endSentence)) {
  160. return `${$setenceBeforeEnd} ${emote}`;
  161. }
  162. else return $0;
  163. }
  164. }
  165.  
  166. /**
  167. * @param {string} inputText
  168. * @param {string} replaceText
  169. */
  170. function subSameCase(inputText, replaceText) {
  171. let result = "";
  172.  
  173. for (let i = 0; i < replaceText.length; i++) {
  174. if (inputText[i] != undefined && replaceText[i] != undefined) {
  175. if (inputText[i].toUpperCase() == inputText[i]) {
  176. result += replaceText[i].toUpperCase();
  177. }
  178. else if (inputText[i].toLowerCase() == inputText[i]) {
  179. result += replaceText[i].toLowerCase();
  180. }
  181. else {
  182. result += replaceText[i];
  183. }
  184. }
  185. else {
  186. result += replaceText[i];
  187. }
  188. }
  189.  
  190. return result;
  191. }
  192.  
  193. /** @param {[string[], ...any[]]} templateArgs */
  194. function reg(...templateArgs) {
  195. const rawString = String.raw(...templateArgs);
  196. const pattern = rawString.substring(1, rawString.lastIndexOf("/"));
  197. const flags = rawString.substring(rawString.lastIndexOf("/")+1, rawString.length);
  198.  
  199. return new RegExp(pattern, flags);
  200. }
  201.  
  202. // mdn docs
  203. /** @param {string} str */
  204. function regexEscape(str) {
  205. return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
  206. }