2017-05-27 08:19:27 +00:00
|
|
|
|
using Discord;
|
|
|
|
|
using Discord.WebSocket;
|
|
|
|
|
using NadekoBot.Extensions;
|
2017-06-22 21:59:54 +00:00
|
|
|
|
using NadekoBot.Services;
|
2017-05-27 08:19:27 +00:00
|
|
|
|
using NadekoBot.Services.Database.Models;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net.Http;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2017-07-17 19:42:36 +00:00
|
|
|
|
using NadekoBot.Modules.Searches.Common;
|
|
|
|
|
using NadekoBot.Modules.Searches.Common.Exceptions;
|
|
|
|
|
using NadekoBot.Services.Impl;
|
2017-05-27 08:19:27 +00:00
|
|
|
|
|
2017-07-17 19:42:36 +00:00
|
|
|
|
namespace NadekoBot.Modules.Searches.Services
|
2017-05-27 08:19:27 +00:00
|
|
|
|
{
|
2017-07-15 03:04:16 +00:00
|
|
|
|
public class StreamNotificationService : INService
|
2017-05-27 08:19:27 +00:00
|
|
|
|
{
|
|
|
|
|
private readonly Timer _streamCheckTimer;
|
|
|
|
|
private bool firstStreamNotifPass { get; set; } = true;
|
|
|
|
|
private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
|
|
|
|
|
|
2017-05-29 04:13:22 +00:00
|
|
|
|
private readonly DbService _db;
|
2017-06-19 13:42:10 +00:00
|
|
|
|
private readonly DiscordSocketClient _client;
|
2017-05-27 08:19:27 +00:00
|
|
|
|
private readonly NadekoStrings _strings;
|
|
|
|
|
|
2017-06-19 13:42:10 +00:00
|
|
|
|
public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings)
|
2017-05-27 08:19:27 +00:00
|
|
|
|
{
|
|
|
|
|
_db = db;
|
|
|
|
|
_client = client;
|
|
|
|
|
_strings = strings;
|
|
|
|
|
_streamCheckTimer = new Timer(async (state) =>
|
|
|
|
|
{
|
|
|
|
|
var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
|
|
|
|
|
_cachedStatuses.Clear();
|
|
|
|
|
IEnumerable<FollowedStream> streams;
|
|
|
|
|
using (var uow = _db.UnitOfWork)
|
|
|
|
|
{
|
2017-06-20 02:23:11 +00:00
|
|
|
|
streams = uow.GuildConfigs.GetAllFollowedStreams(client.Guilds.Select(x => (long)x.Id).ToList());
|
2017-05-27 08:19:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(streams.Select(async fs =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
|
|
|
|
|
if (firstStreamNotifPass)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StreamStatus oldStatus;
|
|
|
|
|
if (oldCachedStatuses.TryGetValue(newStatus.ApiLink, out oldStatus) &&
|
|
|
|
|
oldStatus.IsLive != newStatus.IsLive)
|
|
|
|
|
{
|
|
|
|
|
var server = _client.GetGuild(fs.GuildId);
|
|
|
|
|
var channel = server?.GetTextChannel(fs.ChannelId);
|
|
|
|
|
if (channel == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await channel.EmbedAsync(GetEmbed(fs, newStatus, channel.Guild.Id)).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
firstStreamNotifPass = false;
|
2017-06-22 21:59:54 +00:00
|
|
|
|
}, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
|
2017-05-27 08:19:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
|
|
|
|
|
{
|
|
|
|
|
string response;
|
|
|
|
|
StreamStatus result;
|
|
|
|
|
switch (stream.Type)
|
|
|
|
|
{
|
2017-08-06 15:34:52 +00:00
|
|
|
|
case FollowedStream.FollowedStreamType.Smashcast:
|
2017-05-27 08:19:27 +00:00
|
|
|
|
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
|
|
|
|
|
if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
|
|
|
|
|
return result;
|
|
|
|
|
using (var http = new HttpClient())
|
|
|
|
|
{
|
|
|
|
|
response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
var hbData = JsonConvert.DeserializeObject<HitboxResponse>(response);
|
|
|
|
|
if (!hbData.Success)
|
|
|
|
|
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
|
|
|
|
|
result = new StreamStatus()
|
|
|
|
|
{
|
|
|
|
|
IsLive = hbData.IsLive,
|
|
|
|
|
ApiLink = hitboxUrl,
|
|
|
|
|
Views = hbData.Views
|
|
|
|
|
};
|
|
|
|
|
_cachedStatuses.AddOrUpdate(hitboxUrl, result, (key, old) => result);
|
|
|
|
|
return result;
|
|
|
|
|
case FollowedStream.FollowedStreamType.Twitch:
|
|
|
|
|
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username.ToLowerInvariant())}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
|
|
|
|
|
if (checkCache && _cachedStatuses.TryGetValue(twitchUrl, out result))
|
|
|
|
|
return result;
|
|
|
|
|
using (var http = new HttpClient())
|
|
|
|
|
{
|
|
|
|
|
response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
|
|
|
|
|
if (twData.Error != null)
|
|
|
|
|
{
|
|
|
|
|
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
|
|
|
|
|
}
|
|
|
|
|
result = new StreamStatus()
|
|
|
|
|
{
|
|
|
|
|
IsLive = twData.IsLive,
|
|
|
|
|
ApiLink = twitchUrl,
|
|
|
|
|
Views = twData.Stream?.Viewers.ToString() ?? "0"
|
|
|
|
|
};
|
|
|
|
|
_cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result);
|
|
|
|
|
return result;
|
2017-08-06 14:07:48 +00:00
|
|
|
|
case FollowedStream.FollowedStreamType.Mixer:
|
|
|
|
|
var beamUrl = $"https://mixer.com/api/v1/channels/{stream.Username.ToLowerInvariant()}";
|
2017-05-27 08:19:27 +00:00
|
|
|
|
if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result))
|
|
|
|
|
return result;
|
|
|
|
|
using (var http = new HttpClient())
|
|
|
|
|
{
|
|
|
|
|
response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bmData = JsonConvert.DeserializeObject<BeamResponse>(response);
|
|
|
|
|
if (bmData.Error != null)
|
|
|
|
|
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
|
|
|
|
|
result = new StreamStatus()
|
|
|
|
|
{
|
|
|
|
|
IsLive = bmData.IsLive,
|
|
|
|
|
ApiLink = beamUrl,
|
|
|
|
|
Views = bmData.ViewersCurrent.ToString()
|
|
|
|
|
};
|
|
|
|
|
_cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
|
|
|
|
|
return result;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public EmbedBuilder GetEmbed(FollowedStream fs, StreamStatus status, ulong guildId)
|
|
|
|
|
{
|
|
|
|
|
var embed = new EmbedBuilder().WithTitle(fs.Username)
|
|
|
|
|
.WithUrl(GetLink(fs))
|
|
|
|
|
.AddField(efb => efb.WithName(GetText(fs, "status"))
|
|
|
|
|
.WithValue(status.IsLive ? "Online" : "Offline")
|
|
|
|
|
.WithIsInline(true))
|
|
|
|
|
.AddField(efb => efb.WithName(GetText(fs, "viewers"))
|
|
|
|
|
.WithValue(status.IsLive ? status.Views : "-")
|
|
|
|
|
.WithIsInline(true))
|
|
|
|
|
.AddField(efb => efb.WithName(GetText(fs, "platform"))
|
|
|
|
|
.WithValue(fs.Type.ToString())
|
|
|
|
|
.WithIsInline(true))
|
|
|
|
|
.WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
|
|
|
|
|
|
|
|
|
|
return embed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetText(FollowedStream fs, string key, params object[] replacements) =>
|
|
|
|
|
_strings.GetText(key,
|
|
|
|
|
fs.GuildId,
|
|
|
|
|
"Searches".ToLowerInvariant(),
|
|
|
|
|
replacements);
|
|
|
|
|
|
|
|
|
|
public string GetLink(FollowedStream fs)
|
|
|
|
|
{
|
2017-08-06 15:34:52 +00:00
|
|
|
|
if (fs.Type == FollowedStream.FollowedStreamType.Smashcast)
|
2017-08-06 14:07:48 +00:00
|
|
|
|
return $"https://www.hitbox.tv/{fs.Username}/";
|
2017-05-27 08:19:27 +00:00
|
|
|
|
if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
|
2017-08-06 14:07:48 +00:00
|
|
|
|
return $"https://www.twitch.tv/{fs.Username}/";
|
|
|
|
|
if (fs.Type == FollowedStream.FollowedStreamType.Mixer)
|
|
|
|
|
return $"https://www.mixer.com/{fs.Username}/";
|
2017-05-27 08:19:27 +00:00
|
|
|
|
return "??";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|