mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-03-14 10:52:30 +00:00
Migrate to Avalonia (#1220)
This commit is contained in:
21
DiscordChatExporter.Core/Discord/Data/ChannelNode.cs
Normal file
21
DiscordChatExporter.Core/Discord/Data/ChannelNode.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
public record ChannelNode(Channel Channel, IReadOnlyList<ChannelNode> Children)
|
||||
{
|
||||
public static IReadOnlyList<ChannelNode> BuildTree(IReadOnlyList<Channel> channels)
|
||||
{
|
||||
IReadOnlyList<ChannelNode> GetChildren(Channel parent) =>
|
||||
channels
|
||||
.Where(c => c.Parent?.Id == parent.Id)
|
||||
.Select(c => new ChannelNode(c, GetChildren(c)))
|
||||
.ToArray();
|
||||
|
||||
return channels
|
||||
.Where(c => c.Parent is null)
|
||||
.Select(c => new ChannelNode(c, GetChildren(c)))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -83,16 +83,15 @@ public partial record Message
|
||||
// Find embeds with the same URL that only contain a single image and nothing else
|
||||
var trailingEmbeds = embeds
|
||||
.Skip(i + 1)
|
||||
.TakeWhile(
|
||||
e =>
|
||||
e.Url == embed.Url
|
||||
&& e.Timestamp is null
|
||||
&& e.Author is null
|
||||
&& e.Color is null
|
||||
&& string.IsNullOrWhiteSpace(e.Description)
|
||||
&& !e.Fields.Any()
|
||||
&& e.Images.Count == 1
|
||||
&& e.Footer is null
|
||||
.TakeWhile(e =>
|
||||
e.Url == embed.Url
|
||||
&& e.Timestamp is null
|
||||
&& e.Author is null
|
||||
&& e.Color is null
|
||||
&& string.IsNullOrWhiteSpace(e.Description)
|
||||
&& !e.Fields.Any()
|
||||
&& e.Images.Count == 1
|
||||
&& e.Footer is null
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -66,12 +66,12 @@ public class DiscordClient(string token)
|
||||
if (remainingRequestCount <= 0 && resetAfterDelay is not null)
|
||||
{
|
||||
var delay =
|
||||
// Adding a small buffer to the reset time reduces the chance of getting
|
||||
// rate limited again, because it allows for more requests to be released.
|
||||
(resetAfterDelay.Value + TimeSpan.FromSeconds(1))
|
||||
// Sometimes Discord returns an absurdly high value for the reset time, which
|
||||
// is not actually enforced by the server. So we cap it at a reasonable value.
|
||||
.Clamp(TimeSpan.Zero, TimeSpan.FromSeconds(60));
|
||||
// Adding a small buffer to the reset time reduces the chance of getting
|
||||
// rate limited again, because it allows for more requests to be released.
|
||||
(resetAfterDelay.Value + TimeSpan.FromSeconds(1))
|
||||
// Sometimes Discord returns an absurdly high value for the reset time, which
|
||||
// is not actually enforced by the server. So we cap it at a reasonable value.
|
||||
.Clamp(TimeSpan.Zero, TimeSpan.FromSeconds(60));
|
||||
|
||||
await Task.Delay(delay, innerCancellationToken);
|
||||
}
|
||||
@@ -152,8 +152,13 @@ public class DiscordClient(string token)
|
||||
_
|
||||
=> throw new DiscordChatExporterException(
|
||||
$"""
|
||||
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
|
||||
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
|
||||
Request to '{url}' failed: {response
|
||||
.StatusCode.ToString()
|
||||
.ToSpaceSeparatedWords()
|
||||
.ToLowerInvariant()}.
|
||||
Response content: {await response.Content.ReadAsStringAsync(
|
||||
cancellationToken
|
||||
)}
|
||||
""",
|
||||
true
|
||||
)
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.7" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.28.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="Polly" Version="8.2.0" />
|
||||
<PackageReference Include="RazorBlade" Version="0.5.0" />
|
||||
<PackageReference Include="Polly" Version="8.3.1" />
|
||||
<PackageReference Include="RazorBlade" Version="0.6.0" />
|
||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.14.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.10" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.16.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.13" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -58,16 +58,15 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse)
|
||||
{
|
||||
var lastModified = response
|
||||
.Content.Headers.TryGetValue("Last-Modified")
|
||||
?.Pipe(
|
||||
s =>
|
||||
DateTimeOffset.TryParse(
|
||||
s,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out var instant
|
||||
)
|
||||
? instant
|
||||
: (DateTimeOffset?)null
|
||||
?.Pipe(s =>
|
||||
DateTimeOffset.TryParse(
|
||||
s,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out var instant
|
||||
)
|
||||
? instant
|
||||
: (DateTimeOffset?)null
|
||||
);
|
||||
|
||||
if (lastModified is not null)
|
||||
|
||||
@@ -93,8 +93,7 @@ internal class ExportContext(DiscordClient discord, ExportRequest request)
|
||||
|
||||
public IReadOnlyList<Role> GetUserRoles(Snowflake id) =>
|
||||
TryGetMember(id)
|
||||
?.RoleIds
|
||||
.Select(TryGetRole)
|
||||
?.RoleIds.Select(TryGetRole)
|
||||
.WhereNotNull()
|
||||
.OrderByDescending(r => r.Position)
|
||||
.ToArray() ?? [];
|
||||
|
||||
@@ -22,12 +22,11 @@ internal class ContainsMessageFilter(string text) : MessageFilter
|
||||
|
||||
public override bool IsMatch(Message message) =>
|
||||
IsMatch(message.Content)
|
||||
|| message.Embeds.Any(
|
||||
e =>
|
||||
IsMatch(e.Title)
|
||||
|| IsMatch(e.Author?.Name)
|
||||
|| IsMatch(e.Description)
|
||||
|| IsMatch(e.Footer?.Text)
|
||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
||||
|| message.Embeds.Any(e =>
|
||||
IsMatch(e.Title)
|
||||
|| IsMatch(e.Author?.Name)
|
||||
|| IsMatch(e.Description)
|
||||
|| IsMatch(e.Footer?.Text)
|
||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||
internal class MentionsMessageFilter(string value) : MessageFilter
|
||||
{
|
||||
public override bool IsMatch(Message message) =>
|
||||
message.MentionedUsers.Any(
|
||||
user =>
|
||||
string.Equals(value, user.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.DisplayName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.FullName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
message.MentionedUsers.Any(user =>
|
||||
string.Equals(value, user.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.DisplayName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.FullName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ internal static class FilterGrammar
|
||||
.OneOf(QuotedString, UnquotedString)
|
||||
.Named("text string");
|
||||
|
||||
private static readonly TextParser<MessageFilter> ContainsFilter = String.Select(
|
||||
v => (MessageFilter)new ContainsMessageFilter(v)
|
||||
private static readonly TextParser<MessageFilter> ContainsFilter = String.Select(v =>
|
||||
(MessageFilter)new ContainsMessageFilter(v)
|
||||
);
|
||||
|
||||
private static readonly TextParser<MessageFilter> FromFilter = Span.EqualToIgnoreCase("from:")
|
||||
|
||||
@@ -7,10 +7,9 @@ namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||
internal class ReactionMessageFilter(string value) : MessageFilter
|
||||
{
|
||||
public override bool IsMatch(Message message) =>
|
||||
message.Reactions.Any(
|
||||
r =>
|
||||
string.Equals(value, r.Emoji.Id?.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase)
|
||||
message.Reactions.Any(r =>
|
||||
string.Equals(value, r.Emoji.Id?.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,7 +155,9 @@ internal partial class HtmlMarkdownVisitor(
|
||||
buffer.Append(
|
||||
// lang=html
|
||||
$"""
|
||||
<code class="chatlog__markdown-pre chatlog__markdown-pre--inline">{HtmlEncode(inlineCodeBlock.Code)}</code>
|
||||
<code class="chatlog__markdown-pre chatlog__markdown-pre--inline">{HtmlEncode(
|
||||
inlineCodeBlock.Code
|
||||
)}</code>
|
||||
"""
|
||||
);
|
||||
|
||||
@@ -174,7 +176,9 @@ internal partial class HtmlMarkdownVisitor(
|
||||
buffer.Append(
|
||||
// lang=html
|
||||
$"""
|
||||
<code class="chatlog__markdown-pre chatlog__markdown-pre--multiline {highlightClass}">{HtmlEncode(multiLineCodeBlock.Code)}</code>
|
||||
<code class="chatlog__markdown-pre chatlog__markdown-pre--multiline {highlightClass}">{HtmlEncode(
|
||||
multiLineCodeBlock.Code
|
||||
)}</code>
|
||||
"""
|
||||
);
|
||||
|
||||
@@ -267,7 +271,9 @@ internal partial class HtmlMarkdownVisitor(
|
||||
buffer.Append(
|
||||
// lang=html
|
||||
$"""
|
||||
<span class="chatlog__markdown-mention" title="{HtmlEncode(fullName)}">@{HtmlEncode(displayName)}</span>
|
||||
<span class="chatlog__markdown-mention" title="{HtmlEncode(fullName)}">@{HtmlEncode(
|
||||
displayName
|
||||
)}</span>
|
||||
"""
|
||||
);
|
||||
}
|
||||
@@ -292,8 +298,12 @@ internal partial class HtmlMarkdownVisitor(
|
||||
|
||||
var style = color is not null
|
||||
? $"""
|
||||
color: rgb({color.Value.R}, {color.Value.G}, {color.Value.B}); background-color: rgba({color.Value.R}, {color.Value.G}, {color.Value.B}, 0.1);
|
||||
"""
|
||||
color: rgb({color.Value.R}, {color.Value.G}, {color
|
||||
.Value
|
||||
.B}); background-color: rgba({color.Value.R}, {color.Value.G}, {color
|
||||
.Value
|
||||
.B}, 0.1);
|
||||
"""
|
||||
: null;
|
||||
|
||||
buffer.Append(
|
||||
@@ -321,7 +331,9 @@ internal partial class HtmlMarkdownVisitor(
|
||||
buffer.Append(
|
||||
// lang=html
|
||||
$"""
|
||||
<span class="chatlog__markdown-timestamp" title="{HtmlEncode(formattedLong)}">{HtmlEncode(formatted)}</span>
|
||||
<span class="chatlog__markdown-timestamp" title="{HtmlEncode(
|
||||
formattedLong
|
||||
)}">{HtmlEncode(formatted)}</span>
|
||||
"""
|
||||
);
|
||||
|
||||
@@ -344,10 +356,8 @@ internal partial class HtmlMarkdownVisitor
|
||||
|
||||
var isJumbo =
|
||||
isJumboAllowed
|
||||
&& nodes.All(
|
||||
n =>
|
||||
n is EmojiNode
|
||||
|| n is TextNode textNode && string.IsNullOrWhiteSpace(textNode.Text)
|
||||
&& nodes.All(n =>
|
||||
n is EmojiNode || n is TextNode textNode && string.IsNullOrWhiteSpace(textNode.Text)
|
||||
);
|
||||
|
||||
var buffer = new StringBuilder();
|
||||
|
||||
@@ -25,11 +25,10 @@ public static class Http
|
||||
private static bool IsRetryableException(Exception exception) =>
|
||||
exception
|
||||
.GetSelfAndChildren()
|
||||
.Any(
|
||||
ex =>
|
||||
ex is TimeoutException or SocketException or AuthenticationException
|
||||
|| ex is HttpRequestException hrex
|
||||
&& IsRetryableStatusCode(hrex.StatusCode ?? HttpStatusCode.OK)
|
||||
.Any(ex =>
|
||||
ex is TimeoutException or SocketException or AuthenticationException
|
||||
|| ex is HttpRequestException hrex
|
||||
&& IsRetryableStatusCode(hrex.StatusCode ?? HttpStatusCode.OK)
|
||||
);
|
||||
|
||||
public static ResiliencePipeline ResiliencePipeline { get; } =
|
||||
|
||||
Reference in New Issue
Block a user