mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Fixed some crashes in response strings source generator, reorganized more submodules into their folders
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
public class PicartoProvider : Provider
|
||||
{
|
||||
private static Regex Regex { get; } = new(@"picarto.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override FollowedStream.FType Platform
|
||||
=> FollowedStream.FType.Picarto;
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public PicartoProvider(IHttpClientFactory httpClientFactory)
|
||||
=> _httpClientFactory = httpClientFactory;
|
||||
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> { id });
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new();
|
||||
|
||||
using var http = _httpClientFactory.CreateClient();
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
try
|
||||
{
|
||||
http.DefaultRequestHeaders.Accept.Add(new("application/json"));
|
||||
// get id based on the username
|
||||
var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
|
||||
|
||||
if (!res.IsSuccessStatusCode)
|
||||
continue;
|
||||
|
||||
var userData =
|
||||
JsonConvert.DeserializeObject<PicartoChannelResponse>(await res.Content.ReadAsStringAsync())!;
|
||||
|
||||
toReturn.Add(ToStreamData(userData));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Something went wrong retreiving {StreamPlatform} stream data for {Login}: {ErrorMessage}",
|
||||
Platform,
|
||||
login,
|
||||
ex.Message);
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(PicartoChannelResponse stream)
|
||||
=> new()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Picarto,
|
||||
Name = stream.Name,
|
||||
UniqueName = stream.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Title,
|
||||
IsLive = stream.Online,
|
||||
Preview = stream.Thumbnails.Web,
|
||||
Game = stream.Category,
|
||||
StreamUrl = $"https://picarto.tv/{stream.Name}",
|
||||
AvatarUrl = stream.Avatar
|
||||
};
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class implemented by providers of all supported platforms
|
||||
/// </summary>
|
||||
public abstract class Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the platform.
|
||||
/// </summary>
|
||||
public abstract FollowedStream.FType Platform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream usernames which fail to execute due to an error, and when they started throwing errors.
|
||||
/// This can happen if stream name is invalid, or if the stream doesn't exist anymore.
|
||||
/// </summary>
|
||||
public IEnumerable<(string Login, DateTime ErroringSince)> FailingStreams
|
||||
=> _failingStreams.Select(entry => (entry.Key, entry.Value)).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// When was the first time the stream continually had errors while being retrieved
|
||||
/// </summary>
|
||||
protected readonly ConcurrentDictionary<string, DateTime> _failingStreams = new();
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the specified url is a valid stream url for this platform.
|
||||
/// </summary>
|
||||
/// <param name="url">Url to check</param>
|
||||
/// <returns>True if valid, otherwise false</returns>
|
||||
public abstract Task<bool> IsValidUrl(string url);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of the stream on the specified url on this <see cref="Platform" />
|
||||
/// </summary>
|
||||
/// <param name="url">Url of the stream</param>
|
||||
/// <returns><see cref="StreamData" /> of the specified stream. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataByUrlAsync(string url);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of the specified id/username on this <see cref="Platform" />
|
||||
/// </summary>
|
||||
/// <param name="id">Name (or id where applicable) of the user on the platform</param>
|
||||
/// <returns><see cref="StreamData" /> of the user. Null if none found</returns>
|
||||
public abstract Task<StreamData?> GetStreamDataAsync(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream data of all specified ids/usernames on this <see cref="Platform" />
|
||||
/// </summary>
|
||||
/// <param name="usernames">List of ids/usernames</param>
|
||||
/// <returns><see cref="StreamData" /> of all users, in the same order. Null for every id/user not found.</returns>
|
||||
public abstract Task<List<StreamData>> GetStreamDataAsync(List<string> usernames);
|
||||
|
||||
public void ClearErrorsFor(string login)
|
||||
=> _failingStreams.TryRemove(login, out _);
|
||||
}
|
@@ -0,0 +1,181 @@
|
||||
#nullable disable
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Net.Http;
|
||||
// using System.Text.Json;
|
||||
// using System.Text.Json.Serialization;
|
||||
// using System.Text.RegularExpressions;
|
||||
// using System.Threading.Tasks;
|
||||
// using NadekoBot.Services.Database.Models;
|
||||
// using NadekoBot.Extensions;
|
||||
// using Serilog;
|
||||
// using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
//
|
||||
// namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers
|
||||
// {
|
||||
// public sealed class TwitchHelixProvider : Provider
|
||||
// {
|
||||
// private readonly IHttpClientFactory _httpClientFactory;
|
||||
//
|
||||
// //
|
||||
// private static Regex Regex { get; } = new Regex(@"twitch.tv/(?<name>.+[^/])/?",
|
||||
// RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
//
|
||||
// public override FollowedStream.FType Platform => FollowedStream.FType.Twitch;
|
||||
//
|
||||
// private (string Token, DateTime Expiry) _token = default;
|
||||
//
|
||||
// public TwitchHelixProvider(IHttpClientFactory httpClientFactory)
|
||||
// {
|
||||
// _httpClientFactory = httpClientFactory;
|
||||
// }
|
||||
//
|
||||
// private async Task EnsureTokenValidAsync()
|
||||
// {
|
||||
// if (_token != default && (DateTime.UtcNow - _token.Expiry) > TimeSpan.FromHours(1))
|
||||
// return;
|
||||
//
|
||||
// const string clientId = string.Empty;
|
||||
// const string clientSecret = string.Empty;
|
||||
//
|
||||
// var client = _httpClientFactory.CreateClient();
|
||||
// var res = await client.PostAsync("https://id.twitch.tv/oauth2/token" +
|
||||
// $"?client_id={clientId}" +
|
||||
// $"&client_secret={clientSecret}" +
|
||||
// "&grant_type=client_credentials", new StringContent(""));
|
||||
//
|
||||
// var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
|
||||
//
|
||||
// _token = (data.GetProperty("access_token").GetString(),
|
||||
// DateTime.UtcNow + TimeSpan.FromSeconds(data.GetProperty("expires_in").GetInt32()));
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public override Task<bool> IsValidUrl(string url)
|
||||
// {
|
||||
// var match = Regex.Match(url);
|
||||
// if (!match.Success)
|
||||
// return Task.FromResult(false);
|
||||
//
|
||||
// var username = match.Groups["name"].Value;
|
||||
// return Task.FromResult(true);
|
||||
// }
|
||||
//
|
||||
// public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
// {
|
||||
// var match = Regex.Match(url);
|
||||
// if (match.Success)
|
||||
// {
|
||||
// var name = match.Groups["name"].Value;
|
||||
// return GetStreamDataAsync(name);
|
||||
// }
|
||||
//
|
||||
// return Task.FromResult<StreamData?>(null);
|
||||
// }
|
||||
//
|
||||
// public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
// {
|
||||
// var data = await GetStreamDataAsync(new List<string> {id});
|
||||
//
|
||||
// return data.FirstOrDefault();
|
||||
// }
|
||||
//
|
||||
// public override async Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
// {
|
||||
// if (logins.Count == 0)
|
||||
// return new List<StreamData>();
|
||||
//
|
||||
// await EnsureTokenValidAsync();
|
||||
//
|
||||
// using var http = _httpClientFactory.CreateClient();
|
||||
// http.DefaultRequestHeaders.Clear();
|
||||
// http.DefaultRequestHeaders.Add("client-id","67w6z9i09xv2uoojdm9l0wsyph4hxo6");
|
||||
// http.DefaultRequestHeaders.Add("Authorization",$"Bearer {_token.Token}");
|
||||
//
|
||||
// var res = new TwitchResponse()
|
||||
// {
|
||||
// Data = new List<TwitchResponse.StreamApiData>()
|
||||
// };
|
||||
// foreach (var chunk in logins.Chunk(500))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var str = await http.GetStringAsync($"https://api.twitch.tv/helix/streams" +
|
||||
// $"?user_login={chunk.JoinWith(',')}" +
|
||||
// $"&first=100");
|
||||
//
|
||||
// res = JsonSerializer.Deserialize<TwitchResponse>(str);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Log.Warning(ex, "Something went wrong retreiving {StreamPlatform} streams", Platform);
|
||||
// return new List<StreamData>();
|
||||
// }
|
||||
//
|
||||
// if (res.Data.Count == 0)
|
||||
// {
|
||||
// return new List<StreamData>();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return res.Data.Select(ToStreamData).ToList();
|
||||
// }
|
||||
//
|
||||
// private StreamData ToStreamData(TwitchResponse.StreamApiData apiData)
|
||||
// {
|
||||
// return new StreamData()
|
||||
// {
|
||||
// StreamType = FollowedStream.FType.Twitch,
|
||||
// Name = apiData.UserName,
|
||||
// UniqueName = apiData.UserId,
|
||||
// Viewers = apiData.ViewerCount,
|
||||
// Title = apiData.Title,
|
||||
// IsLive = apiData.Type == "live",
|
||||
// Preview = apiData.ThumbnailUrl
|
||||
// ?.Replace("{width}", "640")
|
||||
// ?.Replace("{height}", "480"),
|
||||
// Game = apiData.GameId,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class TwitchResponse
|
||||
// {
|
||||
// [JsonPropertyName("data")]
|
||||
// public List<StreamApiData> Data { get; set; }
|
||||
//
|
||||
// public class StreamApiData
|
||||
// {
|
||||
// [JsonPropertyName("id")]
|
||||
// public string Id { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("user_id")]
|
||||
// public string UserId { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("user_name")]
|
||||
// public string UserName { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("game_id")]
|
||||
// public string GameId { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("type")]
|
||||
// public string Type { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("title")]
|
||||
// public string Title { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("viewer_count")]
|
||||
// public int ViewerCount { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("language")]
|
||||
// public string Language { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("thumbnail_url")]
|
||||
// public string ThumbnailUrl { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("started_at")]
|
||||
// public DateTime StartedAt { get; set; }
|
||||
// }
|
||||
// }
|
||||
// }
|
@@ -0,0 +1,125 @@
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
|
||||
|
||||
public class TwitchProvider : Provider
|
||||
{
|
||||
private static Regex Regex { get; } = new(@"twitch.tv/(?<name>.+[^/])/?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public override FollowedStream.FType Platform
|
||||
=> FollowedStream.FType.Twitch;
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public TwitchProvider(IHttpClientFactory httpClientFactory)
|
||||
=> _httpClientFactory = httpClientFactory;
|
||||
|
||||
public override Task<bool> IsValidUrl(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (!match.Success)
|
||||
return Task.FromResult(false);
|
||||
|
||||
// var username = match.Groups["name"].Value;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
|
||||
{
|
||||
var match = Regex.Match(url);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups["name"].Value;
|
||||
return GetStreamDataAsync(name);
|
||||
}
|
||||
|
||||
return Task.FromResult<StreamData?>(null);
|
||||
}
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string id)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string> { id });
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new();
|
||||
|
||||
using var http = _httpClientFactory.CreateClient();
|
||||
http.DefaultRequestHeaders.Add("Client-Id", "67w6z9i09xv2uoojdm9l0wsyph4hxo6");
|
||||
http.DefaultRequestHeaders.Add("Accept", "application/vnd.twitchtv.v5+json");
|
||||
|
||||
var toReturn = new List<StreamData>();
|
||||
foreach (var login in logins)
|
||||
try
|
||||
{
|
||||
// get id based on the username
|
||||
var idsStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/users?login={login}");
|
||||
var userData = JsonConvert.DeserializeObject<TwitchUsersResponseV5>(idsStr);
|
||||
var user = userData?.Users.FirstOrDefault();
|
||||
|
||||
// if user can't be found, skip, it means there is no such user
|
||||
if (user is null)
|
||||
continue;
|
||||
|
||||
// get stream data
|
||||
var str = await http.GetStringAsync($"https://api.twitch.tv/kraken/streams/{user.Id}");
|
||||
var resObj = JsonConvert.DeserializeAnonymousType(str, new { Stream = new TwitchResponseV5.Stream() });
|
||||
|
||||
// if stream is null, user is not streaming
|
||||
if (resObj?.Stream is null)
|
||||
{
|
||||
// if user is not streaming, get his offline banner
|
||||
var chStr = await http.GetStringAsync($"https://api.twitch.tv/kraken/channels/{user.Id}");
|
||||
var ch = JsonConvert.DeserializeObject<TwitchResponseV5.Channel>(chStr)!;
|
||||
|
||||
toReturn.Add(new()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = ch.DisplayName,
|
||||
UniqueName = ch.Name,
|
||||
Title = ch.Status,
|
||||
IsLive = false,
|
||||
AvatarUrl = ch.Logo,
|
||||
StreamUrl = $"https://twitch.tv/{ch.Name}",
|
||||
Preview = ch.VideoBanner // set video banner as the preview,
|
||||
});
|
||||
continue; // move on
|
||||
}
|
||||
|
||||
toReturn.Add(ToStreamData(resObj.Stream));
|
||||
_failingStreams.TryRemove(login, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Something went wrong retreiving {StreamPlatform} stream data for {Login}: {ErrorMessage}",
|
||||
Platform,
|
||||
login,
|
||||
ex.Message);
|
||||
_failingStreams.TryAdd(login, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private StreamData ToStreamData(TwitchResponseV5.Stream stream)
|
||||
=> new()
|
||||
{
|
||||
StreamType = FollowedStream.FType.Twitch,
|
||||
Name = stream.Channel.DisplayName,
|
||||
UniqueName = stream.Channel.Name,
|
||||
Viewers = stream.Viewers,
|
||||
Title = stream.Channel.Status,
|
||||
IsLive = true,
|
||||
Preview = stream.Preview.Large,
|
||||
Game = stream.Channel.Game,
|
||||
StreamUrl = $"https://twitch.tv/{stream.Channel.Name}",
|
||||
AvatarUrl = stream.Channel.Logo
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user