NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name LNMTL - Improved translation using Sogou // @namespace sglnmt // @match https://lnmtl.com/chapter/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://userscripts-mirror.org/scripts/source/107941.user.js#sha384=Q8t880BurrlGKTdpvYv2+da12PYnvljdiU8aJvakk1uE3QMbzb190ueXNpAUY98p // @version 1.0.7 // @author Spawner + mmtf // @description 3/16/2020, 6:10:03 PM // @license MIT // ==/UserScript== /* CHANGELOG: 1.0 - Initial Release 1.0.1 - Small Optimizations 1.0.2 - Added word color highlighter and changed dialog style 1.0.3 - Plugin Settings (Credits: mmtf) 1.0.4 - Code fix + Added Button for Translation 1.0.5 - Switch to GM_xmlhttpRequest to fix CORS error + Show/Hide translation 1.0.6 - Bug fixes 1.0.7 - Removed the private API since it's no longer working + using a different approach. NOTE: - Don't worry about the API (key & id), Sogou just needs a valid/existing hash. - I also want to thank mmtf because I have no experience in JS+CSS or web dev in general, so I shamelessly copied parts of his script. */ /* CSS STYLES */ GM_addStyle("hgltr { text-shadow: 0 0 10px #e74c3c, 0 0 10px #e74c3c }"); GM_addStyle(".sgt { color:white; font-size: 2.2rem; margin-bottom:42px; font-family: Roboto }"); /* VARIABLES */ var uniqueWords = new Map(); var autoSogouOn = GM_SuperValue.get('autoSogouOn' , false); var autoShowOn = GM_SuperValue.get('autoShowOn' , false); var autoGlowOn = GM_SuperValue.get('autoGlowOn' , true); var chunk = []; var translatedChunks = []; var translatePromises = []; var isTranslated = false; window.onload = function () { replaceRaws(); createBtnUI(); createSettings(); $( '.original' ).css( 'fontSize', '1.9rem' ); $( '.original' ).css( 'color', 'rgb(128, 142, 155)' ); $( '.translated t' ).each( function ( index ) { var value = $( this ) .attr( 'data-title', $( this ).text() ) .text() .trimLeft(); uniqueWords.set( value, index ); } ); chunks = seperateIntoChunks( getRawParagraphs() ); chunks[ 0 ] = chunks[ 0 ].trimLeft(); console.log(chunks.length); if ( autoSogouOn ) doWork(); if( autoShowOn ) $( '.btn.btn-primary.js-toggle-translated' )[ 0 ].click(); } async function doWork() { for ( var id = 0; id < chunks.length; id++ ) { // We must set the cookies on each send // if not, it will trigger their bot system guard. await sogouSetCookies(); translatedChunks[ id ] = await sogouTranslator( chunks[ id ] ); } isTranslated = true; var finalResult = seperateChunksIntoPars( translatedChunks ); createSentence( finalResult ); } function sogouTranslator( text ) { const userId = () => { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function ( name ) { var M = 0 | 16 * Math.random(); var pid = "x" == name ? M : 8 | 3 & M; return pid.toString( 16 ); } ); }; return new Promise( ( resolve ) => { var formData = { 'from' : 'zh-CHS', 'to' : 'en', 'text' : text, 'client' : 'pc', 'fr' : 'browser_pc', 'pid' : 'sogou-dict-vr', 'dict' : 'true', 'word_group' : 'true', 'second_query': 'true', 'uuid' : userId, 'needQc' : '1', 's' : md5( 'zh-CHS' + 'en' + text + '8511813095152' ) }; GM_xmlhttpRequest( { method : "POST", url : "https://fanyi.sogou.com/reventondc/translateV2", data : $.param( formData ), headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Accept" : "application/json", "Referer" : "https://fanyi.sogou.com/" } , onload: function ( result ) { var jsonObj = JSON.parse( result.response ); resolve( jsonObj.data.translate.dit ); } } ); }); } function sogouSetCookies() { var dateExpires = new Date; var match = "sogou.com"; var id = "1"; // Must be randomized in the next version return new Promise( ( resolve ) => { GM_xmlhttpRequest( { method : "GET", url : "https://fanyi.sogou.com/", headers: { "Cookie" : setCookie("SNUID", id, dateExpires.toGMTString(), match, "/") } , onload: function ( result ) { resolve( result ) } }); }); } function setCookie(a, val, url, c, name) { return a = [a, "=", val], url && a.push(";expires=", url), c && a.push(";domain=", c), name && a.push(";path=", name), document.cookie = a.join(""), a.join(""); } function createSentence( gtpars ) { gtpars.forEach( ( sentence, index ) => { if ( autoGlowOn ) { for ( const [ key, value ] of uniqueWords.entries() ) sentence = sentence.replace( new RegExp( '(\\b)(' + key + ')(\\b)', 'gi' ), '<hgltr style="color:orange">' + key + '</hgltr>' ); } var quotesStyle = ( sentence.startsWith( '"' ) && sentence.endsWith( '"' ) ) ? 'style="color:rgb(22, 160, 133);font-style:italic;font-weight:bold"': ""; $( '.translated' ).eq( index ).after( "<div class='sgt' " + quotesStyle + " tab-index=0><sentence data-index=" + index + ">" + sentence + "</sentence></div>" ); }); } function createBtnUI () { $( '.js-toggle-original' ).after( '<button class="btn btn-enabled js-toggle-sgt">SGT</button>' ); $( '.btn.btn-enabled.js-toggle-sgt' )[0].style.borderColor = "white"; $( '.btn.btn-enabled.js-toggle-sgt' )[0].style.boxShadow = "0px 0px 20px #ecf0f1"; $( '.btn.btn-enabled.js-toggle-sgt' )[0].style.color = "orange"; $( '.js-toggle-sgt' ).click( function () { if ( isTranslated ) $( ".sgt" ).fadeToggle( 'fast', 'linear' ); if ( !autoSogouOn && !isTranslated ) doWork(); } ) .text( 'SGT' ).addClass( 'btn-default' ); } function createSettings() { let title = $( '<h3> Improved Translation Settings </h3>' ); let checked = autoSogouOn ? 'checked' : ''; let optionAutoswitch = $( '<sub><input id="autoSogouOn" type="checkbox" ' + checked + '></sub> <label for="autoSogouOn">Automatically display Sogou translation after loading the page.</label>' ) .on( 'change', function () { autoSogouOn = $( '#autoSogouOn' )[ 0 ].checked; GM_SuperValue.set( 'autoSogouOn', autoSogouOn ); }); let checked2 = autoGlowOn ? 'checked' : ''; let optionGlow = $( '<sub><input id="autoGlowOn" type="checkbox" ' + checked2 + '></sub> <label for="autoGlowOn">Apply the Glow word highlighter.</label>' ) .on( 'change', function () { autoGlowOn = $( '#autoGlowOn' )[ 0 ].checked; GM_SuperValue.set( 'autoGlowOn', autoGlowOn ); }); let checked3 = autoShowOn ? 'checked' : ''; let optionShow = $( '<sub><input id="autoShowOn" type="checkbox" ' + checked3 + '></sub> <label for="autoShowOn"><span style="color:rgb(26, 188, 156)">Show only Translation.</span></label>' ) .on( 'change', function () { autoShowOn = $( '#autoShowOn' )[ 0 ].checked; GM_SuperValue.set( 'autoShowOn', autoShowOn ); }); let row = $( '<div class="row"/>' ); $( "#chapter-display-options-modal .modal-body" ) .append( title ) .append( optionAutoswitch ).append( '<br>' ) .append( optionGlow ).append( '<br>' ) .append( optionShow ).append( '<br>' ) .append( '<span style="color:yellow">Changes apply after the page is reloaded.</span>' ) .append( row ); } function replaceRaws() { $( '.original t' ).each( function () { var mytext = $( this ).text(); $( this ).text( $( this ).attr( 'data-title' ).replace( /\(\w+\)/g, '' ) ); $( this ).attr( 'data-title', mytext ); }); // add whitespace between two english words $( '.original' ).find( 't' ).filter( function ( index ) { var prev = $( this ).get( 0 ).previousSibling; return prev ? $( this ).get( 0 ).previousSibling.nodeName == 'T': false; }).each( function () { $( this ).text( ' ' + $( this ).text() ); }); } function seperateIntoChunks( paragraphs ) { let chunks = []; let currentchunk = ""; for ( let i = 0; i < paragraphs.length; i++ ) { if ( ( currentchunk + paragraphs[ i ] ).length >= 4000 ) { chunks.push( currentchunk ); currentchunk = paragraphs[ i ]; } else currentchunk = currentchunk + "\n\n" + paragraphs[ i ]; } if ( paragraphs.length != 0 ) chunks.push( currentchunk ); return chunks; } function seperateChunksIntoPars( chunks ) { let pars = []; chunks.forEach( ( chunk ) => chunk.split( '\n\n' ).forEach( ( par ) => pars.push( par ) ) ); return pars; } function getRawParagraphs() { return $( '.original' ).text().trim().split( '\n' ) } function md5( str ) { var k = [], i = 0; for ( i = 0; i < 64; ) k[ i ] = 0 | ( Math.abs( Math.sin( ++i ) ) * 4294967296 ); var b, c, d, j, x = [], str2 = unescape( encodeURI( str ) ), a = str2.length, h = [ ( b = 1732584193 ), ( c = -271733879 ), ~b, ~c ]; for ( i = 0; i <= a; ) x[ i >> 2 ] |= ( str2.charCodeAt( i ) || 128 ) << ( 8 * ( i++ % 4 ) ); x[ ( str = ( ( a + 8 ) >> 6 ) * 16 + 14 ) ] = a * 8; i = 0; for ( ; i < str; i += 16 ) { a = h; j = 0; for ( ; j < 64; ) { a = [ ( d = a[ 3 ] ), ( b = a[ 1 ] | 0 ) + ( ( ( d = a[ 0 ] + [ ( b & ( c = a[ 2 ] ) ) | ( ~b & d ), ( d & b ) | ( ~d & c ), b ^ c ^ d, c ^ ( b | ~d ) ][ ( a = j >> 4 ) ] + ( k[ j ] + ( x[ ( [ j, 5 * j + 1, 3 * j + 5, 7 * j ][ a ] % 16 ) + i ] | 0 ) ) ) << ( a = [ 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21 ][ 4 * a + ( j++ % 4 ) ] ) ) | ( d >>> ( 32 - a ) ) ), b, c ]; } for ( j = 4; j; ) { h[ --j ] = h[ j ] + a[ j ]; } } str = ""; for ( ; j < 32; ) { str += ( ( h[ j >> 3 ] >> ( ( 1 ^ ( j++ & 7 ) ) * 4 ) ) & 15 ).toString( 16 ); } return str; }