GoogleTranslateEndpoint.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Net;
  7. using System.Reflection;
  8. using System.Text;
  9. using Harmony;
  10. using SimpleJSON;
  11. using UnityEngine;
  12. using XUnity.AutoTranslator.Plugin.Core.Configuration;
  13. using XUnity.AutoTranslator.Plugin.Core.Constants;
  14. using XUnity.AutoTranslator.Plugin.Core.Extensions;
  15. namespace XUnity.AutoTranslator.Plugin.Core.Web
  16. {
  17. public class GoogleTranslateEndpoint : KnownHttpEndpoint
  18. {
  19. //protected static readonly ConstructorInfo WwwConstructor = Constants.Types.WWW?.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
  20. private static readonly string HttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=t&dt=t&sl={0}&tl={1}&ie=UTF-8&oe=UTF-8&tk={2}&q={3}";
  21. private static readonly string HttpsTranslateUserSite = "https://translate.google.com";
  22. private static readonly string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36";
  23. //private static readonly string UserAgentRepository = "https://techblog.willshouse.com/2012/01/03/most-common-user-agents/";
  24. private static readonly System.Random RandomNumbers = new System.Random();
  25. private static readonly string[] Accepts = new string[] { null, "*/*", "application/json" };
  26. private static readonly string[] AcceptLanguages = new string[] { null, "en-US,en;q=0.9", "en-US", "en" };
  27. private static readonly string[] Referers = new string[] { null, "https://translate.google.com/" };
  28. private static readonly string[] AcceptCharsets = new string[] { null, Encoding.UTF8.WebName };
  29. private static readonly string Accept = Accepts[ RandomNumbers.Next( Accepts.Length ) ];
  30. private static readonly string AcceptLanguage = AcceptLanguages[ RandomNumbers.Next( AcceptLanguages.Length ) ];
  31. private static readonly string Referer = Referers[ RandomNumbers.Next( Referers.Length ) ];
  32. private static readonly string AcceptCharset = AcceptCharsets[ RandomNumbers.Next( AcceptCharsets.Length ) ];
  33. private CookieContainer _cookieContainer;
  34. private bool _hasSetup = false;
  35. //private bool _hasSetupCustomUserAgent = false;
  36. //private string _popularUserAgent;
  37. private long m = 427761;
  38. private long s = 1179739010;
  39. public GoogleTranslateEndpoint()
  40. {
  41. _cookieContainer = new CookieContainer();
  42. // Configure service points / service point manager
  43. ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.google.com", "translate.googleapis.com" );
  44. SetupServicePoints( "https://translate.googleapis.com", "https://translate.google.com" );
  45. }
  46. public override bool SupportsLineSplitting => true;
  47. public override void ApplyHeaders( WebHeaderCollection headers )
  48. {
  49. headers[ HttpRequestHeader.UserAgent ] = Settings.GetUserAgent( DefaultUserAgent );
  50. if( AcceptLanguage != null )
  51. {
  52. headers[ HttpRequestHeader.AcceptLanguage ] = AcceptLanguage;
  53. }
  54. if( Accept != null )
  55. {
  56. headers[ HttpRequestHeader.Accept ] = Accept;
  57. }
  58. if( Referer != null )
  59. {
  60. headers[ HttpRequestHeader.Referer ] = Referer;
  61. }
  62. if( AcceptCharset != null )
  63. {
  64. headers[ HttpRequestHeader.AcceptCharset ] = AcceptCharset;
  65. }
  66. }
  67. public override IEnumerator OnBeforeTranslate( int translationCount )
  68. {
  69. //if( !_hasSetupCustomUserAgent )
  70. //{
  71. // _hasSetupCustomUserAgent = true;
  72. // // setup dynamic user agent
  73. // var enumerator = SetupDynamicUserAgent();
  74. // while( enumerator.MoveNext() )
  75. // {
  76. // yield return enumerator.Current;
  77. // }
  78. //}
  79. if( !_hasSetup || translationCount % 100 == 0 )
  80. {
  81. _hasSetup = true;
  82. // Setup TKK and cookies
  83. var enumerator = SetupTKK();
  84. while( enumerator.MoveNext() )
  85. {
  86. yield return enumerator.Current;
  87. }
  88. }
  89. }
  90. //public IEnumerator SetupDynamicUserAgent()
  91. //{
  92. // // have to use WWW for this because unity mono is broken
  93. // if( WwwConstructor != null )
  94. // {
  95. // object www;
  96. // try
  97. // {
  98. // var headers = new Dictionary<string, string>();
  99. // www = WwwConstructor.Invoke( new object[] { UserAgentRepository, null, headers } );
  100. // }
  101. // catch( Exception e )
  102. // {
  103. // Logger.Current.Warn( e, "An error occurred while retrieving dynamic user agent." );
  104. // yield break;
  105. // }
  106. // yield return www;
  107. // try
  108. // {
  109. // var error = (string)AccessTools.Property( Constants.Types.WWW, "error" ).GetValue( www, null );
  110. // if( error == null )
  111. // {
  112. // var text = (string)AccessTools.Property( Constants.Types.WWW, "text" ).GetValue( www, null );
  113. // var userAgents = text.GetBetween( "<textarea rows=\"10\" class=\"get-the-list\" onclick=\"this.select()\" readonly=\"readonly\">", "</textarea>" );
  114. // if( userAgents.Length > 42 )
  115. // {
  116. // var reader = new StringReader( userAgents );
  117. // var popularUserAgent = reader.ReadLine();
  118. // if( popularUserAgent.Length > 30 && popularUserAgent.Length < 300 && popularUserAgent.StartsWith( "Mozilla/" ) )
  119. // {
  120. // _popularUserAgent = popularUserAgent;
  121. // }
  122. // else
  123. // {
  124. // Logger.Current.Warn( "An error occurred while retrieving dynamic user agent. Could not find a user agent in returned html." );
  125. // }
  126. // }
  127. // else
  128. // {
  129. // Logger.Current.Warn( "An error occurred while retrieving dynamic user agent. Could not find a user agent in returned html." );
  130. // }
  131. // }
  132. // else
  133. // {
  134. // Logger.Current.Warn( "An error occurred while retrieving dynamic user agent. Request failed: " + Environment.NewLine + error );
  135. // }
  136. // }
  137. // catch( Exception e )
  138. // {
  139. // Logger.Current.Warn( e, "An error occurred while retrieving dynamic user agent." );
  140. // }
  141. // }
  142. //}
  143. public IEnumerator SetupTKK()
  144. {
  145. string error = null;
  146. DownloadResult downloadResult = null;
  147. _cookieContainer = new CookieContainer();
  148. var client = GetClient();
  149. try
  150. {
  151. ApplyHeaders( client.Headers );
  152. client.Headers.Remove( HttpRequestHeader.Referer );
  153. downloadResult = client.GetDownloadResult( new Uri( HttpsTranslateUserSite ) );
  154. }
  155. catch( Exception e )
  156. {
  157. error = e.ToString();
  158. }
  159. if( downloadResult != null )
  160. {
  161. if( Features.SupportsCustomYieldInstruction )
  162. {
  163. yield return downloadResult;
  164. }
  165. else
  166. {
  167. while( !downloadResult.IsCompleted )
  168. {
  169. yield return new WaitForSeconds( 0.2f );
  170. }
  171. }
  172. error = downloadResult.Error;
  173. if( downloadResult.Succeeded && downloadResult.Result != null )
  174. {
  175. try
  176. {
  177. var html = downloadResult.Result;
  178. bool found = false;
  179. string[] lookups = new[] { "tkk:'", "TKK='" };
  180. foreach( var lookup in lookups )
  181. {
  182. var index = html.IndexOf( lookup );
  183. if( index > -1 ) // simple string approach
  184. {
  185. var startIndex = index + lookup.Length;
  186. var endIndex = html.IndexOf( "'", startIndex );
  187. var result = html.Substring( startIndex, endIndex - startIndex );
  188. var parts = result.Split( '.' );
  189. if( parts.Length == 2 )
  190. {
  191. m = long.Parse( parts[ 0 ] );
  192. s = long.Parse( parts[ 1 ] );
  193. found = true;
  194. break;
  195. }
  196. }
  197. }
  198. if( !found )
  199. {
  200. Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Could not locate TKK value. Using fallback TKK values instead." );
  201. }
  202. }
  203. catch( Exception e )
  204. {
  205. error = e.ToString();
  206. }
  207. }
  208. }
  209. if( error != null )
  210. {
  211. Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Using fallback TKK values instead." + Environment.NewLine + error );
  212. }
  213. }
  214. // TKK Approach stolen from Translation Aggregator r190, all credits to Sinflower
  215. private long Vi( long r, string o )
  216. {
  217. for( var t = 0 ; t < o.Length ; t += 3 )
  218. {
  219. long a = o[ t + 2 ];
  220. a = a >= 'a' ? a - 87 : a - '0';
  221. a = '+' == o[ t + 1 ] ? r >> (int)a : r << (int)a;
  222. r = '+' == o[ t ] ? r + a & 4294967295 : r ^ a;
  223. }
  224. return r;
  225. }
  226. private string Tk( string r )
  227. {
  228. List<long> S = new List<long>();
  229. for( var v = 0 ; v < r.Length ; v++ )
  230. {
  231. long A = r[ v ];
  232. if( 128 > A )
  233. S.Add( A );
  234. else
  235. {
  236. if( 2048 > A )
  237. S.Add( A >> 6 | 192 );
  238. else if( 55296 == ( 64512 & A ) && v + 1 < r.Length && 56320 == ( 64512 & r[ v + 1 ] ) )
  239. {
  240. A = 65536 + ( ( 1023 & A ) << 10 ) + ( 1023 & r[ ++v ] );
  241. S.Add( A >> 18 | 240 );
  242. S.Add( A >> 12 & 63 | 128 );
  243. }
  244. else
  245. {
  246. S.Add( A >> 12 | 224 );
  247. S.Add( A >> 6 & 63 | 128 );
  248. }
  249. S.Add( 63 & A | 128 );
  250. }
  251. }
  252. const string F = "+-a^+6";
  253. const string D = "+-3^+b+-f";
  254. long p = m;
  255. for( var b = 0 ; b < S.Count ; b++ )
  256. {
  257. p += S[ b ];
  258. p = Vi( p, F );
  259. }
  260. p = Vi( p, D );
  261. p ^= s;
  262. if( 0 > p )
  263. p = ( 2147483647 & p ) + 2147483648;
  264. p %= (long)1e6;
  265. return p.ToString( CultureInfo.InvariantCulture ) + "." + ( p ^ m ).ToString( CultureInfo.InvariantCulture );
  266. }
  267. public override bool TryExtractTranslated( string result, out string translated )
  268. {
  269. try
  270. {
  271. var arr = JSON.Parse( result );
  272. var lineBuilder = new StringBuilder( result.Length );
  273. foreach( JSONNode entry in arr.AsArray[ 0 ].AsArray )
  274. {
  275. var token = entry.AsArray[ 0 ].ToString();
  276. token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
  277. if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
  278. lineBuilder.Append( token );
  279. }
  280. translated = lineBuilder.ToString();
  281. var success = !string.IsNullOrEmpty( translated );
  282. return success;
  283. }
  284. catch
  285. {
  286. translated = null;
  287. return false;
  288. }
  289. }
  290. public override string GetServiceUrl( string untranslatedText, string from, string to )
  291. {
  292. return string.Format( HttpsServicePointTemplateUrl, from, to, Tk( untranslatedText ), WWW.EscapeURL( untranslatedText ) );
  293. }
  294. public override void WriteCookies( HttpWebResponse response )
  295. {
  296. CookieCollection cookies = response.Cookies;
  297. foreach( Cookie cookie in cookies )
  298. {
  299. // redirect cookie to correct domain
  300. cookie.Domain = ".googleapis.com";
  301. }
  302. _cookieContainer.Add( cookies );
  303. }
  304. public override CookieContainer ReadCookies()
  305. {
  306. return _cookieContainer;
  307. }
  308. }
  309. }