From 7711dcf5b2ab7495b35b0ccd8d8a4ab0659fbc42 Mon Sep 17 00:00:00 2001 From: lucasvtiradentes Date: Wed, 22 Feb 2023 14:03:00 -0300 Subject: [PATCH] :bug: bugfix: error when updating gcal events (#56) fix update gcal error See issues: 56 --- dist/GcalSync.min.js | 2 +- src/GcalSync.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dist/GcalSync.min.js b/dist/GcalSync.min.js index 700b1f6..a3621e1 100644 --- a/dist/GcalSync.min.js +++ b/dist/GcalSync.min.js @@ -1 +1 @@ -class GcalSync{constructor(t){this.VERSION="1.2.4",this.APPNAME="gcal-sync",this.GITHUB_REPOSITORY="lucasvtiradentes/gcal-sync",this.TODAY_DATE=(new Date).toISOString().split("T")[0],this.ENVIRONMENT=this.detectEnvironment(),this.APPS_SCRIPTS_PROPERTIES={todayTicktickAddedTasks:"todayTicktickAddedTasks",todayTicktickUpdateTasks:"todayTicktickUpdateTasks",todayTicktickCompletedTasks:"todayTicktickCompletedTasks",todayGithubAddedCommits:"todayGithubAddedCommits",todayGithubDeletedCommits:"todayGithubDeletedCommits",lastReleasedVersionAlerted:"lastReleasedVersionAlerted",lastDailyEmailSentDate:"lastDailyEmailSentDate"},this.ERRORS={productionOnly:"This method cannot run in non-production environments",mustSpecifyConfig:"You must specify the settings when starting the class",incorrectIcsCalendar:"The provided ics/ical URL is incorrect: ",httpsError:"There was a HTTP error when accessing: "},this.validateConfigs(t),this.config=t,this.logger(`${this.APPNAME} is running at version ${this.VERSION} in ${this.ENVIRONMENT} environment`),this.logger(`check the docs for your version here: https://github.com/${this.GITHUB_REPOSITORY}/tree/v${this.VERSION}#readme`)}validateConfigs(t){if(!t)throw new Error(this.ERRORS.mustSpecifyConfig);[{objToCheck:t,requiredKeys:["ticktickSync","githubSync","notifications","options"],name:"configs"},{objToCheck:t.ticktickSync,requiredKeys:["icsCalendars","syncTicktick"],name:"configs.ticktickSync"},{objToCheck:t.githubSync,requiredKeys:["username","googleCalendar","syncGithub","parseGithubEmojis"],name:"configs.githubSync"},{objToCheck:t.notifications,requiredKeys:["email","dailyEmailsTime","timeZoneCorrection","emailNewRelease","emailDailySummary","emailSession"],name:"configs.notifications"},{objToCheck:t.options,requiredKeys:["syncFunction","updateFrequency","showLogs","maintanceMode"],name:"configs.options"}].forEach((t=>{const{objToCheck:e,requiredKeys:s,name:i}=t;s.forEach((t=>{if(!e||!Object.keys(e).includes(t))throw new Error(`missing key in ${i}: ${t}`)}))}))}detectEnvironment(){return"undefined"==typeof Calendar?"development":"production"}logger(t){this.config.options.showLogs&&console.log(t)}getStrBetween(t,e,s){const i=t.slice(t.search(e)).replace(e,"");return i.slice(0,i.search(s))}getParsedTimeStamp(t){const e=t.split("T");return{year:e[0].substring(0,4),month:e[0].substring(4,6),day:e[0].substring(6,8),hours:e[1]?e[1].substring(0,2):"00",minutes:e[1]?e[1].substring(2,4):"00",seconds:e[1]?e[1].substring(4,6):"00"}}getDateFixedByTimezone(t){const e=new Date;return e.setHours(e.getHours()+t),e}isCurrentTimeAfter(t){const e=this.getDateFixedByTimezone(this.config.notifications.timeZoneCorrection),s=60*Number(e.getHours())+Number(e.getMinutes()),i=t.split(":");return s>=60*Number(i[0])+Number(i[1])}getIcsCalendarStr(t){let e="";const s=t.replace("webcal://","https://"),i=this.getGoogleFetch().fetch(s,{validateHttpsCertificates:!1,muteHttpExceptions:!0});if(200!=i.getResponseCode())throw new Error(this.ERRORS.httpsError+s);if(e=i.getContentText(),-1===e.search("BEGIN:VCALENDAR"))throw new Error(this.ERRORS.incorrectIcsCalendar+s);return e}getIcsEvents(t){return t.split("BEGIN:VEVENT\r\n").filter((t=>t.search("SUMMARY")>-1)).reduce(((t,e)=>{const s=e.split("BEGIN:VALARM\r\n");return[...t,{DSTAMP:this.getStrBetween(e,"DTSTAMP:","\r\n"),DTSTART:this.getStrBetween(e,"DTSTART;","\r\n"),DTEND:this.getStrBetween(e,"DTEND;","\r\n"),SUMMARY:this.getStrBetween(e,"SUMMARY:","\r\n"),UID:this.getStrBetween(e,"UID:","\r\n"),DESCRIPTION:this.getStrBetween(e,"DESCRIPTION:","\r\n"),SEQUENCE:this.getStrBetween(e,"SEQUENCE:","\r\n"),TZID:this.getStrBetween(e,"TZID:","\r\n"),ALARM_TRIGGER:1===s.length?"":this.getStrBetween(s[1],"TRIGGER:","\r\n"),ALARM_ACTION:1===s.length?"":this.getStrBetween(s[1],"ACTION:","\r\n"),ALARM_DESCRIPTION:1===s.length?"":this.getStrBetween(s[1],"DESCRIPTION:","\r\n")}]}),[])}getParsedIcsDatetimes(t,e,s){let i=t,r=e;if(i=i.slice(i.search(":")+1),r=r.slice(r.search(":")+1),""===r){const t=this.getParsedTimeStamp(i),e=new Date(Date.UTC(Number(t.year),Number(t.month)-1,Number(t.day),0,0,0));e.setDate(e.getDate()+1),r={date:e.toISOString().split("T")[0]},i={date:`${t.year}-${t.month}-${t.day}`}}else{const t=this.getParsedTimeStamp(i),e=this.getParsedTimeStamp(r);i={dateTime:`${t.year}-${t.month}-${t.day}T${t.hours}:${t.minutes}:${t.seconds}-03:00`,timeZone:s},r={dateTime:`${e.year}-${e.month}-${e.day}T${e.hours}:${e.minutes}:${e.seconds}-03:00`,timeZone:s}}return{finalDtstart:i,finalDtend:r}}parseIcsEvents(t){return t.reduce(((t,e)=>{const s=this.getParsedIcsDatetimes(e.DTSTART,e.DTEND,e.TZID);return[...t,{id:e.UID,name:e.SUMMARY,description:e.DESCRIPTION,tzid:e.TZID,start:s.finalDtstart,end:s.finalDtend}]}),[])}getEventsFromIcsCalendar(t){const e=this.getIcsCalendarStr(t),s=e.search("SUMMARY:No task.")>0?[]:this.getIcsEvents(e);return this.parseIcsEvents(s)}getGoogleAppsScriptsObject(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return PropertiesService.getScriptProperties()}getAppsScriptsProperties(){return this.getGoogleAppsScriptsObject().getKeys()}getAppsScriptsProperty(t){return this.getGoogleAppsScriptsObject().getProperty(t)}updateAppsScriptsProperty(t,e){this.getGoogleAppsScriptsObject().setProperty(t,e)}removeAppsScriptsProperty(t){this.getGoogleAppsScriptsObject().deleteProperty(t)}getGoogleFetch(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return UrlFetchApp}getGoogleAppsScriptsTriggerObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return ScriptApp}getAppsScriptsTriggers(){return this.getGoogleAppsScriptsTriggerObj().getProjectTriggers()}addAppsScriptsTrigger(t,e){this.getGoogleAppsScriptsTriggerObj().newTrigger(t).timeBased().everyMinutes(e).create()}removeAppsScriptsTrigger(t){const e=this.getAppsScriptsTriggers().find((e=>e.getHandlerFunction()===t));e&&this.getGoogleAppsScriptsTriggerObj().deleteTrigger(e)}getGoogleCalendarObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return Calendar}getAllCalendars(){return this.getGoogleCalendarObj().CalendarList.list({showHidden:!0}).items}getAllOwnedCalendars(){return this.getGoogleCalendarObj().CalendarList.list({showHidden:!0}).items.filter((t=>"owner"===t.accessRole))}getCalendarByName(t){return this.getAllCalendars().find((e=>e.summary===t))}createCalendar(t){const e=this.getGoogleCalendarObj();if(this.getAllOwnedCalendars().map((t=>t.summary)).includes(t))throw new Error(`calendar ${t} already exists!`);const s=e.newCalendar();s.summary=t,s.timeZone=e.Settings.get("timezone").value;return e.Calendars.insert(s)}deleteCalendar(t){const e=this.getGoogleCalendarObj(),s=this.getCalendarByName(t);s&&(e.Calendars.remove(s.id),this.logger(`deleted calendar ${s.summary}`))}getEventsFromCalendar(t){return this.getGoogleCalendarObj().Events.list(t.id,{}).items.map((t=>this.parseGoogleEvent(t)))}parseGoogleEvent(t){var e,s,i,r,n;return{id:t.id,summary:t.summary,description:null!==(e=t.description)&&void 0!==e?e:"",htmlLink:t.htmlLink,attendees:null!==(s=t.attendees)&&void 0!==s?s:[],reminders:null!==(i=t.reminders)&&void 0!==i?i:{},visibility:null!==(r=t.visibility)&&void 0!==r?r:"default",start:t.start,end:t.end,created:t.created,updated:t.updated,extendedProperties:null!==(n=t.extendedProperties)&&void 0!==n?n:{}}}addEventToCalendar(t,e){return this.getGoogleCalendarObj().Events.insert(e,t.id)}updateEventFromCalendar(t,e,s){const i=this.getEventById(t,e.id),r=Object.assign(Object.assign({},i),s);this.getGoogleCalendarObj().Events.update(r,t.id,e.id)}moveEventToOtherCalendar(t,e,s){this.getGoogleCalendarObj().Events.move(t.id,e.id,s.id)}removeCalendarEvent(t,e){this.getGoogleCalendarObj().Events.remove(t.id,e.id)}getEventById(t,e){return this.getGoogleCalendarObj().Events.get(t.id,e)}getGoogleEmailObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return MailApp}sendEmail(t){this.getGoogleEmailObj().sendEmail(t)}installGcalSync(){this.removeAppsScriptsTrigger(this.config.options.syncFunction),this.addAppsScriptsTrigger(this.config.options.syncFunction,this.config.options.updateFrequency),this.logger(`${this.APPNAME} was set to run ${this.config.options.syncFunction} every ${this.config.options.updateFrequency} minutes`)}uninstallGcalSync(){this.removeAppsScriptsTrigger(this.config.options.syncFunction),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted),this.logger(`${this.APPNAME} automation was removed from appscript!`)}cleanTodayEventsStats(){this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits,""),this.logger(`${this.TODAY_DATE} stats were reseted!`)}showTodayEventsStats(){const t=t=>t.split("\n").filter((t=>t.length>0)).map((t=>`- ${t}`)).join("\n"),e=this.getTodayEvents();this.logger(`stats for ${this.TODAY_DATE}`),this.logger(`ticktick sync - added tasks : ${this.stringToArray(e.addedTicktickTasks).length}${this.stringToArray(e.addedTicktickTasks).length>0?`\n\n${t(e.addedTicktickTasks)}`:""}`),this.logger(`ticktick sync - updated tasks : ${this.stringToArray(e.updatedTicktickTasks).length}${this.stringToArray(e.updatedTicktickTasks).length>0?`\n\n${t(e.updatedTicktickTasks)}`:""}`),this.logger(`ticktick sync - completed tasks: ${this.stringToArray(e.completedTicktickTasks).length}${this.stringToArray(e.completedTicktickTasks).length>0?`\n\n${t(e.completedTicktickTasks)}`:""}`),this.logger(`github sync - added commmits : ${this.stringToArray(e.addedGithubCommits).length}${this.stringToArray(e.addedGithubCommits).length>0?`\n\n${t(e.addedGithubCommits)}`:""}`),this.logger(`github sync - deleted commits: ${this.stringToArray(e.deletedGithubCommits).length}${this.stringToArray(e.deletedGithubCommits).length>0?`\n\n${t(e.deletedGithubCommits)}`:""}`)}getTodayEvents(){return{addedGithubCommits:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),addedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),completedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),deletedGithubCommits:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),updatedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks)}}sync(){const t=this.syncTicktick(),e=t.addedEvents.length,s=t.updatedEvents.length,i=t.completedEvents.length;this.config.ticktickSync.syncTicktick&&(this.logger(`ticktick sync - added tasks : ${e}`),this.logger(`ticktick sync - updated tasks : ${s}`),this.logger(`ticktick sync - completed tasks: ${i}`));const r=this.syncGihub(),n=r.addedCommits.length,a=r.deletedCommits.length;this.config.githubSync.syncGithub&&(this.logger(`github sync - added commits : ${n}`),this.logger(`github sync - deleted commits: ${a}`));const o={addedTicktickTasks:"",updatedTicktickTasks:"",completedTicktickTasks:"",addedGithubCommits:"",deletedGithubCommits:""};if(e+s+i>0){const e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),s=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks),i=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),r=t=>`${t.start.date?t.start.date:t.start.dateTime.split("T")[0]} | ${t.extendedProperties.private.calendar} | ${t.summary}`;o.addedTicktickTasks=t.addedEvents.map((t=>r(t))).join("\n"),o.updatedTicktickTasks=t.updatedEvents.map((t=>r(t))).join("\n"),o.completedTicktickTasks=t.completedEvents.map((t=>r(t))).join("\n"),this.config.options.maintanceMode||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,`${e?e+"\n":""}${o.addedTicktickTasks}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,`${s?s+"\n":""}${o.updatedTicktickTasks}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,`${i?i+"\n":""}${o.completedTicktickTasks}`))}if(n+a>0){const t=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),s=t=>`${t.commitDate.split("T")[0]} | ${t.commitRepository.replace(`${this.config.githubSync.username}/`,"")} | ${this.config.githubSync.parseGithubEmojis?this.parseGithubEmojisString(t.commitMessage):t.commitMessage}`;o.addedGithubCommits=r.addedCommits.map((t=>s(t))).join("\n"),o.deletedGithubCommits=r.deletedCommits.map((t=>s(t))).join("\n"),this.config.options.maintanceMode||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits,`${t?t+"\n":""}${o.addedGithubCommits}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits,`${e?e+"\n":""}${o.deletedGithubCommits}`))}this.config.options.maintanceMode||this.sendAfterSyncEmails(o)}sendAfterSyncEmails(t){var e;this.config.notifications.emailSession&&this.sendSessionEmail(t);const s=this.TODAY_DATE===this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastDailyEmailSentDate);if(this.isCurrentTimeAfter(this.config.notifications.dailyEmailsTime)&&!s&&(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastDailyEmailSentDate,this.TODAY_DATE),this.config.notifications.emailDailySummary&&(this.sendDailySummaryEmail(this.getTodayEvents()),this.cleanTodayEventsStats()),this.config.notifications.emailNewRelease)){const t=this.getLatestGcalSyncRelease(),s=this.parseGcalVersion(t.tag_name),i=this.parseGcalVersion(this.VERSION),r=null!==(e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted))&&void 0!==e?e:"";s>i&&s.toString()!=r&&(this.sendNewReleaseEmail(t),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted,s.toString()))}}syncGihub(){const t={addedCommits:[],deletedCommits:[]};if(!this.config.githubSync.syncGithub)return t;this.getCalendarByName(this.config.githubSync.googleCalendar)||(this.createCalendar(this.config.githubSync.googleCalendar),this.logger(`created google calendar: [${this.config.githubSync.googleCalendar}]`));const e=this.getCalendarByName(this.config.githubSync.googleCalendar),s=this.getEventsFromCalendar(e),i=this.getAllGithubCommits().map((t=>({commitUrl:t.html_url,commitRepository:t.commit.tree.url.replace("https://api.github.com/repos/","").split("/git")[0],commitMessage:t.commit.message.split("\n")[0],commitDate:t.commit.author.date}))).filter((t=>t.commitRepository.search(this.config.githubSync.username)>-1));return i.forEach((i=>{const r=s.find((t=>t.extendedProperties.private.commitUrl===i.commitUrl)),n=i.commitRepository.replace(`${this.config.githubSync.username}/`,""),a=this.config.githubSync.parseGithubEmojis?this.parseGithubEmojisString(i.commitMessage):i.commitMessage;if(!r){const s={commitUrl:i.commitUrl,commitRepository:i.commitRepository,commitMessage:a,commitDate:i.commitDate},r={summary:`${n} - ${a}`,description:`repository: https://github.com/${i.commitRepository}\ncommit: ${i.commitUrl}`,start:{dateTime:i.commitDate},end:{dateTime:i.commitDate},reminders:{useDefault:!1,overrides:[]},extendedProperties:{private:s}};this.config.options.maintanceMode||(this.addEventToCalendar(e,r),t.addedCommits.push(i)),this.logger(`add commit to gcal: ${n} - ${a}`)}})),this.getEventsFromCalendar(e).forEach((s=>{i.find((t=>t.commitUrl===s.extendedProperties.private.commitUrl))||(this.config.options.maintanceMode||(this.removeCalendarEvent(e,s),t.addedCommits.push(s.extendedProperties.private)),this.logger(`commit ${s.extendedProperties.private.commitUrl} was deleted`))})),t}getAllGithubCommits(){var t;const e=[];let s=1,i=!1;for(;!1===i;){const r=`https://api.github.com/search/commits?q=author:${this.config.githubSync.username}&page=${s}&sort=committer-date&per_page=100`,n=this.getGoogleFetch().fetch(r),a=(null!==(t=JSON.parse(n.getContentText()))&&void 0!==t?t:{}).items;if(0===a.length){i=!0;break}e.push(...a),s++}return e}parseGithubEmojisString(t){const e={":art:":"๐ŸŽจ",":zap:":"โšก๏ธ",":fire:":"๐Ÿ”ฅ",":bug:":"๐Ÿ›",":ambulance:":"๐Ÿš‘๏ธ",":sparkles:":"โœจ",":memo:":"๐Ÿ“",":rocket:":"๐Ÿš€",":lipstick:":"๐Ÿ’„",":tada:":"๐ŸŽ‰",":white_check_mark:":"โœ…",":lock:":"๐Ÿ”’๏ธ",":closed_lock_with_key:":"๐Ÿ”",":bookmark:":"๐Ÿ”–",":rotating_light:":"๐Ÿšจ",":construction:":"๐Ÿšง",":green_heart:":"๐Ÿ’š",":arrow_down:":"โฌ‡๏ธ",":arrow_up:":"โฌ†๏ธ",":pushpin:":"๐Ÿ“Œ",":construction_worker:":"๐Ÿ‘ท",":chart_with_upwards_trend:":"๐Ÿ“ˆ",":recycle:":"โ™ป๏ธ",":heavy_plus_sign:":"โž•",":heavy_minus_sign:":"โž–",":wrench:":"๐Ÿ”ง",":hammer:":"๐Ÿ”จ",":globe_with_meridians:":"๐ŸŒ",":pencil2:":"โœ๏ธ",":poop:":"๐Ÿ’ฉ",":rewind:":"โช๏ธ",":twisted_rightwards_arrows:":"๐Ÿ”€",":package:":"๐Ÿ“ฆ๏ธ",":alien:":"๐Ÿ‘ฝ๏ธ",":truck:":"๐Ÿšš",":page_facing_up:":"๐Ÿ“„",":boom:":"๐Ÿ’ฅ",":bento:":"๐Ÿฑ",":wheelchair:":"โ™ฟ๏ธ",":bulb:":"๐Ÿ’ก",":beers:":"๐Ÿป",":speech_balloon:":"๐Ÿ’ฌ",":card_file_box:":"๐Ÿ—ƒ๏ธ",":loud_sound:":"๐Ÿ”Š",":mute:":"๐Ÿ”‡",":busts_in_silhouette:":"๐Ÿ‘ฅ",":children_crossing:":"๐Ÿšธ",":building_construction:":"๐Ÿ—๏ธ",":iphone:":"๐Ÿ“ฑ",":clown_face:":"๐Ÿคก",":egg:":"๐Ÿฅš",":see_no_evil:":"๐Ÿ™ˆ",":camera_flash:":"๐Ÿ“ธ",":alembic:":"โš—๏ธ",":mag:":"๐Ÿ”๏ธ",":label:":"๐Ÿท๏ธ",":seedling:":"๐ŸŒฑ",":triangular_flag_on_post:":"๐Ÿšฉ",":goal_net:":"๐Ÿฅ…",":dizzy:":"๐Ÿ’ซ",":wastebasket:":"๐Ÿ—‘๏ธ",":passport_control:":"๐Ÿ›‚",":adhesive_bandage:":"๐Ÿฉน",":monocle_face:":"๐Ÿง",":coffin:":"โšฐ๏ธ",":test_tube:":"๐Ÿงช",":necktie:":"๐Ÿ‘”",":stethoscope:":"๐Ÿฉบ",":bricks:":"๐Ÿงฑ",":technologist:":"๐Ÿง‘โ€๐Ÿ’ป",":money_with_wings:":"๐Ÿ’ธ",":thread:":"๐Ÿงต",":safety_vest:":"๐Ÿฆบ"};let s=t;for(const[t,i]of Object.entries(e))s=s.replace(t,i);return s}syncTicktick(){const t={addedEvents:[],updatedEvents:[],completedEvents:[]};if(!this.config.ticktickSync.syncTicktick)return t;this.createMissingGoogleCalendars(),this.createMissingAppsScriptsProperties();const e=this.getTasksFromGoogleCalendars(),s=this.config.ticktickSync.icsCalendars.filter((t=>{var e;return"string"==typeof(null===(e=t[3])||void 0===e?void 0:e.tag)})).map((t=>this.checkCalendarItem(t,e))),i=this.parseResults(s),r=this.config.ticktickSync.icsCalendars.filter((t=>!t[3]||t[3]&&!t[3].tag)).map((t=>this.checkCalendarItem(t,e,s))),n=this.parseResults(r),a=[...i.taggedIcsTasks,...n.taggedIcsTasks];return t.completedEvents=this.checkCalendarCompletedTasks(e,a),t.addedEvents=[...i.added,...n.added],t.updatedEvents=[...i.updated,...n.updated],t}createMissingGoogleCalendars(){[...new Set([...this.config.ticktickSync.icsCalendars.map((t=>t[1])),...this.config.ticktickSync.icsCalendars.map((t=>t[2]))])].forEach((t=>{this.getCalendarByName(t)||(this.createCalendar(t),this.logger(`created google calendar: [${t}]`))}))}createMissingAppsScriptsProperties(){this.getAppsScriptsProperties().includes(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks)||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,""))}getTasksFromGoogleCalendars(){return this.config.ticktickSync.icsCalendars.reduce(((t,e)=>{const s=e[1],i=this.getCalendarByName(s),r=this.getEventsFromCalendar(i);return t=[].concat.apply(t,r)}),[])}checkCalendarItem(t,e,s){const[i,r,n,a]=t;let o=this.getEventsFromIcsCalendar(i);a.ignoredTags&&s&&a.ignoredTags.forEach((t=>{const e=s.find((e=>e.calendarOptions.tag===t));if(e){const t=e.tasksFromIcs.map((t=>t.id));o=o.filter((e=>!1===t.includes(e.id)))}}));const[d,c]=this.checkTicktickAddedAndUpdatedTasks(t,o,e);return{icsCal:i,gCalCorresponding:r,completedCal:n,calendarOptions:a,tasksFromIcs:o,addedTasks:d,updatedTasks:c}}checkTicktickAddedAndUpdatedTasks(t,e,s){const[i,r,n,a]=t,o=[],d=[],c=this.getCalendarByName(r);return e.forEach((t=>{if(s.find((e=>e.extendedProperties.private.tickTaskId===t.id))){const e=s.find((e=>e.extendedProperties.private.tickTaskId===t.id)),i=t.name!==e.summary,r=Object.keys(t.start).length!==Object.keys(e.start).length,n=t.start.date!==e.start.date||t.start.dateTime!==e.start.dateTime,a=t.end.date!==e.end.date||t.end.dateTime!==e.end.dateTime;if(i||r||n||a){const s={summary:t.name,description:t.description,start:t.start,end:t.end};this.config.options.maintanceMode||this.updateEventFromCalendar(c,e,s);const i=Object.assign(Object.assign({},e),s);d.push(i),this.logger(`ticktick task was updated: ${i.summary}`)}}else{const e={tickTaskId:t.id,calendar:r,completedCalendar:n},s={summary:t.name,description:`task: https://ticktick.com/webapp/#q/all/tasks/${t.id.split("@")[0]}\ndescription: ${t.description}`,start:t.start,end:t.end,reminders:{useDefault:!0},extendedProperties:{private:e}};this.config.options.maintanceMode||this.addEventToCalendar(c,s),o.push(s),this.logger(`ticktick task was added to gcal: ${s.summary}`)}})),[o,d]}checkCalendarCompletedTasks(t,e){const s=[];return t.filter((t=>t.extendedProperties.private.tickTaskId)).forEach((t=>{if(!e.map((t=>t.id)).includes(t.extendedProperties.private.tickTaskId)){const e=this.getCalendarByName(t.extendedProperties.private.calendar),i=this.getCalendarByName(t.extendedProperties.private.completedCalendar);this.config.options.maintanceMode||this.moveEventToOtherCalendar(e,t,i),s.push(t),this.logger(`ticktick task was completed: ${t.summary}`)}})),s}parseResults(t){return t.reduce(((t,e)=>(t.added||(t.added=[],t.updated=[],t.taggedIcsTasks=[]),t.added.push(...e.addedTasks),t.updated.push(...e.updatedTasks),t.taggedIcsTasks.push(...e.tasksFromIcs),t)),{})}parseGcalVersion(t){return Number(t.replace("v","").split(".").join(""))}getLatestGcalSyncRelease(){var t;const e=this.getGoogleFetch().fetch(`https://api.github.com/repos/${this.GITHUB_REPOSITORY}/releases?per_page=1`),s=null!==(t=JSON.parse(e.getContentText())[0])&&void 0!==t?t:{};if(0!==Object.keys(s).length)return s}sendNewReleaseEmail(t){const e=`Hi!\n

\n a new ${this.APPNAME} version is available:
\n \n
\n to update just replace the old version number in your [apps scripts gcal sync project](https://script.google.com/) to the new version: ${t.tag_name.replace("v","")}
\n you can check details here.\n `,s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`new version [${t.tag_name}] was released - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`new release email was sent to ${this.config.notifications.email}`)}sendSessionEmail(t){const e=this.generateReportEmailContent(t);if(!e)return;const s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`session report - ${this.getTotalSessionEvents(t)} modifications - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`session email was sent to ${this.config.notifications.email}`)}sendDailySummaryEmail(t){const e=this.generateReportEmailContent(t);if(!e)return;const s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`daily report for ${this.TODAY_DATE} - ${this.getTotalSessionEvents(t)} modifications - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`summary email was sent to ${this.config.notifications.email}`)}generateReportEmailContent(t){const e=this.stringToArray(t.addedTicktickTasks),s=this.stringToArray(t.updatedTicktickTasks),i=this.stringToArray(t.completedTicktickTasks),r=this.stringToArray(t.addedGithubCommits),n=this.stringToArray(t.deletedGithubCommits),a=this.getTotalSessionEvents(t);if(0===a)return;const o='style="border: 1px solid #333; width: 90%"',d='style="width: 100%"',c='style="border: 1px solid #333"',h=t=>{if(!t||0===t.length)return"";return`${this.sortArrayByDate(t,0).map((t=>`\n${t.map((t=>`  ${t}`)).join("\n")}\n`)).join("\n")}`},l=`\ndatecalendartask\n`,p=`\ndaterepositorycommit\n`;let m="";return m=`Hi!

${this.APPNAME} made ${a} changes to your calendar:
\n`,m+=e.length>0?`
added ticktick events : ${e.length}

\n
\n\n${l}\n${h(e)}\n
\n
\n`:"",m+=s.length>0?`
updated ticktick events : ${s.length}

\n
\n\n${l}\n${h(s)}\n
\n
\n`:"",m+=i.length>0?`
completed ticktick events: ${i.length}

\n
\n\n${l}\n${h(i)}\n
\n
\n`:"",m+=r.length>0?`
added commits events : ${r.length}

\n
\n\n${p}\n${h(r)}\n
\n
\n`:"",m+=n.length>0?`
removed commits events : ${n.length}

\n
\n\n${p}\n${h(n)}\n
\n
\n`:"",m+=`
If you want to share feedback, please contact us at github.`,m}getTotalSessionEvents(t){return this.stringToArray(t.addedTicktickTasks).length+this.stringToArray(t.updatedTicktickTasks).length+this.stringToArray(t.completedTicktickTasks).length+this.stringToArray(t.addedGithubCommits).length+this.stringToArray(t.deletedGithubCommits).length}stringToArray(t){return t.split("\n").filter((t=>t.length>0))}sortArrayByDate(t,e){if(!t)return[];return t.map((t=>t.split(" | "))).sort(((t,s)=>Number(new Date(t[e]))-Number(new Date(s[e]))))}} \ No newline at end of file +class GcalSync{constructor(t){this.VERSION="1.3.0",this.APPNAME="gcal-sync",this.GITHUB_REPOSITORY="lucasvtiradentes/gcal-sync",this.TODAY_DATE=(new Date).toISOString().split("T")[0],this.ENVIRONMENT=this.detectEnvironment(),this.APPS_SCRIPTS_PROPERTIES={todayTicktickAddedTasks:"todayTicktickAddedTasks",todayTicktickUpdateTasks:"todayTicktickUpdateTasks",todayTicktickCompletedTasks:"todayTicktickCompletedTasks",todayGithubAddedCommits:"todayGithubAddedCommits",todayGithubDeletedCommits:"todayGithubDeletedCommits",lastReleasedVersionAlerted:"lastReleasedVersionAlerted",lastDailyEmailSentDate:"lastDailyEmailSentDate"},this.ERRORS={productionOnly:"This method cannot run in non-production environments",mustSpecifyConfig:"You must specify the settings when starting the class",incorrectIcsCalendar:"The provided ics/ical URL is incorrect: ",httpsError:"There was a HTTP error when accessing: "},this.validateConfigs(t),this.config=t,this.logger(`${this.APPNAME} is running at version ${this.VERSION} in ${this.ENVIRONMENT} environment`),this.logger(`check the docs for your version here: https://github.com/${this.GITHUB_REPOSITORY}/tree/v${this.VERSION}#readme`)}validateConfigs(t){if(!t)throw new Error(this.ERRORS.mustSpecifyConfig);[{objToCheck:t,requiredKeys:["ticktickSync","githubSync","notifications","options"],name:"configs"},{objToCheck:t.ticktickSync,requiredKeys:["icsCalendars","syncTicktick"],name:"configs.ticktickSync"},{objToCheck:t.githubSync,requiredKeys:["username","googleCalendar","syncGithub","parseGithubEmojis"],name:"configs.githubSync"},{objToCheck:t.notifications,requiredKeys:["email","dailyEmailsTime","timeZoneCorrection","emailNewRelease","emailDailySummary","emailSession"],name:"configs.notifications"},{objToCheck:t.options,requiredKeys:["syncFunction","updateFrequency","showLogs","maintanceMode"],name:"configs.options"}].forEach((t=>{const{objToCheck:e,requiredKeys:s,name:i}=t;s.forEach((t=>{if(!e||!Object.keys(e).includes(t))throw new Error(`missing key in ${i}: ${t}`)}))}))}detectEnvironment(){return"undefined"==typeof Calendar?"development":"production"}logger(t){this.config.options.showLogs&&console.log(t)}getStrBetween(t,e,s){const i=t.slice(t.search(e)).replace(e,"");return i.slice(0,i.search(s))}getParsedTimeStamp(t){const e=t.split("T");return{year:e[0].substring(0,4),month:e[0].substring(4,6),day:e[0].substring(6,8),hours:e[1]?e[1].substring(0,2):"00",minutes:e[1]?e[1].substring(2,4):"00",seconds:e[1]?e[1].substring(4,6):"00"}}getDateFixedByTimezone(t){const e=new Date;return e.setHours(e.getHours()+t),e}isCurrentTimeAfter(t){const e=this.getDateFixedByTimezone(this.config.notifications.timeZoneCorrection),s=60*Number(e.getHours())+Number(e.getMinutes()),i=t.split(":");return s>=60*Number(i[0])+Number(i[1])}getIcsCalendarStr(t){let e="";const s=t.replace("webcal://","https://"),i=this.getGoogleFetch().fetch(s,{validateHttpsCertificates:!1,muteHttpExceptions:!0});if(200!=i.getResponseCode())throw new Error(this.ERRORS.httpsError+s);if(e=i.getContentText(),-1===e.search("BEGIN:VCALENDAR"))throw new Error(this.ERRORS.incorrectIcsCalendar+s);return e}getIcsEvents(t){return t.split("BEGIN:VEVENT\r\n").filter((t=>t.search("SUMMARY")>-1)).reduce(((t,e)=>{const s=e.split("BEGIN:VALARM\r\n");return[...t,{DSTAMP:this.getStrBetween(e,"DTSTAMP:","\r\n"),DTSTART:this.getStrBetween(e,"DTSTART;","\r\n"),DTEND:this.getStrBetween(e,"DTEND;","\r\n"),SUMMARY:this.getStrBetween(e,"SUMMARY:","\r\n"),UID:this.getStrBetween(e,"UID:","\r\n"),DESCRIPTION:this.getStrBetween(e,"DESCRIPTION:","\r\n"),SEQUENCE:this.getStrBetween(e,"SEQUENCE:","\r\n"),TZID:this.getStrBetween(e,"TZID:","\r\n"),ALARM_TRIGGER:1===s.length?"":this.getStrBetween(s[1],"TRIGGER:","\r\n"),ALARM_ACTION:1===s.length?"":this.getStrBetween(s[1],"ACTION:","\r\n"),ALARM_DESCRIPTION:1===s.length?"":this.getStrBetween(s[1],"DESCRIPTION:","\r\n")}]}),[])}getParsedIcsDatetimes(t,e,s){let i=t,r=e;if(i=i.slice(i.search(":")+1),r=r.slice(r.search(":")+1),""===r){const t=this.getParsedTimeStamp(i),e=new Date(Date.UTC(Number(t.year),Number(t.month)-1,Number(t.day),0,0,0));e.setDate(e.getDate()+1),r={date:e.toISOString().split("T")[0]},i={date:`${t.year}-${t.month}-${t.day}`}}else{const t=this.getParsedTimeStamp(i),e=this.getParsedTimeStamp(r);i={dateTime:`${t.year}-${t.month}-${t.day}T${t.hours}:${t.minutes}:${t.seconds}-03:00`,timeZone:s},r={dateTime:`${e.year}-${e.month}-${e.day}T${e.hours}:${e.minutes}:${e.seconds}-03:00`,timeZone:s}}return{finalDtstart:i,finalDtend:r}}parseIcsEvents(t){return t.reduce(((t,e)=>{const s=this.getParsedIcsDatetimes(e.DTSTART,e.DTEND,e.TZID);return[...t,{id:e.UID,name:e.SUMMARY,description:e.DESCRIPTION,tzid:e.TZID,start:s.finalDtstart,end:s.finalDtend}]}),[])}getEventsFromIcsCalendar(t){const e=this.getIcsCalendarStr(t),s=e.search("SUMMARY:No task.")>0?[]:this.getIcsEvents(e);return this.parseIcsEvents(s)}getGoogleAppsScriptsObject(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return PropertiesService.getScriptProperties()}getAppsScriptsProperties(){return this.getGoogleAppsScriptsObject().getKeys()}getAppsScriptsProperty(t){return this.getGoogleAppsScriptsObject().getProperty(t)}updateAppsScriptsProperty(t,e){this.getGoogleAppsScriptsObject().setProperty(t,e)}removeAppsScriptsProperty(t){this.getGoogleAppsScriptsObject().deleteProperty(t)}getGoogleFetch(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return UrlFetchApp}getGoogleAppsScriptsTriggerObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return ScriptApp}getAppsScriptsTriggers(){return this.getGoogleAppsScriptsTriggerObj().getProjectTriggers()}addAppsScriptsTrigger(t,e){this.getGoogleAppsScriptsTriggerObj().newTrigger(t).timeBased().everyMinutes(e).create()}removeAppsScriptsTrigger(t){const e=this.getAppsScriptsTriggers().find((e=>e.getHandlerFunction()===t));e&&this.getGoogleAppsScriptsTriggerObj().deleteTrigger(e)}getGoogleCalendarObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return Calendar}getAllCalendars(){return this.getGoogleCalendarObj().CalendarList.list({showHidden:!0}).items}getAllOwnedCalendars(){return this.getGoogleCalendarObj().CalendarList.list({showHidden:!0}).items.filter((t=>"owner"===t.accessRole))}getCalendarByName(t){return this.getAllCalendars().find((e=>e.summary===t))}createCalendar(t){const e=this.getGoogleCalendarObj();if(this.getAllOwnedCalendars().map((t=>t.summary)).includes(t))throw new Error(`calendar ${t} already exists!`);const s=e.newCalendar();s.summary=t,s.timeZone=e.Settings.get("timezone").value;return e.Calendars.insert(s)}deleteCalendar(t){const e=this.getGoogleCalendarObj(),s=this.getCalendarByName(t);s&&(e.Calendars.remove(s.id),this.logger(`deleted calendar ${s.summary}`))}getEventsFromCalendar(t){return this.getGoogleCalendarObj().Events.list(t.id,{}).items.map((t=>this.parseGoogleEvent(t)))}parseGoogleEvent(t){var e,s,i,r,n;return{id:t.id,summary:t.summary,description:null!==(e=t.description)&&void 0!==e?e:"",htmlLink:t.htmlLink,attendees:null!==(s=t.attendees)&&void 0!==s?s:[],reminders:null!==(i=t.reminders)&&void 0!==i?i:{},visibility:null!==(r=t.visibility)&&void 0!==r?r:"default",start:t.start,end:t.end,created:t.created,updated:t.updated,extendedProperties:null!==(n=t.extendedProperties)&&void 0!==n?n:{}}}addEventToCalendar(t,e){return this.getGoogleCalendarObj().Events.insert(e,t.id)}updateEventFromCalendar(t,e,s){const i=this.getEventById(t,e.id),r=Object.assign(Object.assign({},i),s);this.getGoogleCalendarObj().Events.update(r,t.id,e.id)}moveEventToOtherCalendar(t,e,s){this.getGoogleCalendarObj().Events.move(t.id,e.id,s.id)}removeCalendarEvent(t,e){this.getGoogleCalendarObj().Events.remove(t.id,e.id)}getEventById(t,e){return this.getGoogleCalendarObj().Events.get(t.id,e)}getGoogleEmailObj(){if("development"===this.ENVIRONMENT)throw new Error(this.ERRORS.productionOnly);return MailApp}sendEmail(t){this.getGoogleEmailObj().sendEmail(t)}installGcalSync(){this.removeAppsScriptsTrigger(this.config.options.syncFunction),this.addAppsScriptsTrigger(this.config.options.syncFunction,this.config.options.updateFrequency),this.logger(`${this.APPNAME} was set to run ${this.config.options.syncFunction} every ${this.config.options.updateFrequency} minutes`)}uninstallGcalSync(){this.removeAppsScriptsTrigger(this.config.options.syncFunction),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),this.removeAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted),this.logger(`${this.APPNAME} automation was removed from appscript!`)}cleanTodayEventsStats(){this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits,""),this.logger(`${this.TODAY_DATE} stats were reseted!`)}showTodayEventsStats(){const t=t=>t.split("\n").filter((t=>t.length>0)).map((t=>`- ${t}`)).join("\n"),e=this.getTodayEvents();this.logger(`stats for ${this.TODAY_DATE}`),this.logger(`ticktick sync - added tasks : ${this.stringToArray(e.addedTicktickTasks).length}${this.stringToArray(e.addedTicktickTasks).length>0?`\n\n${t(e.addedTicktickTasks)}`:""}`),this.logger(`ticktick sync - updated tasks : ${this.stringToArray(e.updatedTicktickTasks).length}${this.stringToArray(e.updatedTicktickTasks).length>0?`\n\n${t(e.updatedTicktickTasks)}`:""}`),this.logger(`ticktick sync - completed tasks: ${this.stringToArray(e.completedTicktickTasks).length}${this.stringToArray(e.completedTicktickTasks).length>0?`\n\n${t(e.completedTicktickTasks)}`:""}`),this.logger(`github sync - added commmits : ${this.stringToArray(e.addedGithubCommits).length}${this.stringToArray(e.addedGithubCommits).length>0?`\n\n${t(e.addedGithubCommits)}`:""}`),this.logger(`github sync - deleted commits: ${this.stringToArray(e.deletedGithubCommits).length}${this.stringToArray(e.deletedGithubCommits).length>0?`\n\n${t(e.deletedGithubCommits)}`:""}`)}getTodayEvents(){return{addedGithubCommits:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),addedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),completedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),deletedGithubCommits:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),updatedTicktickTasks:this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks)}}sync(){const t=this.syncTicktick(),e=t.addedEvents.length,s=t.updatedEvents.length,i=t.completedEvents.length;this.config.ticktickSync.syncTicktick&&(this.logger(`ticktick sync - added tasks : ${e}`),this.logger(`ticktick sync - updated tasks : ${s}`),this.logger(`ticktick sync - completed tasks: ${i}`));const r=this.syncGihub(),n=r.addedCommits.length,a=r.deletedCommits.length;this.config.githubSync.syncGithub&&(this.logger(`github sync - added commits : ${n}`),this.logger(`github sync - deleted commits: ${a}`));const o={addedTicktickTasks:"",updatedTicktickTasks:"",completedTicktickTasks:"",addedGithubCommits:"",deletedGithubCommits:""};if(e+s+i>0){const e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks),s=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks),i=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks),r=t=>`${t.start.date?t.start.date:t.start.dateTime.split("T")[0]} | ${t.extendedProperties.private.calendar} | ${t.summary}`;o.addedTicktickTasks=t.addedEvents.map((t=>r(t))).join("\n"),o.updatedTicktickTasks=t.updatedEvents.map((t=>r(t))).join("\n"),o.completedTicktickTasks=t.completedEvents.map((t=>r(t))).join("\n"),this.config.options.maintanceMode||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,`${e?e+"\n":""}${o.addedTicktickTasks}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,`${s?s+"\n":""}${o.updatedTicktickTasks}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,`${i?i+"\n":""}${o.completedTicktickTasks}`))}if(n+a>0){const t=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits),e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits),s=t=>`${t.commitDate.split("T")[0]} | ${t.commitRepository.replace(`${this.config.githubSync.username}/`,"")} | ${this.config.githubSync.parseGithubEmojis?this.parseGithubEmojisString(t.commitMessage):t.commitMessage}`;o.addedGithubCommits=r.addedCommits.map((t=>s(t))).join("\n"),o.deletedGithubCommits=r.deletedCommits.map((t=>s(t))).join("\n"),this.config.options.maintanceMode||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubAddedCommits,`${t?t+"\n":""}${o.addedGithubCommits}`),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayGithubDeletedCommits,`${e?e+"\n":""}${o.deletedGithubCommits}`))}this.config.options.maintanceMode||this.sendAfterSyncEmails(o)}sendAfterSyncEmails(t){var e;this.config.notifications.emailSession&&this.sendSessionEmail(t);const s=this.TODAY_DATE===this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastDailyEmailSentDate);if(this.isCurrentTimeAfter(this.config.notifications.dailyEmailsTime)&&!s&&(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastDailyEmailSentDate,this.TODAY_DATE),this.config.notifications.emailDailySummary&&(this.sendDailySummaryEmail(this.getTodayEvents()),this.cleanTodayEventsStats()),this.config.notifications.emailNewRelease)){const t=this.getLatestGcalSyncRelease(),s=this.parseGcalVersion(t.tag_name),i=this.parseGcalVersion(this.VERSION),r=null!==(e=this.getAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted))&&void 0!==e?e:"";s>i&&s.toString()!=r&&(this.sendNewReleaseEmail(t),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.lastReleasedVersionAlerted,s.toString()))}}syncGihub(){const t={addedCommits:[],deletedCommits:[]};if(!this.config.githubSync.syncGithub)return t;this.getCalendarByName(this.config.githubSync.googleCalendar)||(this.createCalendar(this.config.githubSync.googleCalendar),this.logger(`created google calendar: [${this.config.githubSync.googleCalendar}]`));const e=this.getCalendarByName(this.config.githubSync.googleCalendar),s=this.getEventsFromCalendar(e),i=this.getAllGithubCommits().map((t=>({commitUrl:t.html_url,commitRepository:t.commit.tree.url.replace("https://api.github.com/repos/","").split("/git")[0],commitMessage:t.commit.message.split("\n")[0],commitDate:t.commit.author.date}))).filter((t=>t.commitRepository.search(this.config.githubSync.username)>-1));return i.forEach((i=>{const r=s.find((t=>t.extendedProperties.private.commitUrl===i.commitUrl)),n=i.commitRepository.replace(`${this.config.githubSync.username}/`,""),a=this.config.githubSync.parseGithubEmojis?this.parseGithubEmojisString(i.commitMessage):i.commitMessage;if(!r){const s={commitUrl:i.commitUrl,commitRepository:i.commitRepository,commitMessage:a,commitDate:i.commitDate},r={summary:`${n} - ${a}`,description:`repository: https://github.com/${i.commitRepository}\ncommit: ${i.commitUrl}`,start:{dateTime:i.commitDate},end:{dateTime:i.commitDate},reminders:{useDefault:!1,overrides:[]},extendedProperties:{private:s}};this.config.options.maintanceMode||(this.addEventToCalendar(e,r),t.addedCommits.push(i)),this.logger(`add commit to gcal: ${n} - ${a}`)}})),this.getEventsFromCalendar(e).forEach((s=>{i.find((t=>t.commitUrl===s.extendedProperties.private.commitUrl))||(this.config.options.maintanceMode||(this.removeCalendarEvent(e,s),t.addedCommits.push(s.extendedProperties.private)),this.logger(`commit ${s.extendedProperties.private.commitUrl} was deleted`))})),t}getAllGithubCommits(){var t;const e=[];let s=1,i=!1;for(;!1===i;){const r=`https://api.github.com/search/commits?q=author:${this.config.githubSync.username}&page=${s}&sort=committer-date&per_page=100`,n=this.getGoogleFetch().fetch(r),a=(null!==(t=JSON.parse(n.getContentText()))&&void 0!==t?t:{}).items;if(0===a.length){i=!0;break}e.push(...a),s++}return e}parseGithubEmojisString(t){const e={":art:":"๐ŸŽจ",":zap:":"โšก๏ธ",":fire:":"๐Ÿ”ฅ",":bug:":"๐Ÿ›",":ambulance:":"๐Ÿš‘๏ธ",":sparkles:":"โœจ",":memo:":"๐Ÿ“",":rocket:":"๐Ÿš€",":lipstick:":"๐Ÿ’„",":tada:":"๐ŸŽ‰",":white_check_mark:":"โœ…",":lock:":"๐Ÿ”’๏ธ",":closed_lock_with_key:":"๐Ÿ”",":bookmark:":"๐Ÿ”–",":rotating_light:":"๐Ÿšจ",":construction:":"๐Ÿšง",":green_heart:":"๐Ÿ’š",":arrow_down:":"โฌ‡๏ธ",":arrow_up:":"โฌ†๏ธ",":pushpin:":"๐Ÿ“Œ",":construction_worker:":"๐Ÿ‘ท",":chart_with_upwards_trend:":"๐Ÿ“ˆ",":recycle:":"โ™ป๏ธ",":heavy_plus_sign:":"โž•",":heavy_minus_sign:":"โž–",":wrench:":"๐Ÿ”ง",":hammer:":"๐Ÿ”จ",":globe_with_meridians:":"๐ŸŒ",":pencil2:":"โœ๏ธ",":poop:":"๐Ÿ’ฉ",":rewind:":"โช๏ธ",":twisted_rightwards_arrows:":"๐Ÿ”€",":package:":"๐Ÿ“ฆ๏ธ",":alien:":"๐Ÿ‘ฝ๏ธ",":truck:":"๐Ÿšš",":page_facing_up:":"๐Ÿ“„",":boom:":"๐Ÿ’ฅ",":bento:":"๐Ÿฑ",":wheelchair:":"โ™ฟ๏ธ",":bulb:":"๐Ÿ’ก",":beers:":"๐Ÿป",":speech_balloon:":"๐Ÿ’ฌ",":card_file_box:":"๐Ÿ—ƒ๏ธ",":loud_sound:":"๐Ÿ”Š",":mute:":"๐Ÿ”‡",":busts_in_silhouette:":"๐Ÿ‘ฅ",":children_crossing:":"๐Ÿšธ",":building_construction:":"๐Ÿ—๏ธ",":iphone:":"๐Ÿ“ฑ",":clown_face:":"๐Ÿคก",":egg:":"๐Ÿฅš",":see_no_evil:":"๐Ÿ™ˆ",":camera_flash:":"๐Ÿ“ธ",":alembic:":"โš—๏ธ",":mag:":"๐Ÿ”๏ธ",":label:":"๐Ÿท๏ธ",":seedling:":"๐ŸŒฑ",":triangular_flag_on_post:":"๐Ÿšฉ",":goal_net:":"๐Ÿฅ…",":dizzy:":"๐Ÿ’ซ",":wastebasket:":"๐Ÿ—‘๏ธ",":passport_control:":"๐Ÿ›‚",":adhesive_bandage:":"๐Ÿฉน",":monocle_face:":"๐Ÿง",":coffin:":"โšฐ๏ธ",":test_tube:":"๐Ÿงช",":necktie:":"๐Ÿ‘”",":stethoscope:":"๐Ÿฉบ",":bricks:":"๐Ÿงฑ",":technologist:":"๐Ÿง‘โ€๐Ÿ’ป",":money_with_wings:":"๐Ÿ’ธ",":thread:":"๐Ÿงต",":safety_vest:":"๐Ÿฆบ"};let s=t;for(const[t,i]of Object.entries(e))s=s.replace(t,i);return s}syncTicktick(){const t={addedEvents:[],updatedEvents:[],completedEvents:[]};if(!this.config.ticktickSync.syncTicktick)return t;this.createMissingGoogleCalendars(),this.createMissingAppsScriptsProperties();const e=this.getTasksFromGoogleCalendars(),s=this.config.ticktickSync.icsCalendars.filter((t=>{var e;return"string"==typeof(null===(e=t[3])||void 0===e?void 0:e.tag)})).map((t=>this.checkCalendarItem(t,e))),i=this.parseResults(s),r=this.config.ticktickSync.icsCalendars.filter((t=>!t[3]||t[3]&&!t[3].tag)).map((t=>this.checkCalendarItem(t,e,s))),n=this.parseResults(r),a=[...i.taggedIcsTasks,...n.taggedIcsTasks];return t.completedEvents=this.checkCalendarCompletedTasks(e,a),t.addedEvents=[...i.added,...n.added],t.updatedEvents=[...i.updated,...n.updated],t}createMissingGoogleCalendars(){[...new Set([...this.config.ticktickSync.icsCalendars.map((t=>t[1])),...this.config.ticktickSync.icsCalendars.map((t=>t[2]))])].forEach((t=>{this.getCalendarByName(t)||(this.createCalendar(t),this.logger(`created google calendar: [${t}]`))}))}createMissingAppsScriptsProperties(){this.getAppsScriptsProperties().includes(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks)||(this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickAddedTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickUpdateTasks,""),this.updateAppsScriptsProperty(this.APPS_SCRIPTS_PROPERTIES.todayTicktickCompletedTasks,""))}getTasksFromGoogleCalendars(){return this.config.ticktickSync.icsCalendars.reduce(((t,e)=>{const s=e[1],i=this.getCalendarByName(s),r=this.getEventsFromCalendar(i);return t=[].concat.apply(t,r)}),[])}checkCalendarItem(t,e,s){const[i,r,n,a]=t;let o=this.getEventsFromIcsCalendar(i);a.ignoredTags&&s&&a.ignoredTags.forEach((t=>{const e=s.find((e=>e.calendarOptions.tag===t));if(e){const t=e.tasksFromIcs.map((t=>t.id));o=o.filter((e=>!1===t.includes(e.id)))}}));const[d,c]=this.checkTicktickAddedAndUpdatedTasks(t,o,e);return{icsCal:i,gCalCorresponding:r,completedCal:n,calendarOptions:a,tasksFromIcs:o,addedTasks:d,updatedTasks:c}}checkTicktickAddedAndUpdatedTasks(t,e,s){const[i,r,n,a]=t,o=[],d=[],c=this.getCalendarByName(r),h=t=>`task: https://ticktick.com/webapp/#q/all/tasks/${t.id.split("@")[0]}\n\n${t.description.replace(/\\n/g,"\n")}`;return e.forEach((t=>{if(s.find((e=>e.extendedProperties.private.tickTaskId===t.id))){const e=s.find((e=>e.extendedProperties.private.tickTaskId===t.id)),i=t.name!==e.summary,r=Object.keys(t.start).length!==Object.keys(e.start).length,n=t.start.date!==e.start.date||t.start.dateTime!==e.start.dateTime,a=t.end.date!==e.end.date||t.end.dateTime!==e.end.dateTime;if(i||r||n||a){const s={summary:t.name,description:h(t),start:t.start,end:t.end};this.config.options.maintanceMode||this.updateEventFromCalendar(c,e,s);const i=Object.assign(Object.assign({},e),s);d.push(i),this.logger(`ticktick task was updated: ${i.summary}`)}}else{const e={tickTaskId:t.id,calendar:r,completedCalendar:n},s={summary:t.name,description:h(t),start:t.start,end:t.end,reminders:{useDefault:!0},extendedProperties:{private:e}};this.config.options.maintanceMode||this.addEventToCalendar(c,s),o.push(s),this.logger(`ticktick task was added to gcal: ${s.summary}`)}})),[o,d]}checkCalendarCompletedTasks(t,e){const s=[];return t.filter((t=>t.extendedProperties.private.tickTaskId)).forEach((t=>{if(!e.map((t=>t.id)).includes(t.extendedProperties.private.tickTaskId)){const e=this.getCalendarByName(t.extendedProperties.private.calendar),i=this.getCalendarByName(t.extendedProperties.private.completedCalendar);this.config.options.maintanceMode||this.moveEventToOtherCalendar(e,t,i),s.push(t),this.logger(`ticktick task was completed: ${t.summary}`)}})),s}parseResults(t){return t.reduce(((t,e)=>(t.added||(t.added=[],t.updated=[],t.taggedIcsTasks=[]),t.added.push(...e.addedTasks),t.updated.push(...e.updatedTasks),t.taggedIcsTasks.push(...e.tasksFromIcs),t)),{})}parseGcalVersion(t){return Number(t.replace("v","").split(".").join(""))}getLatestGcalSyncRelease(){var t;const e=this.getGoogleFetch().fetch(`https://api.github.com/repos/${this.GITHUB_REPOSITORY}/releases?per_page=1`),s=null!==(t=JSON.parse(e.getContentText())[0])&&void 0!==t?t:{};if(0!==Object.keys(s).length)return s}sendNewReleaseEmail(t){const e=`Hi!\n

\n a new ${this.APPNAME} version is available:
\n \n
\n to update just replace the old version number in your [apps scripts gcal sync project](https://script.google.com/) to the new version: ${t.tag_name.replace("v","")}
\n you can check details here.\n `,s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`new version [${t.tag_name}] was released - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`new release email was sent to ${this.config.notifications.email}`)}sendSessionEmail(t){const e=this.generateReportEmailContent(t);if(!e)return;const s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`session report - ${this.getTotalSessionEvents(t)} modifications - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`session email was sent to ${this.config.notifications.email}`)}sendDailySummaryEmail(t){const e=this.generateReportEmailContent(t);if(!e)return;const s={to:this.config.notifications.email,name:`${this.APPNAME}`,subject:`daily report for ${this.TODAY_DATE} - ${this.getTotalSessionEvents(t)} modifications - ${this.APPNAME}`,htmlBody:e};this.sendEmail(s),this.logger(`summary email was sent to ${this.config.notifications.email}`)}generateReportEmailContent(t){const e=this.stringToArray(t.addedTicktickTasks),s=this.stringToArray(t.updatedTicktickTasks),i=this.stringToArray(t.completedTicktickTasks),r=this.stringToArray(t.addedGithubCommits),n=this.stringToArray(t.deletedGithubCommits),a=this.getTotalSessionEvents(t);if(0===a)return;const o='style="border: 1px solid #333; width: 90%"',d='style="width: 100%"',c='style="border: 1px solid #333"',h=t=>{if(!t||0===t.length)return"";return`${this.sortArrayByDate(t,0).map((t=>`\n${t.map((t=>`  ${t}`)).join("\n")}\n`)).join("\n")}`},l=`\ndatecalendartask\n`,p=`\ndaterepositorycommit\n`;let m="";return m=`Hi!

${this.APPNAME} made ${a} changes to your calendar:
\n`,m+=e.length>0?`
added ticktick events : ${e.length}

\n
\n\n${l}\n${h(e)}\n
\n
\n`:"",m+=s.length>0?`
updated ticktick events : ${s.length}

\n
\n\n${l}\n${h(s)}\n
\n
\n`:"",m+=i.length>0?`
completed ticktick events: ${i.length}

\n
\n\n${l}\n${h(i)}\n
\n
\n`:"",m+=r.length>0?`
added commits events : ${r.length}

\n
\n\n${p}\n${h(r)}\n
\n
\n`:"",m+=n.length>0?`
removed commits events : ${n.length}

\n
\n\n${p}\n${h(n)}\n
\n
\n`:"",m+=`
If you want to share feedback, please contact us at github.`,m}getTotalSessionEvents(t){return this.stringToArray(t.addedTicktickTasks).length+this.stringToArray(t.updatedTicktickTasks).length+this.stringToArray(t.completedTicktickTasks).length+this.stringToArray(t.addedGithubCommits).length+this.stringToArray(t.deletedGithubCommits).length}stringToArray(t){return t.split("\n").filter((t=>t.length>0))}sortArrayByDate(t,e){if(!t)return[];return t.map((t=>t.split(" | "))).sort(((t,s)=>Number(new Date(t[e]))-Number(new Date(s[e]))))}} \ No newline at end of file diff --git a/src/GcalSync.ts b/src/GcalSync.ts index 49cd372..4660fd5 100644 --- a/src/GcalSync.ts +++ b/src/GcalSync.ts @@ -991,6 +991,7 @@ class GcalSync { const updatedTasks: GoogleEvent[] = []; const taskCalendar = this.getCalendarByName(gCalCorresponding); + const generateGcalDescription = (curIcsTask: ParsedIcsEvent) => `task: https://ticktick.com/webapp/#q/all/tasks/${curIcsTask.id.split('@')[0]}\n\n${curIcsTask.description.replace(/\\n/g, '\n')}`; tasksFromIcs.forEach((curIcsTask) => { const taskOnGcal = tasksFromGoogleCalendars.find((item) => item.extendedProperties.private.tickTaskId === curIcsTask.id); @@ -1004,7 +1005,7 @@ class GcalSync { const taskEvent: GoogleEvent = { summary: curIcsTask.name, - description: `task: https://ticktick.com/webapp/#q/all/tasks/${curIcsTask.id.split('@')[0]}\ndescription: ${curIcsTask.description}`, + description: generateGcalDescription(curIcsTask), start: curIcsTask.start, end: curIcsTask.end, reminders: { @@ -1032,7 +1033,7 @@ class GcalSync { if (changedTaskName || changedDateFormat || changedIntialDate || changedFinalDate) { const modifiedFields = { summary: curIcsTask.name, - description: curIcsTask.description, + description: generateGcalDescription(curIcsTask), start: curIcsTask.start, end: curIcsTask.end };