From 0cf61b553b2dbbce3760b708e2c18e674f95e5eb Mon Sep 17 00:00:00 2001 From: Samarth Asthana Date: Wed, 25 Feb 2026 11:59:06 -0500 Subject: [PATCH 1/4] Add support for short Teams meeting URLs in ParseJoinURL Teams now generates short meeting URLs in the format: https://teams.microsoft.com/meet/?p= The existing ParseJoinURL only supports the legacy long format with context parameters. This change adds a fallback to parse short URLs using JoinMeetingIdMeetingInfo instead of OrganizerMeetingInfo. For short URLs, the tenant ID cannot be inferred from the URL and must be provided separately via the JoinCallBody.TenantId property (added to models that lacked it). Changes: - Updated ParseJoinURL in all 6 JoinInfo implementations to detect and parse both long and short URL formats - Added TenantId property to JoinCallBody in EchoBot, PsiBot, and RecordingBot models - Updated callers to safely handle both OrganizerMeetingInfo and JoinMeetingIdMeetingInfo return types using null-safe tenant extraction with JoinCallBody.TenantId fallback Fixes microsoftgraph/microsoft-graph-comms-samples#829 --- .../Sample.Common.Beta/Meetings/JoinInfo.cs | 53 +++++++++------ .../Sample.Common.V1/Meetings/JoinInfo.cs | 53 +++++++++------ .../Common/Sample.Common/Meetings/JoinInfo.cs | 53 +++++++++------ .../EchoBot/src/EchoBot/Bot/BotService.cs | 4 +- .../src/EchoBot/Models/JoinCallBody.cs | 8 +++ .../EchoBot/src/EchoBot/Models/JoinInfo.cs | 61 ++++++++++------- .../PsiBot.Model/Models/JoinCallBody.cs | 8 +++ .../PsiBot/PsiBot.Service/Bot/BotService.cs | 66 ++++++++++++------- .../RecordingBot.Model/Models/JoinCallBody.cs | 8 +++ .../RecordingBot.Services/Bot/BotService.cs | 4 +- .../RecordingBot.Services/Util/JoinInfo.cs | 62 ++++++++++------- 11 files changed, 249 insertions(+), 131 deletions(-) diff --git a/Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs b/Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs index ad7ff546b..1f7df9cfc 100644 --- a/Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs +++ b/Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs @@ -28,37 +28,52 @@ public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); - //// URL being needs to be in this format. + //// Long URL format: //// https://teams.microsoft.com/l/meetup-join/19:cd9ce3da56624fe69c9d7cd026f9126d@thread.skype/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; + + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + + // meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + return (chatInfo, meetingInfo); + } } - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + //// Short URL format: + //// https://teams.microsoft.com/meet/2414128301281?p=NzhDLMLT8b07DrkwkE + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) { - var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); - var chatInfo = new ChatInfo + var meetingInfo = new JoinMeetingIdMeetingInfo { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - var meetingInfo = new OrganizerMeetingInfo - { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, - }; - - // meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); } /// diff --git a/Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs b/Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs index da5d39316..3d3b8fa42 100644 --- a/Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs +++ b/Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs @@ -28,37 +28,52 @@ public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); - //// URL being needs to be in this format. + //// Long URL format: //// https://teams.microsoft.com/l/meetup-join/19:cd9ce3da56624fe69c9d7cd026f9126d@thread.skype/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; + + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + + // meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + return (chatInfo, meetingInfo); + } } - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + //// Short URL format: + //// https://teams.microsoft.com/meet/2414128301281?p=NzhDLMLT8b07DrkwkE + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) { - var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); - var chatInfo = new ChatInfo + var meetingInfo = new JoinMeetingIdMeetingInfo { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - var meetingInfo = new OrganizerMeetingInfo - { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, - }; - - // meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); } /// diff --git a/Samples/Common/Sample.Common/Meetings/JoinInfo.cs b/Samples/Common/Sample.Common/Meetings/JoinInfo.cs index c43df2d95..bc52e37ee 100644 --- a/Samples/Common/Sample.Common/Meetings/JoinInfo.cs +++ b/Samples/Common/Sample.Common/Meetings/JoinInfo.cs @@ -28,37 +28,52 @@ public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); - //// URL being needs to be in this format. + //// Long URL format: //// https://teams.microsoft.com/l/meetup-join/19:cd9ce3da56624fe69c9d7cd026f9126d@thread.skype/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; + + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + + return (chatInfo, meetingInfo); + } } - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + //// Short URL format: + //// https://teams.microsoft.com/meet/2414128301281?p=NzhDLMLT8b07DrkwkE + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) { - var ctxt = (Context)new DataContractJsonSerializer(typeof(Context)).ReadObject(stream); - var chatInfo = new ChatInfo + var meetingInfo = new JoinMeetingIdMeetingInfo { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - var meetingInfo = new OrganizerMeetingInfo - { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, - }; - meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}.", nameof(joinURL)); } /// diff --git a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs index aa6a3a147..61f59fad6 100644 --- a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs +++ b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs @@ -194,7 +194,9 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL(joinCallBody.JoinUrl); - var tenantId = (meetingInfo as OrganizerMeetingInfo).Organizer.GetPrimaryIdentity().GetTenantId(); + var tenantId = + joinCallBody.TenantId ?? + (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) diff --git a/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinCallBody.cs b/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinCallBody.cs index 0897eec4f..526b2838a 100644 --- a/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinCallBody.cs +++ b/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinCallBody.cs @@ -24,6 +24,14 @@ public class JoinCallBody /// The join URL. public string JoinUrl { get; set; } + /// + /// Gets or sets the tenant id. + /// Required when using short meeting URLs (e.g. https://teams.microsoft.com/meet/...). + /// The tenant id cannot be inferred from short URLs and must be provided separately. + /// + /// The tenant id. + public string? TenantId { get; set; } + /// /// Gets or sets the display name. /// Teams client does not allow changing of ones own display name. diff --git a/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs b/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs index c4e6da7d2..ab19e8213 100644 --- a/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs +++ b/Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs @@ -43,40 +43,55 @@ public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) var decodedURL = WebUtility.UrlDecode(joinURL); + //// Long URL format: var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); - } + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) - { - var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); + if (string.IsNullOrEmpty(ctxt.Tid)) + { + throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); + } - if (string.IsNullOrEmpty(ctxt.Tid)) - { - throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); - } + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; - var chatInfo = new ChatInfo - { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, - }; + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + + return (chatInfo, meetingInfo); + } + } - var meetingInfo = new OrganizerMeetingInfo + //// Short URL format: https://teams.microsoft.com/meet/?p= + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) + { + var meetingInfo = new JoinMeetingIdMeetingInfo { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); } } } diff --git a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Model/Models/JoinCallBody.cs b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Model/Models/JoinCallBody.cs index 6435119a3..34415ce79 100644 --- a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Model/Models/JoinCallBody.cs +++ b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Model/Models/JoinCallBody.cs @@ -16,6 +16,14 @@ public class JoinCallBody /// The join URL. public string JoinURL { get; set; } + /// + /// Gets or sets the tenant id. + /// Required when using short meeting URLs (e.g. https://teams.microsoft.com/meet/...). + /// The tenant id cannot be inferred from short URLs and must be provided separately. + /// + /// The tenant id. + public string TenantId { get; set; } + /// /// Gets or sets the display name. /// Teams client does not allow changing of ones own display name. diff --git a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs index 7c5bc2ab4..3c72c9c94 100644 --- a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs +++ b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs @@ -141,7 +141,9 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var (chatInfo, meetingInfo) = ParseJoinURL(joinCallBody.JoinURL); - var tenantId = (meetingInfo as OrganizerMeetingInfo).Organizer.GetPrimaryIdentity().GetTenantId(); + var tenantId = + joinCallBody.TenantId ?? + (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) @@ -338,43 +340,57 @@ private CallHandler GetHandlerOrThrow(string callLegId) var decodedURL = WebUtility.UrlDecode(joinURL); - //// URL being needs to be in this format. + //// Long URL format: //// https://teams.microsoft.com/l/meetup-join/19:cd9ce3da56624fe69c9d7cd026f9126d@thread.skype/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); - } + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) - { - var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); + if (string.IsNullOrEmpty(ctxt.Tid)) + { + throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); + } - if (string.IsNullOrEmpty(ctxt.Tid)) - { - throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); - } + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; - var chatInfo = new ChatInfo - { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, - }; + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + + return (chatInfo, meetingInfo); + } + } - var meetingInfo = new OrganizerMeetingInfo + //// Short URL format: https://teams.microsoft.com/meet/?p= + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) + { + var meetingInfo = new JoinMeetingIdMeetingInfo { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); } } } diff --git a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Model/Models/JoinCallBody.cs b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Model/Models/JoinCallBody.cs index 2a5bb533d..9e0c9e083 100644 --- a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Model/Models/JoinCallBody.cs +++ b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Model/Models/JoinCallBody.cs @@ -24,6 +24,14 @@ public class JoinCallBody /// The join URL. public string JoinURL { get; set; } + /// + /// Gets or sets the tenant id. + /// Required when using short meeting URLs (e.g. https://teams.microsoft.com/meet/...). + /// The tenant id cannot be inferred from short URLs and must be provided separately. + /// + /// The tenant id. + public string TenantId { get; set; } + /// /// Gets or sets the display name. /// Teams client does not allow changing of ones own display name. diff --git a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs index 36ec997f5..8283acdb9 100644 --- a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs +++ b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs @@ -151,7 +151,9 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL(joinCallBody.JoinURL); - var tenantId = (meetingInfo as OrganizerMeetingInfo).Organizer.GetPrimaryIdentity().GetTenantId(); + var tenantId = + joinCallBody.TenantId ?? + (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) diff --git a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs index 37e776f80..15455208f 100644 --- a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs +++ b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs @@ -44,43 +44,57 @@ public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) var decodedURL = WebUtility.UrlDecode(joinURL); - //// URL being needs to be in this format. + //// Long URL format: //// https://teams.microsoft.com/l/meetup-join/19:cd9ce3da56624fe69c9d7cd026f9126d@thread.skype/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?[^/]+)/(?[^/]+)\\?context=(?{.*})"); var match = regex.Match(decodedURL); - if (!match.Success) + if (match.Success) { - throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); - } + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) + { + var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(match.Groups["context"].Value))) - { - var ctxt = (Meeting)new DataContractJsonSerializer(typeof(Meeting)).ReadObject(stream); + if (string.IsNullOrEmpty(ctxt.Tid)) + { + throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); + } - if (string.IsNullOrEmpty(ctxt.Tid)) - { - throw new ArgumentException("Join URL is invalid: missing Tid", nameof(joinURL)); - } + var chatInfo = new ChatInfo + { + ThreadId = match.Groups["thread"].Value, + MessageId = match.Groups["message"].Value, + ReplyChainMessageId = ctxt.MessageId, + }; - var chatInfo = new ChatInfo - { - ThreadId = match.Groups["thread"].Value, - MessageId = match.Groups["message"].Value, - ReplyChainMessageId = ctxt.MessageId, - }; + var meetingInfo = new OrganizerMeetingInfo + { + Organizer = new IdentitySet + { + User = new Identity { Id = ctxt.Oid }, + }, + }; + meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); + + return (chatInfo, meetingInfo); + } + } - var meetingInfo = new OrganizerMeetingInfo + //// Short URL format: https://teams.microsoft.com/meet/?p= + var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?[^?]+)\\?p=(?.+)"); + var shortUrlMatch = shortUrlRegex.Match(decodedURL); + if (shortUrlMatch.Success) + { + var meetingInfo = new JoinMeetingIdMeetingInfo { - Organizer = new IdentitySet - { - User = new Identity { Id = ctxt.Oid }, - }, + JoinMeetingId = shortUrlMatch.Groups["meetingId"].Value, + Passcode = shortUrlMatch.Groups["passcode"].Value, }; - meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); - return (chatInfo, meetingInfo); + return (new ChatInfo(), meetingInfo); } + + throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL)); } } } From 97381eaba514277c6c5b14fa37e05e7128f91ab4 Mon Sep 17 00:00:00 2001 From: Samarth Asthana Date: Wed, 25 Feb 2026 14:17:12 -0500 Subject: [PATCH 2/4] Update Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs index 3c72c9c94..a2348150f 100644 --- a/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs +++ b/Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs @@ -144,6 +144,13 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var tenantId = joinCallBody.TenantId ?? (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); + + if (string.IsNullOrWhiteSpace(tenantId)) + { + throw new InvalidOperationException( + "TenantId is required to join the meeting. " + + "Ensure JoinCallBody.TenantId is provided or the meeting URL includes organizer tenant information."); + } var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) From de42f7c56e478ecf3f522704d323a26344ef9096 Mon Sep 17 00:00:00 2001 From: Samarth Asthana Date: Wed, 25 Feb 2026 14:18:08 -0500 Subject: [PATCH 3/4] Update Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../EchoBot/src/EchoBot/Bot/BotService.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs index 61f59fad6..5c29de2e7 100644 --- a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs +++ b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs @@ -196,7 +196,15 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var tenantId = joinCallBody.TenantId ?? - (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); + (meetingInfo as OrganizerMeetingInfo)?.Organizer?.GetPrimaryIdentity()?.GetTenantId(); + + if (string.IsNullOrWhiteSpace(tenantId)) + { + throw new HttpRequestException( + "TenantId could not be resolved from the join URL or request body.", + null, + HttpStatusCode.BadRequest); + } var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) From 85bafad97eac1fb6cfef4f06943bd565b8675ca3 Mon Sep 17 00:00:00 2001 From: Samarth Asthana Date: Wed, 25 Feb 2026 14:18:21 -0500 Subject: [PATCH 4/4] Update Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/RecordingBot.Services/Bot/BotService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs index 8283acdb9..6ea9fa4ba 100644 --- a/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs +++ b/Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs @@ -154,6 +154,13 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) var tenantId = joinCallBody.TenantId ?? (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); + + if (string.IsNullOrWhiteSpace(tenantId)) + { + throw new InvalidOperationException( + "TenantId is required to join the call. " + + "Ensure that JoinCallBody.TenantId is set or the meeting URL contains organizer tenant information."); + } var mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession)