using NadekoBot.Db.Models; using System.Net.Http.Json; using System.Text.RegularExpressions; namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers; public class TrovoProvider : Provider { private readonly IHttpClientFactory _httpClientFactory; public override FollowedStream.FType Platform => FollowedStream.FType.Trovo; private readonly Regex _urlRegex = new(@"trovo.live\/(?[\w\d\-_]+)/?", RegexOptions.Compiled); private readonly IBotCredsProvider _creds; public TrovoProvider(IHttpClientFactory httpClientFactory, IBotCredsProvider creds) { (_httpClientFactory, _creds) = (httpClientFactory, creds); if (string.IsNullOrWhiteSpace(creds.GetCreds().TrovoClientId)) { Log.Warning(@"Trovo streams are using a default clientId. If you are experiencing ratelimits, you should create your own application at: https://developer.trovo.live/"); } } public override Task IsValidUrl(string url) => Task.FromResult(_urlRegex.IsMatch(url)); public override Task GetStreamDataByUrlAsync(string url) { var match = _urlRegex.Match(url); if (match.Length == 0) return Task.FromResult(default(StreamData?)); return GetStreamDataAsync(match.Groups["channel"].Value); } public override async Task GetStreamDataAsync(string login) { using var http = _httpClientFactory.CreateClient(); var trovoClientId = _creds.GetCreds().TrovoClientId; if (string.IsNullOrWhiteSpace(trovoClientId)) { trovoClientId = "8b3cc4719b7051803099661a3265e50b"; } http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Add("Accept", "application/json"); http.DefaultRequestHeaders.Add("Client-ID", trovoClientId); // trovo ratelimit is very generous (1200 per minute) // so there is no need for ratelimit checks atm try { using var res = await http.PostAsJsonAsync( $"https://open-api.trovo.live/openplatform/channels/id", new TrovoRequestData() { Username = login }); res.EnsureSuccessStatusCode(); var data = await res.Content.ReadFromJsonAsync(); if (data is null) { Log.Warning("An empty response received while retrieving stream data for trovo.live/{TrovoId}", login); _failingStreams.TryAdd(login, DateTime.UtcNow); return null; } _failingStreams.TryRemove(data.Username, out _); return new() { IsLive = data.IsLive, Game = data.CategoryName, Name = data.Username, Title = data.LiveTitle, Viewers = data.CurrentViewers, AvatarUrl = data.ProfilePic, StreamType = Platform, StreamUrl = data.ChannelUrl, UniqueName = data.Username, Preview = data.Thumbnail }; } catch (Exception ex) { Log.Warning(ex, "Error retrieving stream data for trovo.live/{TrovoId}", login); _failingStreams.TryAdd(login, DateTime.UtcNow); return null; } } public override async Task> GetStreamDataAsync(List usernames) { var trovoClientId = _creds.GetCreds().TrovoClientId; if (string.IsNullOrWhiteSpace(trovoClientId)) { Log.Warning("Trovo streams will be ignored until TrovoClientId is added to creds.yml"); return Array.Empty(); } var results = new List(usernames.Count); foreach (var chunk in usernames.Chunk(10) .Select(x => x.Select(GetStreamDataAsync))) { var chunkResults = await Task.WhenAll(chunk); results.AddRange(chunkResults.Where(x => x is not null)!); await Task.Delay(1000); } return results; } }