Skip to content

Add support for short Teams meeting URLs in ParseJoinURL#858

Open
samarthasthana wants to merge 4 commits intomasterfrom
sasthana/fix-short-teams-url-parsing
Open

Add support for short Teams meeting URLs in ParseJoinURL#858
samarthasthana wants to merge 4 commits intomasterfrom
sasthana/fix-short-teams-url-parsing

Conversation

@samarthasthana
Copy link
Contributor

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 #829

Teams now generates short meeting URLs in the format:
https://teams.microsoft.com/meet/<meetingId>?p=<passcode>

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 #829
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support across multiple samples/libs for parsing the newer short Teams meeting join URL format (https://teams.microsoft.com/meet/<meetingId>?p=<passcode>) by falling back to JoinMeetingIdMeetingInfo, and introduces a TenantId field in join request models where the tenant can’t be inferred from the URL.

Changes:

  • Updated ParseJoinURL/join-url parsing in 6 JoinInfo implementations to handle both long and short URL formats.
  • Added TenantId to JoinCallBody models (EchoBot, PsiBot, RecordingBot) to support short URLs.
  • Updated join-call flows to extract tenant id from JoinCallBody.TenantId with fallback to OrganizerMeetingInfo.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs Adds short-URL parsing fallback via JoinMeetingIdMeetingInfo.
Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Bot/BotService.cs Uses JoinCallBody.TenantId fallback when deriving tenant id.
Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Model/Models/JoinCallBody.cs Adds TenantId to the join request model.
Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs Adds short-URL parsing and tenant-id fallback for joining calls.
Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Model/Models/JoinCallBody.cs Adds TenantId to the join request model.
Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs Adds short-URL parsing fallback via JoinMeetingIdMeetingInfo.
Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinCallBody.cs Adds TenantId to the join request model.
Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs Uses JoinCallBody.TenantId fallback when deriving tenant id.
Samples/Common/Sample.Common/Meetings/JoinInfo.cs Adds short-URL parsing fallback in shared common library.
Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs Adds short-URL parsing fallback in V1 common library.
Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs Adds short-URL parsing fallback in Beta common library.
Comments suppressed due to low confidence (13)

Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs:382

  • The short URL regex captures the passcode with .+, which will include any trailing query params if present. Prefer parsing the URL query (extract p explicitly) or constrain the passcode match to stop at &/#.
            //// Short URL format: https://teams.microsoft.com/meet/<meetingId>?p=<passcode>
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);
            if (shortUrlMatch.Success)

Samples/Common/Sample.Common/Meetings/JoinInfo.cs:35

  • The long URL regex uses a greedy {.*} capture for the context JSON. If any additional query parameters appear after context=..., they’ll be included in the JSON string and deserialization will fail. Consider making the match non-greedy or parsing the context query parameter via Uri/query parsing instead of regex.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);

Samples/Common/Sample.Common/Meetings/JoinInfo.cs:64

  • The short URL regex captures the passcode with .+, which will include any trailing query params (e.g. &foo=bar) if present. Prefer parsing the p query parameter explicitly (Uri/query parsing) or constrain the match to stop at &/#.
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);

Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs:35

  • The long URL regex uses a greedy {.*} capture for the context JSON; extra query parameters after context=... would be captured and break JSON deserialization. Consider making the context capture non-greedy or parsing the query string to extract context safely.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);

Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs:65

  • The short URL regex captures the passcode with .+, which will include any trailing query params if present. Prefer parsing the p query parameter explicitly or constrain the match to stop at &/#.
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);
            if (shortUrlMatch.Success)

Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs:65

  • The short URL regex captures the passcode with .+, which will include any trailing query params if present. Prefer parsing the p query parameter explicitly or constrain the match to stop at &/#.
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);
            if (shortUrlMatch.Success)

Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs:85

  • The short URL regex captures the passcode with .+, which will also include any additional query parameters (e.g. &foo=bar) if Teams adds them later. Prefer parsing via Uri + query parsing, or at least restrict the capture to stop at &/# so Passcode is extracted reliably.
            //// Short URL format: https://teams.microsoft.com/meet/<meetingId>?p=<passcode>
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);

Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs:83

  • The short URL regex captures the passcode with .+, which will include any trailing query params if present. Prefer parsing the URL query (e.g. extract p explicitly) or constrain the passcode match to stop at &/# so Passcode is correct.
            //// Short URL format: https://teams.microsoft.com/meet/<meetingId>?p=<passcode>
            var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
            var shortUrlMatch = shortUrlRegex.Match(decodedURL);
            if (shortUrlMatch.Success)

Samples/V1.0Samples/AksSamples(Deprecated)/teams-recording-bot/src/RecordingBot.Services/Util/JoinInfo.cs:51

  • The long URL regex captures the context JSON with a greedy {.*}. If the URL ever contains additional query parameters after context=..., the capture will include them and JSON deserialization will fail. Consider making the match non-greedy or extracting context via Uri/query parsing instead of regex.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);

Samples/Common/Sample.Common/Meetings/JoinInfo.cs:56

  • Long URL parsing sets the organizer tenant id from ctxt.Tid but doesn’t validate it before calling SetTenantId. If Tid is missing/empty, this will either set an invalid value or throw from inside the SDK. Consider validating required fields (Tid/Oid) and throwing a clearer ArgumentException, as done in other JoinInfo implementations in this PR.
                    var meetingInfo = new OrganizerMeetingInfo
                    {
                        Organizer = new IdentitySet
                        {
                            User = new Identity { Id = ctxt.Oid },
                        },
                    };
                    meetingInfo.Organizer.User.SetTenantId(ctxt.Tid);

Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs:35

  • The long URL regex uses a greedy {.*} capture for the context JSON; extra query parameters after context=... would be captured and break JSON deserialization. Consider making the context capture non-greedy or extracting context from the query string instead of regex.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);

Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs:49

  • The long URL regex captures the context JSON with a greedy {.*}. If additional query parameters appear after context=..., the capture will include them and JSON deserialization will fail. Consider using non-greedy matching or parsing the context query parameter explicitly via Uri/query parsing.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);
            if (match.Success)

Samples/PublicSamples/PsiBot/PsiBot/PsiBot.Service/Bot/BotService.cs:348

  • The long URL regex captures the context JSON with a greedy {.*}. If additional query parameters appear after context=..., the capture will include them and JSON deserialization will fail. Consider extracting the context query parameter via Uri/query parsing or using a non-greedy match.
            var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})");
            var match = regex.Match(decodedURL);
            if (match.Success)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +85 to 92
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);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For short meeting URLs, this returns an empty ChatInfo. In EchoBot, JoinCallAsync later uses joinParams.ChatInfo.ThreadId as a dictionary key; if ThreadId is null/empty this will throw (ArgumentNullException) or cause collisions. Consider returning a non-null ThreadId surrogate (if supported) or adjusting the bot’s call tracking logic for JoinMeetingIdMeetingInfo joins.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

@samarthasthana samarthasthana marked this pull request as ready for review February 25, 2026 19:16
…ice.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

Copilot AI commented Feb 25, 2026

@samarthasthana I've opened a new pull request, #859, to work on those changes. Once the pull request is ready, I'll request review from you.

samarthasthana and others added 2 commits February 25, 2026 14:18
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…/src/RecordingBot.Services/Bot/BotService.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to parse new Teams Short URL

3 participants