private HashSet <TeamsUserWithSource> CollectUsersFromChatAndMessages(Chat chat, List <Message> messages) { var users = new HashSet <TeamsUserWithSource>(); var creatorUserId = TeamsParticipant.FromFirstValid(chat.creator); if (creatorUserId.IsValid) { users.Add(new TeamsUserWithSource(creatorUserId, TeamsUserSource.ChatCreator)); } foreach (var m in messages) { // "https://emea.ng.msg.teams.microsoft.com/v1/users/ME/contacts/8:orgid:00000000-0000-beef-0000-000000000000" // note: for system messages like user leave and user join this is the contact url for the chat id var userId = (TeamsParticipant)m.from; if (userId.IsValid && !(userId.Kind == ParticipantKind.TeamsChat)) { users.Add(new TeamsUserWithSource(userId, TeamsUserSource.SenderOfMessageInChat)); } if (m.properties?.mentions != null) { foreach (var mention in m.properties.mentions) { users.Add(new TeamsUserWithSource((TeamsParticipant)mention.mri, TeamsUserSource.MentionedInChat)); } } } chat.members?.ForEach(member => users.Add(new TeamsUserWithSource((TeamsParticipant)member.mri, TeamsUserSource.OfficialChatMember))); return(users); }
public TeamsUserTokenContext GetOrCreateUserTokenContext(TeamsParticipant userId) { #pragma warning disable CS8603 // Possible null reference return. return(GetUserTokenContext(userId, true)); // cannot be null #pragma warning restore CS8603 // Possible null reference return. }
public ProcessedTeamsUser(TeamsDataContext dataContext, TeamsParticipant userId, TeamsUser?originalUser, TeamsUserState state) { OriginalUser = originalUser; UserId = userId; DataContext = dataContext; State = state; }
public TeamsTokenInfo(TeamsParticipant userId, TeamsTokenType tokenType, string tokenString, string authHeader, DateTime validFromUtc, DateTime validToUtc) { UserId = userId; TokenType = tokenType; TokenString = tokenString; AuthHeader = authHeader; ValidFromUtc = validFromUtc; ValidToUtc = validToUtc; }
public TeamsUserTokenContext?GetUserTokenContext(TeamsParticipant userId, bool createIfNotExisting) { if (!userTokenContexts.TryGetValue(userId, out var context) && createIfNotExisting) { context = new TeamsUserTokenContext(userId); userTokenContexts.Add(userId, context); } return(context); }
public TeamsTokenInfo?GetTokenForIdentity(TeamsParticipant userId, string url) { var context = GetOrCreateUserTokenContext(userId); if (ApiTokenType.TryGetValue(url, out var tokenType)) { return(context[tokenType]); } else { throw new TeasmCompanionException("Handle the case where we need user-dependent hosts"); } }
public async Task <IEnumerable <ProcessedTenant>?> GetTenantsAsync(TeamsParticipant userId) { var userContext = tokenRetriever.GetOrCreateUserTokenContext(userId); var tokenType = TeamsTokenType.MyTenantsAuthHeader; var tokenInfo = userContext[tokenType]; if (tokenInfo == null) { logger.Debug("Cannot get tenants for user {Mri}, no token of type {TokenType} present", userId.ToString().Truncate(Constants.UserIdLogLength), tokenType); return(null); } return(await GetTenantsAsync(tokenInfo)); }
public ProcessedUser(TeamsParticipant userId, MyProperties props) { Properties = props; UserId = userId; }
public TeamsUserWithSource(TeamsParticipant user, TeamsUserSource userSource) { User = user; UserSource = userSource; }
public ProcessedTeamsUser(TeamsDataContext ctx, TeamsParticipant userId) { UserId = userId; DataContext = ctx; }
public TeamsUserTokenContext(TeamsParticipant userId) { UserId = userId; }
public static string? Truncate(this TeamsParticipant userId, int length, bool dotDotDot = false) { return ((string?)userId)?.Truncate(length, dotDotDot); }
protected async Task GenerateTextContentExtractUsersAndUpdateSubject() { if (ctx == null || !ctx.HasValue) { return; } var nonNullContext = ctx.Value; try { var textContent = new StringBuilder(); if (Messagetype == MessageType.RichText_Html) { HtmlContent = Internal_Content; } else if (Messagetype == MessageType.ThreadActivity_AddMember || (Messagetype == MessageType.ThreadActivity_MemberJoined && Internal_Content.StartsWith("<addmember>")) // there seem to be (old?) addmember messages that come with the wrong message type of ThreadActivity_MemberJoined... handle this here ) { XmlDocument doc = new XmlDocument(); // need to force single XML values into array type since we know it also can be an array doc.LoadXml($"<root xmlns:json='http://james.newtonking.com/projects/json'>{Internal_Content.Replace("<target", "<target json:Array='true'").Replace("<detailedtargetinfo", "<detailedtargetinfo json:Array='true'") ?? ""}</root>"); var json = JsonConvert.SerializeXmlNode(doc); var data = JsonUtils.DeserializeObject <ThreadActivityAddMemberWrapper>(logger, json); await Task.WhenAll(data.root.addmember.detailedtargetinfo?.Select(targetInfo => teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)targetInfo.id, targetInfo.friendlyName, OriginalArrivalTime)) ?? new List <Task>()); if (!string.IsNullOrWhiteSpace(data.root.addmember.initiator)) { await teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)data.root.addmember.initiator, data.root.addmember.detailedinitiatorinfo?.friendlyName, OriginalArrivalTime); } // TODO: process alternate user display name var memberNames = await Task.WhenAll(data.root.addmember.target.Select(t => teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)t))); MessageSubject = $"✈️ {await teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)data.root.addmember.initiator)} added: " + string.Join(", ", memberNames); textContent.Append(MessageSubject); } else if (Messagetype == MessageType.ThreadActivity_DeleteMember) { XmlDocument doc = new XmlDocument(); doc.LoadXml($"<root xmlns:json='http://james.newtonking.com/projects/json'>{Internal_Content.Replace("<target", "<target json:Array='true'").Replace("<detailedtargetinfo", "<detailedtargetinfo json:Array='true'")}</root>"); var json = JsonConvert.SerializeXmlNode(doc); var data = JsonUtils.DeserializeObject <ThreadActivityDeleteMemberWrapper>(logger, json); await Task.WhenAll(data.root.deletemember.detailedtargetinfo?.Select(targetInfo => teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)targetInfo.id, targetInfo.friendlyName, OriginalArrivalTime)) ?? new List <Task>()); if (!string.IsNullOrWhiteSpace(data.root.deletemember.initiator)) { await teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)data.root.deletemember.initiator, data.root.deletemember.detailedinitiatorinfo?.friendlyName, OriginalArrivalTime); } var memberNames = await Task.WhenAll(data.root.deletemember.target.Select(t => teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)t))); MessageSubject = $"✈️ {await teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, new TeamsParticipant(data.root.deletemember?.initiator))} removed: " + string.Join(", ", memberNames); textContent.Append(MessageSubject); } else if (Messagetype == MessageType.Event_Call) { XmlDocument doc = new XmlDocument(); // prepare XML to JSON conversion; force "part" being a list which cannot be infered from XML if there is only one element doc.LoadXml($"<root xmlns:json='http://james.newtonking.com/projects/json'>{Internal_Content.Replace("<part ", "<part json:Array='true' ")}</root>"); var json = JsonConvert.SerializeXmlNode(doc); var data = JsonUtils.DeserializeObject <EventCallWrapper>(logger, json); await Task.WhenAll(data.root.partlist?.part?.Select(member => teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)member.identity, member.displayName, OriginalArrivalTime)) ?? new List <Task>()); // sometimes p.name contains the user id and p.identity is empty var memberNames = await Task.WhenAll(data.root.partlist?.part?.Select(p => teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, TeamsParticipant.FromFirstValid(p.identity, p.name))) ?? new List <Task <string> >()); var callEnded = Internal_Content.Contains("<ended/>"); if (callEnded) { MessageSubject = "☎️ Call ended for: " + string.Join(", ", memberNames); } else { var from = (TeamsParticipant)Internal_FromContactUrl; var displayName = await teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, from); MessageSubject = $"☎️ Call started by {displayName}"; } textContent.Append(MessageSubject); } else if (Messagetype == MessageType.ThreadActivity_MemberJoined) { var data = JsonUtils.DeserializeObject <ThreadEventMemberJoined>(logger, Internal_Content); await Task.WhenAll(data.members.Select(member => teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)member.id, member.friendlyname, OriginalArrivalTime))); var memberNames = (await Task.WhenAll(data.members.Select(member => teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)member.id)))); MessageSubject = "✈️ Member(s) joined: " + string.Join(", ", memberNames); // note: friendlyname is sometimes empty; second note: für ehemalige Mitarbeiter kann ein ID-Lookup fehlschlagen, aber der friendlyName dennoch gesetzt sein textContent.Append(MessageSubject); } else if (Messagetype == MessageType.ThreadActivity_MemberLeft) { var data = JsonUtils.DeserializeObject <ThreadEventMemberLeft>(logger, Internal_Content); await Task.WhenAll(data.members.Select(member => teamsUserRegistry.RegisterDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)member.id, member.friendlyname, OriginalArrivalTime))); var memberNames = await Task.WhenAll(data.members.Select(member => teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, (TeamsParticipant)member.id))); MessageSubject = "✈️ Member(s) left: " + string.Join(", ", memberNames); textContent.Append(MessageSubject); } else if (Messagetype == MessageType.RichText_Media_CallRecording) { XmlDocument doc = new XmlDocument(); // prepare XML to JSON conversion; force "part" being a list which cannot be infered from XML if there is only one element doc.LoadXml($"<root xmlns:json='http://james.newtonking.com/projects/json'>{Internal_Content.Replace("<Identifiers>", "<Identifiers json:Array='true'>").Replace("<RecordingContent ", "<RecordingContent json:Array='true' ").Replace("<RequestedExports ", "<RequestedExports json:Array='true' ")}</root>"); var json = JsonConvert.SerializeXmlNode(doc); var data = JsonUtils.DeserializeObject <RichTextMedia_CallRecordingWrapper>(logger, json); MessageSubject = $"✍️ Recording started by {await teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, new TeamsParticipant(data.root?.URIObject?.RecordingInitiatorId?.value))}"; textContent.Append(MessageSubject); } else if (Messagetype == MessageType.Text) { HtmlContent = Internal_Content; } else if (Messagetype == MessageType.ThreadActivity_TopicUpdate) { XmlDocument doc = new XmlDocument(); // <topicupdate><eventtime>0000000000000</eventtime><initiator>8:orgid:00000000-0000-beef-0000-000000000000</initiator><value>New topic</value></topicupdate> doc.LoadXml($"<root xmlns:json='http://james.newtonking.com/projects/json'>{Internal_Content}</root>"); var json = JsonConvert.SerializeXmlNode(doc); var data = JsonUtils.DeserializeObject <ThreadActivityTopicUpdateWrapper>(logger, json); var user = await teamsUserRegistry.GetUserByIdAsync(nonNullContext, (TeamsParticipant)data.root.topicupdate.initiator, false); if (user != null && user.HasDisplayName) { if (From.Count == 1 && (From[0].UserId.Kind == ParticipantKind.TeamsChat || From[0].UserId.Kind == ParticipantKind.Unknown)) { From.Clear(); From.Add(user); } } MessageSubject = $"®️ Topic set to '{data.root.topicupdate.value}' by {await teamsUserRegistry.GetDisplayNameForUserIdAsync(nonNullContext, new TeamsParticipant(data.root.topicupdate.initiator))}"; textContent.Append(MessageSubject); } else { textContent.Append("Unknown message type, don't know how to render: " + Messagetype); } if (string.IsNullOrWhiteSpace(HtmlContent)) { HtmlContent = MessageSubject; } TextContent = textContent.ToString(); } catch (Exception e) { // exceptions here will cancel the whole chat from being parsed; log the message content to analyze it later logger.Error(e, "[{TenantName}] Exception while processing message content in method {MethodName}; Original message content: {MessageContent}", nonNullContext.Tenant.TenantName, nameof(GenerateTextContentExtractUsersAndUpdateSubject), SerializeOriginalMessageAsJson()); // don't just skip a failed message, but cancel chat retrieval and fix the underlying issue, then try again throw; } }
public void TestParticipantCreation() { TeamsParticipant participant; participant = new TeamsParticipant("28:00000000-0000-beef-0000-000000000000"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = new TeamsParticipant("8:orgid:00000000-0000-beef-0000-000000000000"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.User, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.User, participant.Kind); participant = new TeamsParticipant("00000000-0000-beef-0000-000000000000"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.Unknown, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("00000000-0000-beef-0000-000000000000", participant.Id); Assert.AreEqual(ParticipantKind.Unknown, participant.Kind); participant = new TeamsParticipant("https://emea.ng.msg.teams.microsoft.com/v1/users/ME/contacts/28:0af95b67-5890-4306-9c1c-a8591cead09e"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("0af95b67-5890-4306-9c1c-a8591cead09e", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("0af95b67-5890-4306-9c1c-a8591cead09e", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = (TeamsParticipant)"19:00000000-0000-beef-0000-000000000000_00000000-0000-beef-0000-000000000000@unq.gbl.spaces"; Assert.IsTrue(participant.IsValid); Assert.AreEqual("19:00000000-0000-beef-0000-000000000000_00000000-0000-beef-0000-000000000000@unq.gbl.spaces", participant.Id); Assert.AreEqual(ParticipantKind.TeamsChat, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("19:00000000-0000-beef-0000-000000000000_00000000-0000-beef-0000-000000000000@unq.gbl.spaces", participant.Id); Assert.AreEqual(ParticipantKind.TeamsChat, participant.Kind); participant = new TeamsParticipant("https://notifications.skype.net/v1/users/ME/contacts/28:integration:t0ri00alov"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("t0ri00alov", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("t0ri00alov", participant.Id); Assert.AreEqual(ParticipantKind.AppOrBot, participant.Kind); participant = new TeamsParticipant("8:teamsvisitor:a111a1a111aa111a1aa1aa1a11aaaaa1"); Assert.IsTrue(participant.IsValid); Assert.AreEqual("a111a1a111aa111a1aa1aa1a11aaaaa1", participant.Id); Assert.AreEqual(ParticipantKind.User, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsTrue(participant.IsValid); Assert.AreEqual("a111a1a111aa111a1aa1aa1a11aaaaa1", participant.Id); Assert.AreEqual(ParticipantKind.User, participant.Kind); participant = (TeamsParticipant)null; Assert.IsFalse(participant.IsValid); Assert.IsNull(participant.Id); Assert.AreEqual(ParticipantKind.Unknown, participant.Kind); participant = JsonConvert.DeserializeObject <TeamsParticipant>(JsonConvert.SerializeObject(participant)); Assert.IsFalse(participant.IsValid); Assert.IsNull(participant.Id); Assert.AreEqual(ParticipantKind.Unknown, participant.Kind); }