/// <inheritdoc /> protected override Task <bool> SendNotification(NotificationData notification) { var ircClient = this.ircClients[notification.Server]; if (ircClient != null) { ircClient.Command("PRIVMSG", notification.Channel, notification.Text); return(Task.FromResult(true)); } return(Task.FromResult(false)); }
/// <summary> /// Handles service bus messages. /// </summary> private async Task ServiceBusMessageHandler(ProcessMessageEventArgs args) { string body = args.Message.Body.ToString(); NotificationData notificationData = null; try { notificationData = JsonConvert.DeserializeObject <NotificationData>(body); } catch (JsonSerializationException ex) { Log.Warning(ex, "Failed to deserialize notification"); } if (notificationData != null) { // If it's a system notification, handle it directly, otherwise pass it along if (notificationData.Type == NotificationType.System) { switch (notificationData.SubType) { case SubType.SettingsUpdate: await this.UpdateSettingsAsync(); break; case SubType.Shutdown: Log.Information("Shutdown notification received"); this.exitCode = (int)ExitCode.ExpectedShutdown; break; default: Log.Error($"Error processing notification, unrecognized subtype: {notificationData.SubType}"); break; } } else { try { await this.SendNotification(notificationData); } catch (Exception ex) { Log.Error(ex, "Error processing notification"); } } } // For now, no retries. Regardless of notification send success, complete it. await args.CompleteMessageAsync(args.Message); }
/// <inheritdoc /> protected override Task <bool> SendNotification(NotificationData notification) { if (this.ircClients.TryGetValue(notification.Server, out var ircClient)) { ircClient.Command("PRIVMSG", notification.Channel, notification.Text); var props = new Dictionary <string, string> { { "server", ircClient.Host.ToLowerInvariant() }, { "channel", notification.Channel.ToLowerInvariant() }, }; this.TrackEvent("notificationSent", props); return(Task.FromResult(true)); } return(Task.FromResult(false)); }
/// <summary> /// Creates a QueueClient for azure service bus to listen for notifications. /// </summary> private void StartServiceBusListener() { this.queueClient = new QueueClient(this.Config.ServiceBusConnectionString, this.queueName, ReceiveMode.PeekLock); // Register an OnMessage callback this.queueClient.RegisterMessageHandler( async(message, token) => { var body = Encoding.UTF8.GetString(message.Body); NotificationData notificationData = null; try { notificationData = JsonConvert.DeserializeObject <NotificationData>(body); } catch (JsonSerializationException ex) { this.Logger.Log(LogType.Warn, $"Failed to deserialize notification: {ex}"); } if (notificationData != null) { try { await this.SendNotification(notificationData); } catch (Exception ex) { this.Logger.Log(LogType.Error, "Error processing notification: {0}", ex); } } // For now, no retries. Regardless of notification send success, complete it. await queueClient.CompleteAsync(message.SystemProperties.LockToken); }); }
protected override async Task <bool> SendNotification(NotificationData notification) { if (notification.Text.Contains("%%username%%")) { var member = await this.client.GetMemberAsync(new HashId(notification.Server), new HashId(notification.UserId)); if (member != null) { notification.Text = notification.Text.Replace("%%username%%", member.Nickname ?? member.Name); } } Message originalMessage = null; if (!string.IsNullOrEmpty(notification.MessageId)) { try { originalMessage = await this.client.GetMessageAsync(Guid.Parse(notification.Channel), Guid.Parse(notification.MessageId)); } catch (Exception ex) { Log.Error(ex, "Failed to fetch notification base message"); } } if (originalMessage != null) { await originalMessage.ReplyAsync(notification.Text); } else { await this.client.CreateMessageAsync(Guid.Parse(notification.Channel), notification.Text); } return(true); }
/// <summary> /// Attempts to process and send a notification. /// </summary> /// <param name="notification">The notification data.</param> /// <returns>True, if notification was successfully processed.</returns> protected abstract Task <bool> SendNotification(NotificationData notification);
/// <inheritdoc /> protected override async Task <bool> SendNotification(NotificationData notification) { var guild = this.Client.GetGuild(Convert.ToUInt64(notification.Server)) as IGuild; // if the guild wasn't found, it belongs to another shard. if (guild == null) { return(false); } var channelToUse = this.Client.GetChannel(Convert.ToUInt64(notification.Channel)) as ITextChannel; // if the channel doesn't exist or we don't have send permissions, try to get the default channel instead. if (!channelToUse?.GetCurrentUserPermissions().SendMessages ?? true) { channelToUse = await guild.GetDefaultChannelAsync() as ITextChannel; // if the default channel couldn't be found or we don't have permissions, then we're SOL. // flag as processed. if (channelToUse == null || !channelToUse.GetCurrentUserPermissions().SendMessages) { return(true); } } var settings = SettingsConfig.GetSettings(notification.Server); // adjust the notification text to disable discord link parsing, if configured to do so if (settings.DisableLinkParsing) { notification.Text = Consts.UrlRegex.Replace(notification.Text, new MatchEvaluator((Match urlMatch) => { return($"<{urlMatch.Captures[0]}>"); })); } try { if (notification.Embed != null && settings.HasFlag(notification.Type)) { // TODO: discord handles twitter embeds nicely; should adjust the notification data accordingly so we don't need this explicit check here if (notification.Type == NotificationType.Twitter) { await channelToUse.SendMessageAsync(notification.Embed.Url); } else { var url = string.IsNullOrEmpty(notification.Embed.Url) ? string.Empty : $"<{notification.Embed.Url}>"; await channelToUse.SendMessageAsync(url, false, notification.Embed.CreateEmbedBuilder()); } } else { await channelToUse.SendMessageAsync(notification.Text); } } catch (Exception ex) { // TODO: // Add retry support. string extraData = null; if (notification.Embed != null) { extraData = JsonConvert.SerializeObject(notification.Embed); } this.Logger.Log(LogType.Error, $"Failed to send notification {extraData}: {ex}"); } return(true); }
/// <inheritdoc /> protected override async Task <bool> SendNotification(NotificationData notification) { // if the guild wasn't found, it belongs to another shard. if (!(this.Client.GetGuild(Convert.ToUInt64(notification.Server)) is IGuild guild)) { return(false); } Log.Information($"Sending {notification.Type} notification to {notification.Channel} on guild {notification.Server}"); string extraText = string.Empty; var channelToUse = this.Client.GetChannel(Convert.ToUInt64(notification.Channel)) as ITextChannel; // if the channel doesn't exist or we don't have send permissions, try to get the default channel instead. if (!channelToUse?.GetCurrentUserPermissions().SendMessages ?? true) { // add some hint text about the misconfigured channel string hint = "fix it in the admin panel"; if (notification.Type == NotificationType.Reminder) { hint = "server owner: `.remove timer ###` and recreate it"; } if (channelToUse == null) { extraText = $" [Note: the channel configured is missing; {hint}]"; } else { extraText = $" [Note: missing permissions for the channel configured; adjust permissions or {hint}]"; } channelToUse = await guild.GetDefaultChannelAsync(); // if the default channel couldn't be found or we don't have permissions, then we're SOL. // flag as processed. if (channelToUse == null || !channelToUse.GetCurrentUserPermissions().SendMessages) { return(true); } } var settings = SettingsConfig.GetSettings(notification.Server); // adjust the notification text to disable discord link parsing, if configured to do so if (settings.DisableLinkParsing && notification.Type != NotificationType.Reminder) { notification.Text = Consts.UrlRegex.Replace(notification.Text, new MatchEvaluator((Match urlMatch) => { return($"<{urlMatch.Captures[0]}>"); })); } try { var customText = settings.NotificationText.FirstOrDefault(n => n.Type == notification.Type)?.Text; var allowedMentions = notification.AllowMentions ? new AllowedMentions { MentionRepliedUser = false, AllowedTypes = AllowedMentionTypes.Roles | AllowedMentionTypes.Users | AllowedMentionTypes.Everyone } : AllowedMentions.None; MessageReference messageReference = null; if (!string.IsNullOrEmpty(notification.MessageId) && ulong.TryParse(notification.MessageId, out var messageId)) { messageReference = new MessageReference(messageId, failIfNotExists: false); } if (notification.Embed != null && settings.HasFlag(notification.Type)) { // TODO: discord handles twitter embeds nicely; should adjust the notification data accordingly so we don't need this explicit check here if (notification.Type == NotificationType.Twitter) { var messageText = $"{notification.Embed.Title} {notification.Embed.Url} {customText}{extraText}".Trim(); await channelToUse.SendMessageAsync(messageText, allowedMentions : allowedMentions, messageReference : messageReference); } else { var messageText = string.IsNullOrEmpty(notification.Embed.Url) ? string.Empty : $"<{notification.Embed.Url}>"; messageText += $" {customText}{extraText}".TrimEnd(); await channelToUse.SendMessageAsync(messageText, false, notification.Embed.CreateEmbedBuilder().Build(), allowedMentions : allowedMentions, messageReference : messageReference); } } else { var messageText = $"{notification.Text} {customText}{extraText}".TrimEnd(); var sentMesage = await channelToUse.SendMessageAsync(messageText.SubstringUpTo(Discord.DiscordConfig.MaxMessageSize), allowedMentions : allowedMentions, messageReference : messageReference); // update feedback messages to include the message ID if (notification.Type == NotificationType.Feedback && notification.SubType != SubType.Reply) { await sentMesage.ModifyAsync(m => m.Content = $"{sentMesage.Content} (mid: {sentMesage.Id})"); } } var props = new Dictionary <string, string> { { "server", notification.Server }, { "channel", notification.Channel }, { "notificationType", notification.Type.ToString() }, }; this.TrackEvent("notificationSent", props); } catch (Exception ex) { // TODO: // Add retry support. string extraData = null; if (notification.Embed != null) { extraData = JsonConvert.SerializeObject(notification.Embed); } Log.Error(ex, $"Failed to send notification {extraData}"); } return(true); }
/// <inheritdoc /> protected override async Task <bool> SendNotification(NotificationData notification) { // if the guild wasn't found, it belongs to another shard. if (!(this.Client.GetGuild(Convert.ToUInt64(notification.Server)) is IGuild guild)) { return(false); } var channelToUse = this.Client.GetChannel(Convert.ToUInt64(notification.Channel)) as ITextChannel; // if the channel doesn't exist or we don't have send permissions, try to get the default channel instead. if (!channelToUse?.GetCurrentUserPermissions().SendMessages ?? true) { channelToUse = await guild.GetDefaultChannelAsync() as ITextChannel; // if the default channel couldn't be found or we don't have permissions, then we're SOL. // flag as processed. if (channelToUse == null || !channelToUse.GetCurrentUserPermissions().SendMessages) { return(true); } } var settings = SettingsConfig.GetSettings(notification.Server); // adjust the notification text to disable discord link parsing, if configured to do so if (settings.DisableLinkParsing && notification.Type != NotificationType.Reminder) { notification.Text = Consts.UrlRegex.Replace(notification.Text, new MatchEvaluator((Match urlMatch) => { return($"<{urlMatch.Captures[0]}>"); })); } try { if (notification.Embed != null && settings.HasFlag(notification.Type)) { // TODO: discord handles twitter embeds nicely; should adjust the notification data accordingly so we don't need this explicit check here if (notification.Type == NotificationType.Twitter) { await channelToUse.SendMessageAsync(notification.Embed.Url); } else { var customText = settings.NotificationText.FirstOrDefault(n => n.Type == notification.Type)?.Text; var messageText = string.IsNullOrEmpty(notification.Embed.Url) ? string.Empty : $"<{notification.Embed.Url}>"; if (!string.IsNullOrEmpty(customText)) { messageText += $" {customText}"; } await channelToUse.SendMessageAsync(messageText, false, notification.Embed.CreateEmbedBuilder().Build()); } } else { await channelToUse.SendMessageAsync(notification.Text.SubstringUpTo(Discord.DiscordConfig.MaxMessageSize)); } var props = new Dictionary <string, string> { { "server", notification.Server }, { "channel", notification.Channel }, { "notificationType", notification.Type.ToString() }, }; this.TrackEvent("notificationSent", props); } catch (Exception ex) { // TODO: // Add retry support. string extraData = null; if (notification.Embed != null) { extraData = JsonConvert.SerializeObject(notification.Embed); } Log.Error(ex, $"Failed to send notification {extraData}"); } return(true); }