@@ -0,0 +1,6 @@
|
|||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
@@ -59,6 +59,19 @@ internal class TextChunk : Chunk {
|
|||||||
public TextChunk() : base(ChunkSource.None, null) {
|
public TextChunk() : base(ChunkSource.None, null) {
|
||||||
}
|
}
|
||||||
#pragma warning restore CS8618
|
#pragma warning restore CS8618
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextChunk with identical styling to this one.
|
||||||
|
/// </summary>
|
||||||
|
public TextChunk NewWithStyle(ChunkSource source, Payload? link, string content)
|
||||||
|
{
|
||||||
|
return new TextChunk(source, link, content) {
|
||||||
|
FallbackColour = this.FallbackColour,
|
||||||
|
Foreground = this.Foreground,
|
||||||
|
Glow = this.Glow,
|
||||||
|
Italic = this.Italic,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class IconChunk : Chunk {
|
internal class IconChunk : Chunk {
|
||||||
|
|||||||
+88
-1
@@ -1,7 +1,9 @@
|
|||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.Util;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ChatTwo;
|
namespace ChatTwo;
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ internal class Message {
|
|||||||
this.Date = DateTime.UtcNow;
|
this.Date = DateTime.UtcNow;
|
||||||
this.Code = code;
|
this.Code = code;
|
||||||
this.Sender = sender;
|
this.Sender = sender;
|
||||||
this.Content = content;
|
this.Content = ReplaceContentURLs(content);
|
||||||
this.SenderSource = senderSource;
|
this.SenderSource = senderSource;
|
||||||
this.ContentSource = contentSource;
|
this.ContentSource = contentSource;
|
||||||
this.SortCode = new SortCode(this.Code.Type, this.Code.Source);
|
this.SortCode = new SortCode(this.Code.Type, this.Code.Source);
|
||||||
@@ -89,6 +91,8 @@ internal class Message {
|
|||||||
this.Date = date;
|
this.Date = date;
|
||||||
this.Code = BsonMapper.Global.ToObject<ChatCode>(code);
|
this.Code = BsonMapper.Global.ToObject<ChatCode>(code);
|
||||||
this.Sender = BsonMapper.Global.Deserialize<List<Chunk>>(sender);
|
this.Sender = BsonMapper.Global.Deserialize<List<Chunk>>(sender);
|
||||||
|
// Don't call ReplaceContentURLs here since we're loading the message
|
||||||
|
// from the database and it should already have parsed URL data.
|
||||||
this.Content = BsonMapper.Global.Deserialize<List<Chunk>>(content);
|
this.Content = BsonMapper.Global.Deserialize<List<Chunk>>(content);
|
||||||
this.SenderSource = BsonMapper.Global.Deserialize<SeString>(senderSource);
|
this.SenderSource = BsonMapper.Global.Deserialize<SeString>(senderSource);
|
||||||
this.ContentSource = BsonMapper.Global.Deserialize<SeString>(contentSource);
|
this.ContentSource = BsonMapper.Global.Deserialize<SeString>(contentSource);
|
||||||
@@ -108,6 +112,8 @@ internal class Message {
|
|||||||
this.Date = date;
|
this.Date = date;
|
||||||
this.Code = BsonMapper.Global.ToObject<ChatCode>(code);
|
this.Code = BsonMapper.Global.ToObject<ChatCode>(code);
|
||||||
this.Sender = BsonMapper.Global.Deserialize<List<Chunk>>(sender);
|
this.Sender = BsonMapper.Global.Deserialize<List<Chunk>>(sender);
|
||||||
|
// Don't call ReplaceContentURLs here since we're loading the message
|
||||||
|
// from the database and it should already have parsed URL data.
|
||||||
this.Content = BsonMapper.Global.Deserialize<List<Chunk>>(content);
|
this.Content = BsonMapper.Global.Deserialize<List<Chunk>>(content);
|
||||||
this.SenderSource = BsonMapper.Global.Deserialize<SeString>(senderSource);
|
this.SenderSource = BsonMapper.Global.Deserialize<SeString>(senderSource);
|
||||||
this.ContentSource = BsonMapper.Global.Deserialize<SeString>(contentSource);
|
this.ContentSource = BsonMapper.Global.Deserialize<SeString>(contentSource);
|
||||||
@@ -138,4 +144,85 @@ internal class Message {
|
|||||||
|
|
||||||
return Guid.Empty;
|
return Guid.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URLRegex returns a regex object that matches URLs like:
|
||||||
|
/// - https://example.com
|
||||||
|
/// - http://example.com
|
||||||
|
/// - www.example.com
|
||||||
|
/// - https://sub.example.com
|
||||||
|
/// - example.com
|
||||||
|
/// - sub.example.com
|
||||||
|
///
|
||||||
|
/// It matches URLs with www. or https:// prefix, and also matches URLs
|
||||||
|
/// without a prefix on specific TLDs.
|
||||||
|
/// </summary>
|
||||||
|
private static Regex URLRegex = new Regex(
|
||||||
|
@"((https?:\/\/|www\.)[a-z0-9-]+(\.[a-z0-9-]+)*|([a-z0-9-]+(\.[a-z0-9-]+)*\.(com|net|org|co|io|app)))(:[\d]{1,5})?(\/[^\s]+)?",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all URL strings in all TextChunks, splits the parent TextChunk
|
||||||
|
/// apart and inserts a new TextChunk with a URIPayload.
|
||||||
|
/// </summary>
|
||||||
|
private List<Chunk> ReplaceContentURLs(List<Chunk> content)
|
||||||
|
{
|
||||||
|
var newChunks = new List<Chunk>();
|
||||||
|
void AddChunkWithMessage(Chunk chunk) {
|
||||||
|
chunk.Message = this;
|
||||||
|
newChunks.Add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in content)
|
||||||
|
{
|
||||||
|
// Use as is if it's not a text chunk or it already has a payload.
|
||||||
|
if (chunk is not TextChunk text || chunk.Link != null)
|
||||||
|
{
|
||||||
|
// No need to call AddChunkWithMessage here since the chunk
|
||||||
|
// already has the Message field set.
|
||||||
|
newChunks.Add(chunk);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all URLs with the regex and insert a new TextChunk with a
|
||||||
|
// URIPayload.
|
||||||
|
var matches = URLRegex.Matches(text.Content);
|
||||||
|
var remainderIndex = 0;
|
||||||
|
foreach (Match match in matches.Cast<Match>())
|
||||||
|
{
|
||||||
|
// Add the text before the URL.
|
||||||
|
if (match.Index > remainderIndex)
|
||||||
|
{
|
||||||
|
AddChunkWithMessage(text.NewWithStyle(chunk.Source, chunk.Link, text.Content[remainderIndex..match.Index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the remainder index.
|
||||||
|
remainderIndex = match.Index + match.Length;
|
||||||
|
|
||||||
|
// Create a new TextChunk with a URIPayload for the URL text.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var link = URIPayload.ResolveURI(match.Value);
|
||||||
|
AddChunkWithMessage(text.NewWithStyle(chunk.Source, link, match.Value));
|
||||||
|
}
|
||||||
|
catch (UriFormatException)
|
||||||
|
{
|
||||||
|
Plugin.Log.Debug($"Invalid URL accepted by Regex but failed URI parsing: '{match.Value}'");
|
||||||
|
// If the URL is invalid, set the remainder index to the
|
||||||
|
// beginning of the match so it'll get included in the next
|
||||||
|
// regular text chunk.
|
||||||
|
remainderIndex = match.Index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the text after the last URL.
|
||||||
|
if (remainderIndex < text.Content.Length)
|
||||||
|
{
|
||||||
|
AddChunkWithMessage(text.NewWithStyle(chunk.Source, null, text.Content[remainderIndex..]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChunks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||||||
using Action = System.Action;
|
using Action = System.Action;
|
||||||
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
||||||
using ChatTwoPartyFinderPayload = ChatTwo.Util.PartyFinderPayload;
|
using ChatTwoPartyFinderPayload = ChatTwo.Util.PartyFinderPayload;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace ChatTwo;
|
namespace ChatTwo;
|
||||||
|
|
||||||
@@ -86,6 +87,11 @@ public sealed class PayloadHandler {
|
|||||||
drawn = true;
|
drawn = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case URIPayload uri: {
|
||||||
|
DrawUriPopup(uri);
|
||||||
|
drawn = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextFooter(drawn, chunk);
|
ContextFooter(drawn, chunk);
|
||||||
@@ -213,6 +219,11 @@ public sealed class PayloadHandler {
|
|||||||
DoHover(() => HoverItem(item), hoverSize);
|
DoHover(() => HoverItem(item), hoverSize);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case URIPayload uri:
|
||||||
|
{
|
||||||
|
DoHover(() => HoverURI(uri), hoverSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,6 +343,11 @@ public sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HoverURI(URIPayload uri) {
|
||||||
|
ImGui.TextUnformatted(string.Format(Language.Context_URLDomain, uri.Uri.Authority));
|
||||||
|
ImGuiUtil.WarningText(Language.Context_URLWarning);
|
||||||
|
}
|
||||||
|
|
||||||
private void LeftClickPayload(Chunk chunk, Payload? payload) {
|
private void LeftClickPayload(Chunk chunk, Payload? payload) {
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case MapLinkPayload map: {
|
case MapLinkPayload map: {
|
||||||
@@ -370,6 +386,10 @@ public sealed class PayloadHandler {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case URIPayload uri: {
|
||||||
|
TryOpenURI(uri.Uri);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,4 +643,38 @@ public sealed class PayloadHandler {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawUriPopup(URIPayload uri)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(string.Format(Language.Context_URLDomain, uri.Uri.Authority));
|
||||||
|
ImGuiUtil.WarningText(Language.Context_URLWarning, false);
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.Selectable(Language.Context_OpenInBrowser))
|
||||||
|
{
|
||||||
|
TryOpenURI(uri.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable(Language.Context_CopyLink))
|
||||||
|
{
|
||||||
|
ImGui.SetClipboardText(uri.Uri.ToString());
|
||||||
|
WrapperUtil.AddNotification(Language.Context_CopyLinkNotification, NotificationType.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryOpenURI(Uri uri)
|
||||||
|
{
|
||||||
|
new Thread(() => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Plugin.Log.Info($"Opening URI {uri} in default browser");
|
||||||
|
Process.Start(new ProcessStartInfo(uri.ToString()) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Log.Error($"Error opening URI: {ex}");
|
||||||
|
WrapperUtil.AddNotification(Language.Context_OpenInBrowserError, NotificationType.Error);
|
||||||
|
}
|
||||||
|
}).Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+54
@@ -1040,6 +1040,24 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Copy link to clipboard.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_CopyLink {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_CopyLink", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Copied link to clipboard.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_CopyLinkNotification {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_CopyLinkNotification", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Hide chat.
|
/// Looks up a localized string similar to Hide chat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1121,6 +1139,24 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Open link in browser.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_OpenInBrowser {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_OpenInBrowser", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Failed to open the link in the browser, please report this issue.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_OpenInBrowserError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_OpenInBrowserError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Promote.
|
/// Looks up a localized string similar to Promote.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1202,6 +1238,24 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URL at {0}.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_URLDomain {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_URLDomain", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Only open URLs from websites you trust.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Context_URLWarning {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Context_URLWarning", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Chinese (full).
|
/// Looks up a localized string similar to Chinese (full).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -871,4 +871,22 @@
|
|||||||
<data name="Options_TooltipOffset_Desc" xml:space="preserve">
|
<data name="Options_TooltipOffset_Desc" xml:space="preserve">
|
||||||
<value>Use this option if you experience cut-off tooltips.</value>
|
<value>Use this option if you experience cut-off tooltips.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Context_CopyLink" xml:space="preserve">
|
||||||
|
<value>Copy link to clipboard</value>
|
||||||
|
</data>
|
||||||
|
<data name="Context_CopyLinkNotification" xml:space="preserve">
|
||||||
|
<value>Copied link to clipboard</value>
|
||||||
|
</data>
|
||||||
|
<data name="Context_OpenInBrowser" xml:space="preserve">
|
||||||
|
<value>Open link in browser</value>
|
||||||
|
</data>
|
||||||
|
<data name="Context_OpenInBrowserError" xml:space="preserve">
|
||||||
|
<value>Failed to open the link in the browser, please report this issue</value>
|
||||||
|
</data>
|
||||||
|
<data name="Context_URLDomain" xml:space="preserve">
|
||||||
|
<value>URL at {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="Context_URLWarning" xml:space="preserve">
|
||||||
|
<value>Only open URLs from websites you trust</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ internal class Store : IDisposable {
|
|||||||
["Type"] = new("PartyFinder"),
|
["Type"] = new("PartyFinder"),
|
||||||
["Id"] = new(partyFinder.Id),
|
["Id"] = new(partyFinder.Id),
|
||||||
});
|
});
|
||||||
|
case URIPayload uri:
|
||||||
|
return new BsonDocument(new Dictionary<string, BsonValue> {
|
||||||
|
["Type"] = new("URI"),
|
||||||
|
["Uri"] = new(uri.Uri.ToString()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload?.Encode();
|
return payload?.Encode();
|
||||||
@@ -99,6 +104,7 @@ internal class Store : IDisposable {
|
|||||||
return bson["Type"].AsString switch {
|
return bson["Type"].AsString switch {
|
||||||
"Achievement" => new AchievementPayload((uint) bson["Id"].AsInt64),
|
"Achievement" => new AchievementPayload((uint) bson["Id"].AsInt64),
|
||||||
"PartyFinder" => new PartyFinderPayload((uint) bson["Id"].AsInt64),
|
"PartyFinder" => new PartyFinderPayload((uint) bson["Id"].AsInt64),
|
||||||
|
"URI" => new URIPayload(new Uri(bson["Uri"].AsString)),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ChatTwo.Util;
|
namespace ChatTwo.Util;
|
||||||
|
|
||||||
@@ -104,6 +105,10 @@ internal static class ChunkUtil {
|
|||||||
var reader = new BinaryReader(new MemoryStream(rawPayload.Data[4..]));
|
var reader = new BinaryReader(new MemoryStream(rawPayload.Data[4..]));
|
||||||
var id = GetInteger(reader);
|
var id = GetInteger(reader);
|
||||||
link = new AchievementPayload(id);
|
link = new AchievementPayload(id);
|
||||||
|
} else if (rawPayload.Data.Length > 5 && rawPayload.Data[1] == 0x27 && rawPayload.Data[3] == 0x07) {
|
||||||
|
// uri payload
|
||||||
|
var uri = new Uri(Encoding.UTF8.GetString(rawPayload.Data[4..]));
|
||||||
|
link = new URIPayload(uri);
|
||||||
} else if (Equals(rawPayload, RawPayload.LinkTerminator)) {
|
} else if (Equals(rawPayload, RawPayload.LinkTerminator)) {
|
||||||
link = null;
|
link = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,16 +197,16 @@ internal static class ImGuiUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void WarningText(string text) {
|
internal static void WarningText(string text, bool wrap = true) {
|
||||||
var style = StyleModel.GetConfiguredStyle() ?? StyleModel.GetFromCurrent();
|
var style = StyleModel.GetConfiguredStyle() ?? StyleModel.GetFromCurrent();
|
||||||
var dalamudOrange = style.BuiltInColors?.DalamudOrange;
|
var dalamudOrange = style.BuiltInColors?.DalamudOrange;
|
||||||
if (dalamudOrange != null) {
|
if (dalamudOrange != null) {
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, dalamudOrange.Value);
|
ImGui.PushStyleColor(ImGuiCol.Text, dalamudOrange.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PushTextWrapPos();
|
if (wrap) ImGui.PushTextWrapPos();
|
||||||
ImGui.TextUnformatted(text);
|
ImGui.TextUnformatted(text);
|
||||||
ImGui.PopTextWrapPos();
|
if (wrap) ImGui.PopTextWrapPos();
|
||||||
|
|
||||||
if (dalamudOrange != null) {
|
if (dalamudOrange != null) {
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|||||||
@@ -37,3 +37,51 @@ internal class AchievementPayload : Payload {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class URIPayload(Uri uri) : Payload
|
||||||
|
{
|
||||||
|
public override PayloadType Type => (PayloadType) 0x52;
|
||||||
|
|
||||||
|
public Uri Uri { get; init; } = uri;
|
||||||
|
|
||||||
|
private static readonly string[] ExpectedSchemes = ["http", "https"];
|
||||||
|
private static readonly string DefaultScheme = "https";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a URIPayload from a raw URI string. If the URI does not have a
|
||||||
|
/// scheme, it will default to https://.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="UriFormatException">
|
||||||
|
/// If the URI is invalid, or if the scheme is not supported.
|
||||||
|
/// </exception>
|
||||||
|
public static URIPayload ResolveURI(string rawURI)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(rawURI);
|
||||||
|
|
||||||
|
// Check for expected scheme ://, if not add https://
|
||||||
|
foreach (var scheme in ExpectedSchemes)
|
||||||
|
{
|
||||||
|
if (rawURI.StartsWith($"{scheme}://"))
|
||||||
|
{
|
||||||
|
return new URIPayload(new Uri(rawURI));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawURI.Contains("://"))
|
||||||
|
{
|
||||||
|
throw new UriFormatException($"Unsupported scheme in URL: {rawURI}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URIPayload(new Uri($"{DefaultScheme}://{rawURI}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override byte[] EncodeImpl()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user