/// <summary> /// Joins the call asynchronously. /// </summary> /// <param name="joinCallBody">The join call body.</param> /// <returns>The <see cref="ICall"/> that was requested to join.</returns> public async Task <ICall> JoinCallAsync(JoinCallController.JoinCallBody joinCallBody) { // A tracking id for logging purposes. Helps identify this call in logs. var correlationId = Guid.NewGuid(); MeetingInfo meetingInfo; ChatInfo chatInfo; if (!string.IsNullOrWhiteSpace(joinCallBody.MeetingId)) { // Meeting id is a cloud-video-interop numeric meeting id. var onlineMeeting = await this.OnlineMeetings .GetOnlineMeetingAsync(joinCallBody.TenantId, joinCallBody.MeetingId, correlationId) .ConfigureAwait(false); meetingInfo = new OrganizerMeetingInfo { Organizer = onlineMeeting.Participants.Organizer.Identity, }; chatInfo = onlineMeeting.ChatInfo; //// meetingInfo.AllowConversationWithoutHost = joinCallBody.AllowConversationWithoutHost; } else { (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL(joinCallBody.JoinURL); } var tenantId = joinCallBody.TenantId ?? (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); ILocalMediaSession mediaSession = this.CreateLocalMediaSession(); var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession) { RemoveFromDefaultAudioRoutingGroup = joinCallBody.RemoveFromDefaultRoutingGroup, TenantId = tenantId, CorrelationId = correlationId, }; if (!string.IsNullOrWhiteSpace(joinCallBody.DisplayName)) { // Teams client does not allow changing of ones own display name. // If display name is specified, we join as anonymous (guest) user // with the specified display name. This will put bot into lobby // unless lobby bypass is disabled. joinParams.GuestIdentity = new Identity { Id = Guid.NewGuid().ToString(), DisplayName = joinCallBody.DisplayName, }; } var statefulCall = await this.Client.Calls().AddAsync(joinParams).ConfigureAwait(false); this.logger.Info($"Call creation complete: {statefulCall.Id}"); return(statefulCall); }
/// <summary> /// Joins the call asynchronously. /// </summary> /// <param name="joinCallBody">The join call body.</param> /// <param name="incidentId">Incident Id.</param> /// <returns>The <see cref="ICall"/> that was requested to join.</returns> public async Task <ICall> JoinCallAsync(JoinCallRequestData joinCallBody, string incidentId = "") { // A tracking id for logging purposes. Helps identify this call in logs. var correlationId = string.IsNullOrEmpty(joinCallBody.CorrelationId) ? Guid.NewGuid() : new Guid(joinCallBody.CorrelationId); Microsoft.Graph.MeetingInfo meetingInfo; ChatInfo chatInfo; if (!string.IsNullOrWhiteSpace(joinCallBody.MeetingId)) { // Meeting id is a cloud-video-interop numeric meeting id. var onlineMeeting = await this.OnlineMeetings .GetOnlineMeetingAsync(joinCallBody.TenantId, joinCallBody.MeetingId, correlationId) .ConfigureAwait(false); meetingInfo = new OrganizerMeetingInfo { Organizer = onlineMeeting.Participants.Organizer.Identity, }; meetingInfo.AllowConversationWithoutHost = joinCallBody.AllowConversationWithoutHost; chatInfo = onlineMeeting.ChatInfo; } else { (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL(joinCallBody.JoinURL); meetingInfo.AllowConversationWithoutHost = joinCallBody.AllowConversationWithoutHost; } var tenantId = joinCallBody.TenantId ?? (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); var mediaToPrefetch = new List <MediaInfo>(); foreach (var m in this.MediaMap) { mediaToPrefetch.Add(m.Value.MediaInfo); } var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, new[] { Modality.Audio }, mediaToPrefetch) { RemoveFromDefaultAudioRoutingGroup = joinCallBody.RemoveFromDefaultRoutingGroup, TenantId = tenantId, CorrelationId = correlationId, }; var statefulCall = await this.Client.Calls().AddAsync(joinParams).ConfigureAwait(false); this.AddCallToHandlers(statefulCall, new IncidentCallContext(IncidentCallType.BotMeeting, incidentId)); this.graphLogger.Info($"Join Call complete: {statefulCall.Id}"); return(statefulCall); }
/// <summary> /// Parse Join URL into its components. /// </summary> /// <param name="joinURL">Join URL from Team's meeting body.</param> /// <returns>Parsed data.</returns> /// <exception cref="ArgumentException">Join URL cannot be null or empty: {joinURL} - joinURL</exception> /// <exception cref="ArgumentException">Join URL cannot be parsed: {joinURL} - joinURL</exception> /// <exception cref="ArgumentException">Join URL is invalid: missing Tid - joinURL</exception> public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { if (string.IsNullOrEmpty(joinURL)) { throw new ArgumentException($"Join URL cannot be null or empty: {joinURL}", nameof(joinURL)); } var decodedURL = WebUtility.UrlDecode(joinURL); //// URL being needs to be in this format. //// https://teams.microsoft.com/l/meetup-join/19:[email protected]/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) { 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); 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 meetingInfo = new OrganizerMeetingInfo { Organizer = new IdentitySet { User = new Identity { Id = ctxt.Oid }, }, }; meetingInfo.Organizer.User.SetTenantId(ctxt.Tid); return(chatInfo, meetingInfo); } }
/// <summary> /// Joins the call asynchronously. /// </summary> /// <param name="joinCallBody">The join call body.</param> /// <param name="incidentId">Incident Id.</param> /// <returns>The <see cref="ICall"/> that was requested to join.</returns> public async Task <ICall> JoinCallAsync(JoinCallRequestData joinCallBody, string incidentId = "") { // A tracking id for logging purposes. Helps identify this call in logs. var scenarioId = string.IsNullOrEmpty(joinCallBody.ScenarioId) ? Guid.NewGuid() : new Guid(joinCallBody.ScenarioId); MeetingInfo meetingInfo; ChatInfo chatInfo; if (!string.IsNullOrWhiteSpace(joinCallBody.VideoTeleconferenceId)) { // Meeting id is a cloud-video-interop numeric meeting id. var onlineMeeting = await OnlineMeetings .GetOnlineMeetingAsync(joinCallBody.TenantId, joinCallBody.VideoTeleconferenceId, scenarioId) .ConfigureAwait(false); meetingInfo = new OrganizerMeetingInfo { Organizer = onlineMeeting.Participants.Organizer.Identity, }; chatInfo = onlineMeeting.ChatInfo; } else { (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL(joinCallBody.JoinUrl); } var tenantId = joinCallBody.TenantId ?? (meetingInfo as OrganizerMeetingInfo)?.Organizer.GetPrimaryIdentity()?.GetTenantId(); var mediaToPrefetch = new List <MediaInfo>(); foreach (var m in MediaMap) { mediaToPrefetch.Add(m.Value.MediaInfo); } var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, new[] { Modality.Audio }, mediaToPrefetch) { TenantId = tenantId, }; var statefulCall = await Client.Calls().AddAsync(joinParams, scenarioId).ConfigureAwait(false); AddCallToHandlers(statefulCall, new IncidentCallContext(IncidentCallType.BotMeeting, incidentId)); graphLogger.Info($"Join Call complete: {statefulCall.Id}"); return(statefulCall); }
/// <summary> /// Parse Join URL into its components. /// </summary> /// <param name="joinURL">Join URL from Team's meeting body.</param> /// <returns>Parsed data.</returns> public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); //// URL being needs to be in this format. //// https://teams.microsoft.com/l/meetup-join/19:[email protected]/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} //// https://teams.microsoft.com/l/meetup-join/19:meeting_MDQzYmJlMDctMWJiZS00OGExLTlmYjUtZTczNzVhZGM1OTQx@thread.v2/0?context={"Tid":"c80f38d3-c04c-49bf-a48b-9d99278d4ac6","Oid":"782f076f-f6f9-4bff-9673-ea1997283e9c"} var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})"); var match = regex.Match(decodedURL); 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); } }
/// <summary> /// Parse Join URL into its components. /// </summary> /// <param name="joinURL">Join URL from Team's meeting body.</param> /// <returns>Parsed data.</returns> public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); //// URL being needs to be in this format. //// https://teams.microsoft.com/l/meetup-join/19:[email protected]/1509579179399?context={"Tid":"72f988bf-86f1-41af-91ab-2d7cd011db47","Oid":"550fae72-d251-43ec-868c-373732c2704f","MessageId":"1536978844957"} //// https://teams.microsoft.com/l/meetup-join/19%3a35a466cd6e2d4efda6df1ab5456ec170%40thread.skype/1595884031503?context=%7b%22Tid%22%3a%2209c8b911-b715-46dc-a14e-a547989bad27%22%2c%22Oid%22%3a%2287bfe48d-53e2-42c9-af53-50c03ae06611%22%7d var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})"); var match = regex.Match(decodedURL); 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); } }
public static (ChatInfo, MeetingInfo) ParseJoinURL(string joinURL) { var decodedURL = WebUtility.UrlDecode(joinURL); var regex = new Regex("https://teams\\.microsoft\\.com.*/(?<thread>[^/]+)/(?<message>[^/]+)\\?context=(?<context>{.*})"); var match = regex.Match(decodedURL); 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); } }