Twitch Translate Chat Messages

Double-Click to Translate Twitch Chat Messages

  1. // ==UserScript==
  2. // @name Twitch Translate Chat Messages
  3. // @namespace http://userstyles.org
  4. // @description Double-Click to Translate Twitch Chat Messages
  5. // @author 636597
  6. // @include *://*.twitch.tv/*
  7. // @run-at document-start
  8. // @version 1.0
  9. // ==/UserScript==
  10.  
  11. var enableTranslation = false;
  12. var destinationLanguage = "en";
  13. // You have to setup some coors enabled https site with
  14. // var GoogleTranslateBase = https://github.com/matheuss/google-translate-api
  15. // or use
  16. // var GoogleTranslateBase = "https://translation.googleapis.com/language/translate/v2";
  17. // https://console.cloud.google.com/apis/credentials
  18. var gapi_key = "";
  19.  
  20. // Search for Occurrence *anywhere* in message
  21. var enableBlacklist = false;
  22. var blacklist_anywhere_words = [
  23. ];
  24.  
  25. // Search for these exact words
  26. var blacklist_exact_words = [
  27. "BTTV!",
  28. "Prime" ,
  29. "Subscribe",
  30. "subscription",
  31. ];
  32.  
  33. function searchBlacklist( wText ) {
  34. var lower = wText.toLowerCase();
  35. for ( var i = 0; i < blacklist_anywhere_words.length; ++i ) {
  36. if ( lower.indexOf( blacklist_anywhere_words[ i ] ) !== -1 ) {
  37. //console.log( "found anywhere" );
  38. //console.log( wText );
  39. return true;
  40. }
  41. }
  42. var x11 = wText.split( ":" )[ 1 ];
  43. if ( x11 ) {
  44. x11 = x11.split( " " );
  45. for ( var j = 0; j < x11.length; ++j ) {
  46. for ( var i = 0; i < blacklist_exact_words.length; ++i ) {
  47. if ( x11[ j ] === blacklist_exact_words[ i ] ) {
  48. //console.log( "found exact" );
  49. //console.log( wText );
  50. return true;
  51. }
  52. }
  53. }
  54. }
  55. return false;
  56. }
  57.  
  58. function fixedEncodeURIComponent(str){
  59. return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
  60. }
  61.  
  62. var TranslationActive = false;
  63. var gapi_key = "";
  64. var GoogleTranslateEnd = "&key=" + gapi_key;
  65. var TranB1 = "q=";
  66. var TranB2 = "&target=" + destinationLanguage;
  67. function translateText( text , dom_elem ) {
  68. if ( !text ) { return; }
  69. if ( !dom_elem ) { return; }
  70. text = text.trim();
  71. var query_string = TranB1 + fixedEncodeURIComponent( text ) + TranB2 + GoogleTranslateEnd;
  72. var anHttpRequest = new XMLHttpRequest();
  73. anHttpRequest.onreadystatechange = function() {
  74. if ( anHttpRequest.readyState == 4 && anHttpRequest.status == 200 ) {
  75. var response = anHttpRequest.responseText;
  76. console.log( response );
  77. var translation = JSON.parse( response );
  78. translation = translation[ "data" ][ "translations" ][ 0 ][ "translatedText" ];
  79. translation = translation.trim();
  80. var has_mention_fragment = false;
  81. var mention_frag_node = null;
  82. for ( var i = 0; i < dom_elem.childNodes.length; ++i ) {
  83. if ( dom_elem.childNodes[ i ].className === "mention-fragment" ) { has_mention_fragment = true; mention_frag_node = dom_elem.childNodes[ i ]; }
  84. var attr = dom_elem.childNodes[ i ].getAttribute( "data-a-target" );
  85. if ( attr === "chat-message-text" ) {
  86. dom_elem.childNodes[ i ].innerHTML = translation;
  87. dom_elem.setAttribute( "data-showTranslation" , "true" );
  88. dom_elem.setAttribute( "data-translatedText" , translation );
  89. dom_elem.setAttribute( "data-originalText" , text );
  90. }
  91. }
  92. if ( has_mention_fragment ) { dom_elem.removeChild( mention_frag_node ); }
  93. }
  94. };
  95. anHttpRequest.open( "POST", GoogleTranslateBase , true );
  96. anHttpRequest.setRequestHeader( "Content-Type" , "application/x-www-form-urlencoded; charset=UTF-8" );
  97. anHttpRequest.send( query_string );
  98. }
  99.  
  100. var chat_element = null;
  101. var chat_observer = null;
  102. var observerConfig = {
  103. attributes: true,
  104. childList: true,
  105. characterData: true
  106. };
  107. function loadObserver() {
  108. chat_observer = new MutationObserver(function(mutations) {
  109. mutations.forEach(function( mutation , index ) {
  110. if ( mutation.type === "childList" ) {
  111. var addedNode = mutation.addedNodes[0];
  112. if( addedNode ) {
  113.  
  114. var msg = addedNode.innerText;
  115.  
  116. // Revert Back to Original
  117. if ( TranslationActive ) {
  118. addedNode.addEventListener( "dblclick" , function() {
  119. var that = this;
  120. var show_translation = addedNode.getAttribute( "data-showTranslation" );
  121. console.log( show_translation );
  122. if ( show_translation ) {
  123. if ( show_translation === "true" ) {
  124. //that.style.background = "red";
  125. console.log( "reverting" );
  126. console.log( "old text === " );
  127. var original_text = addedNode.getAttribute( "data-originalText" );
  128. //console.log( original_text );
  129. for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
  130. if ( !addedNode.childNodes[ i ] ) { continue; }
  131. var attr = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
  132. if ( attr === "chat-message-text" ) {
  133. addedNode.childNodes[ i ].innerText = original_text;
  134. break;
  135. }
  136. }
  137. addedNode.setAttribute( "data-showTranslation" , "false" );
  138. }
  139. else {
  140. //that.style.background = "green";
  141. addedNode.setAttribute( "data-showTranslation" , "true" );
  142. var already_translated = addedNode.getAttribute( "data-translatedText" );
  143. //console.log( "already_translated text === " );
  144. //console.log( already_translated );
  145. for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
  146. if ( !addedNode.childNodes[ i ] ) { continue; }
  147. var attr = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
  148. if ( attr === "chat-message-text" ) {
  149. addedNode.childNodes[ i ].innerHTML = already_translated;
  150. break;
  151. }
  152. }
  153. }
  154. }
  155. else {
  156. //that.style.background = "green";
  157. var start = msg.indexOf( ":" );
  158. var z1 = msg.substring( ( start + 1 ) );
  159. translateText( z1 , addedNode );
  160. }
  161. } , false );
  162. }
  163.  
  164. if ( enableBlacklist ) {
  165. var remove = searchBlacklist( msg );
  166.  
  167. // If Not Already Set to be Removed , Search Emotes
  168. if ( !remove ) {
  169. for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
  170. if ( !addedNode.childNodes[ i ] ) { continue; }
  171. if ( !remove ) {
  172. var alt_text_target = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
  173. if ( alt_text_target === "emote-name" ) {
  174. for ( var j = 0; j < addedNode.childNodes[ i ].childNodes.length; ++j ) {
  175. if ( !addedNode.childNodes[ i ].childNodes[ j ] ) { continue; }
  176. if ( !remove ) {
  177. if ( addedNode.childNodes[ i ].childNodes[ j ].alt ) {
  178. var test = searchBlacklist( addedNode.childNodes[ i ].childNodes[ j ].alt );
  179. if ( test ) {
  180. remove = true;
  181. break;
  182. }
  183. }
  184. }
  185. else { break; }
  186. }
  187. }
  188. }
  189. else { break; }
  190. }
  191. }
  192.  
  193. if ( remove ) {
  194. if ( addedNode.parentNode ) {
  195. try {
  196. addedNode.setAttribute( "style", "visibility: hidden !important" );
  197. addedNode.setAttribute( "style", "height: 0 !important" );
  198. addedNode.setAttribute( "style", "padding: 0 !important" );
  199. addedNode.innerHTML = "";
  200. }
  201. catch( e ) { console.log( e ); }
  202. }
  203. }
  204. }
  205. }
  206. }
  207. });
  208. });
  209.  
  210. if ( enableTranslation && gapi_key ) {
  211. TranslationActive = true;
  212. console.log( "Translation Option Loaded" );
  213. }
  214. if ( TranslationActive || enableBlacklist ) {
  215. if ( enableBlacklist ) {
  216. console.log( "Blacklist Option Loaded" );
  217. }
  218. chat_observer.observe( chat_element , observerConfig );
  219. }
  220. }
  221.  
  222. (function() {
  223. var ready = setInterval(function(){
  224. var x1 = document.querySelectorAll( '[role="log"]' );
  225. if ( x1 ) { if ( x1[ 0 ] ) { chat_element = x1[0]; clearInterval( ready ); loadObserver(); } }
  226. } , 2 );
  227. })();

QingJ © 2025

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