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; }
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); } }
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; }
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); }
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); }
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); }
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); }