GoogleTranslateEndpoint.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 SimpleJSON;
  10. using XUnity.AutoTranslator.Plugin.Core;
  11. using XUnity.AutoTranslator.Plugin.Core.Configuration;
  12. using XUnity.AutoTranslator.Plugin.Core.Constants;
  13. using XUnity.AutoTranslator.Plugin.Core.Endpoints;
  14. using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http;
  15. using XUnity.AutoTranslator.Plugin.Core.Extensions;
  16. using XUnity.AutoTranslator.Plugin.Core.Utilities;
  17. using XUnity.AutoTranslator.Plugin.Core.Web;
  18. namespace GoogleTranslate
  19. {
  20. internal class GoogleTranslateEndpoint : HttpEndpoint
  21. {
  22. private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
  23. {
  24. "romaji","af","sq","am","ar","hy","az","eu","be","bn","bs","bg","ca","ceb","zh-CN","zh-TW","co","hr","cs","da","nl","en","eo","et","fi","fr","fy","gl","ka","de","el","gu","ht","ha","haw","he","hi","hmn","hu","is","ig","id","ga","it","ja","jw","kn","kk","km","ko","ku","ky","lo","la","lv","lt","lb","mk","mg","ms","ml","mt","mi","mr","mn","my","ne","no","ny","ps","fa","pl","pt","pa","ro","ru","sm","gd","sr","st","sn","sd","si","sk","sl","so","es","su","sw","sv","tl","tg","ta","te","th","tr","uk","ur","uz","vi","cy","xh","yi","yo","zu"
  25. };
  26. private static readonly string HttpsServicePointTranslateTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=webapp&sl={0}&tl={1}&dt=t&tk={2}&q={3}";
  27. private static readonly string HttpsServicePointRomanizeTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=webapp&sl={0}&tl=en&dt=rm&tk={1}&q={2}";
  28. private static readonly string HttpsTranslateUserSite = "https://translate.google.com";
  29. private static readonly Random RandomNumbers = new Random();
  30. private static readonly string[] Accepts = new string[] { null, "*/*", "application/json" };
  31. private static readonly string[] AcceptLanguages = new string[] { null, "en-US,en;q=0.9", "en-US", "en" };
  32. private static readonly string[] Referers = new string[] { null, "https://translate.google.com/" };
  33. private static readonly string[] AcceptCharsets = new string[] { null, Encoding.UTF8.WebName };
  34. private static readonly string Accept = Accepts[ RandomNumbers.Next( Accepts.Length ) ];
  35. private static readonly string AcceptLanguage = AcceptLanguages[ RandomNumbers.Next( AcceptLanguages.Length ) ];
  36. private static readonly string Referer = Referers[ RandomNumbers.Next( Referers.Length ) ];
  37. private static readonly string AcceptCharset = AcceptCharsets[ RandomNumbers.Next( AcceptCharsets.Length ) ];
  38. private CookieContainer _cookieContainer;
  39. private bool _hasSetup = false;
  40. private long m = 427761;
  41. private long s = 1179739010;
  42. private int _translationsPerRequest = 10;
  43. public GoogleTranslateEndpoint()
  44. {
  45. _cookieContainer = new CookieContainer();
  46. }
  47. public override string Id => KnownTranslateEndpointNames.GoogleTranslate;
  48. public override string FriendlyName => "Google! Translate";
  49. public override int MaxTranslationsPerRequest => _translationsPerRequest;
  50. public override void Initialize( IInitializationContext context )
  51. {
  52. context.DisableCerfificateChecksFor( "translate.google.com", "translate.googleapis.com" );
  53. if( context.DestinationLanguage == "romaji" )
  54. {
  55. _translationsPerRequest = 1;
  56. }
  57. if( !SupportedLanguages.Contains( context.SourceLanguage ) ) throw new Exception( $"The source language '{context.SourceLanguage}' is not supported." );
  58. if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language '{context.DestinationLanguage}' is not supported." );
  59. }
  60. public override IEnumerator OnBeforeTranslate( IHttpTranslationContext context )
  61. {
  62. if( !_hasSetup || AutoTranslatorState.TranslationCount % 100 == 0 )
  63. {
  64. _hasSetup = true;
  65. // Setup TKK and cookies
  66. var enumerator = SetupTKK();
  67. while( enumerator.MoveNext() )
  68. {
  69. yield return enumerator.Current;
  70. }
  71. }
  72. }
  73. public override void OnCreateRequest( IHttpRequestCreationContext context )
  74. {
  75. var allUntranslatedText = string.Join( "\n", context.UntranslatedTexts );
  76. XUnityWebRequest request;
  77. if( context.DestinationLanguage == "romaji" )
  78. {
  79. request = new XUnityWebRequest(
  80. string.Format(
  81. HttpsServicePointRomanizeTemplateUrl,
  82. context.SourceLanguage,
  83. Tk( allUntranslatedText ),
  84. Uri.EscapeDataString( allUntranslatedText ) ) );
  85. }
  86. else
  87. {
  88. request = new XUnityWebRequest(
  89. string.Format(
  90. HttpsServicePointTranslateTemplateUrl,
  91. context.SourceLanguage,
  92. context.DestinationLanguage,
  93. Tk( allUntranslatedText ),
  94. Uri.EscapeDataString( allUntranslatedText ) ) );
  95. }
  96. request.Cookies = _cookieContainer;
  97. AddHeaders( request, true );
  98. context.Complete( request );
  99. }
  100. public override void OnInspectResponse( IHttpResponseInspectionContext context )
  101. {
  102. InspectResponse( context.Response );
  103. }
  104. public override void OnExtractTranslation( IHttpTranslationExtractionContext context )
  105. {
  106. var dataIndex = context.DestinationLanguage == "romaji" ? 3 : 0;
  107. var data = context.Response.Data;
  108. var arr = JSON.Parse( data );
  109. var lineBuilder = new StringBuilder( data.Length );
  110. foreach( JSONNode entry in arr.AsArray[ 0 ].AsArray )
  111. {
  112. var token = entry.AsArray[ dataIndex ].ToString();
  113. token = JsonHelper.Unescape( token.Substring( 1, token.Length - 2 ) );
  114. if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( '\n' );
  115. lineBuilder.Append( token );
  116. }
  117. var allTranslation = lineBuilder.ToString();
  118. if( context.UntranslatedTexts.Length == 1 )
  119. {
  120. context.Complete( allTranslation );
  121. }
  122. else
  123. {
  124. var translatedLines = allTranslation.Split( '\n' );
  125. var translatedTexts = new List<string>();
  126. int current = 0;
  127. foreach( var untranslatedText in context.UntranslatedTexts )
  128. {
  129. var untranslatedLines = untranslatedText.Split( '\n' );
  130. var untranslatedLinesCount = untranslatedLines.Length;
  131. var translatedText = string.Empty;
  132. for( int i = 0 ; i < untranslatedLinesCount ; i++ )
  133. {
  134. if( current >= translatedLines.Length ) context.Fail( "Batch operation received incorrect number of translations." );
  135. var translatedLine = translatedLines[ current++ ];
  136. translatedText += translatedLine;
  137. if( i != untranslatedLinesCount - 1 ) translatedText += '\n';
  138. }
  139. translatedTexts.Add( translatedText );
  140. }
  141. if( current != translatedLines.Length ) context.Fail( "Batch operation received incorrect number of translations." );
  142. context.Complete( translatedTexts.ToArray() );
  143. }
  144. }
  145. private XUnityWebRequest CreateWebSiteRequest()
  146. {
  147. var request = new XUnityWebRequest( HttpsTranslateUserSite );
  148. request.Cookies = _cookieContainer;
  149. AddHeaders( request, false );
  150. return request;
  151. }
  152. private void AddHeaders( XUnityWebRequest request, bool isTranslationRequest )
  153. {
  154. request.Headers[ HttpRequestHeader.UserAgent ] = string.IsNullOrEmpty( AutoTranslatorSettings.UserAgent ) ? UserAgents.Chrome_Win10_Latest : AutoTranslatorSettings.UserAgent;
  155. if( AcceptLanguage != null )
  156. {
  157. request.Headers[ HttpRequestHeader.AcceptLanguage ] = AcceptLanguage;
  158. }
  159. if( Accept != null )
  160. {
  161. request.Headers[ HttpRequestHeader.Accept ] = Accept;
  162. }
  163. if( Referer != null && isTranslationRequest )
  164. {
  165. request.Headers[ HttpRequestHeader.Referer ] = Referer;
  166. }
  167. if( AcceptCharset != null )
  168. {
  169. request.Headers[ HttpRequestHeader.AcceptCharset ] = AcceptCharset;
  170. }
  171. }
  172. private void InspectResponse( XUnityWebResponse response )
  173. {
  174. CookieCollection cookies = response.NewCookies;
  175. foreach( Cookie cookie in cookies )
  176. {
  177. // redirect cookie to correct domain
  178. cookie.Domain = ".googleapis.com";
  179. }
  180. // FIXME: Is this needed? Should already be added
  181. _cookieContainer.Add( cookies );
  182. }
  183. public IEnumerator SetupTKK()
  184. {
  185. XUnityWebResponse response = null;
  186. _cookieContainer = new CookieContainer();
  187. try
  188. {
  189. var client = new XUnityWebClient();
  190. var request = CreateWebSiteRequest();
  191. response = client.Send( request );
  192. }
  193. catch( Exception e )
  194. {
  195. XuaLogger.Current.Warn( e, "An error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." );
  196. yield break;
  197. }
  198. // wait for response completion
  199. var iterator = response.GetSupportedEnumerator();
  200. while( iterator.MoveNext() ) yield return iterator.Current;
  201. InspectResponse( response );
  202. // failure
  203. if( response.Error != null )
  204. {
  205. XuaLogger.Current.Warn( response.Error, "An error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." );
  206. yield break;
  207. }
  208. // failure
  209. if( response.Data == null )
  210. {
  211. XuaLogger.Current.Warn( null, "An error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." );
  212. yield break;
  213. }
  214. try
  215. {
  216. var html = response.Data;
  217. bool found = false;
  218. string[] lookups = new[] { "tkk:'", "TKK='" };
  219. foreach( var lookup in lookups )
  220. {
  221. var index = html.IndexOf( lookup );
  222. if( index > -1 ) // simple string approach
  223. {
  224. var startIndex = index + lookup.Length;
  225. var endIndex = html.IndexOf( "'", startIndex );
  226. var result = html.Substring( startIndex, endIndex - startIndex );
  227. var parts = result.Split( '.' );
  228. if( parts.Length == 2 )
  229. {
  230. m = long.Parse( parts[ 0 ] );
  231. s = long.Parse( parts[ 1 ] );
  232. found = true;
  233. break;
  234. }
  235. }
  236. }
  237. if( !found )
  238. {
  239. XuaLogger.Current.Warn( "An error occurred while setting up GoogleTranslate TKK. Could not locate TKK value. Using fallback TKK values instead." );
  240. }
  241. }
  242. catch( Exception e )
  243. {
  244. XuaLogger.Current.Warn( e, "An error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." );
  245. }
  246. }
  247. // TKK Approach stolen from Translation Aggregator r190, all credits to Sinflower
  248. private long Vi( long r, string o )
  249. {
  250. for( var t = 0 ; t < o.Length ; t += 3 )
  251. {
  252. long a = o[ t + 2 ];
  253. a = a >= 'a' ? a - 87 : a - '0';
  254. a = '+' == o[ t + 1 ] ? r >> (int)a : r << (int)a;
  255. r = '+' == o[ t ] ? r + a & 4294967295 : r ^ a;
  256. }
  257. return r;
  258. }
  259. private string Tk( string r )
  260. {
  261. List<long> S = new List<long>();
  262. for( var v = 0 ; v < r.Length ; v++ )
  263. {
  264. long A = r[ v ];
  265. if( 128 > A )
  266. S.Add( A );
  267. else
  268. {
  269. if( 2048 > A )
  270. S.Add( A >> 6 | 192 );
  271. else if( 55296 == ( 64512 & A ) && v + 1 < r.Length && 56320 == ( 64512 & r[ v + 1 ] ) )
  272. {
  273. A = 65536 + ( ( 1023 & A ) << 10 ) + ( 1023 & r[ ++v ] );
  274. S.Add( A >> 18 | 240 );
  275. S.Add( A >> 12 & 63 | 128 );
  276. }
  277. else
  278. {
  279. S.Add( A >> 12 | 224 );
  280. S.Add( A >> 6 & 63 | 128 );
  281. }
  282. S.Add( 63 & A | 128 );
  283. }
  284. }
  285. const string F = "+-a^+6";
  286. const string D = "+-3^+b+-f";
  287. long p = m;
  288. for( var b = 0 ; b < S.Count ; b++ )
  289. {
  290. p += S[ b ];
  291. p = Vi( p, F );
  292. }
  293. p = Vi( p, D );
  294. p ^= s;
  295. if( 0 > p )
  296. p = ( 2147483647 & p ) + 2147483648;
  297. p %= (long)1e6;
  298. return p.ToString( CultureInfo.InvariantCulture ) + "." + ( p ^ m ).ToString( CultureInfo.InvariantCulture );
  299. }
  300. }
  301. }