diff --git a/LibMatrix/StructuredData/MxcUri.cs b/LibMatrix/StructuredData/MxcUri.cs
new file mode 100644
index 0000000..82a9677
--- /dev/null
+++ b/LibMatrix/StructuredData/MxcUri.cs
@@ -0,0 +1,43 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibMatrix.StructuredData;
+
+public class MxcUri {
+ public required string ServerName { get; set; }
+ public required string MediaId { get; set; }
+
+ public static MxcUri Parse([StringSyntax("Uri")] string mxcUri) {
+ if (!mxcUri.StartsWith("mxc://")) throw new ArgumentException("Matrix Content URIs must start with 'mxc://'", nameof(mxcUri));
+ var parts = mxcUri[6..].Split('/');
+ if (parts.Length != 2) throw new ArgumentException($"Invalid Matrix Content URI '{mxcUri}' passed! Matrix Content URIs must exist of only 2 parts!", nameof(mxcUri));
+ return new MxcUri {
+ ServerName = parts[0],
+ MediaId = parts[1]
+ };
+ }
+
+ public static implicit operator MxcUri(string mxcUri) => Parse(mxcUri);
+ public static implicit operator string(MxcUri mxcUri) => $"mxc://{mxcUri.ServerName}/{mxcUri.MediaId}";
+ public static implicit operator (string, string)(MxcUri mxcUri) => (mxcUri.ServerName, mxcUri.MediaId);
+ public static implicit operator MxcUri((string serverName, string mediaId) mxcUri) => (mxcUri.serverName, mxcUri.mediaId);
+ // public override string ToString() => $"mxc://{ServerName}/{MediaId}";
+
+ public string ToDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) {
+ var uri = $"{baseUrl}/_matrix/client/v1/media/download/{ServerName}/{MediaId}";
+ if (filename is not null) uri += $"/{filename}";
+ if (timeout is not null) uri += $"?timeout={timeout}";
+ return uri;
+ }
+
+ public string ToLegacyDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) {
+ var uri = $"{baseUrl}/_matrix/media/v3/download/{ServerName}/{MediaId}";
+ if (filename is not null) uri += $"/{filename}";
+ if (timeout is not null) uri += $"?timeout_ms={timeout}";
+ return uri;
+ }
+
+ public void Deconstruct(out string serverName, out string mediaId) {
+ serverName = ServerName;
+ mediaId = MediaId;
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/StructuredData/UserId.cs b/LibMatrix/StructuredData/UserId.cs
new file mode 100644
index 0000000..02b2e91
--- /dev/null
+++ b/LibMatrix/StructuredData/UserId.cs
@@ -0,0 +1,27 @@
+namespace LibMatrix.StructuredData;
+
+public class UserId {
+ public required string ServerName { get; set; }
+ public required string LocalPart { get; set; }
+
+ public static UserId Parse(string mxid) {
+ if (!mxid.StartsWith('@')) throw new ArgumentException("Matrix User IDs must start with '@'", nameof(mxid));
+ var parts = mxid.Split(':', 2);
+ if (parts.Length != 2) throw new ArgumentException($"Invalid MXID '{mxid}' passed! MXIDs must exist of only 2 parts!", nameof(mxid));
+ return new UserId {
+ LocalPart = parts[0][1..],
+ ServerName = parts[1]
+ };
+ }
+
+ public static implicit operator UserId(string mxid) => Parse(mxid);
+ public static implicit operator string(UserId mxid) => $"@{mxid.LocalPart}:{mxid.ServerName}";
+ public static implicit operator (string, string)(UserId mxid) => (mxid.LocalPart, mxid.ServerName);
+ public static implicit operator UserId((string localPart, string serverName) mxid) => (mxid.localPart, mxid.serverName);
+ // public override string ToString() => $"mxc://{ServerName}/{MediaId}";
+
+ public void Deconstruct(out string serverName, out string localPart) {
+ serverName = ServerName;
+ localPart = LocalPart;
+ }
+}
\ No newline at end of file
|