From 2df34b9f05feeb2f5d332afeadb5c9e5cc7d5a44 Mon Sep 17 00:00:00 2001 From: Paul Woolcock <11843015+phw198@users.noreply.github.com> Date: Sun, 12 Nov 2023 11:58:12 +0000 Subject: [PATCH] String extension method RemoveLineBreaks() Added GMeet logo as a resource. Injection of HTML info block support added. --- .../Extensions/String.cs | 9 + .../OutlookGoogleCalendarSync.csproj | 2 + .../OutlookOgcs/GMeet.cs | 178 +++++++++++++++--- .../OutlookOgcs/OutlookCalendar.cs | 7 +- .../Properties/Resources.Designer.cs | 10 + .../Properties/Resources.resx | 3 + .../Resources/gmeet_logo.png | Bin 0 -> 900 bytes 7 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 src/OutlookGoogleCalendarSync/Extensions/String.cs create mode 100644 src/OutlookGoogleCalendarSync/Resources/gmeet_logo.png diff --git a/src/OutlookGoogleCalendarSync/Extensions/String.cs b/src/OutlookGoogleCalendarSync/Extensions/String.cs new file mode 100644 index 00000000..a19e7c4a --- /dev/null +++ b/src/OutlookGoogleCalendarSync/Extensions/String.cs @@ -0,0 +1,9 @@ +using System; + +namespace OutlookGoogleCalendarSync { + public static class StringExtensions { + public static String RemoveLineBreaks(this String input) { + return input?.Replace("\r", "").Replace("\n", ""); + } + } +} diff --git a/src/OutlookGoogleCalendarSync/OutlookGoogleCalendarSync.csproj b/src/OutlookGoogleCalendarSync/OutlookGoogleCalendarSync.csproj index 22e31354..f7c45dbb 100644 --- a/src/OutlookGoogleCalendarSync/OutlookGoogleCalendarSync.csproj +++ b/src/OutlookGoogleCalendarSync/OutlookGoogleCalendarSync.csproj @@ -291,6 +291,7 @@ Component + Component @@ -455,6 +456,7 @@ PreserveNewest + diff --git a/src/OutlookGoogleCalendarSync/OutlookOgcs/GMeet.cs b/src/OutlookGoogleCalendarSync/OutlookOgcs/GMeet.cs index 79fa8397..bc50da2c 100644 --- a/src/OutlookGoogleCalendarSync/OutlookOgcs/GMeet.cs +++ b/src/OutlookGoogleCalendarSync/OutlookOgcs/GMeet.cs @@ -1,26 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using log4net; using Microsoft.Office.Interop.Outlook; +using System; +using System.Linq; using System.Text.RegularExpressions; -using log4net; namespace OutlookGoogleCalendarSync.OutlookOgcs { - static class GMeet { + public class GMeetLogo { + private static GMeetLogo instance; + private static readonly ILog log = LogManager.GetLogger(typeof(GMeetLogo)); + + public static GMeetLogo Instance { + get { return instance ??= new GMeetLogo(); } + } + public GMeetLogo() { + GMeetLogoBase64 = base64encode(Properties.Resources.gmeet_logo); + } + + public String GMeetLogoBase64 { + get; internal set; + } + + private String base64encode(System.Drawing.Image img) { + try { + byte[] imgBytes = null; + using (var stream = new System.IO.MemoryStream()) { + img.Save(stream, System.Drawing.Imaging.ImageFormat.Png); + imgBytes = stream.ToArray(); + } + return Convert.ToBase64String(imgBytes); + + } catch (System.Exception ex) { + OGCSexception.Analyse("Could not load GMeet logo.", ex); + return ""; + } + } + } + + public static class GMeet { private static readonly ILog log = LogManager.GetLogger(typeof(GMeet)); - private static String meetingIdToken = "GMEETURL"; + private const String meetingIdToken = "GMEETURL"; + private const String meetingLogoToken = "GMEETLOGO"; private static String plainInfo = "\r\nGoogle Meet joining information\r\nGMEETURL\r\nFirst time using Meet? Learn more \r\n\r\n"; - - private static String rtfInfoHeader = @"{\rtf1\ansi\ansicpg1252\deff0\deflang2057"; + private static String plainHtmlInfo = "Google Meet joining informationGMEETURL First time using Meet? Learn more "; /// /// RTF document code for Google Meet details /// - private static String rtfInfo = + private static readonly String rtfHeader = @"{\rtf1\ansi\ansicpg1252\deff0\deflang2057{\fonttbl{\f0\fnil\fcharset0 Calibri;}{\f1\fswiss\fprq2\fcharset0 Calibri;}}"; + private static readonly String rtfInfo = #region RTF document - @"{\fonttbl{\f0\fnil\fcharset0 Calibri;}{\f1\fswiss\fprq2\fcharset0 Calibri;}} + @" {\colortbl ;\red0\green0\blue255;\red5\green99\blue193;} {\*\generator Msftedit 5.41.21.2510;}\viewkind4\uc1\pard\lang9\f0\fs22{\pict\wmetafile8\picw2117\pich794\picwgoal1200\pichgoal450 010009000003480e00000000320e00000000050000000b0200000000050000000c021a03450832 @@ -220,12 +250,98 @@ static class GMeet { }"; #endregion - public static String PlainInfo(String meetingUrl) { - return plainInfo.Replace(meetingIdToken, meetingUrl); + /// + /// RTF HTML document code for Google Meet details + /// + private static readonly String rtfHtmlHeader = @"{\rtf1\ansi\ansicpg1252\fromhtml1 \fbidis \deff0{\fonttbl +{\f0\fswiss\fcharset0 Arial;} +{\f1\fmodern Courier New;} +{\f2\fnil\fcharset2 Symbol;} +{\f3\fmodern\fcharset0 Courier New;}} +{\colortbl\red0\green0\blue0;\red5\green99\blue193;} +\uc1\pard\plain\deftab360 \f0\fs24 "; + private static readonly String rtfHtmlInfo = + #region HTML document + @" +{\*\htmltag19 } +{\*\htmltag34 } +{\*\htmltag241 } +{\*\htmltag41 } + +{\*\htmltag50 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag64

}\htmlrtf {\htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag84 } +{\*\htmltag156 }\htmlrtf }\htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag156 }\htmlrtf }\htmlrtf0 \htmlrtf\par}\htmlrtf0 +{\*\htmltag72

} + +{\*\htmltag64

}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\b \htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 Google Meet joining information +{\*\htmltag156 }\htmlrtf }\htmlrtf0 +{\*\htmltag92 }\htmlrtf }\htmlrtf0 +{\*\htmltag72

} + +{\*\htmltag64

}\htmlrtf {\htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK ""GMEETURL""}}{\fldrslt\cf1\ul \htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 GMEETURL +{\*\htmltag156 }\htmlrtf }\htmlrtf0 \htmlrtf }\htmlrtf0 \htmlrtf }\htmlrtf0 +{\*\htmltag92 } +{\*\htmltag156 }\htmlrtf }\htmlrtf0 \htmlrtf\par}\htmlrtf0 +{\*\htmltag72

} + +{\*\htmltag64

}\htmlrtf {\htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 First time using Meet? +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK ""https://gsuite.google.com/learning-center/products/meet/get-started/""}}{\fldrslt\cf1\ul \htmlrtf0 +{\*\htmltag84 }\htmlrtf {\b \htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag84  }\htmlrtf \'a0\htmlrtf0 Learn more +{\*\htmltag156 }\htmlrtf }\htmlrtf0 +{\*\htmltag92 }\htmlrtf }\htmlrtf0 \htmlrtf }\htmlrtf0 \htmlrtf }\htmlrtf0 +{\*\htmltag92 } +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag84  }\htmlrtf \'a0\htmlrtf0 +{\*\htmltag156 }\htmlrtf }\htmlrtf0 +{\*\htmltag156 }\htmlrtf }\htmlrtf0 \htmlrtf\par}\htmlrtf0 +{\*\htmltag72

} + +{\*\htmltag64

}\htmlrtf {\htmlrtf0 +{\*\htmltag148 }\htmlrtf {\htmlrtf0 +{\*\htmltag244 } +{\*\htmltag84  }\htmlrtf \'a0\htmlrtf0 +{\*\htmltag252 } +{\*\htmltag156 }\htmlrtf }\htmlrtf0 \htmlrtf\par}\htmlrtf0 +{\*\htmltag72

} + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag58 } +{\*\htmltag27 }}"; + #endregion + + public static String PlainInfo(String meetingUrl, OlBodyFormat format) { + if (new OlBodyFormat[] { OlBodyFormat.olFormatPlain, OlBodyFormat.olFormatRichText }.Contains(format)) + return plainInfo.Replace(meetingIdToken, meetingUrl); + else if (format == OlBodyFormat.olFormatHTML) { + String hydratedInfo = plainHtmlInfo.Replace(meetingIdToken, meetingUrl); + return hydratedInfo.Trim(); + } else + return ""; } public static String RtfInfo(String meetingUrl, Boolean includeHeader = true) { - return (includeHeader ? rtfInfoHeader : "") + rtfInfo.Replace(meetingIdToken, meetingUrl); + String hydratedInfo = rtfInfo.Replace(meetingIdToken, meetingUrl); + return (includeHeader ? rtfHeader : "") + hydratedInfo; + } + public static String RtfHtmlInfo(String meetingUrl, Boolean includeHeader = true) { + String hydratedInfo = rtfHtmlInfo.Replace(meetingIdToken, meetingUrl); + hydratedInfo = hydratedInfo.Replace(meetingLogoToken, GMeetLogo.Instance.GMeetLogoBase64); + return (includeHeader ? rtfHtmlHeader : "") + hydratedInfo; } /// @@ -247,16 +363,16 @@ public static void GoogleMeet(this Microsoft.Office.Interop.Outlook.AppointmentI String oGMeetUrl = CustomProperty.Get(ai, CustomProperty.MetadataId.gMeetUrl); if (!String.IsNullOrEmpty(ai.Body?.RemoveLineBreaks().Trim())) { - String gMeetTemplate = PlainInfo(""); + String gMeetTemplate = PlainInfo("", bodyFormat); if (rgxGmeetUrl.Replace(ai.Body, "").RemoveLineBreaks() == gMeetTemplate.RemoveLineBreaks()) { log.Debug("Description only contains GMeet info, which will be removed."); Calendar.Instance.IOutlook.AddRtfBody(ref ai, ""); ai.Body = ""; } else { if (bodyFormat == OlBodyFormat.olFormatPlain) { - if (ai.Body.Replace(PlainInfo(oGMeetUrl), "").Length < ai.Body.Length) { + if (ai.Body.Replace(PlainInfo(oGMeetUrl, bodyFormat), "").Length < ai.Body.Length) { log.Debug("Retaining non-GMeet content."); - ai.Body = ai.Body.Replace(PlainInfo(oGMeetUrl), ""); + ai.Body = ai.Body.Replace(PlainInfo(oGMeetUrl, bodyFormat), ""); } else log.Debug("Not safe to remove GMeet info."); } else { @@ -272,14 +388,14 @@ public static void GoogleMeet(this Microsoft.Office.Interop.Outlook.AppointmentI CustomProperty.Add(ref ai, CustomProperty.MetadataId.gMeetUrl, gMeetUrl); if (String.IsNullOrEmpty(ai.Body?.RemoveLineBreaks().Trim())) { - log.Debug("Adding GMeet RTF body to Outlook"); - Calendar.Instance.IOutlook.AddRtfBody(ref ai, RtfInfo(gMeetUrl)); + log.Debug("Adding GMeet RTF HTML body to Outlook"); + Calendar.Instance.IOutlook.AddRtfBody(ref ai, RtfHtmlInfo(gMeetUrl)); } else { if (bodyFormat == OlBodyFormat.olFormatPlain) { if (!rgxGmeetUrl.IsMatch(ai.Body)) { log.Debug("Appending GMeet plaintext body to Outlook"); - ai.Body += "\r\n" + PlainInfo(gMeetUrl); - } else if (String.IsNullOrEmpty(ai.Body?.Replace(PlainInfo(gMeetUrl), "").RemoveLineBreaks().Trim())) { + ai.Body += "\r\n" + PlainInfo(gMeetUrl, bodyFormat); + } else if (String.IsNullOrEmpty(ai.Body?.Replace(PlainInfo(gMeetUrl, bodyFormat), "").RemoveLineBreaks().Trim())) { log.Debug("Replacing GMeet plaintext with RTF body in Outlook"); Calendar.Instance.IOutlook.AddRtfBody(ref ai, RtfInfo(gMeetUrl)); } else { @@ -290,18 +406,30 @@ public static void GoogleMeet(this Microsoft.Office.Interop.Outlook.AppointmentI if (!rgxGmeetUrl.IsMatch(ai.Body)) { log.Debug("Appending GMeet RTF body to Outlook"); String rtfBody = ai.RTFBodyAsString(); - int lastOccurrenceIdx = rtfBody.LastIndexOf('}'); - String newRtfBody = rtfBody.Substring(0, lastOccurrenceIdx) + @"\r\n\par\r\n" + RtfInfo(gMeetUrl, false) + rtfBody.Substring(lastOccurrenceIdx + 1); + int injectIdx = rtfBody.LastIndexOf('}'); + String newRtfBody = rtfBody.Substring(0, injectIdx) + @"\r\n\par\r\n" + RtfInfo(gMeetUrl, false) + rtfBody.Substring(injectIdx + 1); Calendar.Instance.IOutlook.AddRtfBody(ref ai, newRtfBody); } else { log.Debug("Updating GMeet RTF body in Outlook"); String newRtfBody = rgxGmeetUrl.Replace(ai.RTFBodyAsString(), gMeetUrl); Calendar.Instance.IOutlook.AddRtfBody(ref ai, newRtfBody); } + } else if (bodyFormat == OlBodyFormat.olFormatHTML) { + if (!rgxGmeetUrl.IsMatch(ai.Body)) { + log.Debug("Appending GMeet RTF HTML body to Outlook"); + String rtfHtmlBody = ai.RTFBodyAsString(); + int injectIdx = rtfHtmlBody.LastIndexOf(@"{\*\htmltag58 }"); + String newRtfHtmlBody = rtfHtmlBody.Substring(0, injectIdx) + RtfHtmlInfo(gMeetUrl, false); + Calendar.Instance.IOutlook.AddRtfBody(ref ai, newRtfHtmlBody); + } else { + log.Debug("Updating GMeet RTF HTML body in Outlook"); + String newRtfHtmlBody = rgxGmeetUrl.Replace(ai.RTFBodyAsString(), gMeetUrl); + Calendar.Instance.IOutlook.AddRtfBody(ref ai, newRtfHtmlBody); + } } else { log.Warn(bodyFormat.ToString() + " is not fully supported. Attempting update of pre-existing GMeet URL."); - String newHtmlBody = rgxGmeetUrl.Replace(ai.RTFBodyAsString(), gMeetUrl); - Calendar.Instance.IOutlook.AddRtfBody(ref ai, newHtmlBody); + String newBody = rgxGmeetUrl.Replace(ai.RTFBodyAsString(), gMeetUrl); + Calendar.Instance.IOutlook.AddRtfBody(ref ai, newBody); } } } diff --git a/src/OutlookGoogleCalendarSync/OutlookOgcs/OutlookCalendar.cs b/src/OutlookGoogleCalendarSync/OutlookOgcs/OutlookCalendar.cs index 54e7dda3..1258254f 100644 --- a/src/OutlookGoogleCalendarSync/OutlookOgcs/OutlookCalendar.cs +++ b/src/OutlookGoogleCalendarSync/OutlookOgcs/OutlookCalendar.cs @@ -596,7 +596,12 @@ public Boolean UpdateCalendarEntry(ref AppointmentItem ai, Event ev, ref int ite String oGMeetUrl = CustomProperty.Get(ai, CustomProperty.MetadataId.gMeetUrl); if (profile.SyncDirection.Id == Sync.Direction.GoogleToOutlook.Id || !profile.AddDescription_OnlyToGoogle) { - String aiBody = ai.Body?.RemoveLineBreaks().Replace(GMeet.PlainInfo(oGMeetUrl).RemoveLineBreaks(), ""); + String aiBody = ai.Body?.RemoveLineBreaks(); + if (!String.IsNullOrEmpty(aiBody)) { + Regex htmlDataTag = new Regex(@""); + aiBody = htmlDataTag.Replace(aiBody, ""); + aiBody = aiBody.Replace(GMeet.PlainInfo(oGMeetUrl, ai.BodyFormat()).RemoveLineBreaks(), "").Trim(); + } String bodyObfuscated = Obfuscate.ApplyRegex(Obfuscate.Property.Description, ev.Description, aiBody, Sync.Direction.GoogleToOutlook); if (bodyObfuscated.Length == 8 * 1024 && aiBody.Length > 8 * 1024) { log.Warn("Event description has been truncated, so will not be synced to Outlook."); diff --git a/src/OutlookGoogleCalendarSync/Properties/Resources.Designer.cs b/src/OutlookGoogleCalendarSync/Properties/Resources.Designer.cs index 3a04dc00..b3d58975 100644 --- a/src/OutlookGoogleCalendarSync/Properties/Resources.Designer.cs +++ b/src/OutlookGoogleCalendarSync/Properties/Resources.Designer.cs @@ -140,6 +140,16 @@ internal static System.Drawing.Bitmap github { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap gmeet_logo { + get { + object obj = ResourceManager.GetObject("gmeet_logo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// diff --git a/src/OutlookGoogleCalendarSync/Properties/Resources.resx b/src/OutlookGoogleCalendarSync/Properties/Resources.resx index 03f20c9e..ebe6a38d 100644 --- a/src/OutlookGoogleCalendarSync/Properties/Resources.resx +++ b/src/OutlookGoogleCalendarSync/Properties/Resources.resx @@ -166,4 +166,7 @@ ..\Resources\animated-tray-icon-strip-64x.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\gmeet_logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/src/OutlookGoogleCalendarSync/Resources/gmeet_logo.png b/src/OutlookGoogleCalendarSync/Resources/gmeet_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ebe756bd2725f8573eed8e048fd05d2700bcef GIT binary patch literal 900 zcmV-~1AF|5P)Px#1ZP1_K>z@;j|==^1poj8E=fc|RA@u(m_LXUK@`RpY%I4E3*iv75X5jGf{hW8 zwAWdBARLw{Y=wYwjmV)O2v-E`gh1>Bo9%7VSfxpCljcfc{JzP1o40>v&+P3+z1t5y zcIVB@WapQ+GnRYnkYXC8}UQh8m%HYvufT}z^zG}fW32-6d zj5rySQ$D$dgt5Z_pS<{mA_QnccI{=4rB*=7=!1=z! z36B%cOb+J+q42FUKLpy`fb<8jK9c@?m8;j|08}l2gcl#)TElAxASa;f+*tZpS!vb5 z8Gtwv3fnZtVZU7~{hErS-GDUj$i(>LaV+*}Y$ok}j^PDR8v`7TuOu08?E)k)od74K z!2H;ztpKOe_P|wWr&TL$g*x>KBDA&YmJ(tty&egz1yCCUj6UAq8bE0)BvydJ^lKH4 zd4N3ET#jSy`y@}RIG6|bBv)e%0VJ4D4_$P4XDa|HoZGd^uTd1vLjbEBTHEs0F0300=t5XFseO%P!?|# zV40AU2WW3kZWL^D1JNbGUlWdYwWYJKTqndY)w!$kFaf?soEw1QmZ#HdW+Wu$O~pgRIXzba`-$~$dx#6c><