|
|
|
@ -1,400 +0,0 @@
|
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.Encodings.Web;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Text.Json.Nodes;
|
|
|
|
|
using System.Text.Json.Serialization;
|
|
|
|
|
|
|
|
|
|
namespace STranslateDLL;
|
|
|
|
|
|
|
|
|
|
public static class LocalMode
|
|
|
|
|
{
|
|
|
|
|
private static long _nextId;
|
|
|
|
|
|
|
|
|
|
private static bool _hasInit;
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<bool, string> TextTypeDic = new()
|
|
|
|
|
{
|
|
|
|
|
{ true, "richtext" },
|
|
|
|
|
{ false, "plaintext" }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static JsonSerializerOptions GetOptions =>
|
|
|
|
|
new()
|
|
|
|
|
{
|
|
|
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
|
|
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static void Initial()
|
|
|
|
|
{
|
|
|
|
|
var rand = new Random();
|
|
|
|
|
var number = rand.NextInt64(99999) + 8300000;
|
|
|
|
|
_nextId = number * 1000;
|
|
|
|
|
|
|
|
|
|
_hasInit = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static async Task<string> ExecuteAsync(
|
|
|
|
|
string content,
|
|
|
|
|
string sourceLang = "auto",
|
|
|
|
|
string targetLang = "ZH",
|
|
|
|
|
CancellationToken? token = null
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
if (!_hasInit) Initial();
|
|
|
|
|
var getToken = token ?? CancellationToken.None;
|
|
|
|
|
var splitResult = await SplitTextAsync(content, getToken);
|
|
|
|
|
//TODO: Error handling
|
|
|
|
|
|
|
|
|
|
var splitObj = JsonSerializer.Deserialize<JsonObject>(splitResult);
|
|
|
|
|
if (sourceLang.Equals("auto", StringComparison.CurrentCultureIgnoreCase) || sourceLang.Equals(""))
|
|
|
|
|
{
|
|
|
|
|
sourceLang = splitObj?["result"]?["lang"]?["detected"]?.ToString()?.ToUpper() ?? "auto";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var jobs = new List<Job>();
|
|
|
|
|
var chunks = splitObj?["result"]?["texts"]?[0]?["chunks"];
|
|
|
|
|
if (chunks is JsonArray chunkArray)
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < chunkArray.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var sentence = chunkArray[i]?["sentences"]?[0];
|
|
|
|
|
var contextBefore = Array.Empty<string>();
|
|
|
|
|
var contextAfter = Array.Empty<string>();
|
|
|
|
|
if (i > 0)
|
|
|
|
|
{
|
|
|
|
|
contextBefore = [chunkArray[i - 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i < chunkArray.Count - 1)
|
|
|
|
|
{
|
|
|
|
|
contextAfter = [chunkArray[i + 1]?["sentences"]?[0]?["text"]?.ToString() ?? ""];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var job = new Job
|
|
|
|
|
{
|
|
|
|
|
Kind = "default",
|
|
|
|
|
PreferredNumBeams = 4,
|
|
|
|
|
RawEnContextBefore = contextBefore,
|
|
|
|
|
RawEnContextAfter = contextAfter,
|
|
|
|
|
Sentences =
|
|
|
|
|
[
|
|
|
|
|
new Sentence
|
|
|
|
|
{
|
|
|
|
|
Prefix = sentence?["prefix"]?.ToString() ?? "",
|
|
|
|
|
Text = sentence?["text"]?.ToString() ?? "",
|
|
|
|
|
Id = i + 1,
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
jobs.Add(job);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var hasRegionalVariant = false;
|
|
|
|
|
var targetLangCode = targetLang;
|
|
|
|
|
var targetLangParts = targetLang.Split("-");
|
|
|
|
|
if (targetLangParts.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
targetLangCode = targetLangParts[0];
|
|
|
|
|
hasRegionalVariant = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var id = CreateId();
|
|
|
|
|
var reqData = new DeepLRequest
|
|
|
|
|
{
|
|
|
|
|
Jsonrpc = "2.0",
|
|
|
|
|
Method = "LMT_handle_jobs",
|
|
|
|
|
Id = id,
|
|
|
|
|
Params = new ReqParams
|
|
|
|
|
{
|
|
|
|
|
CommonJobParams = new ReqParamsCommonJobParams
|
|
|
|
|
{
|
|
|
|
|
Mode = "translate",
|
|
|
|
|
RegionalVariant = hasRegionalVariant ? targetLang : null
|
|
|
|
|
},
|
|
|
|
|
Lang = new ReqParamsLang
|
|
|
|
|
{
|
|
|
|
|
// SourceLangUserSelected = sourceLang,
|
|
|
|
|
SourceLangComputed = sourceLang.ToUpper(),
|
|
|
|
|
TargetLang = targetLangCode.ToUpper()
|
|
|
|
|
},
|
|
|
|
|
Jobs = jobs.ToArray(),
|
|
|
|
|
Priority = 1,
|
|
|
|
|
Timestamp = GenerateTimestamp(content)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var json = JsonSerializer.Serialize(reqData, GetOptions);
|
|
|
|
|
json = AdjustJsonContent(json, id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using var client = new HttpClient();
|
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
|
|
|
|
"https://www2.deepl.com/jsonrpc?client=chrome-extension,1.28.0&method=LMT_handle_jobs")
|
|
|
|
|
{
|
|
|
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
request.Headers.Add("Accept", "*/*");
|
|
|
|
|
request.Headers.Add("Accept-Language", "en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh-HK;q=0.6,zh;q=0.5");
|
|
|
|
|
request.Headers.Add("Authorization", "None");
|
|
|
|
|
request.Headers.Add("Cache-Control", "no-cache");
|
|
|
|
|
// request.Headers.Add("Content-Type", "application/json");
|
|
|
|
|
request.Headers.Add("DNT", "1");
|
|
|
|
|
request.Headers.Add("Origin", "chrome-extension://cofdbpoegempjloogbagkncekinflcnj");
|
|
|
|
|
request.Headers.Add("Pragma", "no-cache");
|
|
|
|
|
request.Headers.Add("Priority", "u=1, i");
|
|
|
|
|
request.Headers.Add("Referer", "https://www.deepl.com/");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Dest", "empty");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Mode", "cors");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Site", "none");
|
|
|
|
|
request.Headers.Add("Sec-GPC", "1");
|
|
|
|
|
request.Headers.Add("User-Agent",
|
|
|
|
|
"DeepLBrowserExtension/1.28.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
|
|
|
|
|
|
|
|
|
|
var resp = await client.SendAsync(request, getToken);
|
|
|
|
|
//resp.EnsureSuccessStatusCode();
|
|
|
|
|
|
|
|
|
|
string responseBody;
|
|
|
|
|
|
|
|
|
|
if (resp.Content.Headers.ContentEncoding.Contains("br"))
|
|
|
|
|
{
|
|
|
|
|
await using var responseStream = await resp.Content.ReadAsStreamAsync(getToken);
|
|
|
|
|
await using var decompressionStream = new BrotliStream(
|
|
|
|
|
responseStream,
|
|
|
|
|
CompressionMode.Decompress
|
|
|
|
|
);
|
|
|
|
|
using var streamReader = new StreamReader(decompressionStream);
|
|
|
|
|
responseBody = await streamReader.ReadToEndAsync(getToken);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
responseBody = await resp.Content.ReadAsStringAsync(getToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var jNode = JsonNode.Parse(responseBody);
|
|
|
|
|
var data = jNode?["result"]?["translations"]?[0]?["beams"]?[0]?["sentences"]?[0]?["text"]?.ToString();
|
|
|
|
|
// data = UnicodeToString(data);
|
|
|
|
|
|
|
|
|
|
var errorMsg = jNode?["error"]?["message"]?.ToString();
|
|
|
|
|
var detailsMsg = jNode?["error"]?["data"]?["what"]?.ToString();
|
|
|
|
|
var error = $"Error: {errorMsg}\nDetails: {detailsMsg}";
|
|
|
|
|
|
|
|
|
|
var response = new Response
|
|
|
|
|
{
|
|
|
|
|
Code = resp.StatusCode.GetHashCode(),
|
|
|
|
|
Data = data ?? error
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return JsonSerializer.Serialize(response, GetOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<string> SplitTextAsync(string text, CancellationToken? token)
|
|
|
|
|
{
|
|
|
|
|
var id = CreateId();
|
|
|
|
|
var getToken = token ?? CancellationToken.None;
|
|
|
|
|
var requestData = new DeepLRequest
|
|
|
|
|
{
|
|
|
|
|
Jsonrpc = "2.0",
|
|
|
|
|
Method = "LMT_split_text",
|
|
|
|
|
Id = id,
|
|
|
|
|
Params = new ReqParams
|
|
|
|
|
{
|
|
|
|
|
CommonJobParams = new ReqParamsCommonJobParams
|
|
|
|
|
{
|
|
|
|
|
Mode = "translate"
|
|
|
|
|
},
|
|
|
|
|
Lang = new ReqParamsLang
|
|
|
|
|
{
|
|
|
|
|
SourceLangUserSelected = "auto"
|
|
|
|
|
},
|
|
|
|
|
Texts = [text],
|
|
|
|
|
TextType = TextTypeDic[ /*tagHandling*/false || IsRichText(text)]
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var json = JsonSerializer.Serialize(requestData, GetOptions);
|
|
|
|
|
json = AdjustJsonContent(json, id);
|
|
|
|
|
using var client = new HttpClient();
|
|
|
|
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
|
|
|
|
"https://www2.deepl.com/jsonrpc?client=chrome-extension,1.28.0&method=LMT_split_text")
|
|
|
|
|
{
|
|
|
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
|
|
|
};
|
|
|
|
|
// Add headers to the request
|
|
|
|
|
request.Headers.Add("Accept", "*/*");
|
|
|
|
|
request.Headers.Add("Accept-Language", "en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh-HK;q=0.6,zh;q=0.5");
|
|
|
|
|
request.Headers.Add("Authorization", "None");
|
|
|
|
|
request.Headers.Add("Cache-Control", "no-cache");
|
|
|
|
|
// request.Headers.Add("Content-Type", "application/json");
|
|
|
|
|
request.Headers.Add("DNT", "1");
|
|
|
|
|
request.Headers.Add("Origin", "chrome-extension://cofdbpoegempjloogbagkncekinflcnj");
|
|
|
|
|
request.Headers.Add("Pragma", "no-cache");
|
|
|
|
|
request.Headers.Add("Priority", "u=1, i");
|
|
|
|
|
request.Headers.Add("Referer", "https://www.deepl.com/");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Dest", "empty");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Mode", "cors");
|
|
|
|
|
request.Headers.Add("Sec-Fetch-Site", "none");
|
|
|
|
|
request.Headers.Add("Sec-GPC", "1");
|
|
|
|
|
request.Headers.Add("User-Agent",
|
|
|
|
|
"DeepLBrowserExtension/1.28.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
|
|
|
|
|
|
|
|
|
|
var resp = await client.SendAsync(request, getToken);
|
|
|
|
|
string responseBody;
|
|
|
|
|
|
|
|
|
|
if (resp.Content.Headers.ContentEncoding.Contains("br"))
|
|
|
|
|
{
|
|
|
|
|
await using var responseStream = await resp.Content.ReadAsStreamAsync(getToken);
|
|
|
|
|
await using var decompressionStream = new BrotliStream(
|
|
|
|
|
responseStream,
|
|
|
|
|
CompressionMode.Decompress
|
|
|
|
|
);
|
|
|
|
|
using var streamReader = new StreamReader(decompressionStream);
|
|
|
|
|
responseBody = await streamReader.ReadToEndAsync(getToken);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
responseBody = await resp.Content.ReadAsStringAsync(getToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responseBody;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string? UnicodeToString(string? srcText)
|
|
|
|
|
{
|
|
|
|
|
if (srcText == null) return default;
|
|
|
|
|
var dst = "";
|
|
|
|
|
var src = srcText;
|
|
|
|
|
var len = srcText.Length / 6;
|
|
|
|
|
for (var i = 0; i <= len - 1; i++)
|
|
|
|
|
{
|
|
|
|
|
var str = "";
|
|
|
|
|
str = src[..6][2..];
|
|
|
|
|
src = src[6..];
|
|
|
|
|
var bytes = new byte[2];
|
|
|
|
|
bytes[1] = byte.Parse(int.Parse(str[..2], System.Globalization.NumberStyles.HexNumber).ToString());
|
|
|
|
|
bytes[0] = byte.Parse(int.Parse(str.Substring(2, 2), System.Globalization.NumberStyles.HexNumber).ToString());
|
|
|
|
|
dst += Encoding.Unicode.GetString(bytes);
|
|
|
|
|
}
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsRichText(string text)
|
|
|
|
|
{
|
|
|
|
|
return text.Contains('<') && text.Contains('>');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static long GenerateTimestamp(string texts)
|
|
|
|
|
{
|
|
|
|
|
long iCount = texts.Split('i').Length - 1;
|
|
|
|
|
var ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
|
return iCount != 0 ? ts - ts % (iCount + 1) + iCount + 1 : ts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static long CreateId()
|
|
|
|
|
{
|
|
|
|
|
return Interlocked.Increment(ref _nextId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string AdjustJsonContent(string json, long id)
|
|
|
|
|
{
|
|
|
|
|
string method;
|
|
|
|
|
if ((id + 3) % 13 == 0 || (id + 5) % 29 == 0)
|
|
|
|
|
method = "\"method\" : \"";
|
|
|
|
|
else
|
|
|
|
|
method = "\"method\": \"";
|
|
|
|
|
return json.Replace("\"method\":\"", method);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Response
|
|
|
|
|
{
|
|
|
|
|
public int Code { get; set; }
|
|
|
|
|
public string Data { get; set; } = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Request
|
|
|
|
|
|
|
|
|
|
public class DeepLRequest
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("jsonrpc")] public string Jsonrpc { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("method")] public string Method { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("params")] public ReqParams? Params { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("id")] public long Id { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ReqParams
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("commonJobParams")] public ReqParamsCommonJobParams? CommonJobParams { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("lang")] public ReqParamsLang? Lang { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("texts")] public string[]? Texts { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("textType")] public string? TextType { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("jobs")] public Job[]? Jobs { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("priority")] public int Priority { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("timestamp")] public long Timestamp { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ReqParamsLang
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("source_lang_user_selected")]
|
|
|
|
|
public string? SourceLangUserSelected { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("source_lang_computed")]
|
|
|
|
|
public string? SourceLangComputed { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("target_lang")] public string TargetLang { get; set; } = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ReqParamsCommonJobParams
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("mode")] public string Mode { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("regionalVariant")] public string? RegionalVariant { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Job
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("kind")] public string Kind { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("preferred_num_beams")]
|
|
|
|
|
public int PreferredNumBeams { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("raw_en_context_before")]
|
|
|
|
|
public string[]? RawEnContextBefore { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("raw_en_context_after")]
|
|
|
|
|
public string[]? RawEnContextAfter { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("sentences")] public Sentence[]? Sentences { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Sentence
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("id")] public int Id { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("prefix")] public string Prefix { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("text")] public string Text { get; set; } = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class RespError
|
|
|
|
|
{
|
|
|
|
|
[JsonPropertyName("code")] public int Code { get; set; }
|
|
|
|
|
|
|
|
|
|
[JsonPropertyName("message")] public string Message { get; set; } = "";
|
|
|
|
|
}
|