Call recorder: parse conference links from calendar event text (#22555)

This commit is contained in:
nitin
2026-07-05 21:14:13 +05:30
committed by GitHub
parent 3cd4498bb2
commit fe8ec0463a
6 changed files with 225 additions and 7 deletions
@@ -7,7 +7,7 @@ import {
fetchAllNodes,
type ConnectionPage,
} from 'src/logic-functions/data/fetch-all-nodes.util';
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
import { resolveConferenceLinkUrl } from 'src/logic-functions/domain/resolve-conference-link-url.util';
import { stripRestrictedFieldValue } from 'src/logic-functions/data/strip-restricted-field-value.util';
type CalendarEventNode = {
@@ -18,6 +18,8 @@ type CalendarEventNode = {
endsAt?: string | null;
iCalUid?: string | null;
conferenceLink?: { primaryLinkUrl?: string | null } | null;
location?: string | null;
description?: string | null;
callRecorderPreference?: string | null;
};
@@ -49,6 +51,8 @@ export const fetchCalendarEventsByFilter = async (
conferenceLink: {
primaryLinkUrl: true,
},
location: true,
description: true,
callRecorderPreference: true,
},
},
@@ -68,11 +72,13 @@ export const fetchCalendarEventsByFilter = async (
startsAt: calendarEvent.startsAt ?? undefined,
endsAt: calendarEvent.endsAt ?? undefined,
iCalUid: calendarEvent.iCalUid ?? undefined,
conferenceLinkUrl: isNonEmptyString(
calendarEvent.conferenceLink?.primaryLinkUrl,
)
? calendarEvent.conferenceLink.primaryLinkUrl
: undefined,
conferenceLinkUrl: resolveConferenceLinkUrl({
conferenceLinkUrl: calendarEvent.conferenceLink?.primaryLinkUrl,
location: stripRestrictedFieldValue(calendarEvent.location ?? undefined),
description: stripRestrictedFieldValue(
calendarEvent.description ?? undefined,
),
}),
callRecorderPreference: isString(calendarEvent.callRecorderPreference)
? calendarEvent.callRecorderPreference
: undefined,
@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest';
import { extractConferenceLinkUrlFromText } from 'src/logic-functions/domain/extract-conference-link-url-from-text.util';
describe('extractConferenceLinkUrlFromText', () => {
it.each([
[
'extracts a Zoom link from a plain text invitation',
'Felix is inviting you to a scheduled Zoom meeting.\n\nJoin Zoom Meeting\nhttps://us02web.zoom.us/j/81234567890?pwd=aBcDeF123\n\nMeeting ID: 812 3456 7890',
'https://us02web.zoom.us/j/81234567890?pwd=aBcDeF123',
],
[
'extracts a bare Zoom link (location field)',
'https://zoom.us/j/81234567890',
'https://zoom.us/j/81234567890',
],
[
'extracts a Zoom personal room link',
'Join me at https://company.zoom.us/my/felix today',
'https://company.zoom.us/my/felix',
],
[
'extracts a Zoom Government link',
'Join: https://example.zoomgov.com/j/1609618851',
'https://example.zoomgov.com/j/1609618851',
],
[
'extracts a Zoom link from an HTML body and decodes &',
'<div><a href="https://us02web.zoom.us/j/81234567890?pwd=aBcDeF123&amp;uname=Felix">Join Zoom Meeting</a></div>',
'https://us02web.zoom.us/j/81234567890?pwd=aBcDeF123&uname=Felix',
],
[
'strips trailing sentence punctuation',
'Join here: https://zoom.us/j/81234567890.',
'https://zoom.us/j/81234567890',
],
[
'extracts a Google Meet link',
'Join the call on https://meet.google.com/abc-defg-hij',
'https://meet.google.com/abc-defg-hij',
],
[
'extracts a Microsoft Teams link',
'Click here to join: https://teams.microsoft.com/l/meetup-join/19%3ameeting_ABC123%40thread.v2/0?context=%7b%22Tid%22%3a%22111%22%7d',
'https://teams.microsoft.com/l/meetup-join/19%3ameeting_ABC123%40thread.v2/0?context=%7b%22Tid%22%3a%22111%22%7d',
],
[
'extracts a Webex link',
'Join at https://company.webex.com/company/j.php?MTID=m1234abcd',
'https://company.webex.com/company/j.php?MTID=m1234abcd',
],
[
'extracts a GoTo Meeting link',
'https://global.gotomeeting.com/join/123456789',
'https://global.gotomeeting.com/join/123456789',
],
[
'prefers a Zoom link over a Google Meet link when both are present',
'Video call: https://meet.google.com/abc-defg-hij\nJoin Zoom Meeting\nhttps://zoom.us/j/81234567890',
'https://zoom.us/j/81234567890',
],
])('%s', (_label, text, expectedUrl) => {
expect(extractConferenceLinkUrlFromText(text)).toBe(expectedUrl);
});
it.each([
['a non-conferencing URL', 'Agenda: https://docs.google.com/document/d/abc'],
['a Zoom marketing page URL', 'Learn more at https://zoom.us/pricing'],
['plain text without links', 'Conference Room A'],
['an empty string', ''],
['a blank string', ' '],
])('returns undefined for %s', (_label, text) => {
expect(extractConferenceLinkUrlFromText(text)).toBeUndefined();
});
it('returns undefined for null and undefined', () => {
expect(extractConferenceLinkUrlFromText(null)).toBeUndefined();
expect(extractConferenceLinkUrlFromText(undefined)).toBeUndefined();
});
});
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { resolveConferenceLinkUrl } from 'src/logic-functions/domain/resolve-conference-link-url.util';
describe('resolveConferenceLinkUrl', () => {
it('returns the structured conference link when present', () => {
expect(
resolveConferenceLinkUrl({
conferenceLinkUrl: 'https://meet.google.com/abc-defg-hij',
location: 'https://zoom.us/j/81234567890',
description: 'Join Zoom Meeting https://zoom.us/j/99999999999',
}),
).toBe('https://meet.google.com/abc-defg-hij');
});
it('parses the location when the structured link is empty', () => {
expect(
resolveConferenceLinkUrl({
conferenceLinkUrl: undefined,
location: 'https://zoom.us/j/81234567890',
description: 'Weekly sync',
}),
).toBe('https://zoom.us/j/81234567890');
});
it('parses the description when the structured link and location have none', () => {
expect(
resolveConferenceLinkUrl({
conferenceLinkUrl: '',
location: 'Conference Room A',
description:
'Felix is inviting you to a scheduled Zoom meeting.\nJoin Zoom Meeting\nhttps://us02web.zoom.us/j/81234567890?pwd=aBcDeF123',
}),
).toBe('https://us02web.zoom.us/j/81234567890?pwd=aBcDeF123');
});
it('prefers the location link over the description link', () => {
expect(
resolveConferenceLinkUrl({
conferenceLinkUrl: null,
location: 'https://zoom.us/j/11111111111',
description: 'Join Zoom Meeting https://zoom.us/j/22222222222',
}),
).toBe('https://zoom.us/j/11111111111');
});
it('returns undefined when no conference link exists anywhere', () => {
expect(
resolveConferenceLinkUrl({
conferenceLinkUrl: undefined,
location: 'Conference Room A',
description: 'Quarterly review agenda',
}),
).toBeUndefined();
});
});
@@ -0,0 +1,33 @@
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
// Ordered by provider precedence: the first matching provider wins (e.g. a
// pasted Zoom invitation beats an auto-added Meet link).
const CONFERENCE_LINK_URL_PATTERNS: RegExp[] = [
/https:\/\/(?:[\w-]+\.)*(?:zoom\.us|zoomgov\.com)\/(?:j|my|s|w|wc\/join)\/[^\s"'<>\\]+/i,
/https:\/\/meet\.google\.com\/[^\s"'<>\\]+/i,
/https:\/\/teams\.(?:microsoft|live)\.com\/(?:l\/meetup-join|meet)\/[^\s"'<>\\]+/i,
/https:\/\/(?:[\w-]+\.)*webex\.com\/(?:meet\/|join\/|[\w-]+\/j\.php\?)[^\s"'<>\\]+/i,
/https:\/\/(?:[\w-]+\.)*(?:gotomeeting\.com\/join|gotomeet\.me)\/[^\s"'<>\\]+/i,
];
// Outlook HTML bodies encode ampersands; plain-text links can end mid-sentence.
const cleanExtractedConferenceLinkUrl = (url: string): string =>
url.replace(/&amp;/gi, '&').replace(/[.,;:!?)\]]+$/, '');
export const extractConferenceLinkUrlFromText = (
text: string | null | undefined,
): string | undefined => {
if (!isNonEmptyString(text)) {
return undefined;
}
for (const pattern of CONFERENCE_LINK_URL_PATTERNS) {
const match = text.match(pattern);
if (match !== null) {
return cleanExtractedConferenceLinkUrl(match[0]);
}
}
return undefined;
};
@@ -0,0 +1,25 @@
import { extractConferenceLinkUrlFromText } from 'src/logic-functions/domain/extract-conference-link-url-from-text.util';
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
type ResolveConferenceLinkUrlInput = {
conferenceLinkUrl: string | null | undefined;
location: string | null | undefined;
description: string | null | undefined;
};
// The server only fills conferenceLink from structured provider data; Zoom and
// other third-party links often exist only as text in location/description.
export const resolveConferenceLinkUrl = ({
conferenceLinkUrl,
location,
description,
}: ResolveConferenceLinkUrlInput): string | undefined => {
if (isNonEmptyString(conferenceLinkUrl)) {
return conferenceLinkUrl;
}
return (
extractConferenceLinkUrlFromText(location) ??
extractConferenceLinkUrlFromText(description)
);
};
@@ -11,6 +11,8 @@ import { type RemovedCallRecorderOccurrence } from 'src/logic-functions/types/re
import { computeRealMeetingKey } from 'src/logic-functions/domain/compute-real-meeting-key.util';
import { getUniqueSortedIds } from 'src/logic-functions/utils/get-unique-sorted-ids.util';
import { reconcileCallRecorderForCalendarEventIds } from 'src/logic-functions/flows/reconcile-call-recorder.util';
import { resolveConferenceLinkUrl } from 'src/logic-functions/domain/resolve-conference-link-url.util';
import { stripRestrictedFieldValue } from 'src/logic-functions/data/strip-restricted-field-value.util';
const CALENDAR_EVENT_OBJECT_NAME = 'calendarEvent';
@@ -18,14 +20,20 @@ const CALL_RECORDER_RELEVANT_CALENDAR_EVENT_FIELDS = [
'title',
'callRecorderPreference',
'conferenceLink',
'location',
'description',
'startsAt',
'endsAt',
'isCanceled',
'iCalUid',
];
// location and description are key fields because the conference link is
// parsed out of them when the structured conferenceLink is empty.
const CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS = [
'conferenceLink',
'location',
'description',
'startsAt',
'iCalUid',
];
@@ -33,6 +41,8 @@ const CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS = [
type CalendarEventForDatabaseEvent = {
id: string;
conferenceLink?: { primaryLinkUrl?: string | null } | null;
location?: string | null;
description?: string | null;
iCalUid?: string | null;
startsAt?: string | null;
};
@@ -156,7 +166,15 @@ const buildRemovedOccurrence = (
calendarEventId: calendarEvent.id,
realMeetingKey: computeRealMeetingKey({
calendarEventId: calendarEvent.id,
conferenceLinkUrl: calendarEvent.conferenceLink?.primaryLinkUrl,
conferenceLinkUrl: resolveConferenceLinkUrl({
conferenceLinkUrl: calendarEvent.conferenceLink?.primaryLinkUrl,
location: stripRestrictedFieldValue(
calendarEvent.location ?? undefined,
),
description: stripRestrictedFieldValue(
calendarEvent.description ?? undefined,
),
}),
iCalUid: calendarEvent.iCalUid ?? undefined,
startsAt: calendarEvent.startsAt ?? undefined,
}),