BingTranslateEndpoint.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Reflection;
  9. using System.Text;
  10. using SimpleJSON;
  11. using XUnity.AutoTranslator.Plugin.Core;
  12. using XUnity.AutoTranslator.Plugin.Core.Configuration;
  13. using XUnity.AutoTranslator.Plugin.Core.Constants;
  14. using XUnity.AutoTranslator.Plugin.Core.Endpoints;
  15. using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http;
  16. using XUnity.AutoTranslator.Plugin.Core.Extensions;
  17. using XUnity.AutoTranslator.Plugin.Core.Utilities;
  18. using XUnity.AutoTranslator.Plugin.Core.Web;
  19. namespace BingTranslate
  20. {
  21. internal class BingTranslateEndpoint : HttpEndpoint
  22. {
  23. private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
  24. {
  25. "af","ar","bn","bs","bg","yue","ca","zh-Hans","zh-Hant","hr","cs","da","nl","en","et","fj","fil","fi","fr","de","el","ht","he","hi","mww","hu","is","id","it","ja","sw","tlh","tlh-Qaak","ko","lv","lt","mg","ms","mt","nb","fa","pl","pt","otq","ro","ru","sm","sr-Cyrl","sr-Latn","sk","sl","es","sv","ty","ta","te","th","to","tr","uk","ur","vi","cy","yua"
  26. };
  27. private static readonly string HttpsServicePointTemplateUrl = "https://www.bing.com/ttranslate?&category=&IG={0}&IID={1}.{2}";
  28. private static readonly string HttpsServicePointTemplateUrlWithoutIG = "https://www.bing.com/ttranslate?&category=";
  29. private static readonly string HttpsTranslateUserSite = "https://www.bing.com/translator";
  30. private static readonly string RequestTemplate = "&text={0}&from={1}&to={2}";
  31. private static readonly Random RandomNumbers = new Random();
  32. private static readonly string[] Accepts = new string[] { "*/*" };
  33. private static readonly string[] AcceptLanguages = new string[] { null, "en-US,en;q=0.9", "en-US", "en" };
  34. private static readonly string[] Referers = new string[] { "https://bing.com/translator" };
  35. private static readonly string[] Origins = new string[] { "https://www.bing.com" };
  36. private static readonly string[] AcceptCharsets = new string[] { null, Encoding.UTF8.WebName };
  37. private static readonly string[] ContentTypes = new string[] { "application/x-www-form-urlencoded" };
  38. private static readonly string Accept = Accepts[ RandomNumbers.Next( Accepts.Length ) ];
  39. private static readonly string AcceptLanguage = AcceptLanguages[ RandomNumbers.Next( AcceptLanguages.Length ) ];
  40. private static readonly string Referer = Referers[ RandomNumbers.Next( Referers.Length ) ];
  41. private static readonly string Origin = Origins[ RandomNumbers.Next( Origins.Length ) ];
  42. private static readonly string AcceptCharset = AcceptCharsets[ RandomNumbers.Next( AcceptCharsets.Length ) ];
  43. private static readonly string ContentType = ContentTypes[ RandomNumbers.Next( ContentTypes.Length ) ];
  44. private CookieContainer _cookieContainer;
  45. private bool _hasSetup = false;
  46. private string _ig;
  47. private string _iid;
  48. private int _translationCount = 0;
  49. private int _resetAfter = RandomNumbers.Next( 75, 125 );
  50. public BingTranslateEndpoint()
  51. {
  52. _cookieContainer = new CookieContainer();
  53. }
  54. public override string Id => "BingTranslate";
  55. public override string FriendlyName => "Bing Translator";
  56. public override void Initialize( IInitializationContext context )
  57. {
  58. // Configure service points / service point manager
  59. context.DisableCertificateChecksFor( "www.bing.com" );
  60. if( !SupportedLanguages.Contains( context.SourceLanguage ) ) throw new Exception( $"The source language '{context.SourceLanguage}' is not supported." );
  61. if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language '{context.DestinationLanguage}' is not supported." );
  62. }
  63. public override IEnumerator OnBeforeTranslate( IHttpTranslationContext context )
  64. {
  65. if( !_hasSetup || _translationCount % _resetAfter == 0 )
  66. {
  67. _resetAfter = RandomNumbers.Next( 75, 125 );
  68. _hasSetup = true;
  69. // Setup TKK and cookies
  70. var enumerator = SetupIGAndIID();
  71. while( enumerator.MoveNext() ) yield return enumerator.Current;
  72. }
  73. }
  74. public override void OnCreateRequest( IHttpRequestCreationContext context )
  75. {
  76. _translationCount++;
  77. string address = null;
  78. if( _ig == null || _iid == null )
  79. {
  80. address = HttpsServicePointTemplateUrlWithoutIG;
  81. }
  82. else
  83. {
  84. address = string.Format( HttpsServicePointTemplateUrl, _ig, _iid, _translationCount );
  85. }
  86. var data = string.Format(
  87. RequestTemplate,
  88. Uri.EscapeDataString( context.UntranslatedText ),
  89. context.SourceLanguage,
  90. context.DestinationLanguage );
  91. var request = new XUnityWebRequest( "POST", address, data );
  92. request.Cookies = _cookieContainer;
  93. AddHeaders( request, true );
  94. context.Complete( request );
  95. }
  96. public override void OnInspectResponse( IHttpResponseInspectionContext context )
  97. {
  98. InspectResponse( context.Response );
  99. }
  100. public override void OnExtractTranslation( IHttpTranslationExtractionContext context )
  101. {
  102. var obj = JSON.Parse( context.Response.Data );
  103. var code = obj[ "statusCode" ].AsInt;
  104. if( code != 200 ) context.Fail( "Bad response code received from service: " + code );
  105. var token = obj[ "translationResponse" ].ToString();
  106. var translatedText = JsonHelper.Unescape( token.Substring( 1, token.Length - 2 ) );
  107. context.Complete( translatedText );
  108. }
  109. private XUnityWebRequest CreateWebSiteRequest()
  110. {
  111. var request = new XUnityWebRequest( HttpsTranslateUserSite );
  112. request.Cookies = _cookieContainer;
  113. AddHeaders( request, false );
  114. return request;
  115. }
  116. private void AddHeaders( XUnityWebRequest request, bool isTranslationRequest )
  117. {
  118. request.Headers[ HttpRequestHeader.UserAgent ] = string.IsNullOrEmpty( AutoTranslatorSettings.UserAgent ) ? UserAgents.Chrome_Win10_Latest : AutoTranslatorSettings.UserAgent;
  119. if( AcceptLanguage != null )
  120. {
  121. request.Headers[ HttpRequestHeader.AcceptLanguage ] = AcceptLanguage;
  122. }
  123. if( Accept != null )
  124. {
  125. request.Headers[ HttpRequestHeader.Accept ] = Accept;
  126. }
  127. if( Referer != null && isTranslationRequest )
  128. {
  129. request.Headers[ HttpRequestHeader.Referer ] = Referer;
  130. }
  131. if( Origin != null && isTranslationRequest )
  132. {
  133. request.Headers[ "Origin" ] = Origin;
  134. }
  135. if( AcceptCharset != null )
  136. {
  137. request.Headers[ HttpRequestHeader.AcceptCharset ] = AcceptCharset;
  138. }
  139. if( ContentType != null && isTranslationRequest )
  140. {
  141. request.Headers[ HttpRequestHeader.ContentType ] = ContentType;
  142. }
  143. }
  144. private void InspectResponse( XUnityWebResponse response )
  145. {
  146. CookieCollection cookies = response.NewCookies;
  147. // FIXME: Is this needed? Should already be added
  148. _cookieContainer.Add( cookies );
  149. }
  150. public IEnumerator SetupIGAndIID()
  151. {
  152. XUnityWebResponse response = null;
  153. _cookieContainer = new CookieContainer();
  154. _translationCount = 0;
  155. try
  156. {
  157. var client = new XUnityWebClient();
  158. var request = CreateWebSiteRequest();
  159. response = client.Send( request );
  160. }
  161. catch( Exception e )
  162. {
  163. XuaLogger.Current.Warn( e, "An error occurred while setting up BingTranslate IG. Proceeding without..." );
  164. yield break;
  165. }
  166. var iterator = response.GetSupportedEnumerator();
  167. while( iterator.MoveNext() ) yield return iterator.Current;
  168. InspectResponse( response );
  169. // failure
  170. if( response.Error != null )
  171. {
  172. XuaLogger.Current.Warn( response.Error, "An error occurred while setting up BingTranslate IG. Proceeding without..." );
  173. yield break;
  174. }
  175. // failure
  176. if( response.Data == null )
  177. {
  178. XuaLogger.Current.Warn( null, "An error occurred while setting up BingTranslate IG. Proceeding without..." );
  179. yield break;
  180. }
  181. try
  182. {
  183. var html = response.Data;
  184. _ig = Lookup( "ig\":\"", html );
  185. _iid = Lookup( ".init(\"/feedback/submission?\",\"", html );
  186. if( _ig == null || _iid == null )
  187. {
  188. XuaLogger.Current.Warn( "An error occurred while setting up BingTranslate IG/IID. Proceeding without..." );
  189. }
  190. }
  191. catch( Exception e )
  192. {
  193. XuaLogger.Current.Warn( e, "An error occurred while setting up BingTranslate IG. Proceeding without..." );
  194. }
  195. }
  196. private string Lookup( string lookFor, string html )
  197. {
  198. var index = html.IndexOf( lookFor );
  199. if( index > -1 ) // simple string approach
  200. {
  201. var startIndex = index + lookFor.Length;
  202. var endIndex = html.IndexOf( "\"", startIndex );
  203. if( startIndex > -1 && endIndex > -1 )
  204. {
  205. var result = html.Substring( startIndex, endIndex - startIndex );
  206. return result;
  207. }
  208. }
  209. return null;
  210. }
  211. }
  212. }