Esempio n. 1
0
 public NetflixHandler(INetflixAccountStore netflixAccountStore, ILogger <NetflixHandler> log, IOptionsSnapshot <NetflixAccountOptions> netflixAccountOptions, IOptionsSnapshot <EinherjiOptions> einherjiOptions)
 {
     this._netflixAccountStore = netflixAccountStore;
     this._log = log;
     this._netflixAccountOptions = netflixAccountOptions.Value;
     this._einherjiOptions       = einherjiOptions.Value;
 }
Esempio n. 2
0
        private async Task CmdRandomizeStatus(SocketCommandContext context, CancellationToken cancellationToken = default)
        {
            EinherjiOptions einherjiOptions = this._einherjiOptions.CurrentValue;

            if (context.User.Id != einherjiOptions.AuthorID)
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} You have no rights to tell me ~~how to live my life~~ do this!",
                                         cancellationToken).ConfigureAwait(false);

                return;
            }

            Status status = await RandomizeStatusAsync(context.Client, cancellationToken).ConfigureAwait(false);

            if (status != null)
            {
                await context.ReplyAsync($"{einherjiOptions.SuccessSymbol} Status changed: `{status.Text.Replace("`", "\\`")}`", cancellationToken).ConfigureAwait(false);

                // restart loop
                StartBackgroundLoop();
            }
            else if (this._options.CurrentValue.Statuses?.Any() != true)
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Status not changed - ensure status list is not empty.", cancellationToken).ConfigureAwait(false);
            }
        }
Esempio n. 3
0
 public StellarisModsHandler(IStellarisModsStore stellarisModsStore, ILogger <StellarisModsHandler> log, IOptionsSnapshot <EinherjiOptions> einherjiOptions, IOptionsSnapshot <CommandsOptions> commandsOptions)
 {
     this._stellarisModsStore = stellarisModsStore;
     this._log             = log;
     this._einherjiOptions = einherjiOptions.Value;
     this._commandsOptions = commandsOptions.Value;
 }
Esempio n. 4
0
 public GameServersHandler(ILogger <GameServersHandler> log, IGameServerStore gameServersStore,
                           IOptionsSnapshot <EinherjiOptions> einherjiOptions, IOptionsSnapshot <GameServersOptions> gameServersOptions)
 {
     this._gameServersStore   = gameServersStore;
     this._einherjiOptions    = einherjiOptions.Value;
     this._gameServersOptions = gameServersOptions.Value;
     this._log = log;
 }
        // helper method for verifying a valid channel ID
        private async Task <SocketVoiceChannel> VerifyValidVoiceChannelAsync(Group matchGroup, IGuild guild, ISocketMessageChannel responseChannel, CancellationToken cancellationToken)
        {
            if (matchGroup == null || !matchGroup.Success || matchGroup.Length < 1)
            {
                return(null);
            }

            EinherjiOptions options = _einherjiOptions.CurrentValue;

            if (!ulong.TryParse(matchGroup.Value, out ulong id))
            {
                await responseChannel.SendMessageAsync($"{options.FailureSymbol} `{matchGroup.Value}` is not a valid channel ID.`", cancellationToken).ConfigureAwait(false);

                return(null);
            }

            // instead of doing quick check, do a series to help user pinpoint any issue
            // find channel first
            SocketChannel channel = _client.GetChannel(id);

            if (channel == null || !(channel is SocketGuildChannel guildChannel))
            {
                await responseChannel.SendMessageAsync($"{options.FailureSymbol} I don't know any guild channel with ID `{id}`.", cancellationToken).ConfigureAwait(false);

                return(null);
            }

            // verify channel is in guild
            if (guildChannel.Guild.Id != guild.Id)
            {
                await responseChannel.SendMessageAsync($"{options.FailureSymbol} Channel **#{guildChannel.Name}** doesn't exist in **{guild.Name}** guild.", cancellationToken).ConfigureAwait(false);

                return(null);
            }

            // lastly make sure it is a voice channel
            if (!(guildChannel is SocketVoiceChannel voiceChannel))
            {
                await responseChannel.SendMessageAsync($"{options.FailureSymbol} {MentionUtils.MentionChannel(id)} is not a voice channel.", cancellationToken).ConfigureAwait(false);

                return(null);
            }

            return(voiceChannel);
        }
Esempio n. 6
0
        private async Task CmdInstanceInfoAsync(SocketCommandContext context, Match match, CancellationToken cancellationToken = default)
        {
            using IDisposable logScope = _log.BeginCommandScope(context, this);
            PiholeOptions   piholeOptions   = _piholeOptions.CurrentValue;
            EinherjiOptions einherjiOptions = _einherjiOptions.CurrentValue;
            string          instanceID      = match.Groups[1].Value;

            // check instance exists
            if (!piholeOptions.Instances.TryGetValue(instanceID, out PiholeInstanceOptions instance))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Unknown PiHole instance `{instanceID}`.", cancellationToken).ConfigureAwait(false);

                return;
            }
            string instanceName = string.IsNullOrWhiteSpace(instance.DisplayName) ? instanceID : instance.DisplayName;

            // check user is authorized to manage that instance
            if (!instance.IsAuthorized(context.User))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} You have no permissions to manage PiHole instance `{instanceName}`.", cancellationToken).ConfigureAwait(false);

                return;
            }

            // send notification to user that we're working on it
            RestUserMessage workingNotification = await context.ReplyAsync("Querying pihole API, please wait...", cancellationToken).ConfigureAwait(false);

            EmbedBuilder embed = new EmbedBuilder()
                                 .WithThumbnailUrl("https://upload.wikimedia.org/wikipedia/en/thumb/1/15/Pi-hole_vector_logo.svg/1200px-Pi-hole_vector_logo.svg.png")
                                 .WithDescription($"Information on **{instanceName}** Kathara PiHole instance");

            // add stored instance config
            if (!instance.HideURL)
            {
                embed.AddField("PiHole Address", instance.PiholeURL, false);
            }
            if (instance.AuthorizedUserIDs.Count > 0)
            {
                embed.AddField("Authorized users", string.Join(", ", instance.AuthorizedUserIDs.Select(uid => MentionUtils.MentionUser(uid))), true);
            }
            if (instance.AuthorizedRoleIDs.Count > 0)
            {
                embed.AddField("Authorized roles", string.Join(", ", instance.AuthorizedRoleIDs.Select(rid => MentionUtils.MentionRole(rid))), true);
            }
            if (instance.AuthorizedUserIDs.Count + instance.AuthorizedRoleIDs.Count == 0)
            {
                embed.AddField("Authorized users", "-", true);
            }

            // communicate with pihole API
            _log.LogDebug("Requesting status from Pihole API at {URL} (Instance: {Instance})", instance.PiholeURL, instanceName);
            HttpClient client = _httpClientFactory.CreateClient();

            using (HttpResponseMessage response = await client.GetAsync($"{instance.PiholeURL}/admin/api.php?summary", cancellationToken).ConfigureAwait(false))
            {
                if (!response.IsSuccessStatusCode)
                {
                    embed.WithColor(einherjiOptions.EmbedErrorColor);
                    await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Request to PiHole API failed: {response.ReasonPhrase} ({response.StatusCode})", false, embed.Build(), cancellationToken).ConfigureAwait(false);

                    await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);

                    return;
                }
                string responseContentRaw = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                if (!TryParseJObject(responseContentRaw, out JObject responseContentJson))
                {
                    embed.WithColor(einherjiOptions.EmbedErrorColor);
                    await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Failed to query PiHole. Please refer to bot logs.", false, embed.Build(), cancellationToken).ConfigureAwait(false);

                    await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);

                    _log.LogError("Failed querying PiHole instance {InstanceID}: {ResponseMessage}", instanceName, responseContentRaw);
                    return;
                }

                // deserialize response
                JsonSerializer serializer = JsonSerializer.CreateDefault();
                serializer.Culture = CultureInfo.InvariantCulture;
                PiholeApiStatusResponse data = responseContentJson.ToObject <PiholeApiStatusResponse>(serializer);

                // put into embed
                embed.AddField("Status", data.IsEnabled ? $"{einherjiOptions.SuccessSymbol} Enabled" : $"{einherjiOptions.FailureSymbol} Disabled", false);
                embed.WithColor(data.IsEnabled ? einherjiOptions.EmbedSuccessColor : einherjiOptions.EmbedErrorColor);
                embed.AddField("Domains on block list", data.DomainsBeingBlocked.ToString("N0"), true);
                embed.AddField("DNS queries today", data.DnsQueriesToday.ToString("N0"), true);
                embed.AddField("Ads blocked today", $"{data.AdsBlockedToday:N0} ({data.AdsPercentageToday}%)", true);
                embed.AddField("Unique clients", $"{data.UniqueRecentClients:N0} recently, {data.ClientsEverSeen:N0} all-time", true);
                embed.WithFooter("Gravity last updated");
                embed.WithTimestamp(data.GravityLastUpdated);
            }
            using (HttpResponseMessage response = await client.GetAsync($"{instance.PiholeURL}/admin/api.php?version"))
            {
                if (response.IsSuccessStatusCode &&
                    TryParseJObject(await response.Content.ReadAsStringAsync(), out JObject responseContentJson) &&
                    !string.IsNullOrWhiteSpace(responseContentJson["version"]?.ToString()))
                {
                    embed.AddField("PiHole version", responseContentJson["version"].ToString());
                }
            }

            await context.ReplyAsync(null, false, embed.Build(), cancellationToken).ConfigureAwait(false);

            await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);
        }
Esempio n. 7
0
        private async Task CmdDisableAsync(SocketCommandContext context, Match match, CancellationToken cancellationToken = default)
        {
            using IDisposable logScope = _log.BeginCommandScope(context, this);
            PiholeOptions   piholeOptions   = _piholeOptions.CurrentValue;
            EinherjiOptions einherjiOptions = _einherjiOptions.CurrentValue;
            string          instanceID      = match.Groups[1].Value;

            // check instance exists
            if (!piholeOptions.Instances.TryGetValue(instanceID, out PiholeInstanceOptions instance))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Unknown PiHole instance `{instanceID}`.", cancellationToken).ConfigureAwait(false);

                return;
            }
            string instanceName = string.IsNullOrWhiteSpace(instance.DisplayName) ? instanceID : instance.DisplayName;

            // check user is authorized to access that instance
            if (!instance.IsAuthorized(context.User))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} You have no permissions to access PiHole instance `{instanceName}`.", cancellationToken).ConfigureAwait(false);

                return;
            }

            // parse disable time, defaulting to 5 mins
            TimeSpan disableTime = piholeOptions.DefaultDisableTime;

            if (uint.TryParse(match.Groups[2]?.Value, out uint disableMinutes))
            {
                disableTime = TimeSpan.FromMinutes(disableMinutes);
            }
            if (disableTime <= TimeSpan.FromMinutes(1))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Minimum disable time is 1 minute.", cancellationToken).ConfigureAwait(false);

                return;
            }

            // send notification to user that we're working on it
            RestUserMessage workingNotification = await context.ReplyAsync("Querying pihole API, please wait...", cancellationToken).ConfigureAwait(false);

            // communicate with pihole API
            _log.LogDebug("Disabling Pihole through API at {URL} for {Duration} (Instance: {Instance})", instance.PiholeURL, instanceName, disableTime);
            HttpClient client = _httpClientFactory.CreateClient();

            using HttpResponseMessage response =
                      await client.GetAsync($"{instance.PiholeURL}/admin/api.php?disable={disableMinutes * 60}&auth={instance.AuthToken}", cancellationToken).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Request to PiHole API failed: {response.ReasonPhrase} ({response.StatusCode})", cancellationToken).ConfigureAwait(false);

                await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);

                return;
            }
            string responseContentRaw = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            if (!TryParseJObject(responseContentRaw, out JObject responseContentJson))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Failed to disable PiHole. Please refer to bot logs.", cancellationToken).ConfigureAwait(false);

                await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);

                _log.LogError("Failed disabling PiHole instance {InstanceID}: {ResponseMessage}", instanceName, responseContentRaw);
                return;
            }
            if (!string.Equals(responseContentJson["status"]?.ToString(), "disabled", StringComparison.OrdinalIgnoreCase))
            {
                await context.ReplyAsync($"{einherjiOptions.FailureSymbol} Failed to disable PiHole. Please refer to bot logs.", cancellationToken).ConfigureAwait(false);

                await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);

                _log.LogError("Failed disabling PiHole instance {InstanceID}: 'status' is not 'disabled'", instanceName);
                return;
            }

            await context.ReplyAsync($"{einherjiOptions.FailureSymbol} PiHole instance `{instanceName}` has been disabled for {disableTime.TotalMinutes} minutes.", cancellationToken).ConfigureAwait(false);

            await workingNotification.DeleteAsync(cancellationToken).ConfigureAwait(false);
        }
        private async Task CmdMoveAllAsync(SocketCommandContext context, Match match, CancellationToken cancellationToken = default)
        {
            using IDisposable logScope = _log.BeginCommandScope(context, this);
            EinherjiOptions options = _einherjiOptions.CurrentValue;

            // verify it's a guild message
            SocketTextChannel channel = await this.VerifyGuildChannelAsync(context, cancellationToken).ConfigureAwait(false);

            if (channel == null)
            {
                return;
            }

            // verify command has proper arguments
            if (match.Groups.Count < 3)
            {
                string prefix = _commandsOptions.CurrentValue.Prefix;
                await context.ReplyAsync($"{options.FailureSymbol} Please specify __both__ channels IDs.\n***{_commandsOptions.CurrentValue.Prefix}move all from <original channel ID> to <target channel ID>***", cancellationToken).ConfigureAwait(false);

                return;
            }

            // verify channels exist
            // we can do both at once, it's okay if user gets warn about both at once, and it just simplifies the code
            SocketVoiceChannel channelFrom = await VerifyValidVoiceChannelAsync(match.Groups[1], context.Guild, context.Channel, cancellationToken).ConfigureAwait(false);

            SocketVoiceChannel channelTo = await VerifyValidVoiceChannelAsync(match.Groups[2], context.Guild, context.Channel, cancellationToken).ConfigureAwait(false);

            if (channelFrom == null || channelTo == null)
            {
                return;
            }

            // verify user can see both channels, and has move permission in both
            _log.LogTrace("Verifying user {ID} has permission to move users between channels {ChannelFromName} ({ChannelFromID}) and {ChannelToName} ({ChannelToID})",
                          context.User.Id, channelFrom.Name, channelFrom.Id, channelTo.Name, channelTo.Id);
            if (!await this.VerifyUserPermissionsAsync(context, channelFrom, context.User.Id, ChannelPermission.MoveMembers, cancellationToken).ConfigureAwait(false) ||
                !await this.VerifyUserPermissionsAsync(context, channelTo, context.User.Id, ChannelPermission.MoveMembers, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            // verify bot also has permissions
            _log.LogTrace("Verifying the bot has permission to move users between channels {ChannelFromName} ({ChannelFromID}) and {ChannelToName} ({ChannelToID})",
                          channelFrom.Name, channelFrom.Id, channelTo.Name, channelTo.Id);
            if (!await this.VerifyUserPermissionsAsync(context, channelFrom, context.Client.CurrentUser.Id, ChannelPermission.MoveMembers, cancellationToken).ConfigureAwait(false) ||
                !await this.VerifyUserPermissionsAsync(context, channelTo, context.Client.CurrentUser.Id, ChannelPermission.MoveMembers, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            // move the users
            SocketGuildUser[] users = channelFrom.Users.ToArray();
            string            channelFromMention = GetVoiceChannelMention(channelFrom);
            string            channelToMention   = GetVoiceChannelMention(channelTo);

            _log.LogDebug("Moving {Count} users from channel {ChannelFromName} ({ChannelFromID}) to {ChannelToName} ({ChannelToID})", users.Length, channelFrom.Name, channelFrom.Id, channelTo.Name, channelTo.Id);
            RestUserMessage response = await context.ReplyAsync($"Moving {users.Length} user{(users.Length > 1 ? "s" : null)} from {channelFromMention} to {channelToMention}.", cancellationToken).ConfigureAwait(false);

            int errorCount = 0;

            foreach (SocketGuildUser user in users)
            {
                try
                {
                    await user.ModifyAsync(props => props.Channel = channelTo, cancellationToken).ConfigureAwait(false);
                }
                catch { errorCount++; }
            }
            // display confirmation
            StringBuilder builder      = new StringBuilder();
            int           successCount = users.Length - errorCount;

            builder.AppendFormat("{0} user{3} moved from {1} to {2}.", successCount.ToString(), channelFromMention, channelToMention, successCount > 1 || successCount == 0 ? "s" : null);
            if (errorCount > 0)
            {
                builder.AppendFormat("\nFailed to move {0} user{2}. {1}", errorCount.ToString(), options.FailureSymbol, errorCount > 1 ? "s" : null);
            }
            await response.ModifyAsync(props => props.Content = builder.ToString(), cancellationToken).ConfigureAwait(false);
        }
Esempio n. 9
0
        private async Task CmdPurgeAsync(SocketCommandContext message, Match match, CancellationToken cancellationToken = default)
        {
            using IDisposable logScope = _log.BeginCommandScope(message, this);
            EinherjiOptions options = _einherjiOptions.CurrentValue;

            if (!(message.Channel is SocketTextChannel channel))
            {
                await message.ReplyAsync($"{options.FailureSymbol} Sir, this command is only applicable in guild channels.", cancellationToken).ConfigureAwait(false);

                return;
            }
            SocketGuildUser user = await message.Guild.GetGuildUserAsync(message.User).ConfigureAwait(false);

            if (!user.GetPermissions(channel).ManageMessages)
            {
                await channel.SendMessageAsync($"{options.FailureSymbol} You can't order me to do that.", cancellationToken).ConfigureAwait(false);

                return;
            }
            SocketGuildUser botUser = await message.Guild.GetGuildUserAsync(message.Client.CurrentUser.Id).ConfigureAwait(false);

            if (!botUser.GetPermissions(channel).ManageMessages)
            {
                await channel.SendMessageAsync($"{options.FailureSymbol} I am missing *Manage Messages* permission in this channel.", cancellationToken).ConfigureAwait(false);

                return;
            }
            if (match.Groups.Count == 1 || match.Groups[1]?.Length < 1)
            {
                await channel.SendMessageAsync($"{options.FailureSymbol} Sir, I need a positive number of messages to take down.", cancellationToken).ConfigureAwait(false);

                return;
            }
            string countString = match.Groups[1].Value;

            if (!int.TryParse(countString, out int count))
            {
                await channel.SendMessageAsync($"{options.FailureSymbol} Sir, `{countString} is not a valid number.", cancellationToken).ConfigureAwait(false);

                return;
            }
            if (count < 0)
            {
                await channel.SendMessageAsync($"{options.FailureSymbol} Sir, how am I supposed to execute removal of {count} messages?.", cancellationToken).ConfigureAwait(false);

                return;
            }

            // start a new task to prevent deletion from blocking gateway task
            _ = Task.Run(async() =>
            {
                try
                {
                    // get last X messages
                    IEnumerable <IMessage> msgs     = await channel.GetMessagesAsync(count + 1, cancellationToken).FlattenAsync().ConfigureAwait(false);
                    RestUserMessage confirmationMsg = null;
                    // bulk can only delete messages not older than 2 weeks
                    DateTimeOffset bulkMaxAge            = DateTimeOffset.UtcNow - TimeSpan.FromDays(14) - TimeSpan.FromSeconds(2);
                    IEnumerable <IMessage> newerMessages = msgs.Where(msg => msg.Timestamp >= bulkMaxAge);
                    IEnumerable <IMessage> olderMessages = msgs.Except(newerMessages);
                    int olderCount  = olderMessages.Count();
                    int actualCount = msgs.Count() - 1;
                    _log.LogDebug("Removing {TotalCount} messages. {BulkIncompatibleCount} messages cannot be removed in bulk", msgs.Count(), olderCount);
                    // first delete bulk-deletable
                    await channel.DeleteMessagesAsync(newerMessages, cancellationToken).ConfigureAwait(false);
                    // delete older msgs one by one
                    if (olderCount > 0)
                    {
                        await SendOrUpdateConfirmationAsync($"You are requesting deletion of {actualCount} messages, {olderCount} of which are older than 2 weeks.\n" +
                                                            "Deleting these messages may take a while due to Discord's rate limiting, so please be patient.").ConfigureAwait(false);
                        foreach (IMessage msg in olderMessages)
                        {
                            await Task.Delay(1500).ConfigureAwait(false);
                            await channel.DeleteMessageAsync(msg, cancellationToken).ConfigureAwait(false);
                        }
                    }
                    await SendOrUpdateConfirmationAsync(actualCount > 0 ?
                                                        $"{options.SuccessSymbol} Sir, your message and {actualCount} previous message{(actualCount > 1 ? "s were" : " was")} taken down." :
                                                        $"{options.SuccessSymbol} Sir, I deleted your message. Specify count greater than 0 to remove more than just that.").ConfigureAwait(false);
                    await Task.Delay(6 * 1000, cancellationToken).ConfigureAwait(false);
                    await channel.DeleteMessageAsync(confirmationMsg, cancellationToken).ConfigureAwait(false);

                    async Task SendOrUpdateConfirmationAsync(string text)
                    {
                        if (confirmationMsg == null)
                        {
                            confirmationMsg = await channel.SendMessageAsync(text, cancellationToken).ConfigureAwait(false);
                        }
                        else
                        {
                            await confirmationMsg.ModifyAsync(props => props.Content = text, cancellationToken).ConfigureAwait(false);
                        }
                    }
                }
                catch (OperationCanceledException) { }
                catch (Exception ex) when(ex.LogAsError(_log, "Exception occured when purging messages"))
                {
                }
            }, cancellationToken).ConfigureAwait(false);
        }