DeeplinkParseCore.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. 
  2. using COSXML.Network;
  3. using dodohold.core;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Microsoft.Extensions.FileSystemGlobbing.Internal;
  6. using Sayaka.Common;
  7. using Spire.Pdf.Annotations;
  8. using Spire.Pdf.Exporting.XPS.Schema;
  9. using System.Security.Cryptography;
  10. using System.Text.RegularExpressions;
  11. using System.Web;
  12. using System.Xml.Linq;
  13. using TencentCloud.Tcss.V20201101.Models;
  14. namespace molilian.core
  15. {
  16. public partial class DeeplinkParseCore
  17. {
  18. private static string _end_point;
  19. private static readonly string[] separators = ["\r\n", "\n"];
  20. static DeeplinkParseCore()
  21. {
  22. _end_point = Environment.GetEnvironmentVariable("EndPoint");
  23. }
  24. public static DeeplinkParseDataDTO GetFormattedObject(string content, string channel, string ip = "", string oaid = "")
  25. {
  26. return new DeeplinkParseDataDTO()
  27. {
  28. message = string.Empty,
  29. success = false,
  30. content = content,
  31. ip = ip,
  32. oaid = oaid,
  33. elapsedTime = 0,
  34. create_time = DateTime.Now,
  35. end_point = _end_point,
  36. };
  37. }
  38. public static async Task<ActionResult> ParseAsync(string content, string channel, string ip, string oaid)
  39. {
  40. var result = GetFormattedObject(content, channel, ip, oaid);
  41. content = content.Trim();
  42. var rule = DeeplinkParseRuleCore.GetOne(channel);
  43. if (rule == null)
  44. {
  45. result.success = false;
  46. result.message = "转链失败";
  47. result.reason = "无效平台";
  48. _ = TkLogCore.ParseLogAsync(result);
  49. return new APIResult(new
  50. {
  51. result.success,
  52. result.message,
  53. result.reason,
  54. });
  55. }
  56. if (ShouldIgnoreRequest(rule.IgnorePercentageCity, ip, oaid, out string reason))
  57. {
  58. result.success = false;
  59. result.message = "放弃转链";
  60. result.reason = reason;
  61. _ = TkLogCore.ParseLogAsync(result);
  62. return new APIResult(new
  63. {
  64. result.success,
  65. result.message,
  66. result.reason,
  67. });
  68. }
  69. result.channel_id = rule.channel_id;
  70. result.channel_name = rule.channel_name;
  71. result = await GenerateDeeplink(content, rule, result);
  72. _ = TkLogCore.ParseLogAsync(result);
  73. return new APIResult(new
  74. {
  75. result.success,
  76. result.message,
  77. channel = result.channel_name,
  78. result.deeplink_url,
  79. result.itemName,
  80. });
  81. }
  82. public static bool ShouldIgnoreRequest(string ignorePercentageCity, string ip, string oaid, out string reason)
  83. {
  84. reason = string.Empty;
  85. try
  86. {
  87. // IP和流量控制
  88. string? ipInfo = IP2RegionPlus.Search(ip);
  89. if (AlimamaPlus.IgnoreRegionIncluded(ignorePercentageCity, ipInfo, out string regionInfo))
  90. {
  91. reason = $"地区控制:{regionInfo}";
  92. return true;
  93. }
  94. }
  95. catch (Exception ex)
  96. {
  97. reason = "配置异常";
  98. }
  99. return false;
  100. }
  101. public static async Task<DeeplinkParseDataDTO> GenerateDeeplink(string text, DeeplinkParseRuleDTO config, DeeplinkParseDataDTO result)
  102. {
  103. try
  104. {
  105. var list = config.rules.Convert2Object<List<DeeplinkParseRule>>();
  106. foreach (var rule in list)
  107. {
  108. var e = await WorkEachRule(text, config, result, rule);
  109. if (e != null) return e;
  110. }
  111. }
  112. catch (Exception ex)
  113. {
  114. }
  115. result.success = false;
  116. return result;
  117. }
  118. private static async Task<DeeplinkParseDataDTO?> WorkEachRule(string text, DeeplinkParseRuleDTO config, DeeplinkParseDataDTO result, DeeplinkParseRule rule)
  119. {
  120. if (!rule.enable) return null;
  121. string content = text;
  122. if (!string.IsNullOrEmpty(rule.url_pattern))
  123. {
  124. var regex = new Regex(rule.url_pattern);
  125. var match = regex.Match(content);
  126. if (!match.Success) return null;
  127. content = match.Groups[1].Value;
  128. }
  129. string url = ToolParsePlus.GetLink(text);
  130. try
  131. {
  132. switch (rule.url_action)
  133. {
  134. case UrlAction.Redirect:
  135. {
  136. var response = await new WebClientUtility
  137. {
  138. Proxy = ProxyNodesCore.RandomOne(),
  139. UserAgent = ProviderFakeUserAgent.RandomMobile,
  140. AllowAutoRedirect = false
  141. }.RequestAsync(url);
  142. if (response.ResponseMessage != null)
  143. {
  144. content = response.ResponseMessage.Headers.Location.ToString();
  145. content = content.UrlDecode();
  146. }
  147. }
  148. break;
  149. case UrlAction.Body:
  150. {
  151. var response = await new WebClientUtility
  152. {
  153. Proxy = ProxyNodesCore.RandomOne(),
  154. UserAgent = ProviderFakeUserAgent.RandomMobile,
  155. AllowAutoRedirect = true
  156. }.RequestAsync(url);
  157. content = response.Body();
  158. }
  159. break;
  160. case UrlAction.None:
  161. //{
  162. // content = url;
  163. //}
  164. break;
  165. }
  166. }
  167. catch (Exception ex)
  168. {
  169. //Console.WriteLine($"{url}\t{rule.url_action}\t{ex.Message}");
  170. }
  171. if (rule.childs.Count > 0)
  172. {
  173. foreach (var sub_rule in rule.childs)
  174. {
  175. var e = await WorkEachRule(content, config, result, sub_rule);
  176. if (e != null) return e;
  177. }
  178. return null;
  179. }
  180. if (!string.IsNullOrEmpty(rule.body_pattern))
  181. {
  182. var regex = new Regex(rule.body_pattern);
  183. var match = regex.Match(content);
  184. if (!match.Success) return null;
  185. }
  186. var deeplink = rule.deeplink_template;
  187. var prompt_text = rule.prompt_text;
  188. try
  189. {
  190. var regex = new Regex(rule.pattern);
  191. var match = regex.Match(content);
  192. if (match.Success)
  193. {
  194. if (rule.resources.Count > 0)
  195. {
  196. foreach (var resource in rule.resources)
  197. {
  198. Dictionary<string, string> resResult = await ResourceProcessing(match, resource);
  199. foreach (var e in resResult)
  200. {
  201. if (deeplink.Contains(e.Key))
  202. {
  203. deeplink = deeplink.Replace(e.Key, e.Value);
  204. }
  205. }
  206. }
  207. }
  208. if (match.Groups.Count > 1)
  209. {
  210. for (var i = 1; i < match.Groups.Count; i++)
  211. {
  212. string val = match.Groups[i].Value;
  213. switch (rule.plugin)
  214. {
  215. case "redu":
  216. string deeplink_url = await PluginDyParseAsync(text, val, result);
  217. if (!string.IsNullOrEmpty(deeplink_url))
  218. {
  219. result.itemName = prompt_text;
  220. result.deeplink_url = deeplink_url;
  221. result.success = true;
  222. return result;
  223. }
  224. break;
  225. }
  226. ReplaceProcessing(val, i, ref deeplink, ref prompt_text);
  227. }
  228. if (rule.replace != null && rule.replace.Count > 0)
  229. {
  230. foreach (var r in rule.replace)
  231. {
  232. string key = r[0];
  233. if (string.IsNullOrEmpty(key)) continue;
  234. string val = string.Empty;
  235. if (r.Length > 1)
  236. {
  237. val = r[1];
  238. }
  239. deeplink = Regex.Replace(deeplink, key, val);
  240. }
  241. }
  242. deeplink = Regex.Replace(deeplink, @"\{\d+\}", string.Empty);
  243. prompt_text = Regex.Replace(prompt_text, @"\{\d+\}", string.Empty);
  244. if (!string.IsNullOrEmpty(deeplink))
  245. {
  246. result.deeplink_url = deeplink;
  247. result.itemName = prompt_text;
  248. result.success = true;
  249. return result;
  250. }
  251. }
  252. else
  253. {
  254. result.deeplink_url = deeplink;
  255. result.itemName = prompt_text;
  256. result.success = true;
  257. return result;
  258. }
  259. }
  260. }
  261. catch (Exception ex)
  262. {
  263. Console.WriteLine($"Error processing pattern '{rule.pattern}': {ex.Message}");
  264. }
  265. return null;
  266. }
  267. /// <summary>
  268. /// 占位符的处理
  269. /// </summary>
  270. /// <param name="word"></param>
  271. /// <param name="i"></param>
  272. /// <param name="deeplink"></param>
  273. /// <param name="prompt_text"></param>
  274. private static void ReplaceProcessing(string word, int i, ref string deeplink, ref string prompt_text)
  275. {
  276. if (deeplink.Contains("{url:"))
  277. {
  278. string encodedUrl = HttpUtility.UrlEncode(word);
  279. deeplink = deeplink.Replace($"{{url:{i - 1}}}", encodedUrl);
  280. }
  281. if (prompt_text.Contains("{url:"))
  282. {
  283. string encodedUrl = HttpUtility.UrlEncode(word);
  284. prompt_text = prompt_text.Replace($"{{url:{i - 1}}}", encodedUrl);
  285. }
  286. if (deeplink.Contains("{decode:"))
  287. {
  288. string encodedUrl = HttpUtility.UrlDecode(word);
  289. deeplink = deeplink.Replace($"{{decode:{i - 1}}}", encodedUrl);
  290. }
  291. if (prompt_text.Contains("{decode:"))
  292. {
  293. string encodedUrl = HttpUtility.UrlDecode(word);
  294. prompt_text = prompt_text.Replace($"{{decode:{i - 1}}}", encodedUrl);
  295. }
  296. //两次url编码
  297. if (deeplink.Contains("{url2:"))
  298. {
  299. string encodedUrl = HttpUtility.UrlEncode(word);
  300. encodedUrl = HttpUtility.UrlEncode(encodedUrl);
  301. deeplink = deeplink.Replace($"{{url2:{i - 1}}}", encodedUrl);
  302. }
  303. if (prompt_text.Contains("{url2:"))
  304. {
  305. string encodedUrl = HttpUtility.UrlEncode(word);
  306. encodedUrl = HttpUtility.UrlEncode(encodedUrl);
  307. prompt_text = prompt_text.Replace($"{{url2:{i - 1}}}", encodedUrl);
  308. }
  309. //三次url解码
  310. if (deeplink.Contains("{url3:"))
  311. {
  312. string encodedUrl = HttpUtility.UrlEncode(word);
  313. encodedUrl = HttpUtility.UrlEncode(encodedUrl);
  314. deeplink = deeplink.Replace($"{{url3:{i - 1}}}", encodedUrl);
  315. }
  316. if (prompt_text.Contains("{url3:"))
  317. {
  318. string encodedUrl = HttpUtility.UrlEncode(word);
  319. encodedUrl = HttpUtility.UrlEncode(encodedUrl);
  320. prompt_text = prompt_text.Replace($"{{url3:{i - 1}}}", encodedUrl);
  321. }
  322. //atob
  323. if (deeplink.Contains("{atob:"))
  324. {
  325. string encodedUrl = HttpUtility.UrlDecode(word);
  326. deeplink = deeplink.Replace($"{{atob:{i - 1}}}", encodedUrl.FromBase64());
  327. }
  328. if (prompt_text.Contains("{atob:"))
  329. {
  330. string encodedUrl = HttpUtility.UrlDecode(word);
  331. prompt_text = prompt_text.Replace($"{{atob:{i - 1}}}", encodedUrl.FromBase64());
  332. }
  333. //btoa
  334. if (deeplink.Contains("{btoa:"))
  335. {
  336. string encodedUrl = HttpUtility.UrlEncode(word);
  337. deeplink = deeplink.Replace($"{{btoa:{i - 1}}}", word.ToBase64());
  338. }
  339. if (prompt_text.Contains("{btoa:"))
  340. {
  341. string encodedUrl = HttpUtility.UrlEncode(word);
  342. prompt_text = prompt_text.Replace($"{{btoa:{i - 1}}}", word.ToBase64());
  343. }
  344. deeplink = deeplink.Replace($"{{{i - 1}}}", word);
  345. prompt_text = prompt_text.Replace($"{{{i - 1}}}", word);
  346. }
  347. private static async Task<Dictionary<string, string>> ResourceProcessing(Match baseMatch, DeeplinkResources resource)
  348. {
  349. Dictionary<string, string> result = new Dictionary<string, string>();
  350. string url = resource.url_pattern;
  351. string prompt_text = string.Empty;
  352. for (var i = 1; i < baseMatch.Groups.Count; i++)
  353. {
  354. string val = baseMatch.Groups[i].Value;
  355. ReplaceProcessing(val, i, ref url, ref prompt_text);
  356. }
  357. string content = string.Empty;
  358. try
  359. {
  360. switch (resource.url_action)
  361. {
  362. case UrlAction.Redirect:
  363. {
  364. var response = await new WebClientUtility
  365. {
  366. Proxy = ProxyNodesCore.RandomOne(),
  367. UserAgent = ProviderFakeUserAgent.RandomMobile,
  368. AllowAutoRedirect = false
  369. }.RequestAsync(url);
  370. if (response.ResponseMessage != null)
  371. {
  372. content = response.ResponseMessage.Headers.Location.ToString();
  373. content = content.UrlDecode();
  374. }
  375. }
  376. break;
  377. case UrlAction.Body:
  378. {
  379. var response = await new WebClientUtility
  380. {
  381. Proxy = ProxyNodesCore.RandomOne(),
  382. UserAgent = ProviderFakeUserAgent.RandomMobile,
  383. AllowAutoRedirect = true
  384. }.RequestAsync(url);
  385. content = response.Body();
  386. }
  387. break;
  388. case UrlAction.None:
  389. //{
  390. // content = url;
  391. //}
  392. break;
  393. }
  394. }
  395. catch (Exception ex)
  396. {
  397. //Console.WriteLine($"{url}\t{rule.url_action}\t{ex.Message}");
  398. }
  399. if (string.IsNullOrEmpty(content)) { return result; }
  400. for (int i = 0; i < resource.pattern.Count; i++)
  401. {
  402. string val = string.Empty;
  403. var regex = new Regex(resource.pattern[i]);
  404. var match = regex.Match(content);
  405. if (match.Success)
  406. {
  407. val = match.Groups[1].Value;
  408. }
  409. string key = $"{{{i}@{resource.name}}}";
  410. result.Add(key, val);
  411. }
  412. return result;
  413. }
  414. public static async Task<string> PluginDyParseAsync(string content, string matchText, DeeplinkParseDataDTO deeplinkResult, CancellationToken cancellationToken = default)
  415. {
  416. string ip = deeplinkResult.ip;
  417. string oaid = deeplinkResult.oaid;
  418. var result = DyUnionPlus.GetFormattedObject(content, ip, oaid);
  419. result.create_time = deeplinkResult.create_time;
  420. result.rawContent = content;
  421. bool IfExceptional = false;
  422. try
  423. {
  424. if (DyUnionPlus.ShouldIgnoreRequest(ip, oaid, out string reason))
  425. {
  426. result.success = false;
  427. result.message = "放弃转链";
  428. result.reason = reason;
  429. _ = TkLogCore.ParseLogAsync(result);
  430. return null;
  431. }
  432. var account = ReduPoolCore.GetOne();
  433. if (account == null)
  434. {
  435. result.success = false;
  436. result.message = "放弃转链";
  437. result.reason = "没有匹配账号";
  438. _ = TkLogCore.ParseLogAsync(result);
  439. return null;
  440. }
  441. var now = DateTime.Now;
  442. var ts2 = now - result.create_time;
  443. result.elapsedTime2 = (int)ts2.TotalMilliseconds;
  444. result = await DyUnionPlus.DyParseAsync(account, matchText, result, cancellationToken);
  445. switch (oaid)
  446. {
  447. case "3B191CFA4C6B48F9BA459E915B57743BEC7D424979CC9C51BCAD0C245B1C7BA2":
  448. case "6D5D56F3073844F2912B4552CEDF9E3BEC7D424979CC9C51C5461106F1915660":
  449. case "4CA55979100C4B5BAAA2AF4ABE15C360602e915cedae5786405419ccc9f4e3f8":
  450. break;
  451. default:
  452. result.deeplink_url = result.deeplink_url.Replace("snssdk1128://", "snssdk561124://");
  453. break;
  454. }
  455. var ts3 = DateTime.Now - now;
  456. result.elapsedTime3 = (int)ts3.TotalMilliseconds;
  457. }
  458. catch (Exception ex)
  459. {
  460. if (ex.Message.Contains("was canceled"))
  461. {
  462. result.success = false;
  463. result.message = "放弃转链";
  464. result.reason = "请求超时";
  465. }
  466. else
  467. {
  468. IfExceptional = true;
  469. _ = new LoggerLibrary("unionParse", "dy_error")
  470. .Info(ip, oaid)
  471. .Info(content)
  472. .Info(ex.Message, ex.StackTrace)
  473. .SaveAsync();
  474. NotifyCore.Notify(new NifyMessage
  475. {
  476. message = $"【转链异常DY】\n{content}\n\n{ex.Message}\n{ex.StackTrace}",
  477. priority = NifyMessagePriority.high,
  478. tags = ["red_circle"]
  479. });
  480. result.success = false;
  481. result.message = "内部错误";
  482. result.reason = "转链接口异常";
  483. }
  484. }
  485. _ = TkLogCore.ParseLogAsync(result);
  486. return result.deeplink_url;
  487. }
  488. }
  489. }