From 798e2199c424ea606a34207528b414dfc86f2b62 Mon Sep 17 00:00:00 2001 From: tgstation-server Date: Mon, 10 Apr 2023 14:39:37 +0000 Subject: [PATCH] Deploy code docs to GitHub Pages for workflow run 1410 Commit: 9112efa3106050d21a6ed16af4c1abab9363f2c4 --- _discord_provider_8cs_source.html | 1332 +++++++++-------- ...s_1_1_chat_1_1_channel_representation.html | 2 +- ..._components_1_1_chat_1_1_chat_manager.html | 2 +- ...at_1_1_providers_1_1_discord_provider.html | 1324 ++++++++-------- ...s_1_1_chat_1_1_providers_1_1_provider.html | 4 +- ...components_1_1_interop_1_1_chat_embed.html | 14 +- ...nts_1_1_interop_1_1_chat_embed_footer.html | 2 +- ...ents_1_1_interop_1_1_chat_embed_media.html | 2 +- ...s_1_1_interop_1_1_chat_embed_provider.html | 2 +- ...m_1_1_i_assembly_information_provider.html | 2 +- 10 files changed, 1353 insertions(+), 1333 deletions(-) diff --git a/_discord_provider_8cs_source.html b/_discord_provider_8cs_source.html index 783066c418..3337ee2a39 100644 --- a/_discord_provider_8cs_source.html +++ b/_discord_provider_8cs_source.html @@ -237,557 +237,557 @@ $(function() {
224 public override async Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
225 {
226 Optional<IMessageReference> replyToReference = default;
-
227 if (replyTo != null && replyTo is DiscordMessage discordMessage)
-
228 {
-
229 replyToReference = discordMessage.MessageReference;
-
230 }
-
231
-
232 var embeds = ConvertEmbed(message.Embed);
-
233
-
234 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
235 async Task SendToChannel(Snowflake channelId)
-
236 {
-
237 var result = await channelsClient.CreateMessageAsync(
-
238 channelId,
-
239 message.Text,
-
240 embeds: embeds,
-
241 messageReference: replyToReference,
-
242 ct: cancellationToken);
-
243
-
244 if (!result.IsSuccess)
-
245 Logger.LogWarning(
-
246 "Failed to send to channel {0}: {1}",
-
247 channelId,
-
248 result.Error);
-
249 }
-
250
-
251 try
-
252 {
-
253 if (channelId == 0)
-
254 {
-
255 var usersClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
-
256 var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
-
257 if (!currentGuildsResponse.IsSuccess)
-
258 {
-
259 Logger.LogWarning(
-
260 "Error retrieving current discord guilds: {0}",
-
261 currentGuildsResponse.Error.Message);
-
262 return;
-
263 }
-
264
-
265 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
266
-
267 var guildsChannelsTasks = currentGuildsResponse.Entity.Select(
-
268 guild => guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken));
-
269
-
270 await Task.WhenAll(guildsChannelsTasks);
-
271
-
272 var unmappedTextChannels = guildsChannelsTasks
-
273 .Select(task => task.Result)
-
274 .SelectMany(guildChannels => guildChannels.Entity)
-
275 .Where(guildChannel => guildChannel.Type == ChannelType.GuildText);
+
227 Optional<IAllowedMentions> allowedMentions = default;
+
228 if (replyTo != null && replyTo is DiscordMessage discordMessage)
+
229 {
+
230 replyToReference = discordMessage.MessageReference;
+
231 allowedMentions = new AllowedMentions(
+
232 Parse: new List<MentionType> // reset settings back to how discord acts if this is not passed (which is different than the default if empty)
+
233 {
+
234 MentionType.Everyone,
+
235 MentionType.Roles,
+
236 MentionType.Users,
+
237 },
+
238 MentionRepliedUser: false); // disable reply mentions
+
239 }
+
240
+
241 var embeds = ConvertEmbed(message.Embed);
+
242
+
243 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
244 async Task SendToChannel(Snowflake channelId)
+
245 {
+
246 var result = await channelsClient.CreateMessageAsync(
+
247 channelId,
+
248 message.Text,
+
249 embeds: embeds,
+
250 messageReference: replyToReference,
+
251 allowedMentions: allowedMentions,
+
252 ct: cancellationToken);
+
253
+
254 if (!result.IsSuccess)
+
255 Logger.LogWarning(
+
256 "Failed to send to channel {0}: {1}",
+
257 channelId,
+
258 result.Error);
+
259 }
+
260
+
261 try
+
262 {
+
263 if (channelId == 0)
+
264 {
+
265 var usersClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
+
266 var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
+
267 if (!currentGuildsResponse.IsSuccess)
+
268 {
+
269 Logger.LogWarning(
+
270 "Error retrieving current discord guilds: {0}",
+
271 currentGuildsResponse.Error.Message);
+
272 return;
+
273 }
+
274
+
275 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
276
-
277 lock (mappedChannels)
-
278 unmappedTextChannels = unmappedTextChannels
-
279 .Where(x => !mappedChannels.Contains(x.ID.Value))
-
280 .ToList();
+
277 var guildsChannelsTasks = currentGuildsResponse.Entity.Select(
+
278 guild => guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken));
+
279
+
280 await Task.WhenAll(guildsChannelsTasks);
281
-
282 // discord API confirmed weak boned: https://stackoverflow.com/a/52462336
-
283 if (unmappedTextChannels.Any())
-
284 {
-
285 Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
-
286 await Task.WhenAll(
-
287 unmappedTextChannels.Select(
-
288 x => SendToChannel(x.ID)));
-
289 }
-
290
-
291 return;
-
292 }
-
293
-
294 await SendToChannel(new Snowflake(channelId));
-
295 }
-
296 catch (Exception e)
-
297 {
-
298 if (e is OperationCanceledException)
-
299 cancellationToken.ThrowIfCancellationRequested();
-
300 Logger.LogWarning(e, "Error sending discord message!");
-
301 }
-
302 }
+
282 var unmappedTextChannels = guildsChannelsTasks
+
283 .Select(task => task.Result)
+
284 .SelectMany(guildChannels => guildChannels.Entity)
+
285 .Where(guildChannel => guildChannel.Type == ChannelType.GuildText);
+
286
+
287 lock (mappedChannels)
+
288 unmappedTextChannels = unmappedTextChannels
+
289 .Where(x => !mappedChannels.Contains(x.ID.Value))
+
290 .ToList();
+
291
+
292 // discord API confirmed weak boned: https://stackoverflow.com/a/52462336
+
293 if (unmappedTextChannels.Any())
+
294 {
+
295 Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
+
296 await Task.WhenAll(
+
297 unmappedTextChannels.Select(
+
298 x => SendToChannel(x.ID)));
+
299 }
+
300
+
301 return;
+
302 }
303
-
305 public override async Task<Func<string, string, Task>> SendUpdateMessage(
-
306 Models.RevisionInformation revisionInformation,
-
307 Version byondVersion,
-
308 DateTimeOffset? estimatedCompletionTime,
-
309 string gitHubOwner,
-
310 string gitHubRepo,
-
311 ulong channelId,
-
312 bool localCommitPushed,
-
313 CancellationToken cancellationToken)
-
314 {
-
315 localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
-
316
-
317 var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
-
318 var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
-
319 {
-
320 Url = "https://github.com/tgstation/tgstation-server",
-
321 IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
-
322 };
-
323 var embed = new Embed
-
324 {
-
325 Author = deploymentBranding ? author : default,
-
326 Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
-
327 Description = "TGS has begun deploying active repository code to production.",
-
328 Fields = fields,
-
329 Title = "Code Deployment",
-
330 Footer = new EmbedFooter(
-
331 $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"),
-
332 Timestamp = estimatedCompletionTime ?? default,
-
333 };
-
334
-
335 Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
-
336 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
337
-
338 var messageResponse = await channelsClient.CreateMessageAsync(
-
339 new Snowflake(channelId),
-
340 "DM: Deployment in Progress...",
-
341 embeds: new List<IEmbed> { embed },
-
342 ct: cancellationToken)
-
343 ;
+
304 await SendToChannel(new Snowflake(channelId));
+
305 }
+
306 catch (Exception e)
+
307 {
+
308 if (e is OperationCanceledException)
+
309 cancellationToken.ThrowIfCancellationRequested();
+
310 Logger.LogWarning(e, "Error sending discord message!");
+
311 }
+
312 }
+
313
+
315 public override async Task<Func<string, string, Task>> SendUpdateMessage(
+
316 Models.RevisionInformation revisionInformation,
+
317 Version byondVersion,
+
318 DateTimeOffset? estimatedCompletionTime,
+
319 string gitHubOwner,
+
320 string gitHubRepo,
+
321 ulong channelId,
+
322 bool localCommitPushed,
+
323 CancellationToken cancellationToken)
+
324 {
+
325 localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
+
326
+
327 var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
+
328 var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
+
329 {
+
330 Url = "https://github.com/tgstation/tgstation-server",
+
331 IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
+
332 };
+
333 var embed = new Embed
+
334 {
+
335 Author = deploymentBranding ? author : default,
+
336 Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
+
337 Description = "TGS has begun deploying active repository code to production.",
+
338 Fields = fields,
+
339 Title = "Code Deployment",
+
340 Footer = new EmbedFooter(
+
341 $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"),
+
342 Timestamp = estimatedCompletionTime ?? default,
+
343 };
344
-
345 if (!messageResponse.IsSuccess)
-
346 Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+
345 Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
+
346 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
347
-
348 return async (errorMessage, dreamMakerOutput) =>
-
349 {
-
350 var completionString = errorMessage == null ? "Succeeded" : "Failed";
-
351
-
352 embed = new Embed
-
353 {
-
354 Author = embed.Author,
-
355 Colour = errorMessage == null ? Color.Green : Color.Red,
-
356 Description = errorMessage == null
-
357 ? "The deployment completed successfully and will be available at the next server reboot."
-
358 : "The deployment failed.",
-
359 Fields = fields,
-
360 Title = embed.Title,
-
361 Footer = new EmbedFooter(
-
362 completionString),
-
363 Timestamp = DateTimeOffset.UtcNow,
-
364 };
-
365
-
366 var showDMOutput = outputDisplayType switch
-
367 {
-
368 DiscordDMOutputDisplayType.Always => true,
-
369 DiscordDMOutputDisplayType.Never => false,
-
370 DiscordDMOutputDisplayType.OnError => errorMessage != null,
-
371 _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"),
-
372 };
-
373
-
374 if (dreamMakerOutput != null)
-
375 {
-
376 // https://github.com/discord-net/Discord.Net/blob/8349cd7e1eb92e9a3baff68082c30a7b43e8e9b7/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs#L431
-
377 const int MaxFieldValueLength = 1024;
-
378 showDMOutput = showDMOutput && dreamMakerOutput.Length < MaxFieldValueLength - (6 + Environment.NewLine.Length);
-
379 if (showDMOutput)
-
380 fields.Add(new EmbedField(
-
381 "DreamMaker Output",
-
382 $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
-
383 false));
-
384 }
-
385
-
386 if (errorMessage != null)
-
387 fields.Add(new EmbedField(
-
388 "Error Message",
-
389 errorMessage,
-
390 false));
-
391
-
392 var updatedMessage = $"DM: Deployment {completionString}!";
-
393
-
394 async Task CreateUpdatedMessage()
-
395 {
-
396 var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
-
397 new Snowflake(channelId),
-
398 updatedMessage,
-
399 embeds: new List<IEmbed> { embed },
-
400 ct: cancellationToken)
-
401 ;
-
402
-
403 if (!createUpdatedMessageResponse.IsSuccess)
-
404 Logger.LogWarning(
-
405 "Creating updated deploy embed failed! Error: {0}",
-
406 createUpdatedMessageResponse.Error.Message);
-
407 }
-
408
-
409 if (!messageResponse.IsSuccess)
-
410 await CreateUpdatedMessage();
-
411 else
-
412 {
-
413 var editResponse = await channelsClient.EditMessageAsync(
-
414 new Snowflake(channelId),
-
415 messageResponse.Entity.ID,
-
416 updatedMessage,
-
417 embeds: new List<IEmbed> { embed },
-
418 ct: cancellationToken)
-
419 ;
-
420
-
421 if (!editResponse.IsSuccess)
-
422 {
-
423 Logger.LogWarning(
-
424 "Updating deploy embed {0} failed, attempting new post! Error: {1}",
-
425 messageResponse.Entity.ID,
-
426 editResponse.Error.Message);
-
427 await CreateUpdatedMessage();
-
428 }
-
429 }
-
430 };
-
431 }
-
432
-
434 public async Task<Result> RespondAsync(IMessageCreate messageCreateEvent, CancellationToken cancellationToken)
-
435 {
-
436 if (messageCreateEvent == null)
-
437 throw new ArgumentNullException(nameof(messageCreateEvent));
-
438
-
439 if ((messageCreateEvent.Type != MessageType.Default
-
440 && messageCreateEvent.Type != MessageType.InlineReply)
-
441 || messageCreateEvent.Author.ID == currentUserId)
-
442 return Result.FromSuccess();
-
443
-
444 var messageReference = new MessageReference
-
445 {
-
446 ChannelID = messageCreateEvent.ChannelID,
-
447 GuildID = messageCreateEvent.GuildID,
-
448 MessageID = messageCreateEvent.ID,
-
449 FailIfNotExists = false,
-
450 };
-
451
-
452 if (basedMeme && messageCreateEvent.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
-
453 {
-
454 // DCT: None available
-
455 await SendMessage(
-
456 new DiscordMessage
-
457 {
-
458 MessageReference = messageReference,
-
459 },
-
460 new MessageContent
-
461 {
-
462 Text = "https://youtu.be/LrNu-SuFF_o",
-
463 },
-
464 messageCreateEvent.ChannelID.Value,
-
465 default);
-
466 return Result.FromSuccess();
-
467 }
-
468
-
469 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
470 var channelResponse = await channelsClient.GetChannelAsync(messageCreateEvent.ChannelID, cancellationToken);
-
471 if (!channelResponse.IsSuccess)
-
472 {
-
473 Logger.LogWarning(
-
474 "Failed to get channel {0} in response to message {1}!",
-
475 messageCreateEvent.ChannelID,
-
476 messageCreateEvent.ID);
-
477
-
478 // we'll handle the errors ourselves
-
479 return Result.FromSuccess();
-
480 }
-
481
-
482 var pm = channelResponse.Entity.Type == ChannelType.DM || channelResponse.Entity.Type == ChannelType.GroupDM;
-
483 var shouldNotAnswer = !pm;
-
484 if (shouldNotAnswer)
-
485 lock (mappedChannels)
-
486 shouldNotAnswer = !mappedChannels.Contains(messageCreateEvent.ChannelID.Value) && !mappedChannels.Contains(0);
+
348 var messageResponse = await channelsClient.CreateMessageAsync(
+
349 new Snowflake(channelId),
+
350 "DM: Deployment in Progress...",
+
351 embeds: new List<IEmbed> { embed },
+
352 ct: cancellationToken)
+
353 ;
+
354
+
355 if (!messageResponse.IsSuccess)
+
356 Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+
357
+
358 return async (errorMessage, dreamMakerOutput) =>
+
359 {
+
360 var completionString = errorMessage == null ? "Succeeded" : "Failed";
+
361
+
362 embed = new Embed
+
363 {
+
364 Author = embed.Author,
+
365 Colour = errorMessage == null ? Color.Green : Color.Red,
+
366 Description = errorMessage == null
+
367 ? "The deployment completed successfully and will be available at the next server reboot."
+
368 : "The deployment failed.",
+
369 Fields = fields,
+
370 Title = embed.Title,
+
371 Footer = new EmbedFooter(
+
372 completionString),
+
373 Timestamp = DateTimeOffset.UtcNow,
+
374 };
+
375
+
376 var showDMOutput = outputDisplayType switch
+
377 {
+
378 DiscordDMOutputDisplayType.Always => true,
+
379 DiscordDMOutputDisplayType.Never => false,
+
380 DiscordDMOutputDisplayType.OnError => errorMessage != null,
+
381 _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"),
+
382 };
+
383
+
384 if (dreamMakerOutput != null)
+
385 {
+
386 // https://github.com/discord-net/Discord.Net/blob/8349cd7e1eb92e9a3baff68082c30a7b43e8e9b7/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs#L431
+
387 const int MaxFieldValueLength = 1024;
+
388 showDMOutput = showDMOutput && dreamMakerOutput.Length < MaxFieldValueLength - (6 + Environment.NewLine.Length);
+
389 if (showDMOutput)
+
390 fields.Add(new EmbedField(
+
391 "DreamMaker Output",
+
392 $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
+
393 false));
+
394 }
+
395
+
396 if (errorMessage != null)
+
397 fields.Add(new EmbedField(
+
398 "Error Message",
+
399 errorMessage,
+
400 false));
+
401
+
402 var updatedMessage = $"DM: Deployment {completionString}!";
+
403
+
404 async Task CreateUpdatedMessage()
+
405 {
+
406 var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
+
407 new Snowflake(channelId),
+
408 updatedMessage,
+
409 embeds: new List<IEmbed> { embed },
+
410 ct: cancellationToken)
+
411 ;
+
412
+
413 if (!createUpdatedMessageResponse.IsSuccess)
+
414 Logger.LogWarning(
+
415 "Creating updated deploy embed failed! Error: {0}",
+
416 createUpdatedMessageResponse.Error.Message);
+
417 }
+
418
+
419 if (!messageResponse.IsSuccess)
+
420 await CreateUpdatedMessage();
+
421 else
+
422 {
+
423 var editResponse = await channelsClient.EditMessageAsync(
+
424 new Snowflake(channelId),
+
425 messageResponse.Entity.ID,
+
426 updatedMessage,
+
427 embeds: new List<IEmbed> { embed },
+
428 ct: cancellationToken)
+
429 ;
+
430
+
431 if (!editResponse.IsSuccess)
+
432 {
+
433 Logger.LogWarning(
+
434 "Updating deploy embed {0} failed, attempting new post! Error: {1}",
+
435 messageResponse.Entity.ID,
+
436 editResponse.Error.Message);
+
437 await CreateUpdatedMessage();
+
438 }
+
439 }
+
440 };
+
441 }
+
442
+
444 public async Task<Result> RespondAsync(IMessageCreate messageCreateEvent, CancellationToken cancellationToken)
+
445 {
+
446 if (messageCreateEvent == null)
+
447 throw new ArgumentNullException(nameof(messageCreateEvent));
+
448
+
449 if ((messageCreateEvent.Type != MessageType.Default
+
450 && messageCreateEvent.Type != MessageType.InlineReply)
+
451 || messageCreateEvent.Author.ID == currentUserId)
+
452 return Result.FromSuccess();
+
453
+
454 var messageReference = new MessageReference
+
455 {
+
456 ChannelID = messageCreateEvent.ChannelID,
+
457 GuildID = messageCreateEvent.GuildID,
+
458 MessageID = messageCreateEvent.ID,
+
459 FailIfNotExists = false,
+
460 };
+
461
+
462 if (basedMeme && messageCreateEvent.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
+
463 {
+
464 // DCT: None available
+
465 await SendMessage(
+
466 new DiscordMessage
+
467 {
+
468 MessageReference = messageReference,
+
469 },
+
470 new MessageContent
+
471 {
+
472 Text = "https://youtu.be/LrNu-SuFF_o",
+
473 },
+
474 messageCreateEvent.ChannelID.Value,
+
475 default);
+
476 return Result.FromSuccess();
+
477 }
+
478
+
479 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
480 var channelResponse = await channelsClient.GetChannelAsync(messageCreateEvent.ChannelID, cancellationToken);
+
481 if (!channelResponse.IsSuccess)
+
482 {
+
483 Logger.LogWarning(
+
484 "Failed to get channel {0} in response to message {1}!",
+
485 messageCreateEvent.ChannelID,
+
486 messageCreateEvent.ID);
487
-
488 var content = NormalizeMentions(messageCreateEvent.Content);
-
489 var mentionedUs = messageCreateEvent.Mentions.Any(x => x.ID == currentUserId)
-
490 || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
+
488 // we'll handle the errors ourselves
+
489 return Result.FromSuccess();
+
490 }
491
-
492 if (shouldNotAnswer)
-
493 {
-
494 if (mentionedUs)
-
495 Logger.LogTrace(
-
496 "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
-
497 messageCreateEvent.ChannelID,
-
498 channelResponse.Entity.Name,
-
499 messageCreateEvent.Author.ID,
-
500 messageCreateEvent.Author.Username);
+
492 var pm = channelResponse.Entity.Type == ChannelType.DM || channelResponse.Entity.Type == ChannelType.GroupDM;
+
493 var shouldNotAnswer = !pm;
+
494 if (shouldNotAnswer)
+
495 lock (mappedChannels)
+
496 shouldNotAnswer = !mappedChannels.Contains(messageCreateEvent.ChannelID.Value) && !mappedChannels.Contains(0);
+
497
+
498 var content = NormalizeMentions(messageCreateEvent.Content);
+
499 var mentionedUs = messageCreateEvent.Mentions.Any(x => x.ID == currentUserId)
+
500 || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
501
-
502 return Result.FromSuccess();
-
503 }
-
504
-
505 string guildName = "UNKNOWN";
-
506 if (!pm)
-
507 {
-
508 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
509 var messageGuildResponse = await guildsClient.GetGuildAsync(messageCreateEvent.GuildID.Value, false, cancellationToken);
-
510 if (messageGuildResponse.IsSuccess)
-
511 guildName = messageGuildResponse.Entity.Name;
-
512 else
-
513 Logger.LogWarning(
-
514 "Failed to get channel {0} in response to message {1}!",
-
515 messageCreateEvent.ChannelID,
-
516 messageCreateEvent.ID);
-
517 }
-
518
-
519 var result = new DiscordMessage
-
520 {
-
521 MessageReference = messageReference,
-
522 Content = content,
-
523 User = new ChatUser
-
524 {
-
525 RealId = messageCreateEvent.Author.ID.Value,
-
526 Channel = new ChannelRepresentation
-
527 {
-
528 RealId = messageCreateEvent.ChannelID.Value,
-
529 IsPrivateChannel = pm,
-
530 ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
-
531 FriendlyName = channelResponse.Entity.Name.Value,
-
532 EmbedsSupported = true,
-
533
-
534 // isAdmin and Tag populated by manager
-
535 },
-
536 FriendlyName = messageCreateEvent.Author.Username,
-
537 Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
-
538 },
-
539 };
-
540
-
541 EnqueueMessage(result);
-
542 return Result.FromSuccess();
-
543 }
-
544
-
546 public Task<Result> RespondAsync(IReady readyEvent, CancellationToken cancellationToken)
-
547 {
-
548 if (readyEvent == null)
-
549 throw new ArgumentNullException(nameof(readyEvent));
+
502 if (shouldNotAnswer)
+
503 {
+
504 if (mentionedUs)
+
505 Logger.LogTrace(
+
506 "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
+
507 messageCreateEvent.ChannelID,
+
508 channelResponse.Entity.Name,
+
509 messageCreateEvent.Author.ID,
+
510 messageCreateEvent.Author.Username);
+
511
+
512 return Result.FromSuccess();
+
513 }
+
514
+
515 string guildName = "UNKNOWN";
+
516 if (!pm)
+
517 {
+
518 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
+
519 var messageGuildResponse = await guildsClient.GetGuildAsync(messageCreateEvent.GuildID.Value, false, cancellationToken);
+
520 if (messageGuildResponse.IsSuccess)
+
521 guildName = messageGuildResponse.Entity.Name;
+
522 else
+
523 Logger.LogWarning(
+
524 "Failed to get channel {0} in response to message {1}!",
+
525 messageCreateEvent.ChannelID,
+
526 messageCreateEvent.ID);
+
527 }
+
528
+
529 var result = new DiscordMessage
+
530 {
+
531 MessageReference = messageReference,
+
532 Content = content,
+
533 User = new ChatUser
+
534 {
+
535 RealId = messageCreateEvent.Author.ID.Value,
+
536 Channel = new ChannelRepresentation
+
537 {
+
538 RealId = messageCreateEvent.ChannelID.Value,
+
539 IsPrivateChannel = pm,
+
540 ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
+
541 FriendlyName = channelResponse.Entity.Name.Value,
+
542 EmbedsSupported = true,
+
543
+
544 // isAdmin and Tag populated by manager
+
545 },
+
546 FriendlyName = messageCreateEvent.Author.Username,
+
547 Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
+
548 },
+
549 };
550
-
551 Logger.LogTrace("Gatway ready. Version: {version}", readyEvent.Version);
-
552 gatewayReadyTcs?.TrySetResult(null);
-
553 return Task.FromResult(Result.FromSuccess());
-
554 }
-
555
-
557 protected override async Task Connect(CancellationToken cancellationToken)
-
558 {
-
559 try
-
560 {
-
561 lock (connectDisconnectLock)
-
562 {
-
563 if (gatewayCts != null)
-
564 throw new InvalidOperationException("Discord gateway still active!");
+
551 EnqueueMessage(result);
+
552 return Result.FromSuccess();
+
553 }
+
554
+
556 public Task<Result> RespondAsync(IReady readyEvent, CancellationToken cancellationToken)
+
557 {
+
558 if (readyEvent == null)
+
559 throw new ArgumentNullException(nameof(readyEvent));
+
560
+
561 Logger.LogTrace("Gatway ready. Version: {version}", readyEvent.Version);
+
562 gatewayReadyTcs?.TrySetResult(null);
+
563 return Task.FromResult(Result.FromSuccess());
+
564 }
565
-
566 gatewayCts = new CancellationTokenSource();
-
567 }
-
568
-
569 var gatewayCancellationToken = gatewayCts.Token;
-
570 var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
-
571
-
572 Task<Result> localGatewayTask;
-
573 gatewayReadyTcs = new TaskCompletionSource<object>();
-
574
-
575 using var gatewayConnectionAbortRegistration = cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled());
-
576 gatewayCancellationToken.Register(() => Logger.LogTrace("Stopping gateway client..."));
-
577
-
578 // reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
-
579 localGatewayTask = gatewayClient.RunAsync(gatewayCancellationToken);
-
580 try
-
581 {
-
582 await Task.WhenAny(gatewayReadyTcs.Task, localGatewayTask);
-
583
-
584 if (localGatewayTask.IsCompleted || cancellationToken.IsCancellationRequested)
-
585 throw new JobException(ErrorCode.ChatCannotConnectProvider);
-
586
-
587 var userClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
-
588
-
589 using var localCombinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, gatewayCancellationToken);
-
590 var currentUserResult = await userClient.GetCurrentUserAsync(localCombinedCts.Token);
-
591 if (!currentUserResult.IsSuccess)
-
592 {
-
593 Logger.LogWarning("Unable to retrieve current user: {0}", currentUserResult.Error.Message);
-
594 throw new JobException(ErrorCode.ChatCannotConnectProvider);
-
595 }
+
567 protected override async Task Connect(CancellationToken cancellationToken)
+
568 {
+
569 try
+
570 {
+
571 lock (connectDisconnectLock)
+
572 {
+
573 if (gatewayCts != null)
+
574 throw new InvalidOperationException("Discord gateway still active!");
+
575
+
576 gatewayCts = new CancellationTokenSource();
+
577 }
+
578
+
579 var gatewayCancellationToken = gatewayCts.Token;
+
580 var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
+
581
+
582 Task<Result> localGatewayTask;
+
583 gatewayReadyTcs = new TaskCompletionSource<object>();
+
584
+
585 using var gatewayConnectionAbortRegistration = cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled());
+
586 gatewayCancellationToken.Register(() => Logger.LogTrace("Stopping gateway client..."));
+
587
+
588 // reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
+
589 localGatewayTask = gatewayClient.RunAsync(gatewayCancellationToken);
+
590 try
+
591 {
+
592 await Task.WhenAny(gatewayReadyTcs.Task, localGatewayTask);
+
593
+
594 if (localGatewayTask.IsCompleted || cancellationToken.IsCancellationRequested)
+
595 throw new JobException(ErrorCode.ChatCannotConnectProvider);
596
-
597 currentUserId = currentUserResult.Entity.ID;
-
598 initialUserName = currentUserResult.Entity.Username;
-
599 }
-
600 finally
-
601 {
-
602 gatewayTask = localGatewayTask;
-
603 }
-
604 }
-
605 catch
-
606 {
-
607 // will handle cleanup
-
608 // DCT: Musn't abort
-
609 await DisconnectImpl(default);
-
610 throw;
-
611 }
-
612 }
-
613
-
615 protected override async Task DisconnectImpl(CancellationToken cancellationToken)
-
616 {
-
617 Task<Result> localGatewayTask;
-
618 CancellationTokenSource localGatewayCts;
-
619 lock (connectDisconnectLock)
-
620 {
-
621 localGatewayTask = gatewayTask;
-
622 localGatewayCts = gatewayCts;
-
623 gatewayTask = null;
-
624 gatewayCts = null;
-
625 if (localGatewayTask == null)
-
626 return;
-
627 }
-
628
-
629 localGatewayCts.Cancel();
-
630 var gatewayResult = await localGatewayTask;
-
631 if (!gatewayResult.IsSuccess)
-
632 Logger.LogWarning("Gateway issue: {0}", gatewayResult.Error.Message);
-
633
-
634 localGatewayCts.Dispose();
-
635 }
-
636
-
638 protected override async Task<IReadOnlyCollection<Tuple<Models.ChatChannel, ChannelRepresentation>>> MapChannelsImpl(IEnumerable<Models.ChatChannel> channels, CancellationToken cancellationToken)
-
639 {
-
640 if (channels == null)
-
641 throw new ArgumentNullException(nameof(channels));
-
642
-
643 var remapRequired = false;
-
644
-
645 async Task<Tuple<Models.ChatChannel, ChannelRepresentation>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
-
646 {
-
647 if (!channelFromDB.DiscordChannelId.HasValue)
-
648 throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
-
649
-
650 var channelId = channelFromDB.DiscordChannelId.Value;
-
651 string connectionName;
-
652 string friendlyName;
-
653 if (channelId == 0)
-
654 {
-
655 connectionName = initialUserName;
-
656 friendlyName = "(Unmapped accessible channels)";
-
657 }
-
658 else
-
659 {
-
660 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
661 var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
-
662 if (!discordChannelResponse.IsSuccess)
-
663 {
-
664 Logger.LogWarning(
-
665 "Error retrieving discord channel {channelId}: {error} Inner: {innerError}",
-
666 channelId,
-
667 discordChannelResponse.Error.Message,
-
668 discordChannelResponse.Inner?.Error?.Message);
-
669 remapRequired = true;
-
670 return null;
-
671 }
-
672
-
673 var channelType = discordChannelResponse.Entity.Type;
-
674 if (channelType != ChannelType.GuildText && channelType != ChannelType.GuildAnnouncement)
-
675 {
-
676 Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
-
677 return null;
-
678 }
-
679
-
680 friendlyName = discordChannelResponse.Entity.Name.Value;
-
681 var guildId = discordChannelResponse.Entity.GuildID.Value;
+
597 var userClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
+
598
+
599 using var localCombinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, gatewayCancellationToken);
+
600 var currentUserResult = await userClient.GetCurrentUserAsync(localCombinedCts.Token);
+
601 if (!currentUserResult.IsSuccess)
+
602 {
+
603 Logger.LogWarning("Unable to retrieve current user: {0}", currentUserResult.Error.Message);
+
604 throw new JobException(ErrorCode.ChatCannotConnectProvider);
+
605 }
+
606
+
607 currentUserId = currentUserResult.Entity.ID;
+
608 initialUserName = currentUserResult.Entity.Username;
+
609 }
+
610 finally
+
611 {
+
612 gatewayTask = localGatewayTask;
+
613 }
+
614 }
+
615 catch
+
616 {
+
617 // will handle cleanup
+
618 // DCT: Musn't abort
+
619 await DisconnectImpl(default);
+
620 throw;
+
621 }
+
622 }
+
623
+
625 protected override async Task DisconnectImpl(CancellationToken cancellationToken)
+
626 {
+
627 Task<Result> localGatewayTask;
+
628 CancellationTokenSource localGatewayCts;
+
629 lock (connectDisconnectLock)
+
630 {
+
631 localGatewayTask = gatewayTask;
+
632 localGatewayCts = gatewayCts;
+
633 gatewayTask = null;
+
634 gatewayCts = null;
+
635 if (localGatewayTask == null)
+
636 return;
+
637 }
+
638
+
639 localGatewayCts.Cancel();
+
640 var gatewayResult = await localGatewayTask;
+
641 if (!gatewayResult.IsSuccess)
+
642 Logger.LogWarning("Gateway issue: {0}", gatewayResult.Error.Message);
+
643
+
644 localGatewayCts.Dispose();
+
645 }
+
646
+
648 protected override async Task<IReadOnlyCollection<Tuple<Models.ChatChannel, ChannelRepresentation>>> MapChannelsImpl(IEnumerable<Models.ChatChannel> channels, CancellationToken cancellationToken)
+
649 {
+
650 if (channels == null)
+
651 throw new ArgumentNullException(nameof(channels));
+
652
+
653 var remapRequired = false;
+
654
+
655 async Task<Tuple<Models.ChatChannel, ChannelRepresentation>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
+
656 {
+
657 if (!channelFromDB.DiscordChannelId.HasValue)
+
658 throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
+
659
+
660 var channelId = channelFromDB.DiscordChannelId.Value;
+
661 string connectionName;
+
662 string friendlyName;
+
663 if (channelId == 0)
+
664 {
+
665 connectionName = initialUserName;
+
666 friendlyName = "(Unmapped accessible channels)";
+
667 }
+
668 else
+
669 {
+
670 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
671 var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
+
672 if (!discordChannelResponse.IsSuccess)
+
673 {
+
674 Logger.LogWarning(
+
675 "Error retrieving discord channel {channelId}: {error} Inner: {innerError}",
+
676 channelId,
+
677 discordChannelResponse.Error.Message,
+
678 discordChannelResponse.Inner?.Error?.Message);
+
679 remapRequired = true;
+
680 return null;
+
681 }
682
-
683 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
684 var guildsResponse = await guildsClient.GetGuildAsync(
-
685 guildId,
-
686 false,
-
687 cancellationToken);
-
688 if (!guildsResponse.IsSuccess)
-
689 {
-
690 Logger.LogWarning(
-
691 "Error retrieving discord guild {guildID}: {error} Inner: {innerError}",
-
692 guildId,
-
693 guildsResponse.Error.Message,
-
694 guildsResponse.Inner?.Error?.Message);
-
695 remapRequired = true;
-
696 return null;
-
697 }
-
698
-
699 connectionName = guildsResponse.Entity.Name;
-
700 }
-
701
-
702 var channelModel = new ChannelRepresentation
-
703 {
-
704 RealId = channelId,
-
705 IsAdminChannel = channelFromDB.IsAdminChannel == true,
-
706 ConnectionName = connectionName,
-
707 FriendlyName = friendlyName,
-
708 IsPrivateChannel = false,
-
709 Tag = channelFromDB.Tag,
-
710 EmbedsSupported = true,
-
711 };
-
712
-
713 Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
-
714 return Tuple.Create(channelFromDB, channelModel);
-
715 }
-
716
-
717 var tasks = channels
-
718 .Select(x => GetModelChannelFromDBChannel(x))
-
719 .ToList();
-
720
-
721 await Task.WhenAll(tasks);
+
683 var channelType = discordChannelResponse.Entity.Type;
+
684 if (channelType != ChannelType.GuildText && channelType != ChannelType.GuildAnnouncement)
+
685 {
+
686 Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
+
687 return null;
+
688 }
+
689
+
690 friendlyName = discordChannelResponse.Entity.Name.Value;
+
691 var guildId = discordChannelResponse.Entity.GuildID.Value;
+
692
+
693 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
+
694 var guildsResponse = await guildsClient.GetGuildAsync(
+
695 guildId,
+
696 false,
+
697 cancellationToken);
+
698 if (!guildsResponse.IsSuccess)
+
699 {
+
700 Logger.LogWarning(
+
701 "Error retrieving discord guild {guildID}: {error} Inner: {innerError}",
+
702 guildId,
+
703 guildsResponse.Error.Message,
+
704 guildsResponse.Inner?.Error?.Message);
+
705 remapRequired = true;
+
706 return null;
+
707 }
+
708
+
709 connectionName = guildsResponse.Entity.Name;
+
710 }
+
711
+
712 var channelModel = new ChannelRepresentation
+
713 {
+
714 RealId = channelId,
+
715 IsAdminChannel = channelFromDB.IsAdminChannel == true,
+
716 ConnectionName = connectionName,
+
717 FriendlyName = friendlyName,
+
718 IsPrivateChannel = false,
+
719 Tag = channelFromDB.Tag,
+
720 EmbedsSupported = true,
+
721 };
722
-
723 var enumerator = tasks
-
724 .Select(x => x.Result)
-
725 .Where(x => x != null)
-
726 .ToList();
-
727
-
728 lock (mappedChannels)
-
729 {
-
730 mappedChannels.Clear();
-
731 mappedChannels.AddRange(enumerator.Select(x => x.Item2.RealId));
-
732 }
-
733
-
734 if (remapRequired)
-
735 EnqueueMessage(null);
-
736
-
737 return enumerator;
-
738 }
-
739
-
745 #pragma warning disable CA1502
-
746 private Optional<IReadOnlyList<IEmbed>> ConvertEmbed(ChatEmbed embed)
-
747 {
-
748 if (embed == null)
-
749 return default;
-
750
-
751 List<string> embedErrors = new List<string>();
-
752 Optional<Color> colour = default;
-
753 if (embed.Colour != null)
-
754 if (Int32.TryParse(embed.Colour.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
-
755 colour = Color.FromArgb(argb);
-
756 else
-
757 embedErrors.Add(
-
758 String.Format(
-
759 CultureInfo.InvariantCulture,
-
760 "Invalid embed colour: {0}",
-
761 embed.Colour));
-
762
-
763 if (embed.Author != null && String.IsNullOrWhiteSpace(embed.Author.Name))
-
764 {
-
765 embedErrors.Add("Null or whitespace embed author name!");
-
766 embed.Author = null;
-
767 }
-
768
-
769 List<IEmbedField> fields = null;
-
770 if (embed.Fields != null)
-
771 {
-
772 fields = new List<IEmbedField>();
-
773 var i = -1;
-
774 foreach (var field in embed.Fields)
-
775 {
-
776 ++i;
-
777 var invalid = false;
-
778 if (String.IsNullOrWhiteSpace(field.Name))
-
779 {
-
780 embedErrors.Add(
-
781 String.Format(
-
782 CultureInfo.InvariantCulture,
-
783 "Null or whitespace field author at index {0}!",
-
784 i));
-
785 invalid = true;
-
786 }
-
787
-
788 if (String.IsNullOrWhiteSpace(field.Value))
+
723 Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
+
724 return Tuple.Create(channelFromDB, channelModel);
+
725 }
+
726
+
727 var tasks = channels
+
728 .Select(x => GetModelChannelFromDBChannel(x))
+
729 .ToList();
+
730
+
731 await Task.WhenAll(tasks);
+
732
+
733 var enumerator = tasks
+
734 .Select(x => x.Result)
+
735 .Where(x => x != null)
+
736 .ToList();
+
737
+
738 lock (mappedChannels)
+
739 {
+
740 mappedChannels.Clear();
+
741 mappedChannels.AddRange(enumerator.Select(x => x.Item2.RealId));
+
742 }
+
743
+
744 if (remapRequired)
+
745 EnqueueMessage(null);
+
746
+
747 return enumerator;
+
748 }
+
749
+
755 #pragma warning disable CA1502
+
756 private Optional<IReadOnlyList<IEmbed>> ConvertEmbed(ChatEmbed embed)
+
757 {
+
758 if (embed == null)
+
759 return default;
+
760
+
761 List<string> embedErrors = new List<string>();
+
762 Optional<Color> colour = default;
+
763 if (embed.Colour != null)
+
764 if (Int32.TryParse(embed.Colour.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
+
765 colour = Color.FromArgb(argb);
+
766 else
+
767 embedErrors.Add(
+
768 String.Format(
+
769 CultureInfo.InvariantCulture,
+
770 "Invalid embed colour: {0}",
+
771 embed.Colour));
+
772
+
773 if (embed.Author != null && String.IsNullOrWhiteSpace(embed.Author.Name))
+
774 {
+
775 embedErrors.Add("Null or whitespace embed author name!");
+
776 embed.Author = null;
+
777 }
+
778
+
779 List<IEmbedField> fields = null;
+
780 if (embed.Fields != null)
+
781 {
+
782 fields = new List<IEmbedField>();
+
783 var i = -1;
+
784 foreach (var field in embed.Fields)
+
785 {
+
786 ++i;
+
787 var invalid = false;
+
788 if (String.IsNullOrWhiteSpace(field.Name))
789 {
790 embedErrors.Add(
791 String.Format(
@@ -797,124 +797,134 @@ $(function() {
795 invalid = true;
796 }
797
-
798 if (invalid)
-
799 continue;
-
800
-
801 fields.Add(new EmbedField(field.Name, field.Value)
-
802 {
-
803 IsInline = field.IsInline ?? default(Optional<bool>),
-
804 });
-
805 }
-
806 }
+
798 if (String.IsNullOrWhiteSpace(field.Value))
+
799 {
+
800 embedErrors.Add(
+
801 String.Format(
+
802 CultureInfo.InvariantCulture,
+
803 "Null or whitespace field author at index {0}!",
+
804 i));
+
805 invalid = true;
+
806 }
807
-
808 if (embed.Footer != null && String.IsNullOrWhiteSpace(embed.Footer.Text))
-
809 {
-
810 embedErrors.Add("Null or whitespace embed footer text!");
-
811 embed.Footer = null;
-
812 }
-
813
-
814 if (embed.Image != null && String.IsNullOrWhiteSpace(embed.Image.Url))
-
815 {
-
816 embedErrors.Add("Null or whitespace embed image url!");
-
817 embed.Image = null;
-
818 }
-
819
-
820 if (embed.Thumbnail != null && String.IsNullOrWhiteSpace(embed.Thumbnail.Url))
-
821 {
-
822 embedErrors.Add("Null or whitespace embed thumbnail url!");
-
823 embed.Thumbnail = null;
-
824 }
-
825
-
826 Optional<DateTimeOffset> timestampOptional = default;
-
827 if (embed.Timestamp != null)
-
828 if (DateTimeOffset.TryParse(embed.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var timestamp))
-
829 timestampOptional = timestamp.ToUniversalTime();
-
830 else
-
831 embedErrors.Add(
-
832 String.Format(
-
833 CultureInfo.InvariantCulture,
-
834 "Invalid embed timestamp: {0}",
-
835 embed.Timestamp));
-
836
-
837 var discordEmbed = new Embed
-
838 {
-
839 Author = embed.Author != null
-
840 ? new EmbedAuthor(embed.Author.Name)
-
841 {
-
842 IconUrl = embed.Author.IconUrl ?? default(Optional<string>),
-
843 ProxyIconUrl = embed.Author.ProxyIconUrl ?? default(Optional<string>),
-
844 Url = embed.Author.Url ?? default(Optional<string>),
-
845 }
-
846 : default(Optional<IEmbedAuthor>),
-
847 Colour = colour,
-
848 Description = embed.Description ?? default(Optional<string>),
-
849 Fields = fields ?? default(Optional<IReadOnlyList<IEmbedField>>),
-
850 Footer = embed.Footer != null
-
851 ? new EmbedFooter(embed.Footer.Text)
-
852 {
-
853 IconUrl = embed.Footer.IconUrl ?? default(Optional<string>),
-
854 ProxyIconUrl = embed.Footer.ProxyIconUrl ?? default(Optional<string>),
+
808 if (invalid)
+
809 continue;
+
810
+
811 fields.Add(new EmbedField(field.Name, field.Value)
+
812 {
+
813 IsInline = field.IsInline ?? default(Optional<bool>),
+
814 });
+
815 }
+
816 }
+
817
+
818 if (embed.Footer != null && String.IsNullOrWhiteSpace(embed.Footer.Text))
+
819 {
+
820 embedErrors.Add("Null or whitespace embed footer text!");
+
821 embed.Footer = null;
+
822 }
+
823
+
824 if (embed.Image != null && String.IsNullOrWhiteSpace(embed.Image.Url))
+
825 {
+
826 embedErrors.Add("Null or whitespace embed image url!");
+
827 embed.Image = null;
+
828 }
+
829
+
830 if (embed.Thumbnail != null && String.IsNullOrWhiteSpace(embed.Thumbnail.Url))
+
831 {
+
832 embedErrors.Add("Null or whitespace embed thumbnail url!");
+
833 embed.Thumbnail = null;
+
834 }
+
835
+
836 Optional<DateTimeOffset> timestampOptional = default;
+
837 if (embed.Timestamp != null)
+
838 if (DateTimeOffset.TryParse(embed.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var timestamp))
+
839 timestampOptional = timestamp.ToUniversalTime();
+
840 else
+
841 embedErrors.Add(
+
842 String.Format(
+
843 CultureInfo.InvariantCulture,
+
844 "Invalid embed timestamp: {0}",
+
845 embed.Timestamp));
+
846
+
847 var discordEmbed = new Embed
+
848 {
+
849 Author = embed.Author != null
+
850 ? new EmbedAuthor(embed.Author.Name)
+
851 {
+
852 IconUrl = embed.Author.IconUrl ?? default(Optional<string>),
+
853 ProxyIconUrl = embed.Author.ProxyIconUrl ?? default(Optional<string>),
+
854 Url = embed.Author.Url ?? default(Optional<string>),
855 }
-
856 : default,
-
857 Image = embed.Image != null
-
858 ? new EmbedImage(embed.Image.Url)
-
859 {
-
860 Width = embed.Image.Width ?? default(Optional<int>),
-
861 Height = embed.Image.Height ?? default(Optional<int>),
-
862 ProxyUrl = embed.Image.ProxyUrl ?? default(Optional<string>),
-
863 }
-
864 : default(Optional<IEmbedImage>),
-
865 Provider = embed.Provider != null
-
866 ? new EmbedProvider
-
867 {
-
868 Name = embed.Provider.Name ?? default(Optional<string>),
-
869 Url = embed.Provider.Url ?? default(Optional<string>),
-
870 }
-
871 : default(Optional<IEmbedProvider>),
-
872 Thumbnail = embed.Thumbnail != null
-
873 ? new EmbedThumbnail(embed.Thumbnail.Url)
-
874 {
-
875 Width = embed.Thumbnail.Width ?? default(Optional<int>),
-
876 Height = embed.Thumbnail.Height ?? default(Optional<int>),
-
877 ProxyUrl = embed.Thumbnail.ProxyUrl ?? default(Optional<string>),
-
878 }
-
879 : default(Optional<IEmbedThumbnail>),
-
880 Timestamp = timestampOptional,
-
881 Title = embed.Title ?? default(Optional<string>),
-
882 Url = embed.Url ?? default(Optional<string>),
-
883 Video = embed.Video != null
-
884 ? new EmbedVideo
-
885 {
-
886 Url = embed.Video.Url ?? default(Optional<string>),
-
887 Width = embed.Video.Width ?? default(Optional<int>),
-
888 Height = embed.Video.Height ?? default(Optional<int>),
-
889 ProxyUrl = embed.Video.ProxyUrl ?? default(Optional<string>),
-
890 }
-
891 : default(Optional<IEmbedVideo>),
-
892 };
-
893
-
894 var result = new List<IEmbed> { discordEmbed };
-
895
-
896 if (embedErrors.Count > 0)
-
897 {
-
898 var joinedErrors = String.Join(Environment.NewLine, embedErrors);
-
899 Logger.LogError("Embed description contains errors:{newLine}{issues}", Environment.NewLine, joinedErrors);
-
900 result.Add(new Embed
-
901 {
-
902 Title = "TGS Embed Errors",
-
903 Description = joinedErrors,
-
904 Colour = Color.Red,
-
905 Footer = new EmbedFooter("Please report this to your codebase's maintainers."),
-
906 Timestamp = DateTimeOffset.UtcNow,
-
907 });
-
908 }
-
909
-
910 return result;
-
911 }
-
912 #pragma warning restore CA1502
-
913 }
-
914#pragma warning restore CA1506
-
915}
+
856 : default(Optional<IEmbedAuthor>),
+
857 Colour = colour,
+
858 Description = embed.Description ?? default(Optional<string>),
+
859 Fields = fields ?? default(Optional<IReadOnlyList<IEmbedField>>),
+
860 Footer = embed.Footer != null
+
861 ? new EmbedFooter(embed.Footer.Text)
+
862 {
+
863 IconUrl = embed.Footer.IconUrl ?? default(Optional<string>),
+
864 ProxyIconUrl = embed.Footer.ProxyIconUrl ?? default(Optional<string>),
+
865 }
+
866 : default,
+
867 Image = embed.Image != null
+
868 ? new EmbedImage(embed.Image.Url)
+
869 {
+
870 Width = embed.Image.Width ?? default(Optional<int>),
+
871 Height = embed.Image.Height ?? default(Optional<int>),
+
872 ProxyUrl = embed.Image.ProxyUrl ?? default(Optional<string>),
+
873 }
+
874 : default(Optional<IEmbedImage>),
+
875 Provider = embed.Provider != null
+
876 ? new EmbedProvider
+
877 {
+
878 Name = embed.Provider.Name ?? default(Optional<string>),
+
879 Url = embed.Provider.Url ?? default(Optional<string>),
+
880 }
+
881 : default(Optional<IEmbedProvider>),
+
882 Thumbnail = embed.Thumbnail != null
+
883 ? new EmbedThumbnail(embed.Thumbnail.Url)
+
884 {
+
885 Width = embed.Thumbnail.Width ?? default(Optional<int>),
+
886 Height = embed.Thumbnail.Height ?? default(Optional<int>),
+
887 ProxyUrl = embed.Thumbnail.ProxyUrl ?? default(Optional<string>),
+
888 }
+
889 : default(Optional<IEmbedThumbnail>),
+
890 Timestamp = timestampOptional,
+
891 Title = embed.Title ?? default(Optional<string>),
+
892 Url = embed.Url ?? default(Optional<string>),
+
893 Video = embed.Video != null
+
894 ? new EmbedVideo
+
895 {
+
896 Url = embed.Video.Url ?? default(Optional<string>),
+
897 Width = embed.Video.Width ?? default(Optional<int>),
+
898 Height = embed.Video.Height ?? default(Optional<int>),
+
899 ProxyUrl = embed.Video.ProxyUrl ?? default(Optional<string>),
+
900 }
+
901 : default(Optional<IEmbedVideo>),
+
902 };
+
903
+
904 var result = new List<IEmbed> { discordEmbed };
+
905
+
906 if (embedErrors.Count > 0)
+
907 {
+
908 var joinedErrors = String.Join(Environment.NewLine, embedErrors);
+
909 Logger.LogError("Embed description contains errors:{newLine}{issues}", Environment.NewLine, joinedErrors);
+
910 result.Add(new Embed
+
911 {
+
912 Title = "TGS Embed Errors",
+
913 Description = joinedErrors,
+
914 Colour = Color.Red,
+
915 Footer = new EmbedFooter("Please report this to your codebase's maintainers."),
+
916 Timestamp = DateTimeOffset.UtcNow,
+
917 });
+
918 }
+
919
+
920 return result;
+
921 }
+
922 #pragma warning restore CA1502
+
923 }
+
924#pragma warning restore CA1506
+
925}
ChatConnectionStringBuilder for ChatProvider.Discord.
string? ConnectionString
The information used to connect to the Provider.
Represents a Providers.IProvider channel.
@@ -935,20 +945,20 @@ $(function() {
Task< Result > gatewayTask
The Task representing the lifetime of the client.
Snowflake currentUserId
The bot's Snowflake.
static string NormalizeMentions(string fromDiscord)
Normalize a discord mention string.
-
override async Task< IReadOnlyCollection< Tuple< Models.ChatChannel, ChannelRepresentation > > > MapChannelsImpl(IEnumerable< Models.ChatChannel > channels, CancellationToken cancellationToken)
+
override async Task< IReadOnlyCollection< Tuple< Models.ChatChannel, ChannelRepresentation > > > MapChannelsImpl(IEnumerable< Models.ChatChannel > channels, CancellationToken cancellationToken)
CancellationTokenSource gatewayCts
The CancellationTokenSource for the gatewayTask.
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the DiscordProvider.
-
async Task< Result > RespondAsync(IMessageCreate messageCreateEvent, CancellationToken cancellationToken)
+
async Task< Result > RespondAsync(IMessageCreate messageCreateEvent, CancellationToken cancellationToken)
readonly DiscordDMOutputDisplayType outputDisplayType
The DiscordDMOutputDisplayType.
static List< IEmbedField > BuildUpdateEmbedFields(Models.RevisionInformation revisionInformation, Version byondVersion, string gitHubOwner, string gitHubRepo, bool localCommitPushed)
Create a List<T> of IEmbedFields for a discord update embed.
-
override async Task Connect(CancellationToken cancellationToken)
Attempt to connect the Provider. A Task representing the running operation.
-
override async Task< Func< string, string, Task > > SendUpdateMessage(Models.RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, ulong channelId, bool localCommitPushed, CancellationToken cancellationToken)
+
override async Task Connect(CancellationToken cancellationToken)
Attempt to connect the Provider. A Task representing the running operation.
+
override async Task< Func< string, string, Task > > SendUpdateMessage(Models.RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, ulong channelId, bool localCommitPushed, CancellationToken cancellationToken)
override async ValueTask DisposeAsync()
override bool Connected
If the IProvider is currently connected.
-
Optional< IReadOnlyList< IEmbed > > ConvertEmbed(ChatEmbed embed)
Convert a ChatEmbed to an IEmbed parameters.
+
Optional< IReadOnlyList< IEmbed > > ConvertEmbed(ChatEmbed embed)
Convert a ChatEmbed to an IEmbed parameters.
TaskCompletionSource< object > gatewayReadyTcs
The TaskCompletionSource<TResult> for the initial gateway connection event.
-
Task< Result > RespondAsync(IReady readyEvent, CancellationToken cancellationToken)
-
override async Task DisconnectImpl(CancellationToken cancellationToken)
Gracefully disconnects the provider. A Task representing the running operation.
+
Task< Result > RespondAsync(IReady readyEvent, CancellationToken cancellationToken)
+
override async Task DisconnectImpl(CancellationToken cancellationToken)
Gracefully disconnects the provider. A Task representing the running operation.
readonly ServiceProvider serviceProvider
The ServiceProvider containing Discord services.
override string BotMention
The string that indicates the IProvider was mentioned.
override async Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
Send a message to the IProvider. A Task representing the running operation.
diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html index f92ad5f586..d9352de6a2 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html @@ -345,7 +345,7 @@ Properties

Definition at line 53 of file ChannelRepresentation.cs.

53{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html index 6d796291d0..1b93058e96 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html @@ -2284,7 +2284,7 @@ Here is the caller graph for this function:

Definition at line 29 of file ChatManager.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html index a109b986b4..c3c89f9514 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html @@ -588,7 +588,7 @@ Private Attributes
167 }
Many to many relationship for Models.RevisionInformation and Models.TestMerge.
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

Here is the caller graph for this function:
@@ -636,73 +636,73 @@ Here is the caller graph for this function:

Implements Tgstation.Server.Host.Components.Chat.Providers.Provider.

-

Definition at line 557 of file DiscordProvider.cs.

-
558 {
-
559 try
-
560 {
- -
562 {
-
563 if (gatewayCts != null)
-
564 throw new InvalidOperationException("Discord gateway still active!");
-
565
-
566 gatewayCts = new CancellationTokenSource();
-
567 }
-
568
-
569 var gatewayCancellationToken = gatewayCts.Token;
-
570 var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
-
571
-
572 Task<Result> localGatewayTask;
-
573 gatewayReadyTcs = new TaskCompletionSource<object>();
-
574
-
575 using var gatewayConnectionAbortRegistration = cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled());
-
576 gatewayCancellationToken.Register(() => Logger.LogTrace("Stopping gateway client..."));
-
577
-
578 // reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
-
579 localGatewayTask = gatewayClient.RunAsync(gatewayCancellationToken);
-
580 try
-
581 {
-
582 await Task.WhenAny(gatewayReadyTcs.Task, localGatewayTask);
-
583
-
584 if (localGatewayTask.IsCompleted || cancellationToken.IsCancellationRequested)
-
585 throw new JobException(ErrorCode.ChatCannotConnectProvider);
-
586
-
587 var userClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
-
588
-
589 using var localCombinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, gatewayCancellationToken);
-
590 var currentUserResult = await userClient.GetCurrentUserAsync(localCombinedCts.Token);
-
591 if (!currentUserResult.IsSuccess)
-
592 {
-
593 Logger.LogWarning("Unable to retrieve current user: {0}", currentUserResult.Error.Message);
-
594 throw new JobException(ErrorCode.ChatCannotConnectProvider);
-
595 }
+

Definition at line 567 of file DiscordProvider.cs.

+
568 {
+
569 try
+
570 {
+ +
572 {
+
573 if (gatewayCts != null)
+
574 throw new InvalidOperationException("Discord gateway still active!");
+
575
+
576 gatewayCts = new CancellationTokenSource();
+
577 }
+
578
+
579 var gatewayCancellationToken = gatewayCts.Token;
+
580 var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
+
581
+
582 Task<Result> localGatewayTask;
+
583 gatewayReadyTcs = new TaskCompletionSource<object>();
+
584
+
585 using var gatewayConnectionAbortRegistration = cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled());
+
586 gatewayCancellationToken.Register(() => Logger.LogTrace("Stopping gateway client..."));
+
587
+
588 // reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
+
589 localGatewayTask = gatewayClient.RunAsync(gatewayCancellationToken);
+
590 try
+
591 {
+
592 await Task.WhenAny(gatewayReadyTcs.Task, localGatewayTask);
+
593
+
594 if (localGatewayTask.IsCompleted || cancellationToken.IsCancellationRequested)
+
595 throw new JobException(ErrorCode.ChatCannotConnectProvider);
596
-
597 currentUserId = currentUserResult.Entity.ID;
-
598 initialUserName = currentUserResult.Entity.Username;
-
599 }
-
600 finally
-
601 {
-
602 gatewayTask = localGatewayTask;
-
603 }
-
604 }
-
605 catch
-
606 {
-
607 // will handle cleanup
-
608 // DCT: Musn't abort
-
609 await DisconnectImpl(default);
-
610 throw;
-
611 }
-
612 }
+
597 var userClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
+
598
+
599 using var localCombinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, gatewayCancellationToken);
+
600 var currentUserResult = await userClient.GetCurrentUserAsync(localCombinedCts.Token);
+
601 if (!currentUserResult.IsSuccess)
+
602 {
+
603 Logger.LogWarning("Unable to retrieve current user: {0}", currentUserResult.Error.Message);
+
604 throw new JobException(ErrorCode.ChatCannotConnectProvider);
+
605 }
+
606
+
607 currentUserId = currentUserResult.Entity.ID;
+
608 initialUserName = currentUserResult.Entity.Username;
+
609 }
+
610 finally
+
611 {
+
612 gatewayTask = localGatewayTask;
+
613 }
+
614 }
+
615 catch
+
616 {
+
617 // will handle cleanup
+
618 // DCT: Musn't abort
+
619 await DisconnectImpl(default);
+
620 throw;
+
621 }
+
622 }
string initialUserName
The bot's username at the time of connection.
Task< Result > gatewayTask
The Task representing the lifetime of the client.
CancellationTokenSource gatewayCts
The CancellationTokenSource for the gatewayTask.
TaskCompletionSource< object > gatewayReadyTcs
The TaskCompletionSource<TResult> for the initial gateway connection event.
-
override async Task DisconnectImpl(CancellationToken cancellationToken)
Gracefully disconnects the provider. A Task representing the running operation.
+
override async Task DisconnectImpl(CancellationToken cancellationToken)
Gracefully disconnects the provider. A Task representing the running operation.
ILogger< Provider > Logger
The ILogger for the Provider.
Definition: Provider.cs:30
Operation exceptions thrown from the context of a Models.Job.
Definition: JobException.cs:11
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition: ErrorCode.cs:11
-

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.connectDisconnectLock, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.currentUserId, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayCts, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayReadyTcs, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayTask, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.initialUserName, Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.serviceProvider.

+

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.connectDisconnectLock, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.currentUserId, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayCts, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayReadyTcs, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayTask, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.initialUserName, Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.serviceProvider.

Here is the call graph for this function:
@@ -748,49 +748,39 @@ Here is the call graph for this function:
Returns
The parameter for sending a single IEmbed.
-

Definition at line 746 of file DiscordProvider.cs.

-
747 {
-
748 if (embed == null)
-
749 return default;
-
750
-
751 List<string> embedErrors = new List<string>();
-
752 Optional<Color> colour = default;
-
753 if (embed.Colour != null)
-
754 if (Int32.TryParse(embed.Colour.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
-
755 colour = Color.FromArgb(argb);
-
756 else
-
757 embedErrors.Add(
-
758 String.Format(
-
759 CultureInfo.InvariantCulture,
-
760 "Invalid embed colour: {0}",
-
761 embed.Colour));
-
762
-
763 if (embed.Author != null && String.IsNullOrWhiteSpace(embed.Author.Name))
-
764 {
-
765 embedErrors.Add("Null or whitespace embed author name!");
-
766 embed.Author = null;
-
767 }
-
768
-
769 List<IEmbedField> fields = null;
-
770 if (embed.Fields != null)
-
771 {
-
772 fields = new List<IEmbedField>();
-
773 var i = -1;
-
774 foreach (var field in embed.Fields)
-
775 {
-
776 ++i;
-
777 var invalid = false;
-
778 if (String.IsNullOrWhiteSpace(field.Name))
-
779 {
-
780 embedErrors.Add(
-
781 String.Format(
-
782 CultureInfo.InvariantCulture,
-
783 "Null or whitespace field author at index {0}!",
-
784 i));
-
785 invalid = true;
-
786 }
-
787
-
788 if (String.IsNullOrWhiteSpace(field.Value))
+

Definition at line 756 of file DiscordProvider.cs.

+
757 {
+
758 if (embed == null)
+
759 return default;
+
760
+
761 List<string> embedErrors = new List<string>();
+
762 Optional<Color> colour = default;
+
763 if (embed.Colour != null)
+
764 if (Int32.TryParse(embed.Colour.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
+
765 colour = Color.FromArgb(argb);
+
766 else
+
767 embedErrors.Add(
+
768 String.Format(
+
769 CultureInfo.InvariantCulture,
+
770 "Invalid embed colour: {0}",
+
771 embed.Colour));
+
772
+
773 if (embed.Author != null && String.IsNullOrWhiteSpace(embed.Author.Name))
+
774 {
+
775 embedErrors.Add("Null or whitespace embed author name!");
+
776 embed.Author = null;
+
777 }
+
778
+
779 List<IEmbedField> fields = null;
+
780 if (embed.Fields != null)
+
781 {
+
782 fields = new List<IEmbedField>();
+
783 var i = -1;
+
784 foreach (var field in embed.Fields)
+
785 {
+
786 ++i;
+
787 var invalid = false;
+
788 if (String.IsNullOrWhiteSpace(field.Name))
789 {
790 embedErrors.Add(
791 String.Format(
@@ -800,120 +790,130 @@ Here is the call graph for this function:
795 invalid = true;
796 }
797
-
798 if (invalid)
-
799 continue;
-
800
-
801 fields.Add(new EmbedField(field.Name, field.Value)
-
802 {
-
803 IsInline = field.IsInline ?? default(Optional<bool>),
-
804 });
-
805 }
-
806 }
+
798 if (String.IsNullOrWhiteSpace(field.Value))
+
799 {
+
800 embedErrors.Add(
+
801 String.Format(
+
802 CultureInfo.InvariantCulture,
+
803 "Null or whitespace field author at index {0}!",
+
804 i));
+
805 invalid = true;
+
806 }
807
-
808 if (embed.Footer != null && String.IsNullOrWhiteSpace(embed.Footer.Text))
-
809 {
-
810 embedErrors.Add("Null or whitespace embed footer text!");
-
811 embed.Footer = null;
-
812 }
-
813
-
814 if (embed.Image != null && String.IsNullOrWhiteSpace(embed.Image.Url))
-
815 {
-
816 embedErrors.Add("Null or whitespace embed image url!");
-
817 embed.Image = null;
-
818 }
-
819
-
820 if (embed.Thumbnail != null && String.IsNullOrWhiteSpace(embed.Thumbnail.Url))
-
821 {
-
822 embedErrors.Add("Null or whitespace embed thumbnail url!");
-
823 embed.Thumbnail = null;
-
824 }
-
825
-
826 Optional<DateTimeOffset> timestampOptional = default;
-
827 if (embed.Timestamp != null)
-
828 if (DateTimeOffset.TryParse(embed.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var timestamp))
-
829 timestampOptional = timestamp.ToUniversalTime();
-
830 else
-
831 embedErrors.Add(
-
832 String.Format(
-
833 CultureInfo.InvariantCulture,
-
834 "Invalid embed timestamp: {0}",
-
835 embed.Timestamp));
-
836
-
837 var discordEmbed = new Embed
-
838 {
-
839 Author = embed.Author != null
-
840 ? new EmbedAuthor(embed.Author.Name)
-
841 {
-
842 IconUrl = embed.Author.IconUrl ?? default(Optional<string>),
-
843 ProxyIconUrl = embed.Author.ProxyIconUrl ?? default(Optional<string>),
-
844 Url = embed.Author.Url ?? default(Optional<string>),
-
845 }
-
846 : default(Optional<IEmbedAuthor>),
-
847 Colour = colour,
-
848 Description = embed.Description ?? default(Optional<string>),
-
849 Fields = fields ?? default(Optional<IReadOnlyList<IEmbedField>>),
-
850 Footer = embed.Footer != null
-
851 ? new EmbedFooter(embed.Footer.Text)
-
852 {
-
853 IconUrl = embed.Footer.IconUrl ?? default(Optional<string>),
-
854 ProxyIconUrl = embed.Footer.ProxyIconUrl ?? default(Optional<string>),
+
808 if (invalid)
+
809 continue;
+
810
+
811 fields.Add(new EmbedField(field.Name, field.Value)
+
812 {
+
813 IsInline = field.IsInline ?? default(Optional<bool>),
+
814 });
+
815 }
+
816 }
+
817
+
818 if (embed.Footer != null && String.IsNullOrWhiteSpace(embed.Footer.Text))
+
819 {
+
820 embedErrors.Add("Null or whitespace embed footer text!");
+
821 embed.Footer = null;
+
822 }
+
823
+
824 if (embed.Image != null && String.IsNullOrWhiteSpace(embed.Image.Url))
+
825 {
+
826 embedErrors.Add("Null or whitespace embed image url!");
+
827 embed.Image = null;
+
828 }
+
829
+
830 if (embed.Thumbnail != null && String.IsNullOrWhiteSpace(embed.Thumbnail.Url))
+
831 {
+
832 embedErrors.Add("Null or whitespace embed thumbnail url!");
+
833 embed.Thumbnail = null;
+
834 }
+
835
+
836 Optional<DateTimeOffset> timestampOptional = default;
+
837 if (embed.Timestamp != null)
+
838 if (DateTimeOffset.TryParse(embed.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var timestamp))
+
839 timestampOptional = timestamp.ToUniversalTime();
+
840 else
+
841 embedErrors.Add(
+
842 String.Format(
+
843 CultureInfo.InvariantCulture,
+
844 "Invalid embed timestamp: {0}",
+
845 embed.Timestamp));
+
846
+
847 var discordEmbed = new Embed
+
848 {
+
849 Author = embed.Author != null
+
850 ? new EmbedAuthor(embed.Author.Name)
+
851 {
+
852 IconUrl = embed.Author.IconUrl ?? default(Optional<string>),
+
853 ProxyIconUrl = embed.Author.ProxyIconUrl ?? default(Optional<string>),
+
854 Url = embed.Author.Url ?? default(Optional<string>),
855 }
-
856 : default,
-
857 Image = embed.Image != null
-
858 ? new EmbedImage(embed.Image.Url)
-
859 {
-
860 Width = embed.Image.Width ?? default(Optional<int>),
-
861 Height = embed.Image.Height ?? default(Optional<int>),
-
862 ProxyUrl = embed.Image.ProxyUrl ?? default(Optional<string>),
-
863 }
-
864 : default(Optional<IEmbedImage>),
-
865 Provider = embed.Provider != null
-
866 ? new EmbedProvider
-
867 {
-
868 Name = embed.Provider.Name ?? default(Optional<string>),
-
869 Url = embed.Provider.Url ?? default(Optional<string>),
-
870 }
-
871 : default(Optional<IEmbedProvider>),
-
872 Thumbnail = embed.Thumbnail != null
-
873 ? new EmbedThumbnail(embed.Thumbnail.Url)
-
874 {
-
875 Width = embed.Thumbnail.Width ?? default(Optional<int>),
-
876 Height = embed.Thumbnail.Height ?? default(Optional<int>),
-
877 ProxyUrl = embed.Thumbnail.ProxyUrl ?? default(Optional<string>),
-
878 }
-
879 : default(Optional<IEmbedThumbnail>),
-
880 Timestamp = timestampOptional,
-
881 Title = embed.Title ?? default(Optional<string>),
-
882 Url = embed.Url ?? default(Optional<string>),
-
883 Video = embed.Video != null
-
884 ? new EmbedVideo
-
885 {
-
886 Url = embed.Video.Url ?? default(Optional<string>),
-
887 Width = embed.Video.Width ?? default(Optional<int>),
-
888 Height = embed.Video.Height ?? default(Optional<int>),
-
889 ProxyUrl = embed.Video.ProxyUrl ?? default(Optional<string>),
-
890 }
-
891 : default(Optional<IEmbedVideo>),
-
892 };
-
893
-
894 var result = new List<IEmbed> { discordEmbed };
-
895
-
896 if (embedErrors.Count > 0)
-
897 {
-
898 var joinedErrors = String.Join(Environment.NewLine, embedErrors);
-
899 Logger.LogError("Embed description contains errors:{newLine}{issues}", Environment.NewLine, joinedErrors);
-
900 result.Add(new Embed
-
901 {
-
902 Title = "TGS Embed Errors",
-
903 Description = joinedErrors,
-
904 Colour = Color.Red,
-
905 Footer = new EmbedFooter("Please report this to your codebase's maintainers."),
-
906 Timestamp = DateTimeOffset.UtcNow,
-
907 });
-
908 }
-
909
-
910 return result;
-
911 }
+
856 : default(Optional<IEmbedAuthor>),
+
857 Colour = colour,
+
858 Description = embed.Description ?? default(Optional<string>),
+
859 Fields = fields ?? default(Optional<IReadOnlyList<IEmbedField>>),
+
860 Footer = embed.Footer != null
+
861 ? new EmbedFooter(embed.Footer.Text)
+
862 {
+
863 IconUrl = embed.Footer.IconUrl ?? default(Optional<string>),
+
864 ProxyIconUrl = embed.Footer.ProxyIconUrl ?? default(Optional<string>),
+
865 }
+
866 : default,
+
867 Image = embed.Image != null
+
868 ? new EmbedImage(embed.Image.Url)
+
869 {
+
870 Width = embed.Image.Width ?? default(Optional<int>),
+
871 Height = embed.Image.Height ?? default(Optional<int>),
+
872 ProxyUrl = embed.Image.ProxyUrl ?? default(Optional<string>),
+
873 }
+
874 : default(Optional<IEmbedImage>),
+
875 Provider = embed.Provider != null
+
876 ? new EmbedProvider
+
877 {
+
878 Name = embed.Provider.Name ?? default(Optional<string>),
+
879 Url = embed.Provider.Url ?? default(Optional<string>),
+
880 }
+
881 : default(Optional<IEmbedProvider>),
+
882 Thumbnail = embed.Thumbnail != null
+
883 ? new EmbedThumbnail(embed.Thumbnail.Url)
+
884 {
+
885 Width = embed.Thumbnail.Width ?? default(Optional<int>),
+
886 Height = embed.Thumbnail.Height ?? default(Optional<int>),
+
887 ProxyUrl = embed.Thumbnail.ProxyUrl ?? default(Optional<string>),
+
888 }
+
889 : default(Optional<IEmbedThumbnail>),
+
890 Timestamp = timestampOptional,
+
891 Title = embed.Title ?? default(Optional<string>),
+
892 Url = embed.Url ?? default(Optional<string>),
+
893 Video = embed.Video != null
+
894 ? new EmbedVideo
+
895 {
+
896 Url = embed.Video.Url ?? default(Optional<string>),
+
897 Width = embed.Video.Width ?? default(Optional<int>),
+
898 Height = embed.Video.Height ?? default(Optional<int>),
+
899 ProxyUrl = embed.Video.ProxyUrl ?? default(Optional<string>),
+
900 }
+
901 : default(Optional<IEmbedVideo>),
+
902 };
+
903
+
904 var result = new List<IEmbed> { discordEmbed };
+
905
+
906 if (embedErrors.Count > 0)
+
907 {
+
908 var joinedErrors = String.Join(Environment.NewLine, embedErrors);
+
909 Logger.LogError("Embed description contains errors:{newLine}{issues}", Environment.NewLine, joinedErrors);
+
910 result.Add(new Embed
+
911 {
+
912 Title = "TGS Embed Errors",
+
913 Description = joinedErrors,
+
914 Colour = Color.Red,
+
915 Footer = new EmbedFooter("Please report this to your codebase's maintainers."),
+
916 Timestamp = DateTimeOffset.UtcNow,
+
917 });
+
918 }
+
919
+
920 return result;
+
921 }
Provider(IJobManager jobManager, ILogger< Provider > logger, ChatBot chatBot)
Initializes a new instance of the Provider class.
Definition: Provider.cs:73
ChatEmbedMedia Image
The ChatEmbedMedia for an image.
Definition: ChatEmbed.cs:45
@@ -977,31 +977,31 @@ Here is the caller graph for this function:

Implements Tgstation.Server.Host.Components.Chat.Providers.Provider.

-

Definition at line 615 of file DiscordProvider.cs.

-
616 {
-
617 Task<Result> localGatewayTask;
-
618 CancellationTokenSource localGatewayCts;
- -
620 {
-
621 localGatewayTask = gatewayTask;
-
622 localGatewayCts = gatewayCts;
-
623 gatewayTask = null;
-
624 gatewayCts = null;
-
625 if (localGatewayTask == null)
-
626 return;
-
627 }
-
628
-
629 localGatewayCts.Cancel();
-
630 var gatewayResult = await localGatewayTask;
-
631 if (!gatewayResult.IsSuccess)
-
632 Logger.LogWarning("Gateway issue: {0}", gatewayResult.Error.Message);
-
633
-
634 localGatewayCts.Dispose();
-
635 }
+

Definition at line 625 of file DiscordProvider.cs.

+
626 {
+
627 Task<Result> localGatewayTask;
+
628 CancellationTokenSource localGatewayCts;
+ +
630 {
+
631 localGatewayTask = gatewayTask;
+
632 localGatewayCts = gatewayCts;
+
633 gatewayTask = null;
+
634 gatewayCts = null;
+
635 if (localGatewayTask == null)
+
636 return;
+
637 }
+
638
+
639 localGatewayCts.Cancel();
+
640 var gatewayResult = await localGatewayTask;
+
641 if (!gatewayResult.IsSuccess)
+
642 Logger.LogWarning("Gateway issue: {0}", gatewayResult.Error.Message);
+
643
+
644 localGatewayCts.Dispose();
+
645 }

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.connectDisconnectLock, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayCts, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayTask, and Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect().

Here is the caller graph for this function:
@@ -1102,107 +1102,107 @@ Here is the caller graph for this function:

-

Definition at line 638 of file DiscordProvider.cs.

-
639 {
-
640 if (channels == null)
-
641 throw new ArgumentNullException(nameof(channels));
-
642
-
643 var remapRequired = false;
-
644
-
645 async Task<Tuple<Models.ChatChannel, ChannelRepresentation>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
-
646 {
-
647 if (!channelFromDB.DiscordChannelId.HasValue)
-
648 throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
-
649
-
650 var channelId = channelFromDB.DiscordChannelId.Value;
-
651 string connectionName;
-
652 string friendlyName;
-
653 if (channelId == 0)
-
654 {
-
655 connectionName = initialUserName;
-
656 friendlyName = "(Unmapped accessible channels)";
-
657 }
-
658 else
-
659 {
-
660 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
661 var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
-
662 if (!discordChannelResponse.IsSuccess)
-
663 {
-
664 Logger.LogWarning(
-
665 "Error retrieving discord channel {channelId}: {error} Inner: {innerError}",
-
666 channelId,
-
667 discordChannelResponse.Error.Message,
-
668 discordChannelResponse.Inner?.Error?.Message);
-
669 remapRequired = true;
-
670 return null;
-
671 }
-
672
-
673 var channelType = discordChannelResponse.Entity.Type;
-
674 if (channelType != ChannelType.GuildText && channelType != ChannelType.GuildAnnouncement)
-
675 {
-
676 Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
-
677 return null;
-
678 }
-
679
-
680 friendlyName = discordChannelResponse.Entity.Name.Value;
-
681 var guildId = discordChannelResponse.Entity.GuildID.Value;
+

Definition at line 648 of file DiscordProvider.cs.

+
649 {
+
650 if (channels == null)
+
651 throw new ArgumentNullException(nameof(channels));
+
652
+
653 var remapRequired = false;
+
654
+
655 async Task<Tuple<Models.ChatChannel, ChannelRepresentation>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
+
656 {
+
657 if (!channelFromDB.DiscordChannelId.HasValue)
+
658 throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
+
659
+
660 var channelId = channelFromDB.DiscordChannelId.Value;
+
661 string connectionName;
+
662 string friendlyName;
+
663 if (channelId == 0)
+
664 {
+
665 connectionName = initialUserName;
+
666 friendlyName = "(Unmapped accessible channels)";
+
667 }
+
668 else
+
669 {
+
670 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
671 var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
+
672 if (!discordChannelResponse.IsSuccess)
+
673 {
+
674 Logger.LogWarning(
+
675 "Error retrieving discord channel {channelId}: {error} Inner: {innerError}",
+
676 channelId,
+
677 discordChannelResponse.Error.Message,
+
678 discordChannelResponse.Inner?.Error?.Message);
+
679 remapRequired = true;
+
680 return null;
+
681 }
682
-
683 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
684 var guildsResponse = await guildsClient.GetGuildAsync(
-
685 guildId,
-
686 false,
-
687 cancellationToken);
-
688 if (!guildsResponse.IsSuccess)
-
689 {
-
690 Logger.LogWarning(
-
691 "Error retrieving discord guild {guildID}: {error} Inner: {innerError}",
-
692 guildId,
-
693 guildsResponse.Error.Message,
-
694 guildsResponse.Inner?.Error?.Message);
-
695 remapRequired = true;
-
696 return null;
-
697 }
-
698
-
699 connectionName = guildsResponse.Entity.Name;
-
700 }
-
701
-
702 var channelModel = new ChannelRepresentation
-
703 {
-
704 RealId = channelId,
-
705 IsAdminChannel = channelFromDB.IsAdminChannel == true,
-
706 ConnectionName = connectionName,
-
707 FriendlyName = friendlyName,
-
708 IsPrivateChannel = false,
-
709 Tag = channelFromDB.Tag,
-
710 EmbedsSupported = true,
-
711 };
-
712
-
713 Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
-
714 return Tuple.Create(channelFromDB, channelModel);
-
715 }
-
716
-
717 var tasks = channels
-
718 .Select(x => GetModelChannelFromDBChannel(x))
-
719 .ToList();
-
720
-
721 await Task.WhenAll(tasks);
+
683 var channelType = discordChannelResponse.Entity.Type;
+
684 if (channelType != ChannelType.GuildText && channelType != ChannelType.GuildAnnouncement)
+
685 {
+
686 Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
+
687 return null;
+
688 }
+
689
+
690 friendlyName = discordChannelResponse.Entity.Name.Value;
+
691 var guildId = discordChannelResponse.Entity.GuildID.Value;
+
692
+
693 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
+
694 var guildsResponse = await guildsClient.GetGuildAsync(
+
695 guildId,
+
696 false,
+
697 cancellationToken);
+
698 if (!guildsResponse.IsSuccess)
+
699 {
+
700 Logger.LogWarning(
+
701 "Error retrieving discord guild {guildID}: {error} Inner: {innerError}",
+
702 guildId,
+
703 guildsResponse.Error.Message,
+
704 guildsResponse.Inner?.Error?.Message);
+
705 remapRequired = true;
+
706 return null;
+
707 }
+
708
+
709 connectionName = guildsResponse.Entity.Name;
+
710 }
+
711
+
712 var channelModel = new ChannelRepresentation
+
713 {
+
714 RealId = channelId,
+
715 IsAdminChannel = channelFromDB.IsAdminChannel == true,
+
716 ConnectionName = connectionName,
+
717 FriendlyName = friendlyName,
+
718 IsPrivateChannel = false,
+
719 Tag = channelFromDB.Tag,
+
720 EmbedsSupported = true,
+
721 };
722
-
723 var enumerator = tasks
-
724 .Select(x => x.Result)
-
725 .Where(x => x != null)
-
726 .ToList();
-
727
-
728 lock (mappedChannels)
-
729 {
-
730 mappedChannels.Clear();
-
731 mappedChannels.AddRange(enumerator.Select(x => x.Item2.RealId));
-
732 }
-
733
-
734 if (remapRequired)
-
735 EnqueueMessage(null);
-
736
-
737 return enumerator;
-
738 }
+
723 Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
+
724 return Tuple.Create(channelFromDB, channelModel);
+
725 }
+
726
+
727 var tasks = channels
+
728 .Select(x => GetModelChannelFromDBChannel(x))
+
729 .ToList();
+
730
+
731 await Task.WhenAll(tasks);
+
732
+
733 var enumerator = tasks
+
734 .Select(x => x.Result)
+
735 .Where(x => x != null)
+
736 .ToList();
+
737
+
738 lock (mappedChannels)
+
739 {
+
740 mappedChannels.Clear();
+
741 mappedChannels.AddRange(enumerator.Select(x => x.Item2.RealId));
+
742 }
+
743
+
744 if (remapRequired)
+
745 EnqueueMessage(null);
+
746
+
747 return enumerator;
+
748 }
void EnqueueMessage(Message message)
Queues a message for NextMessage(CancellationToken).
Definition: Provider.cs:210

References Tgstation.Server.Host.Components.Chat.Providers.Provider.EnqueueMessage(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.initialUserName, Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.mappedChannels, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.serviceProvider, and Tgstation.Server.Host.Components.Chat.ChannelRepresentation.Tag.

@@ -1251,7 +1251,7 @@ Here is the call graph for this function:
Returns
The normalized mention string.
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

Here is the caller graph for this function:
@@ -1292,116 +1292,116 @@ Here is the caller graph for this function:

-

Definition at line 434 of file DiscordProvider.cs.

-
435 {
-
436 if (messageCreateEvent == null)
-
437 throw new ArgumentNullException(nameof(messageCreateEvent));
-
438
-
439 if ((messageCreateEvent.Type != MessageType.Default
-
440 && messageCreateEvent.Type != MessageType.InlineReply)
-
441 || messageCreateEvent.Author.ID == currentUserId)
-
442 return Result.FromSuccess();
-
443
-
444 var messageReference = new MessageReference
-
445 {
-
446 ChannelID = messageCreateEvent.ChannelID,
-
447 GuildID = messageCreateEvent.GuildID,
-
448 MessageID = messageCreateEvent.ID,
-
449 FailIfNotExists = false,
-
450 };
-
451
-
452 if (basedMeme && messageCreateEvent.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
-
453 {
-
454 // DCT: None available
-
455 await SendMessage(
-
456 new DiscordMessage
-
457 {
-
458 MessageReference = messageReference,
-
459 },
- -
461 {
-
462 Text = "https://youtu.be/LrNu-SuFF_o",
-
463 },
-
464 messageCreateEvent.ChannelID.Value,
-
465 default);
-
466 return Result.FromSuccess();
-
467 }
-
468
-
469 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
470 var channelResponse = await channelsClient.GetChannelAsync(messageCreateEvent.ChannelID, cancellationToken);
-
471 if (!channelResponse.IsSuccess)
-
472 {
-
473 Logger.LogWarning(
-
474 "Failed to get channel {0} in response to message {1}!",
-
475 messageCreateEvent.ChannelID,
-
476 messageCreateEvent.ID);
-
477
-
478 // we'll handle the errors ourselves
-
479 return Result.FromSuccess();
-
480 }
-
481
-
482 var pm = channelResponse.Entity.Type == ChannelType.DM || channelResponse.Entity.Type == ChannelType.GroupDM;
-
483 var shouldNotAnswer = !pm;
-
484 if (shouldNotAnswer)
-
485 lock (mappedChannels)
-
486 shouldNotAnswer = !mappedChannels.Contains(messageCreateEvent.ChannelID.Value) && !mappedChannels.Contains(0);
+

Definition at line 444 of file DiscordProvider.cs.

+
445 {
+
446 if (messageCreateEvent == null)
+
447 throw new ArgumentNullException(nameof(messageCreateEvent));
+
448
+
449 if ((messageCreateEvent.Type != MessageType.Default
+
450 && messageCreateEvent.Type != MessageType.InlineReply)
+
451 || messageCreateEvent.Author.ID == currentUserId)
+
452 return Result.FromSuccess();
+
453
+
454 var messageReference = new MessageReference
+
455 {
+
456 ChannelID = messageCreateEvent.ChannelID,
+
457 GuildID = messageCreateEvent.GuildID,
+
458 MessageID = messageCreateEvent.ID,
+
459 FailIfNotExists = false,
+
460 };
+
461
+
462 if (basedMeme && messageCreateEvent.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase))
+
463 {
+
464 // DCT: None available
+
465 await SendMessage(
+
466 new DiscordMessage
+
467 {
+
468 MessageReference = messageReference,
+
469 },
+ +
471 {
+
472 Text = "https://youtu.be/LrNu-SuFF_o",
+
473 },
+
474 messageCreateEvent.ChannelID.Value,
+
475 default);
+
476 return Result.FromSuccess();
+
477 }
+
478
+
479 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
480 var channelResponse = await channelsClient.GetChannelAsync(messageCreateEvent.ChannelID, cancellationToken);
+
481 if (!channelResponse.IsSuccess)
+
482 {
+
483 Logger.LogWarning(
+
484 "Failed to get channel {0} in response to message {1}!",
+
485 messageCreateEvent.ChannelID,
+
486 messageCreateEvent.ID);
487
-
488 var content = NormalizeMentions(messageCreateEvent.Content);
-
489 var mentionedUs = messageCreateEvent.Mentions.Any(x => x.ID == currentUserId)
-
490 || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
+
488 // we'll handle the errors ourselves
+
489 return Result.FromSuccess();
+
490 }
491
-
492 if (shouldNotAnswer)
-
493 {
-
494 if (mentionedUs)
-
495 Logger.LogTrace(
-
496 "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
-
497 messageCreateEvent.ChannelID,
-
498 channelResponse.Entity.Name,
-
499 messageCreateEvent.Author.ID,
-
500 messageCreateEvent.Author.Username);
+
492 var pm = channelResponse.Entity.Type == ChannelType.DM || channelResponse.Entity.Type == ChannelType.GroupDM;
+
493 var shouldNotAnswer = !pm;
+
494 if (shouldNotAnswer)
+
495 lock (mappedChannels)
+
496 shouldNotAnswer = !mappedChannels.Contains(messageCreateEvent.ChannelID.Value) && !mappedChannels.Contains(0);
+
497
+
498 var content = NormalizeMentions(messageCreateEvent.Content);
+
499 var mentionedUs = messageCreateEvent.Mentions.Any(x => x.ID == currentUserId)
+
500 || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase));
501
-
502 return Result.FromSuccess();
-
503 }
-
504
-
505 string guildName = "UNKNOWN";
-
506 if (!pm)
-
507 {
-
508 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
509 var messageGuildResponse = await guildsClient.GetGuildAsync(messageCreateEvent.GuildID.Value, false, cancellationToken);
-
510 if (messageGuildResponse.IsSuccess)
-
511 guildName = messageGuildResponse.Entity.Name;
-
512 else
-
513 Logger.LogWarning(
-
514 "Failed to get channel {0} in response to message {1}!",
-
515 messageCreateEvent.ChannelID,
-
516 messageCreateEvent.ID);
-
517 }
-
518
-
519 var result = new DiscordMessage
-
520 {
-
521 MessageReference = messageReference,
-
522 Content = content,
-
523 User = new ChatUser
-
524 {
-
525 RealId = messageCreateEvent.Author.ID.Value,
-
526 Channel = new ChannelRepresentation
-
527 {
-
528 RealId = messageCreateEvent.ChannelID.Value,
-
529 IsPrivateChannel = pm,
-
530 ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
-
531 FriendlyName = channelResponse.Entity.Name.Value,
-
532 EmbedsSupported = true,
-
533
-
534 // isAdmin and Tag populated by manager
-
535 },
-
536 FriendlyName = messageCreateEvent.Author.Username,
-
537 Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
-
538 },
-
539 };
-
540
-
541 EnqueueMessage(result);
-
542 return Result.FromSuccess();
-
543 }
+
502 if (shouldNotAnswer)
+
503 {
+
504 if (mentionedUs)
+
505 Logger.LogTrace(
+
506 "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
+
507 messageCreateEvent.ChannelID,
+
508 channelResponse.Entity.Name,
+
509 messageCreateEvent.Author.ID,
+
510 messageCreateEvent.Author.Username);
+
511
+
512 return Result.FromSuccess();
+
513 }
+
514
+
515 string guildName = "UNKNOWN";
+
516 if (!pm)
+
517 {
+
518 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
+
519 var messageGuildResponse = await guildsClient.GetGuildAsync(messageCreateEvent.GuildID.Value, false, cancellationToken);
+
520 if (messageGuildResponse.IsSuccess)
+
521 guildName = messageGuildResponse.Entity.Name;
+
522 else
+
523 Logger.LogWarning(
+
524 "Failed to get channel {0} in response to message {1}!",
+
525 messageCreateEvent.ChannelID,
+
526 messageCreateEvent.ID);
+
527 }
+
528
+
529 var result = new DiscordMessage
+
530 {
+
531 MessageReference = messageReference,
+
532 Content = content,
+
533 User = new ChatUser
+
534 {
+
535 RealId = messageCreateEvent.Author.ID.Value,
+
536 Channel = new ChannelRepresentation
+
537 {
+
538 RealId = messageCreateEvent.ChannelID.Value,
+
539 IsPrivateChannel = pm,
+
540 ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
+
541 FriendlyName = channelResponse.Entity.Name.Value,
+
542 EmbedsSupported = true,
+
543
+
544 // isAdmin and Tag populated by manager
+
545 },
+
546 FriendlyName = messageCreateEvent.Author.Username,
+
547 Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
+
548 },
+
549 };
+
550
+
551 EnqueueMessage(result);
+
552 return Result.FromSuccess();
+
553 }
static string NormalizeMentions(string fromDiscord)
Normalize a discord mention string.
override async Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
Send a message to the IProvider. A Task representing the running operation.
@@ -1452,15 +1452,15 @@ Here is the call graph for this function:

-

Definition at line 546 of file DiscordProvider.cs.

-
547 {
-
548 if (readyEvent == null)
-
549 throw new ArgumentNullException(nameof(readyEvent));
-
550
-
551 Logger.LogTrace("Gatway ready. Version: {version}", readyEvent.Version);
-
552 gatewayReadyTcs?.TrySetResult(null);
-
553 return Task.FromResult(Result.FromSuccess());
-
554 }
+

Definition at line 556 of file DiscordProvider.cs.

+
557 {
+
558 if (readyEvent == null)
+
559 throw new ArgumentNullException(nameof(readyEvent));
+
560
+
561 Logger.LogTrace("Gatway ready. Version: {version}", readyEvent.Version);
+
562 gatewayReadyTcs?.TrySetResult(null);
+
563 return Task.FromResult(Result.FromSuccess());
+
564 }

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.gatewayReadyTcs, and Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger.

@@ -1529,89 +1529,99 @@ Here is the call graph for this function:

Definition at line 224 of file DiscordProvider.cs.

225 {
226 Optional<IMessageReference> replyToReference = default;
-
227 if (replyTo != null && replyTo is DiscordMessage discordMessage)
-
228 {
-
229 replyToReference = discordMessage.MessageReference;
-
230 }
-
231
-
232 var embeds = ConvertEmbed(message.Embed);
-
233
-
234 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
235 async Task SendToChannel(Snowflake channelId)
-
236 {
-
237 var result = await channelsClient.CreateMessageAsync(
-
238 channelId,
-
239 message.Text,
-
240 embeds: embeds,
-
241 messageReference: replyToReference,
-
242 ct: cancellationToken);
-
243
-
244 if (!result.IsSuccess)
-
245 Logger.LogWarning(
-
246 "Failed to send to channel {0}: {1}",
-
247 channelId,
-
248 result.Error);
-
249 }
-
250
-
251 try
-
252 {
-
253 if (channelId == 0)
-
254 {
-
255 var usersClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
-
256 var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
-
257 if (!currentGuildsResponse.IsSuccess)
-
258 {
-
259 Logger.LogWarning(
-
260 "Error retrieving current discord guilds: {0}",
-
261 currentGuildsResponse.Error.Message);
-
262 return;
-
263 }
-
264
-
265 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
-
266
-
267 var guildsChannelsTasks = currentGuildsResponse.Entity.Select(
-
268 guild => guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken));
-
269
-
270 await Task.WhenAll(guildsChannelsTasks);
-
271
-
272 var unmappedTextChannels = guildsChannelsTasks
-
273 .Select(task => task.Result)
-
274 .SelectMany(guildChannels => guildChannels.Entity)
-
275 .Where(guildChannel => guildChannel.Type == ChannelType.GuildText);
+
227 Optional<IAllowedMentions> allowedMentions = default;
+
228 if (replyTo != null && replyTo is DiscordMessage discordMessage)
+
229 {
+
230 replyToReference = discordMessage.MessageReference;
+
231 allowedMentions = new AllowedMentions(
+
232 Parse: new List<MentionType> // reset settings back to how discord acts if this is not passed (which is different than the default if empty)
+
233 {
+
234 MentionType.Everyone,
+
235 MentionType.Roles,
+
236 MentionType.Users,
+
237 },
+
238 MentionRepliedUser: false); // disable reply mentions
+
239 }
+
240
+
241 var embeds = ConvertEmbed(message.Embed);
+
242
+
243 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
+
244 async Task SendToChannel(Snowflake channelId)
+
245 {
+
246 var result = await channelsClient.CreateMessageAsync(
+
247 channelId,
+
248 message.Text,
+
249 embeds: embeds,
+
250 messageReference: replyToReference,
+
251 allowedMentions: allowedMentions,
+
252 ct: cancellationToken);
+
253
+
254 if (!result.IsSuccess)
+
255 Logger.LogWarning(
+
256 "Failed to send to channel {0}: {1}",
+
257 channelId,
+
258 result.Error);
+
259 }
+
260
+
261 try
+
262 {
+
263 if (channelId == 0)
+
264 {
+
265 var usersClient = serviceProvider.GetRequiredService<IDiscordRestUserAPI>();
+
266 var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
+
267 if (!currentGuildsResponse.IsSuccess)
+
268 {
+
269 Logger.LogWarning(
+
270 "Error retrieving current discord guilds: {0}",
+
271 currentGuildsResponse.Error.Message);
+
272 return;
+
273 }
+
274
+
275 var guildsClient = serviceProvider.GetRequiredService<IDiscordRestGuildAPI>();
276
-
277 lock (mappedChannels)
-
278 unmappedTextChannels = unmappedTextChannels
-
279 .Where(x => !mappedChannels.Contains(x.ID.Value))
-
280 .ToList();
+
277 var guildsChannelsTasks = currentGuildsResponse.Entity.Select(
+
278 guild => guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken));
+
279
+
280 await Task.WhenAll(guildsChannelsTasks);
281
-
282 // discord API confirmed weak boned: https://stackoverflow.com/a/52462336
-
283 if (unmappedTextChannels.Any())
-
284 {
-
285 Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
-
286 await Task.WhenAll(
-
287 unmappedTextChannels.Select(
-
288 x => SendToChannel(x.ID)));
-
289 }
-
290
-
291 return;
-
292 }
-
293
-
294 await SendToChannel(new Snowflake(channelId));
-
295 }
-
296 catch (Exception e)
-
297 {
-
298 if (e is OperationCanceledException)
-
299 cancellationToken.ThrowIfCancellationRequested();
-
300 Logger.LogWarning(e, "Error sending discord message!");
-
301 }
-
302 }
-
Optional< IReadOnlyList< IEmbed > > ConvertEmbed(ChatEmbed embed)
Convert a ChatEmbed to an IEmbed parameters.
+
282 var unmappedTextChannels = guildsChannelsTasks
+
283 .Select(task => task.Result)
+
284 .SelectMany(guildChannels => guildChannels.Entity)
+
285 .Where(guildChannel => guildChannel.Type == ChannelType.GuildText);
+
286
+
287 lock (mappedChannels)
+
288 unmappedTextChannels = unmappedTextChannels
+
289 .Where(x => !mappedChannels.Contains(x.ID.Value))
+
290 .ToList();
+
291
+
292 // discord API confirmed weak boned: https://stackoverflow.com/a/52462336
+
293 if (unmappedTextChannels.Any())
+
294 {
+
295 Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
+
296 await Task.WhenAll(
+
297 unmappedTextChannels.Select(
+
298 x => SendToChannel(x.ID)));
+
299 }
+
300
+
301 return;
+
302 }
+
303
+
304 await SendToChannel(new Snowflake(channelId));
+
305 }
+
306 catch (Exception e)
+
307 {
+
308 if (e is OperationCanceledException)
+
309 cancellationToken.ThrowIfCancellationRequested();
+
310 Logger.LogWarning(e, "Error sending discord message!");
+
311 }
+
312 }
+
Optional< IReadOnlyList< IEmbed > > ConvertEmbed(ChatEmbed embed)
Convert a ChatEmbed to an IEmbed parameters.
-

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed(), Tgstation.Server.Host.Components.Interop.MessageContent.Embed, Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.mappedChannels, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.serviceProvider, and Tgstation.Server.Host.Components.Interop.MessageContent.Text.

+

References Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed(), Tgstation.Server.Host.Components.Interop.MessageContent.Embed, Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.mappedChannels, Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.serviceProvider, and Tgstation.Server.Host.Components.Interop.MessageContent.Text.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

Here is the call graph for this function:
@@ -1697,125 +1707,125 @@ Here is the caller graph for this function:

-

Definition at line 305 of file DiscordProvider.cs.

-
314 {
-
315 localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
-
316
-
317 var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
-
318 var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
-
319 {
-
320 Url = "https://github.com/tgstation/tgstation-server",
-
321 IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
-
322 };
-
323 var embed = new Embed
-
324 {
-
325 Author = deploymentBranding ? author : default,
-
326 Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
-
327 Description = "TGS has begun deploying active repository code to production.",
-
328 Fields = fields,
-
329 Title = "Code Deployment",
-
330 Footer = new EmbedFooter(
-
331 $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"),
-
332 Timestamp = estimatedCompletionTime ?? default,
-
333 };
-
334
-
335 Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
-
336 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
-
337
-
338 var messageResponse = await channelsClient.CreateMessageAsync(
-
339 new Snowflake(channelId),
-
340 "DM: Deployment in Progress...",
-
341 embeds: new List<IEmbed> { embed },
-
342 ct: cancellationToken)
-
343 ;
+

Definition at line 315 of file DiscordProvider.cs.

+
324 {
+
325 localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
+
326
+
327 var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
+
328 var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
+
329 {
+
330 Url = "https://github.com/tgstation/tgstation-server",
+
331 IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
+
332 };
+
333 var embed = new Embed
+
334 {
+
335 Author = deploymentBranding ? author : default,
+
336 Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
+
337 Description = "TGS has begun deploying active repository code to production.",
+
338 Fields = fields,
+
339 Title = "Code Deployment",
+
340 Footer = new EmbedFooter(
+
341 $"In progress...{(estimatedCompletionTime.HasValue ? " ETA" : String.Empty)}"),
+
342 Timestamp = estimatedCompletionTime ?? default,
+
343 };
344
-
345 if (!messageResponse.IsSuccess)
-
346 Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+
345 Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
+
346 var channelsClient = serviceProvider.GetRequiredService<IDiscordRestChannelAPI>();
347
-
348 return async (errorMessage, dreamMakerOutput) =>
-
349 {
-
350 var completionString = errorMessage == null ? "Succeeded" : "Failed";
-
351
-
352 embed = new Embed
-
353 {
-
354 Author = embed.Author,
-
355 Colour = errorMessage == null ? Color.Green : Color.Red,
-
356 Description = errorMessage == null
-
357 ? "The deployment completed successfully and will be available at the next server reboot."
-
358 : "The deployment failed.",
-
359 Fields = fields,
-
360 Title = embed.Title,
-
361 Footer = new EmbedFooter(
-
362 completionString),
-
363 Timestamp = DateTimeOffset.UtcNow,
-
364 };
-
365
-
366 var showDMOutput = outputDisplayType switch
-
367 {
-
368 DiscordDMOutputDisplayType.Always => true,
-
369 DiscordDMOutputDisplayType.Never => false,
-
370 DiscordDMOutputDisplayType.OnError => errorMessage != null,
-
371 _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"),
-
372 };
-
373
-
374 if (dreamMakerOutput != null)
-
375 {
-
376 // https://github.com/discord-net/Discord.Net/blob/8349cd7e1eb92e9a3baff68082c30a7b43e8e9b7/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs#L431
-
377 const int MaxFieldValueLength = 1024;
-
378 showDMOutput = showDMOutput && dreamMakerOutput.Length < MaxFieldValueLength - (6 + Environment.NewLine.Length);
-
379 if (showDMOutput)
-
380 fields.Add(new EmbedField(
-
381 "DreamMaker Output",
-
382 $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
-
383 false));
-
384 }
-
385
-
386 if (errorMessage != null)
-
387 fields.Add(new EmbedField(
-
388 "Error Message",
-
389 errorMessage,
-
390 false));
-
391
-
392 var updatedMessage = $"DM: Deployment {completionString}!";
-
393
-
394 async Task CreateUpdatedMessage()
-
395 {
-
396 var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
-
397 new Snowflake(channelId),
-
398 updatedMessage,
-
399 embeds: new List<IEmbed> { embed },
-
400 ct: cancellationToken)
-
401 ;
-
402
-
403 if (!createUpdatedMessageResponse.IsSuccess)
-
404 Logger.LogWarning(
-
405 "Creating updated deploy embed failed! Error: {0}",
-
406 createUpdatedMessageResponse.Error.Message);
-
407 }
-
408
-
409 if (!messageResponse.IsSuccess)
-
410 await CreateUpdatedMessage();
-
411 else
-
412 {
-
413 var editResponse = await channelsClient.EditMessageAsync(
-
414 new Snowflake(channelId),
-
415 messageResponse.Entity.ID,
-
416 updatedMessage,
-
417 embeds: new List<IEmbed> { embed },
-
418 ct: cancellationToken)
-
419 ;
-
420
-
421 if (!editResponse.IsSuccess)
-
422 {
-
423 Logger.LogWarning(
-
424 "Updating deploy embed {0} failed, attempting new post! Error: {1}",
-
425 messageResponse.Entity.ID,
-
426 editResponse.Error.Message);
-
427 await CreateUpdatedMessage();
-
428 }
-
429 }
-
430 };
-
431 }
+
348 var messageResponse = await channelsClient.CreateMessageAsync(
+
349 new Snowflake(channelId),
+
350 "DM: Deployment in Progress...",
+
351 embeds: new List<IEmbed> { embed },
+
352 ct: cancellationToken)
+
353 ;
+
354
+
355 if (!messageResponse.IsSuccess)
+
356 Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+
357
+
358 return async (errorMessage, dreamMakerOutput) =>
+
359 {
+
360 var completionString = errorMessage == null ? "Succeeded" : "Failed";
+
361
+
362 embed = new Embed
+
363 {
+
364 Author = embed.Author,
+
365 Colour = errorMessage == null ? Color.Green : Color.Red,
+
366 Description = errorMessage == null
+
367 ? "The deployment completed successfully and will be available at the next server reboot."
+
368 : "The deployment failed.",
+
369 Fields = fields,
+
370 Title = embed.Title,
+
371 Footer = new EmbedFooter(
+
372 completionString),
+
373 Timestamp = DateTimeOffset.UtcNow,
+
374 };
+
375
+
376 var showDMOutput = outputDisplayType switch
+
377 {
+
378 DiscordDMOutputDisplayType.Always => true,
+
379 DiscordDMOutputDisplayType.Never => false,
+
380 DiscordDMOutputDisplayType.OnError => errorMessage != null,
+
381 _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"),
+
382 };
+
383
+
384 if (dreamMakerOutput != null)
+
385 {
+
386 // https://github.com/discord-net/Discord.Net/blob/8349cd7e1eb92e9a3baff68082c30a7b43e8e9b7/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs#L431
+
387 const int MaxFieldValueLength = 1024;
+
388 showDMOutput = showDMOutput && dreamMakerOutput.Length < MaxFieldValueLength - (6 + Environment.NewLine.Length);
+
389 if (showDMOutput)
+
390 fields.Add(new EmbedField(
+
391 "DreamMaker Output",
+
392 $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```",
+
393 false));
+
394 }
+
395
+
396 if (errorMessage != null)
+
397 fields.Add(new EmbedField(
+
398 "Error Message",
+
399 errorMessage,
+
400 false));
+
401
+
402 var updatedMessage = $"DM: Deployment {completionString}!";
+
403
+
404 async Task CreateUpdatedMessage()
+
405 {
+
406 var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
+
407 new Snowflake(channelId),
+
408 updatedMessage,
+
409 embeds: new List<IEmbed> { embed },
+
410 ct: cancellationToken)
+
411 ;
+
412
+
413 if (!createUpdatedMessageResponse.IsSuccess)
+
414 Logger.LogWarning(
+
415 "Creating updated deploy embed failed! Error: {0}",
+
416 createUpdatedMessageResponse.Error.Message);
+
417 }
+
418
+
419 if (!messageResponse.IsSuccess)
+
420 await CreateUpdatedMessage();
+
421 else
+
422 {
+
423 var editResponse = await channelsClient.EditMessageAsync(
+
424 new Snowflake(channelId),
+
425 messageResponse.Entity.ID,
+
426 updatedMessage,
+
427 embeds: new List<IEmbed> { embed },
+
428 ct: cancellationToken)
+
429 ;
+
430
+
431 if (!editResponse.IsSuccess)
+
432 {
+
433 Logger.LogWarning(
+
434 "Updating deploy embed {0} failed, attempting new post! Error: {1}",
+
435 messageResponse.Entity.ID,
+
436 editResponse.Error.Message);
+
437 await CreateUpdatedMessage();
+
438 }
+
439 }
+
440 };
+
441 }
static List< IEmbedField > BuildUpdateEmbedFields(Models.RevisionInformation revisionInformation, Version byondVersion, string gitHubOwner, string gitHubRepo, bool localCommitPushed)
Create a List<T> of IEmbedFields for a discord update embed.
@@ -1857,7 +1867,7 @@ Here is the call graph for this function:

Definition at line 53 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

@@ -1885,7 +1895,7 @@ Here is the call graph for this function:

Definition at line 73 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

@@ -1913,7 +1923,7 @@ Here is the call graph for this function:

Definition at line 68 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider().

@@ -1941,7 +1951,7 @@ Here is the call graph for this function:

Definition at line 103 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

@@ -1969,7 +1979,7 @@ Here is the call graph for this function:

Definition at line 78 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

@@ -2025,7 +2035,7 @@ Here is the call graph for this function:

Definition at line 88 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync().

@@ -2053,7 +2063,7 @@ Here is the call graph for this function:

Definition at line 93 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

@@ -2081,7 +2091,7 @@ Here is the call graph for this function:

Definition at line 98 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl().

@@ -2109,7 +2119,7 @@ Here is the call graph for this function:

Definition at line 108 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl().

@@ -2137,7 +2147,7 @@ Here is the call graph for this function:

Definition at line 63 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage().

@@ -2165,7 +2175,7 @@ Here is the call graph for this function:

Definition at line 83 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

@@ -2193,7 +2203,7 @@ Here is the call graph for this function:

Definition at line 58 of file DiscordProvider.cs.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DiscordProvider(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html index 659410103e..85738a7e4b 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html @@ -633,7 +633,7 @@ Here is the call graph for this function:

References Tgstation.Server.Host.Components.Chat.Providers.Provider.Logger, Tgstation.Server.Host.Components.Chat.Providers.Provider.messageQueue, and Tgstation.Server.Host.Components.Chat.Providers.Provider.nextMessage.

-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.HandleMessage(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.Provider.ReconnectionLoop(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.HandleMessage(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.Provider.ReconnectionLoop(), and Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync().

Here is the caller graph for this function:
@@ -1652,7 +1652,7 @@ Here is the caller graph for this function:

Definition at line 30 of file Provider.cs.

30{ get; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed(), Tgstation.Server.Host.Components.Chat.Providers.Provider.Disconnect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.Provider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.Provider.EnqueueMessage(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.HardDisconnect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.NonBlockingListen(), Tgstation.Server.Host.Components.Chat.Providers.Provider.Provider(), Tgstation.Server.Host.Components.Chat.Providers.Provider.ReconnectionLoop(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.SaslAuthenticate(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.SendMessage(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage(), and Tgstation.Server.Host.Components.Chat.Providers.Provider.StopReconnectionTimer().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.Connect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed(), Tgstation.Server.Host.Components.Chat.Providers.Provider.Disconnect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.DisconnectImpl(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.Provider.DisposeAsync(), Tgstation.Server.Host.Components.Chat.Providers.Provider.EnqueueMessage(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.HardDisconnect(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannelsImpl(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.NonBlockingListen(), Tgstation.Server.Host.Components.Chat.Providers.Provider.Provider(), Tgstation.Server.Host.Components.Chat.Providers.Provider.ReconnectionLoop(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.RespondAsync(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.SaslAuthenticate(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendMessage(), Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.SendMessage(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage(), and Tgstation.Server.Host.Components.Chat.Providers.Provider.StopReconnectionTimer().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed.html index 7e492e6009..05fa29496e 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed.html @@ -154,7 +154,7 @@ Properties

Definition at line 65 of file ChatEmbed.cs.

65{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -183,7 +183,7 @@ Properties

Definition at line 35 of file ChatEmbed.cs.

35{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -239,7 +239,7 @@ Properties

Definition at line 70 of file ChatEmbed.cs.

70{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -268,7 +268,7 @@ Properties

Definition at line 40 of file ChatEmbed.cs.

40{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -297,7 +297,7 @@ Properties

Definition at line 45 of file ChatEmbed.cs.

45{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -353,7 +353,7 @@ Properties

Definition at line 50 of file ChatEmbed.cs.

50{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

@@ -382,7 +382,7 @@ Properties

Definition at line 30 of file ChatEmbed.cs.

30{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_footer.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_footer.html index a39b3116ba..8023f11980 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_footer.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_footer.html @@ -181,7 +181,7 @@ Properties

Definition at line 11 of file ChatEmbedFooter.cs.

11{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_media.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_media.html index 9f635e41d6..afaf81899e 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_media.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_media.html @@ -184,7 +184,7 @@ Properties

Definition at line 12 of file ChatEmbedMedia.cs.

12{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_provider.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_provider.html index 1f38a7db9e..c67f23ee4b 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_provider.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_chat_embed_provider.html @@ -133,7 +133,7 @@ Properties

Definition at line 11 of file ChatEmbedProvider.cs.

11{ get; set; }
-

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

+

Referenced by Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.ConvertEmbed().

diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html b/interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html index 5273fc34b1..d840f24afc 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html +++ b/interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html @@ -271,7 +271,7 @@ Properties

Definition at line 25 of file IAssemblyInformationProvider.cs.

25{ get; }
-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.ConfigureDatabase(), Tgstation.Server.Host.Configuration.FileLoggingConfiguration.GetFullLogDirectory(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage(), and Tgstation.Server.Host.Extensions.ApplicationBuilderExtensions.UseServerBranding().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.ConfigureDatabase(), Tgstation.Server.Host.Configuration.FileLoggingConfiguration.GetFullLogDirectory(), Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.SendUpdateMessage(), and Tgstation.Server.Host.Extensions.ApplicationBuilderExtensions.UseServerBranding().