public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "train/{profileName}/{typeName?}")] HttpRequest req, string profileName, string typeName, ILogger logger) { var profile = ProfileFactory.Get(profileName); if (profile == null) { return(new NotFoundResult()); } var profileParser = ParserFactory.GetParser(profile.Type, logger); if (profileParser == null) { logger.LogWarning($"Profile parser for {profile.Type.ToString()} is not implemented!"); return(new NotFoundResult()); } IProfileParser targetProfileParser = ParserFactory.GetParser(typeName ?? "", logger); if (targetProfileParser == null) { var userAgent = req.Headers["user-agent"]; var probableType = GuessTypeFromUserAgent(userAgent); targetProfileParser = ParserFactory.GetParser(probableType, logger); logger.LogInformation("Attempt to guess target type from user agent, UserAgent={userAgent}, Result={targetType}", userAgent, targetProfileParser.GetType()); } string newProfile; var profileContent = await profile.Download(); var fileName = $"{profileName}{targetProfileParser.ExtName()}"; if (profile.AllowDirectAccess && profileParser.GetType() == targetProfileParser.GetType()) { newProfile = profileContent; } else { var servers = profileParser.Parse(profileContent); logger.LogInformation($"Download profile `{profile.Name}` and get {servers.Length} servers"); foreach (var filter in profile.Filters) { var previousCount = servers.Length; servers = filter.Do(servers); logger.LogInformation($"Performed filter `{filter.GetType()}`, result: {servers.Length} servers"); if (servers.Length == 0) { break; } } if (targetProfileParser is SurgeParser) { newProfile = targetProfileParser.Encode(servers, new SurgeEncodeOptions() { ProfileURL = GetCurrentURL(req), }); } else { newProfile = targetProfileParser.Encode(servers); } } var result = new FileContentResult(Encoding.UTF8.GetBytes(newProfile), $"{MimeTypes.GetMimeType(fileName)}; charset=UTF-8"); result.FileDownloadName = fileName; return(result); }
public static async Task <IActionResult> GetTrain( [HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "train/{profileName}/{typeName?}")] HttpRequest req, string profileName, string typeName, ILogger logger) { var templateUrlOrName = req.Query.ContainsKey("template") ? req.Query["template"].ToString() : String.Empty; var requestUrl = Functions.GetCurrentURL(req); ProfileFactory.SetEnvironmentManager(Functions.EnvironmentManager); TemplateFactory.SetEnvironmentManager(Functions.EnvironmentManager); // Parse target parser type IProfileParser targetProfileParser = ParserFactory.GetParser(typeName ?? "", logger, Functions.Downloader); if (targetProfileParser == null) { var userAgent = req.Headers["user-agent"]; var probableType = Functions.GuessTypeFromUserAgent(userAgent); targetProfileParser = ParserFactory.GetParser(probableType, logger, Functions.Downloader); logger.LogInformation("Attempt to guess target type from user agent, UserAgent={userAgent}, Result={targetType}", userAgent, targetProfileParser.GetType()); } // Get profile chain var profileChain = new List <Profile>(); var nextProfileName = Misc.KebabCase2PascalCase(profileName); while (true) { var profile = ProfileFactory.Get(nextProfileName, logger); if (profile == null) { var chainString = Functions.ProfileChainToString(profileChain); if (!string.IsNullOrEmpty(chainString)) { chainString += "->"; } chainString += nextProfileName; logger.LogError($"Profile `{chainString}` is not found."); return(new NotFoundResult()); } if (profileChain.Contains(profile)) { return(new ForbidResult()); } profileChain.Add(profile); if (profile.Type != ProfileType.Alias) { break; } nextProfileName = profile.Source; } var sourceProfile = profileChain.Last(); var profileParser = ParserFactory.GetParser(sourceProfile.Type, logger, Functions.Downloader); if (profileParser == null) { logger.LogError($"Profile parser for {sourceProfile.Type} is not implemented! Complete profile alias chain is `{Functions.ProfileChainToString(profileChain)}`"); return(new ForbidResult()); } // Download content and determine if original profile should be returned var profileContent = await sourceProfile.Download(logger, Functions.Downloader); if (targetProfileParser is NullParser) { if (!sourceProfile.AllowDirectAccess) { logger.LogError($"Original profile access is denied for profile `{Functions.ProfileChainToString(profileChain)}`."); return(new ForbidResult()); } logger.LogInformation("Return original profile"); return(new FileContentResult(Encoding.UTF8.GetBytes(profileContent), "text/plain; charset=UTF-8") { FileDownloadName = profileChain.First().Name + profileParser.ExtName(), }); } // Download template, parse profile and apply filters var template = await Functions.GetTemplate(logger, templateUrlOrName); var servers = profileParser.Parse(profileContent); logger.LogInformation($"Download profile `{Functions.ProfileChainToString(profileChain)}` and get {servers.Length} servers"); foreach (var profile in profileChain.AsEnumerable().Reverse()) { foreach (var filter in profile.Filters) { servers = filter.Do(servers, logger); logger.LogInformation($"Apply filter `{filter.GetType()} from profile `{profile.Name}` and get {servers.Length} servers"); if (servers.Length == 0) { break; } } if (servers.Length == 0) { break; } } if (servers.Length == 0) { logger.LogError($"There are no available servers left. Complete profile alias chain is `{Functions.ProfileChainToString(profileChain)}`"); return(new NoContentResult()); } // Encode profile logger.LogInformation($"{servers.Length} will be encoded"); var options = targetProfileParser switch { SurgeParser _ => new SurgeEncodeOptions() { ProfileURL = requestUrl + (string.IsNullOrEmpty(template) ? "" : $"?template={HttpUtility.UrlEncode(templateUrlOrName)}") }, QuantumultXParser _ => new QuantumultXEncodeOptions() { QuantumultXListUrl = Functions.GetCurrentURL(req) + "-list", }, ClashParser _ => new ClashEncodeOptions() { ClashProxyProviderUrl = Functions.GetCurrentURL(req) + "-proxy-provider", }, _ => new EncodeOptions(), }; options.Template = template; options.ProfileName = profileChain.First().Name; try { var newProfile = targetProfileParser.Encode(options, servers, out Server[] encodedServer); if (encodedServer.Length == 0) { return(new NoContentResult()); } return(new FileContentResult(Encoding.UTF8.GetBytes(newProfile), "text/plain; charset=UTF-8") { FileDownloadName = profileChain.First().Name + targetProfileParser.ExtName(), }); } catch (InvalidTemplateException) { return(new BadRequestResult()); } }