Huge amount of work. Migrations, bugfixes, fixes to stream notifications, etc

This commit is contained in:
Kwoth
2016-10-11 05:27:36 +02:00
parent 633a6fb5ba
commit eecaf9cd15
20 changed files with 551 additions and 2464 deletions

View File

@@ -13,6 +13,8 @@ using Newtonsoft.Json;
using NLog;
using NadekoBot.Modules.Administration.Commands.Migration;
using System.Collections.Concurrent;
using NadekoBot.Extensions;
using NadekoBot.Services.Database;
namespace NadekoBot.Modules.Administration
{
@@ -62,10 +64,158 @@ namespace NadekoBot.Modules.Administration
private async Task Migrate0_9To1_0()
{
Config0_9 oldData;
using (var uow = DbHandler.UnitOfWork())
{
var botConfig = uow.BotConfig.GetOrCreate();
MigrateConfig0_9(botConfig);
MigratePermissions0_9(uow);
MigrateServerSpecificConfigs0_9(uow);
//NOW save it
botConfig.MigrationVersion = 1;
await uow.CompleteAsync().ConfigureAwait(false);
}
}
private void MigrateServerSpecificConfigs0_9(IUnitOfWork uow)
{
const string specificConfigsPath = "data/ServerSpecificConfigs.json";
var configs = new ConcurrentDictionary<ulong, ServerSpecificConfig>();
try
{
oldData = JsonConvert.DeserializeObject<Config0_9>(File.ReadAllText("./data/config.json"));
configs = JsonConvert
.DeserializeObject<ConcurrentDictionary<ulong, ServerSpecificConfig>>(
File.ReadAllText(specificConfigsPath), new JsonSerializerSettings()
{
Error = (s, e) =>
{
if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels")
{
e.ErrorContext.Handled = true;
}
}
});
}
catch (Exception ex)
{
_log.Warn(ex, "ServerSpecificConfig deserialization failed");
return;
}
foreach (var config in configs)
{
var guildId = config.Key;
var data = config.Value;
var guildConfig = uow.GuildConfigs.For(guildId);
guildConfig.AutoAssignRoleId = data.AutoAssignedRole;
guildConfig.DeleteMessageOnCommand = data.AutoDeleteMessagesOnCommand;
guildConfig.DefaultMusicVolume = data.DefaultMusicVolume;
guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles;
guildConfig.GenerateCurrencyChannelIds = new HashSet<GCChannelId>(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key }));
uow.SelfAssignedRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildId, RoleId = r }).ToArray());
var logSetting = guildConfig.LogSetting;
guildConfig.LogSetting.IgnoredChannels = new HashSet<IgnoredLogChannel>(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id }));
guildConfig.FollowedStreams = new HashSet<FollowedStream>(data.ObservingStreams.Select(x =>
{
FollowedStream.FollowedStreamType type = FollowedStream.FollowedStreamType.Twitch;
switch (x.Type)
{
case StreamNotificationConfig0_9.StreamType.Twitch:
type = FollowedStream.FollowedStreamType.Twitch;
break;
case StreamNotificationConfig0_9.StreamType.Beam:
type = FollowedStream.FollowedStreamType.Beam;
break;
case StreamNotificationConfig0_9.StreamType.Hitbox:
type = FollowedStream.FollowedStreamType.Hitbox;
break;
default:
break;
}
return new FollowedStream()
{
ChannelId = x.ChannelId,
GuildId = guildId,
Username = x.Username.ToLowerInvariant(),
Type = type
};
}));
guildConfig.VoicePlusTextEnabled = data.VoicePlusTextEnabled;
}
try { File.Move("data/ServerSpecificConfigs.json", "data/DELETE_ME_ServerSpecificCOnfigs.json"); } catch { }
}
private void MigratePermissions0_9(IUnitOfWork uow)
{
var PermissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>();
Directory.CreateDirectory("data/permissions");
foreach (var file in Directory.EnumerateFiles("data/permissions/"))
{
try
{
var strippedFileName = Path.GetFileNameWithoutExtension(file);
if (string.IsNullOrWhiteSpace(strippedFileName)) continue;
var id = ulong.Parse(strippedFileName);
var data = JsonConvert.DeserializeObject<ServerPermissions0_9>(File.ReadAllText(file));
PermissionsDict.TryAdd(id, data);
}
catch { }
}
foreach (var perms in PermissionsDict)
{
var guildId = perms.Key;
var data = perms.Value;
_log.Info("Migrating data from permissions folder for {0}", guildId);
var gconfig = uow.GuildConfigs.For(guildId);
gconfig.PermissionRole = data.PermissionsControllerRole;
gconfig.VerbosePermissions = data.Verbose;
gconfig.FilteredWords = new HashSet<FilteredWord>(data.Words.Select(w => w.ToLowerInvariant())
.Distinct()
.Select(w => new FilteredWord() { Word = w }));
gconfig.FilterWords = data.Permissions.FilterWords;
gconfig.FilterInvites = data.Permissions.FilterInvites;
gconfig.FilterInvitesChannelIds = new HashSet<FilterChannelId>();
gconfig.FilterInvitesChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterInvites)
.Select(cp => new FilterChannelId()
{
ChannelId = cp.Key
}));
gconfig.FilterWordsChannelIds = new HashSet<FilterChannelId>();
gconfig.FilterWordsChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterWords)
.Select(cp => new FilterChannelId()
{
ChannelId = cp.Key
}));
gconfig.CommandCooldowns = new HashSet<CommandCooldown>(data.CommandCooldowns
.Where(cc => !string.IsNullOrWhiteSpace(cc.Key) && cc.Value > 0)
.Select(cc => new CommandCooldown()
{
CommandName = cc.Key,
Seconds = cc.Value
}));
var smodules = data.Permissions.Modules.Where(m => !m.Value);
try { Directory.Move("data/permissions","data/DELETE_ME_permissions"); } catch { }
}
}
private void MigrateConfig0_9(BotConfig botConfig)
{
Config0_9 oldConfig;
const string configPath = "./data/config.json";
try
{
oldConfig = JsonConvert.DeserializeObject<Config0_9>(File.ReadAllText(configPath));
}
catch (FileNotFoundException)
{
@@ -77,94 +227,89 @@ namespace NadekoBot.Modules.Administration
_log.Error("Unknow error while deserializing file config.json, pls check its integrity, aborting migration");
throw new MigrationException();
}
using (var uow = DbHandler.UnitOfWork())
{
var botConfig = uow.BotConfig.GetOrCreate();
//Basic
botConfig.DontJoinServers = oldData.DontJoinServers;
botConfig.ForwardMessages = oldData.ForwardMessages;
botConfig.ForwardToAllOwners = oldData.ForwardToAllOwners;
botConfig.BufferSize = (ulong) oldData.BufferSize;
botConfig.RemindMessageFormat = oldData.RemindMessageFormat;
botConfig.CurrencySign = oldData.CurrencySign;
botConfig.CurrencyName = oldData.CurrencyName;
botConfig.DMHelpString = oldData.DMHelpString;
botConfig.HelpString = oldData.HelpString;
//Basic
botConfig.DontJoinServers = oldConfig.DontJoinServers;
botConfig.ForwardMessages = oldConfig.ForwardMessages;
botConfig.ForwardToAllOwners = oldConfig.ForwardToAllOwners;
botConfig.BufferSize = (ulong)oldConfig.BufferSize;
botConfig.RemindMessageFormat = oldConfig.RemindMessageFormat;
botConfig.CurrencySign = oldConfig.CurrencySign;
botConfig.CurrencyName = oldConfig.CurrencyName;
botConfig.DMHelpString = oldConfig.DMHelpString;
botConfig.HelpString = oldConfig.HelpString;
//messages
botConfig.RotatingStatuses = oldData.IsRotatingStatus;
var messages = new List<PlayingStatus>();
//messages
botConfig.RotatingStatuses = oldConfig.IsRotatingStatus;
var messages = new List<PlayingStatus>();
oldData.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i }));
botConfig.RotatingStatusMessages = messages;
oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i }));
botConfig.RotatingStatusMessages = messages;
//races
var races = new HashSet<RaceAnimal>();
oldData.RaceAnimals.ForEach(i => races.Add(new RaceAnimal() { Icon = i, Name = i }));
//races
var races = new HashSet<RaceAnimal>();
oldConfig.RaceAnimals.ForEach(i => races.Add(new RaceAnimal() { Icon = i, Name = i }));
if (races.Any())
botConfig.RaceAnimals = races;
//Prefix
var prefix = new HashSet<ModulePrefix>
//Prefix
var prefix = new HashSet<ModulePrefix>
{
new ModulePrefix()
{
ModuleName = "Administration",
Prefix = oldData.CommandPrefixes.Administration
Prefix = oldConfig.CommandPrefixes.Administration
},
new ModulePrefix()
{
ModuleName = "Searches",
Prefix = oldData.CommandPrefixes.Searches
Prefix = oldConfig.CommandPrefixes.Searches
},
new ModulePrefix() {ModuleName = "NSFW", Prefix = oldData.CommandPrefixes.NSFW},
new ModulePrefix() {ModuleName = "NSFW", Prefix = oldConfig.CommandPrefixes.NSFW},
new ModulePrefix()
{
ModuleName = "Conversations",
Prefix = oldData.CommandPrefixes.Conversations
Prefix = oldConfig.CommandPrefixes.Conversations
},
new ModulePrefix()
{
ModuleName = "ClashOfClans",
Prefix = oldData.CommandPrefixes.ClashOfClans
Prefix = oldConfig.CommandPrefixes.ClashOfClans
},
new ModulePrefix() {ModuleName = "Help", Prefix = oldData.CommandPrefixes.Help},
new ModulePrefix() {ModuleName = "Music", Prefix = oldData.CommandPrefixes.Music},
new ModulePrefix() {ModuleName = "Trello", Prefix = oldData.CommandPrefixes.Trello},
new ModulePrefix() {ModuleName = "Games", Prefix = oldData.CommandPrefixes.Games},
new ModulePrefix() {ModuleName = "Help", Prefix = oldConfig.CommandPrefixes.Help},
new ModulePrefix() {ModuleName = "Music", Prefix = oldConfig.CommandPrefixes.Music},
new ModulePrefix() {ModuleName = "Trello", Prefix = oldConfig.CommandPrefixes.Trello},
new ModulePrefix() {ModuleName = "Games", Prefix = oldConfig.CommandPrefixes.Games},
new ModulePrefix()
{
ModuleName = "Gambling",
Prefix = oldData.CommandPrefixes.Gambling
Prefix = oldConfig.CommandPrefixes.Gambling
},
new ModulePrefix()
{
ModuleName = "Permissions",
Prefix = oldData.CommandPrefixes.Permissions
Prefix = oldConfig.CommandPrefixes.Permissions
},
new ModulePrefix()
{
ModuleName = "Programming",
Prefix = oldData.CommandPrefixes.Programming
Prefix = oldConfig.CommandPrefixes.Programming
},
new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldData.CommandPrefixes.Pokemon},
new ModulePrefix() {ModuleName = "Utility", Prefix = oldData.CommandPrefixes.Utility}
new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldConfig.CommandPrefixes.Pokemon},
new ModulePrefix() {ModuleName = "Utility", Prefix = oldConfig.CommandPrefixes.Utility}
};
botConfig.ModulePrefixes = prefix;
botConfig.ModulePrefixes = prefix;
//Blacklist
var blacklist = oldData.ServerBlacklist.Select(server => new BlacklistItem() {ItemId = server, Type = BlacklistItem.BlacklistType.Server}).ToList();
blacklist.AddRange(oldData.ChannelBlacklist.Select(channel => new BlacklistItem() {ItemId = channel, Type = BlacklistItem.BlacklistType.Channel}));
blacklist.AddRange(oldData.UserBlacklist.Select(user => new BlacklistItem() {ItemId = user, Type = BlacklistItem.BlacklistType.User}));
botConfig.Blacklist = new HashSet<BlacklistItem>(blacklist);
//Blacklist
var blacklist = new HashSet<BlacklistItem>(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistItem.BlacklistType.Server }));
blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistItem.BlacklistType.Channel }));
blacklist.AddRange(oldConfig.UserBlacklist.Select(user => new BlacklistItem() { ItemId = user, Type = BlacklistItem.BlacklistType.User }));
botConfig.Blacklist = blacklist;
//Eightball
botConfig.EightBallResponses = new HashSet<EightBallResponse>(oldData._8BallResponses.Select(response => new EightBallResponse() {Text = response}));
//Eightball
botConfig.EightBallResponses = new HashSet<EightBallResponse>(oldConfig._8BallResponses.Select(response => new EightBallResponse() { Text = response }));
//NOW save it
botConfig.MigrationVersion = 1;
await uow.CompleteAsync().ConfigureAwait(false);
}
try { File.Move(configPath, "./data/DELETE_ME_config.json"); } catch { }
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -45,4 +46,153 @@ namespace NadekoBot.Modules.Administration.Commands.Migration
public string DMHelpString { get; set; }
public string HelpString { get; set; }
}
/// <summary>
/// Holds a permission list
/// </summary>
public class Permissions
{
/// <summary>
/// Name of the parent object whose permissions these are
/// </summary>
public string Name { get; set; }
/// <summary>
/// Module name with allowed/disallowed
/// </summary>
public ConcurrentDictionary<string, bool> Modules { get; set; }
/// <summary>
/// Command name with allowed/disallowed
/// </summary>
public ConcurrentDictionary<string, bool> Commands { get; set; }
/// <summary>
/// Should the bot filter invites to other discord servers (and ref links in the future)
/// </summary>
public bool FilterInvites { get; set; }
/// <summary>
/// Should the bot filter words which are specified in the Words hashset
/// </summary>
public bool FilterWords { get; set; }
public Permissions(string name)
{
Name = name;
Modules = new ConcurrentDictionary<string, bool>();
Commands = new ConcurrentDictionary<string, bool>();
FilterInvites = false;
FilterWords = false;
}
public void CopyFrom(Permissions other)
{
Modules.Clear();
foreach (var mp in other.Modules)
Modules.AddOrUpdate(mp.Key, mp.Value, (s, b) => mp.Value);
Commands.Clear();
foreach (var cp in other.Commands)
Commands.AddOrUpdate(cp.Key, cp.Value, (s, b) => cp.Value);
FilterInvites = other.FilterInvites;
FilterWords = other.FilterWords;
}
public override string ToString()
{
var toReturn = "";
var bannedModules = Modules.Where(kvp => kvp.Value == false);
var bannedModulesArray = bannedModules as KeyValuePair<string, bool>[] ?? bannedModules.ToArray();
if (bannedModulesArray.Any())
{
toReturn += "`Banned Modules:`\n";
toReturn = bannedModulesArray.Aggregate(toReturn, (current, m) => current + $"\t`[x] {m.Key}`\n");
}
var bannedCommands = Commands.Where(kvp => kvp.Value == false);
var bannedCommandsArr = bannedCommands as KeyValuePair<string, bool>[] ?? bannedCommands.ToArray();
if (bannedCommandsArr.Any())
{
toReturn += "`Banned Commands:`\n";
toReturn = bannedCommandsArr.Aggregate(toReturn, (current, c) => current + $"\t`[x] {c.Key}`\n");
}
return toReturn;
}
}
public class ServerPermissions0_9
{
/// <summary>
/// The guy who can edit the permissions
/// </summary>
public string PermissionsControllerRole { get; set; }
/// <summary>
/// Does it print the error when a restriction occurs
/// </summary>
public bool Verbose { get; set; }
/// <summary>
/// The id of the thing (user/server/channel)
/// </summary>
public ulong Id { get; set; } //a string because of the role name.
/// <summary>
/// Permission object bound to the id of something/role name
/// </summary>
public Permissions Permissions { get; set; }
/// <summary>
/// Banned words, usually profanities, like word "java"
/// </summary>
public HashSet<string> Words { get; set; }
public Dictionary<ulong, Permissions> UserPermissions { get; set; }
public Dictionary<ulong, Permissions> ChannelPermissions { get; set; }
public Dictionary<ulong, Permissions> RolePermissions { get; set; }
/// <summary>
/// Dictionary of command names with their respective cooldowns
/// </summary>
public ConcurrentDictionary<string, int> CommandCooldowns { get; set; }
public ServerPermissions0_9(ulong id, string name)
{
Id = id;
PermissionsControllerRole = "Nadeko";
Verbose = true;
Permissions = new Permissions(name);
Permissions.Modules.TryAdd("NSFW", false);
UserPermissions = new Dictionary<ulong, Permissions>();
ChannelPermissions = new Dictionary<ulong, Permissions>();
RolePermissions = new Dictionary<ulong, Permissions>();
CommandCooldowns = new ConcurrentDictionary<string, int>();
Words = new HashSet<string>();
}
}
internal class ServerSpecificConfig
{
public bool VoicePlusTextEnabled { get; set; }
private bool SendPrivateMessageOnMention { get; set; }
private ulong? LogChannel { get; set; } = null;
public HashSet<ulong> LogserverIgnoreChannels { get; set; }
private ulong? LogPresenceChannel = null;
public ConcurrentDictionary<ulong, ulong> VoiceChannelLog { get; set; }
public HashSet<ulong> ListOfSelfAssignableRoles { get; set; }
public ulong AutoAssignedRole { get; set; }
public ConcurrentDictionary<ulong, int> GenerateCurrencyChannels { get; set; }
public bool AutoDeleteMessagesOnCommand { get; set; }
public bool ExclusiveSelfAssignedRoles { get; set; }
public float DefaultMusicVolume { get; set; }
public HashSet<StreamNotificationConfig0_9> ObservingStreams { get; set; }
}
public class StreamNotificationConfig0_9
{
public string Username { get; set; }
public StreamType Type { get; set; }
public ulong ServerId { get; set; }
public ulong ChannelId { get; set; }
public bool LastStatus { get; set; }
public enum StreamType
{
Twitch,
Beam,
Hitbox,
YoutubeGaming
}
}
}

View File

@@ -19,7 +19,7 @@ namespace NadekoBot.Modules.ClashOfClans
{
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
public ClashOfClans(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
static ClashOfClans()
{
using (var uow = DbHandler.UnitOfWork())
{
@@ -36,6 +36,9 @@ namespace NadekoBot.Modules.ClashOfClans
.ToDictionary(g => g.Key, g => g.ToList()));
}
}
public ClashOfClans(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client)
{
}
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
{

View File

@@ -5,6 +5,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
@@ -33,7 +34,7 @@ namespace NadekoBot.Modules.Games
{
private Random rng;
private ConcurrentDictionary<ulong, bool> generationChannels = new ConcurrentDictionary<ulong, bool>();
private ConcurrentHashSet<ulong> generationChannels = new ConcurrentHashSet<ulong>();
//channelid/message
private ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
@@ -53,9 +54,8 @@ namespace NadekoBot.Modules.Games
{
var conf = uow.BotConfig.GetOrCreate();
var x =
generationChannels = new ConcurrentDictionary<ulong, bool>(uow.GuildConfigs.GetAll()
.Where(c => c.GenerateCurrencyChannelId != null)
.ToDictionary(c => c.GenerateCurrencyChannelId.Value, c => true));
generationChannels = new ConcurrentHashSet<ulong>(uow.GuildConfigs.GetAll()
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj=>obj.ChannelId)));
chance = conf.CurrencyGenerationChance;
cooldown = conf.CurrencyGenerationCooldown;
}
@@ -71,8 +71,7 @@ namespace NadekoBot.Modules.Games
if (channel == null)
return Task.CompletedTask;
bool shouldGenerate;
if (!generationChannels.TryGetValue(channel.Id, out shouldGenerate) || !shouldGenerate)
if (!generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var t = Task.Run(async () =>
@@ -135,8 +134,6 @@ namespace NadekoBot.Modules.Games
public async Task Plant(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
if (channel == null)
return;
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)imsg.Author, "Planted a flower.", 1, false).ConfigureAwait(false);
if (!removed)
@@ -167,25 +164,23 @@ namespace NadekoBot.Modules.Games
public async Task Gencurrency(IUserMessage imsg)
{
var channel = (ITextChannel)imsg.Channel;
if (channel == null)
return;
bool enabled;
using (var uow = DbHandler.UnitOfWork())
{
var guildConfig = uow.GuildConfigs.For(channel.Id);
if (guildConfig.GenerateCurrencyChannelId == null)
var toAdd = new GCChannelId() { ChannelId = channel.Id };
if (guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelId = channel.Id;
generationChannels.TryAdd(channel.Id, true);
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
generationChannels.Add(channel.Id);
enabled = true;
}
else
{
guildConfig.GenerateCurrencyChannelId = null;
bool throwaway;
generationChannels.TryRemove(channel.Id, out throwaway);
guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
generationChannels.TryRemove(channel.Id);
enabled = false;
}
await uow.CompleteAsync();

View File

@@ -154,8 +154,8 @@ namespace NadekoBot.Modules.Help
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync(
@"**LIST OF COMMANDS**: <http://nadekobot.readthedocs.io/en/latest/Commands%20List/>
**Hosting Guides and docs can be found here**: <http://nadekobot.rtfd.io>").ConfigureAwait(false);
@"**LIST OF COMMANDS**: <http://nadekobot.readthedocs.io/en/1.0/Commands%20List/>
**Hosting Guides and docs can be found here**: <http://nadekobot.readthedocs.io/en/1.0/>").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@@ -165,7 +165,7 @@ namespace NadekoBot.Modules.Help
var channel = (ITextChannel)umsg.Channel;
await channel.SendMessageAsync(
$@"You can support the project on patreon. <https://patreon.com/nadekobot> or
$@"You can support the NadekoBot project on patreon. <https://patreon.com/nadekobot> or
You can send donations to `nadekodiscordbot@gmail.com`
Don't forget to leave your discord name or id in the message.

View File

@@ -18,17 +18,32 @@ namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
public class StreamStatus
{
public StreamStatus(string link, bool isLive, string views)
{
Link = link;
IsLive = isLive;
Views = views;
}
public bool IsLive { get; set; }
public string Link { get; set; }
public string Views { get; set; }
}
[Group]
public class StreamNotificationCommands
{
private Timer checkTimer { get; }
private ConcurrentDictionary<string, Tuple<bool, string>> cachedStatuses = new ConcurrentDictionary<string, Tuple<bool, string>>();
private ConcurrentDictionary<string, StreamStatus> oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private ConcurrentDictionary<string, StreamStatus> cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private bool FirstPass { get; set; } = true;
public StreamNotificationCommands()
{
checkTimer = new Timer(async (state) =>
{
oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(cachedStatuses);
cachedStatuses.Clear();
try
{
@@ -39,19 +54,23 @@ namespace NadekoBot.Modules.Searches
}
foreach (var stream in streams)
{
Tuple<bool, string> data;
StreamStatus data;
try
{
data = await GetStreamStatus(stream).ConfigureAwait(false);
if (data == null)
return;
}
catch
{
continue;
}
if (data.Item1 != stream.LastStatus)
StreamStatus oldData;
oldCachedStatuses.TryGetValue(data.Link, out oldData);
if (oldData == null || data.IsLive != oldData.IsLive)
{
stream.LastStatus = data.Item1;
if (FirstPass)
continue;
var server = NadekoBot.Client.GetGuild(stream.GuildId);
@@ -59,32 +78,30 @@ namespace NadekoBot.Modules.Searches
if (channel == null)
continue;
var msg = $"`{stream.Username}`'s stream is now " +
$"**{(data.Item1 ? "ONLINE" : "OFFLINE")}** with " +
$"**{data.Item2}** viewers.";
if (stream.LastStatus)
$"**{(data.IsLive ? "ONLINE" : "OFFLINE")}** with " +
$"**{data.Views}** viewers.";
if (data.IsLive)
if (stream.Type == FollowedStream.FollowedStreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (stream.Type == FollowedStream.FollowedStreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (stream.Type == FollowedStream.FollowedStreamType.Beam)
msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】";
//else if (stream.Type == FollowedStream.FollowedStreamType.YoutubeGaming)
// msg += $"\n`Here is the Link:`【 not implemented yet - {stream.Username} 】";
try { await channel.SendMessageAsync(msg).ConfigureAwait(false); } catch { }
}
}
FirstPass = false;
}
catch { }
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
private async Task<Tuple<bool, string>> GetStreamStatus(FollowedStream stream, bool checkCache = true)
private async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
{
bool isLive;
string response;
JObject data;
Tuple<bool, string> result;
StreamStatus result;
switch (stream.Type)
{
case FollowedStream.FollowedStreamType.Hitbox:
@@ -97,7 +114,7 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = data["media_is_live"].ToString() == "1";
result = new Tuple<bool, string>(isLive, data["media_views"].ToString());
result = new StreamStatus(hitboxUrl, isLive, data["media_views"].ToString());
cachedStatuses.TryAdd(hitboxUrl, result);
return result;
case FollowedStream.FollowedStreamType.Twitch:
@@ -110,7 +127,7 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString());
result = new Tuple<bool, string>(isLive, isLive ? data["stream"]["viewers"].ToString() : stream.Username);
result = new StreamStatus(twitchUrl, isLive, isLive ? data["stream"]["viewers"].ToString() : "0");
cachedStatuses.TryAdd(twitchUrl, result);
return result;
case FollowedStream.FollowedStreamType.Beam:
@@ -123,13 +140,13 @@ namespace NadekoBot.Modules.Searches
}
data = JObject.Parse(response);
isLive = data["online"].ToObject<bool>() == true;
result = new Tuple<bool, string>(isLive, data["viewersCurrent"].ToString());
result = new StreamStatus(beamUrl, isLive, data["viewersCurrent"].ToString());
cachedStatuses.TryAdd(beamUrl, result);
return result;
default:
break;
}
return new Tuple<bool, string>(false, "0");
return null;
}
[NadekoCommand, Usage, Description, Aliases]
@@ -181,6 +198,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.ManageMessages)]
public async Task RemoveStream(IUserMessage msg, [Remainder] string username)
{
var channel = (ITextChannel)msg.Channel;
@@ -223,9 +241,9 @@ namespace NadekoBot.Modules.Searches
Username = stream,
Type = platform
}));
if (streamStatus.Item1)
if (streamStatus.IsLive)
{
await channel.SendMessageAsync($"`Streamer {username} is online with {streamStatus.Item2}.`");
await channel.SendMessageAsync($"`Streamer {username} is online with {streamStatus.Views} viewers.`");
}
else
{
@@ -258,7 +276,7 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync($":anger: I am already following `{username}` ({type}) stream on this channel.").ConfigureAwait(false);
return;
}
Tuple<bool, string> data;
StreamStatus data;
try
{
data = await GetStreamStatus(stream).ConfigureAwait(false);
@@ -268,17 +286,14 @@ namespace NadekoBot.Modules.Searches
await channel.SendMessageAsync(":anger: Stream probably doesn't exist.").ConfigureAwait(false);
return;
}
var msg = $"Stream is currently **{(data.Item1 ? "ONLINE" : "OFFLINE")}** with **{data.Item2}** viewers";
if (data.Item1)
var msg = $"Stream is currently **{(data.IsLive ? "ONLINE" : "OFFLINE")}** with **{data.Views}** viewers";
if (data.IsLive)
if (type == FollowedStream.FollowedStreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (type == FollowedStream.FollowedStreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (type == FollowedStream.FollowedStreamType.Beam)
msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】";
//else if (type == FollowedStream.FollowedStreamType.YoutubeGaming)
// msg += $"\n`Here is the Link:` not implemented yet - {stream.Username}";
stream.LastStatus = data.Item1;
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Add(stream);