Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions Samples/Common/Sample.Common.Beta/Meetings/JoinInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<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/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
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));
}

/// <summary>
Expand Down
53 changes: 34 additions & 19 deletions Samples/Common/Sample.Common.V1/Meetings/JoinInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<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/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
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));
}

/// <summary>
Expand Down
53 changes: 34 additions & 19 deletions Samples/Common/Sample.Common/Meetings/JoinInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<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/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
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));
}

/// <summary>
Expand Down
12 changes: 11 additions & 1 deletion Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,17 @@ public async Task<ICall> 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();

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class JoinCallBody
/// <value>The join URL.</value>
public string JoinUrl { get; set; }

/// <summary>
/// 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.
/// </summary>
/// <value>The tenant id.</value>
public string? TenantId { get; set; }

/// <summary>
/// Gets or sets the display name.
/// Teams client does not allow changing of ones own display name.
Expand Down
61 changes: 38 additions & 23 deletions Samples/PublicSamples/EchoBot/src/EchoBot/Models/JoinInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<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/<meetingId>?p=<passcode>
var shortUrlRegex = new Regex("https://teams\\.microsoft\\.com/meet/(?<meetingId>[^?]+)\\?p=(?<passcode>.+)");
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);
}
Comment on lines +85 to 92
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


throw new ArgumentException($"Join URL cannot be parsed: {joinURL}", nameof(joinURL));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ public class JoinCallBody
/// <value>The join URL.</value>
public string JoinURL { get; set; }

/// <summary>
/// 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.
/// </summary>
/// <value>The tenant id.</value>
public string TenantId { get; set; }

/// <summary>
/// Gets or sets the display name.
/// Teams client does not allow changing of ones own display name.
Expand Down
Loading