From aa0346df85e50a8db0aa4baa141f3d40565af4ce Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Sun, 28 Jan 2024 18:56:46 -0300 Subject: [PATCH] :sparkles: feature: get user timezone from google calendar (#111) See issues: 111 --- README.md | 4 +- dist/index.js | 74 ++++++++++++++++++++++-------- dist/index.min.js | 2 +- dist/setup/gcalsync_dev.js | 74 ++++++++++++++++++++++-------- dist/setup/setup.js | 6 +-- resources/configs.ts | 2 +- src/consts/types.ts | 4 +- src/index.ts | 18 ++++++-- src/methods/handle_session_data.ts | 2 +- src/methods/sync_github.ts | 2 + src/methods/sync_ticktick.ts | 25 +++++----- src/methods/validate_configs.ts | 2 +- src/modules/GoogleCalendar.ts | 2 +- src/modules/ICS.ts | 8 ++-- 14 files changed, 155 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index da61801..51b6843 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ function getConfigs() { const configs = { settings: { sync_function: 'sync', // function name to run every x minutes - timezone_correction: -3, // hour difference from your timezone to utc timezone | https://www.utctime.net/ + timezone_offset_correction: 0, // hour correction to match maybe a daylight saving difference (if you want the events 1 hour "before", then put -1) update_frequency: 5, // wait time between sync checks (must be multiple of 5: 10, 15, etc) skip_mode: false, // if set to true, it will skip every sync (useful for not messing up your data if any bug occurs repeatedly) per_day_emails: { @@ -378,8 +378,6 @@ if you want to change the google event color, you can choose from 12 options: ### General tips -- if you don't want to sync ticktick tasks, remove the `ticktick_sync` object on the configs; -- if you don't want to sync github commits, remove the `github_sync` object on the configs; - in case of deleted ticktick tasks (that means, you dont intend to do it anymore) that are in gcal, make sure to delete in gcal as well. If not, they will be moved to its corresponding completed calendar; - it is not necessary to generate a github token in order to sync commits, it is only required if you want to sync your contributions to private repos as well; - every update in ticktick may take 5 minutes to propagate to its ics calendars; diff --git a/dist/index.js b/dist/index.js index 3e70c69..d130fd9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8,7 +8,7 @@ name: 'gcal-sync', github_repository: 'lucasvtiradentes/gcal-sync', version: '1.10.0', - build_date_time: '25/01/2024 22:36:15' + build_date_time: '28/01/2024 18:56:48' }; const mergeArraysOfArrays = (arr) => arr.reduce((acc, val) => acc.concat(val), []); @@ -318,6 +318,30 @@ const specifiedStamp = Number(timeArr[0]) * 60 + Number(timeArr[1]); return curStamp >= specifiedStamp; } + function getCurrentDateInSpecifiedTimezone(timeZone) { + const date = new Date(); + const formatter = new Intl.DateTimeFormat('en-CA', { + timeZone: timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + const parts = formatter.formatToParts(date); + const findPart = (type) => parts.find((part) => part.type === type).value; + const isoDate = `${findPart('year')}-${findPart('month')}-${findPart('day')}T${findPart('hour')}:${findPart('minute')}:${findPart('second')}.000`; + return isoDate; + } + function getTimezoneOffset(timezone) { + const date = new Date(); + const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); + const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); + const offset = (Number(tzDate) - Number(utcDate)) / (1000 * 60 * 60); + return offset; + } function getTodayStats() { const todayStats = { @@ -379,7 +403,7 @@ const sessionEmail = getSessionEmail(userEmail, sessionData); sendEmail(sessionEmail); } - const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.configs.settings.timezone_correction); + const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.timezone_offset); const alreadySentTodaySummaryEmail = extendedConfigs.today_date === getGASProperty(GAS_PROPERTIES_ENUM.last_daily_email_sent_date); if (isNowTimeAfterDailyEmails && extendedConfigs.configs.settings.per_day_emails.email_daily_summary && !alreadySentTodaySummaryEmail) { updateGASProperty(GAS_PROPERTIES_ENUM.last_daily_email_sent_date, extendedConfigs.today_date); @@ -549,6 +573,10 @@ return curString; } + // ============================================================================= + const getCurrentTimezoneFromGoogleCalendar = () => { + return CalendarApp.getDefaultCalendar().getTimeZone(); + }; const createMissingCalendars = (allGcalendarsNames) => { let createdCalendar = false; allGcalendarsNames.forEach((calName) => { @@ -657,6 +685,7 @@ return filteredRepos; } function syncGithub(configs) { + logger.info(`syncing github commits`); const info = { githubCommits: getAllGithubCommits(configs[githubConfigsKey].username, configs[githubConfigsKey].personal_token), githubGcalCommits: getTasksFromGoogleCalendars([configs[githubConfigsKey].commits_configs.commits_calendar]) @@ -802,7 +831,7 @@ return newStr.slice(0, newStr.search(substr2)); }; - const getIcsCalendarTasks = (icsLink, timezoneCorrection) => { + const getIcsCalendarTasks = (icsLink, timezone_offset) => { const parsedLink = icsLink.replace('webcal://', 'https://'); const urlResponse = UrlFetchApp.fetch(parsedLink, { validateHttpsCertificates: false, muteHttpExceptions: true }); const data = urlResponse.getContentText() || ''; @@ -833,7 +862,7 @@ return [...acc, eventObj]; }, []); const allEventsParsedArr = allEventsArr.map((item) => { - const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezoneCorrection); + const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezone_offset); return { id: item.UID, name: item.SUMMARY, @@ -845,7 +874,7 @@ }); return allEventsParsedArr; }; - function getParsedIcsDatetimes(dtstart, dtend, timezone, timezoneCorrection) { + function getParsedIcsDatetimes(dtstart, dtend, timezone, timezone_offset) { let finalDtstart = dtstart; let finalDtend = dtend; finalDtstart = finalDtstart.slice(finalDtstart.search(':') + 1); @@ -866,7 +895,7 @@ } return `${fixer < 0 ? '-' : '+'}${String(Math.abs(fixer)).padStart(2, '0')}:00`; }; - const timezoneFixedString = getTimeZoneFixedString(timezoneCorrection); + const timezoneFixedString = getTimeZoneFixedString(timezone_offset); finalDtstart = { dateTime: `${startDateObj.year}-${startDateObj.month}-${startDateObj.day}T${startDateObj.hours}:${startDateObj.minutes}:${startDateObj.seconds}${timezoneFixedString}`, timeZone: timezone @@ -882,10 +911,11 @@ }; } - function syncTicktick(configs) { - const icsCalendarsConfigs = configs[ticktickConfigsKey].ics_calendars; + function syncTicktick(extendedConfigs) { + logger.info(`syncing ticktick tasks`); + const icsCalendarsConfigs = extendedConfigs.configs[ticktickConfigsKey].ics_calendars; const info = { - ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, configs.settings.timezone_correction), + ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, extendedConfigs.timezone_offset), ticktickGcalTasks: getTasksFromGoogleCalendars([...new Set(icsCalendarsConfigs.map((item) => item.gcal))]) }; const resultInfo = Object.assign(Object.assign({}, addAndUpdateTasksOnGcal(info)), moveCompletedTasksToDoneGcal(info)); @@ -952,23 +982,23 @@ ]; return resultArr.filter((item) => item.hasChanged).map((item) => item.field); } - function getTicktickTasks(icsCalendarsArr, timezoneCorrection) { + function getTicktickTasks(icsCalendarsArr, timezone_offset) { const extendedTasks = []; for (const icsCal of icsCalendarsArr) { - const tasks = getIcsCalendarTasks(icsCal.link, timezoneCorrection); + const tasks = getIcsCalendarTasks(icsCal.link, timezone_offset); const extendedItem = tasks.map((item) => (Object.assign(Object.assign({}, item), icsCal))); extendedTasks.push(extendedItem); } return mergeArraysOfArrays(extendedTasks); } - function getAllTicktickTasks(icsCalendars, timezoneCorrection) { - const taggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.tag), timezoneCorrection); - const ignoredTaggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.ignoredTags), timezoneCorrection).filter((item) => { + function getAllTicktickTasks(icsCalendars, timezone_offset) { + const taggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.tag), timezone_offset); + const ignoredTaggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.ignoredTags), timezone_offset).filter((item) => { const ignoredTasks = taggedTasks.map((it) => `${it.tag}${it.id}`); const shouldIgnoreTask = item.ignoredTags.some((ignoredTag) => ignoredTasks.includes(`${ignoredTag}${item.id}`)); return shouldIgnoreTask === false; }); - const commonTasks = getTicktickTasks(icsCalendars.filter((icsCal) => !icsCal.tag && !icsCal.ignoredTags), timezoneCorrection); + const commonTasks = getTicktickTasks(icsCalendars.filter((icsCal) => !icsCal.tag && !icsCal.ignoredTags), timezone_offset); return [...taggedTasks, ...ignoredTaggedTasks, ...commonTasks]; } function addAndUpdateTasksOnGcal({ ticktickGcalTasks, ticktickTasks }) { @@ -1067,7 +1097,7 @@ settings: { sync_function: '', skip_mode: false, - timezone_correction: -3, + timezone_offset_correction: 0, update_frequency: 4, per_day_emails: { time_to_send: '15:00', @@ -1126,6 +1156,8 @@ class GcalSync { constructor(configs) { this.extended_configs = { + timezone: '', + timezone_offset: 0, today_date: '', user_email: '', configs: {} @@ -1136,8 +1168,12 @@ if (!isRunningOnGAS()) { throw new Error(ERRORS.production_only); } + const timezone = getCurrentTimezoneFromGoogleCalendar(); + this.extended_configs.timezone = timezone; + this.extended_configs.timezone_offset = getTimezoneOffset(timezone) + configs.settings.timezone_offset_correction * -1; + const todayFixedByTimezone = getCurrentDateInSpecifiedTimezone(timezone); + this.extended_configs.today_date = todayFixedByTimezone.split('T')[0]; this.extended_configs.user_email = getUserEmail(); - this.extended_configs.today_date = getDateFixedByTimezone(configs.settings.timezone_correction).toISOString().split('T')[0]; this.extended_configs.configs = configs; logger.info(`${APP_INFO.name} is running at version ${APP_INFO.version}!`); } @@ -1175,7 +1211,7 @@ return logger.logs; } getTicktickTasks() { - return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.configs.settings.timezone_correction); + return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.timezone_offset); } getGoogleEvents() { return getTasksFromGoogleCalendars([...new Set(this.extended_configs.configs[ticktickConfigsKey].ics_calendars.map((item) => item.gcal))]); @@ -1219,7 +1255,7 @@ commits_tracked_to_be_added: [], commits_tracked_to_be_deleted: [] }; - const sessionData = Object.assign(Object.assign(Object.assign({}, emptySessionData), (shouldSyncTicktick && syncTicktick(this.extended_configs.configs))), (shouldSyncGithub && syncGithub(this.extended_configs.configs))); + const sessionData = Object.assign(Object.assign(Object.assign({}, emptySessionData), (shouldSyncTicktick && syncTicktick(this.extended_configs))), (shouldSyncGithub && syncGithub(this.extended_configs.configs))); const parsedSessionData = handleSessionData(this.extended_configs, sessionData); return parsedSessionData; } diff --git a/dist/index.min.js b/dist/index.min.js index 5d021cc..ff397c7 100644 --- a/dist/index.min.js +++ b/dist/index.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).GcalSync=t()}(this,(function(){"use strict";const e={name:"gcal-sync",github_repository:"lucasvtiradentes/gcal-sync",version:"1.10.0",build_date_time:"25/01/2024 22:36:15"},t=e=>e.reduce(((e,t)=>e.concat(t)),[]);function i(e,t){const i=e.filter((e=>!t.includes(e))),n=t.filter((t=>!e.includes(t)));return i.concat(n)}const n=(e,t,i)=>e.reduce(((e,n)=>{const s=n[t],o=n[i];return e[s]=o,e}),{});var s;const o={DEBUG_MODE:!0,MAX_GCAL_TASKS:2500,REQUIRED_GITHUB_VALIDATIONS_COUNT:3,IS_TEST_ENVIRONMENT:"object"==typeof process&&(null===(s=null===process||void 0===process?void 0:process.env)||void 0===s?void 0:s.NODE_ENV)},r=[{key:"today_ticktick_added_tasks",initial_value:[]},{key:"today_ticktick_updated_tasks",initial_value:[]},{key:"today_ticktick_completed_tasks",initial_value:[]},{key:"today_github_added_commits",initial_value:[]},{key:"today_github_deleted_commits",initial_value:[]},{key:"last_released_version_alerted",initial_value:""},{key:"last_released_version_sent_date",initial_value:""},{key:"last_daily_email_sent_date",initial_value:""},{key:"github_commits_tracked_to_be_added",initial_value:[]},{key:"github_commits_tracked_to_be_deleted",initial_value:[]},{key:"github_commit_changes_count",initial_value:""}],a=n(r,"key","initial_value"),c=n(r,"key","key"),d={invalid_configs:"schema invalid",production_only:"This method cannot run in non-production environments",incorrect_ics_calendar:"The link you provided is not a valid ICS calendar: ",abusive_google_calendar_api_use:"Due to the numerous operations in the last few hours, the google api is not responding.",invalid_ics_calendar_link:"You provided an invalid ICS calendar link: ",invalid_github_token:"You provided an invalid github token",invalid_github_username:"You provided an invalid github username"},l="ticktick_sync",_="github_sync";const m={tableStyle:'style="border: 1px solid #333; width: 90%"',tableRowStyle:'style="width: 100%"',tableRowColumnStyle:'style="border: 1px solid #333"'},u=e=>"date"in e?e.date:e.dateTime;function g(e){return e.added_tasks.length+e.updated_tasks.length+e.completed_tasks.length+e.commits_added.length+e.commits_deleted.length}function p(t){let i="";return i=`Hi!

there were ${g(t)} changes made to your google calendar:
\n`,i+=function(e){const t=e.added_tasks,i=e.updated_tasks,n=e.completed_tasks,s=e=>0===e.length?"":`${e.map((e=>{const t=[u(e.start).split("T")[0],e.extendedProperties.private.calendar,`${e.summary}`].map((e=>`  ${e}`)).join("\n");return`\n${t}\n`})).join("\n")}`,o=`\ndatecalendartask\n`;let r="";return r+=t.length>0?`
added ticktick events : ${t.length}

\n
\n\n${o}\n${s(t)}\n
\n
\n`:"",r+=i.length>0?`
updated ticktick events : ${i.length}

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

\n
\n\n${o}\n${s(n)}\n
\n
\n`:"",r}(t),i+=function(e){const t=e.commits_added,i=e.commits_deleted,n=e=>0===e.length?"":`${e.map((e=>{const{repositoryLink:t,commitMessage:i,repositoryName:n}=e.extendedProperties.private,s=[u(e.start).split("T")[0],`${n}`,`${i}`].map((e=>`  ${e}`)).join("\n");return`\n${s}\n`})).join("\n")}`,s=`\ndaterepositorycommit\n`;let o="";return o+=t.length>0?`
added commits events : ${t.length}

\n
\n\n${s}\n${n(t)}\n
\n
\n`:"",o+=i.length>0?`
removed commits events : ${i.length}

\n
\n\n${s}\n${n(i)}\n
\n
\n`:"",o}(t),i+=`
Regards,
your ${e.name} bot`,i}function h(e){return{shouldSyncGithub:e.configs[_].commits_configs.should_sync,shouldSyncTicktick:e.configs[l].should_sync}}function f(e){const t=PropertiesService.getScriptProperties().getProperty(e);let i;try{i=JSON.parse(t)}catch(e){i=t}return i}function b(e,t){const i="string"==typeof t?t:JSON.stringify(t);PropertiesService.getScriptProperties().setProperty(e,i)}function y(e){const t=ScriptApp.getProjectTriggers().find((t=>t.getHandlerFunction()===e));t&&ScriptApp.deleteTrigger(t)}function k(e){MailApp.sendEmail(e)}const v=new class{constructor(){this.logs=[]}info(e,...t){o.IS_TEST_ENVIRONMENT||(console.log(e,...t),this.logs.push(e))}error(e,...t){o.IS_TEST_ENVIRONMENT||(console.error(e,...t),this.logs.push(e))}};function $(e){const t=new Date;return t.setHours(t.getHours()+e),t}function T(e){const t=e.split("T");return{year:t[0].substring(0,4),month:t[0].substring(4,6),day:t[0].substring(6,8),hours:t[1]?t[1].substring(0,2):"00",minutes:t[1]?t[1].substring(2,4):"00",seconds:t[1]?t[1].substring(4,6):"00"}}function S(t,i){const{shouldSyncGithub:n,shouldSyncTicktick:s}=h(t),o=i.added_tasks.length+i.updated_tasks.length+i.completed_tasks.length;if(s&&o>0){const e=f(c.today_ticktick_added_tasks),t=f(c.today_ticktick_updated_tasks),n=f(c.today_ticktick_completed_tasks);b(c.today_ticktick_added_tasks,[...e,...i.added_tasks]),b(c.today_ticktick_updated_tasks,[...t,...i.updated_tasks]),b(c.today_ticktick_completed_tasks,[...n,...i.completed_tasks]),v.info(`added ${o} new ticktick items to today's stats`)}const r=i.commits_added.length+i.commits_deleted.length;if(n&&r>0){const e=f(c.today_github_added_commits),t=f(c.today_github_deleted_commits);b(c.today_github_added_commits,[...e,...i.commits_added]),b(c.today_github_deleted_commits,[...t,...i.commits_deleted]),v.info(`added ${r} new github items to today's stats`)}!function(t,i,n){var s;const o=t.user_email;if(t.configs.settings.per_sync_emails.email_session&&n>0){k(function(t,i){const n=p(i);return{to:t,name:`${e.name}`,subject:`session report - ${g(i)} modifications - ${e.name}`,htmlBody:n}}(o,i))}const r=function(e,t){const i=$(t),n=60*Number(i.getHours())+Number(i.getMinutes()),s=e.split(":");return n>=60*Number(s[0])+Number(s[1])}(t.configs.settings.per_day_emails.time_to_send,t.configs.settings.timezone_correction),a=t.today_date===f(c.last_daily_email_sent_date);if(r&&t.configs.settings.per_day_emails.email_daily_summary&&!a){b(c.last_daily_email_sent_date,t.today_date);k(function(t,i,n){const s=p(i);return{to:t,name:`${e.name}`,subject:`daily report for ${n} - ${g(i)} modifications - ${e.name}`,htmlBody:s}}(o,{added_tasks:f(c.today_ticktick_added_tasks),updated_tasks:f(c.today_ticktick_updated_tasks),completed_tasks:f(c.today_ticktick_completed_tasks),commits_added:f(c.today_github_added_commits),commits_deleted:f(c.today_github_deleted_commits)},t.today_date)),b(c.today_github_added_commits,[]),b(c.today_github_deleted_commits,[]),b(c.today_ticktick_added_tasks,[]),b(c.today_ticktick_completed_tasks,[]),b(c.today_ticktick_updated_tasks,[]),v.info("today stats were reseted!")}const d=t.today_date===f(c.last_released_version_sent_date),l=e=>Number(e.replace("v","").split(".").join("")),_=()=>{var t;const i=UrlFetchApp.fetch(`https://api.github.com/repos/${e.github_repository}/releases?per_page=1`);return null!==(t=JSON.parse(i.getContentText())[0])&&void 0!==t?t:{tag_name:e.version}};if(r&&t.configs.settings.per_day_emails.email_new_gcal_sync_release&&!d){b(c.last_released_version_sent_date,t.today_date);const i=_(),n=l(i.tag_name),r=l(e.version),a=null!==(s=f(c.last_released_version_alerted))&&void 0!==s?s:"";if(n>r&&n.toString()!=a){k(function(t,i){const n=`Hi!\n

\n a new ${e.name} version is available:
\n \n to update, replace the old version number in your apps scripts gcal sync project to the new version: ${i.tag_name.replace("v","")}
\n and also check if you need to change the setup code in the installation section.\n

\n Regards,\n your ${e.name} bot\n `;return{to:t,name:`${e.name}`,subject:`new version [${i.tag_name}] was released - ${e.name}`,htmlBody:n}}(o,i)),b(c.last_released_version_alerted,n.toString())}}}(t,i,o+r);const{added_tasks:a,updated_tasks:d,completed_tasks:l,commits_added:_,commits_deleted:m,commits_tracked_to_be_added:u,commits_tracked_to_be_deleted:y}=i;return{added_tasks:a.length,updated_tasks:d.length,completed_tasks:l.length,commits_added:_.length,commits_deleted:m.length,commits_tracked_to_be_added:u.length,commits_tracked_to_be_deleted:y.length}}function I(e,t){var i;const n=[];let s=1,o=!1;for(;!1===o;){const r=`https://api.github.com/search/commits?q=author:${e}&page=${s}&sort=committer-date&per_page=100`;let a;a=""!==t?UrlFetchApp.fetch(r,{muteHttpExceptions:!0,headers:{Authorization:`Bearer ${t}`}}):UrlFetchApp.fetch(r,{muteHttpExceptions:!0});const c=null!==(i=JSON.parse(a.getContentText()))&&void 0!==i?i:{};if(200!==a.getResponseCode()){if("Validation Failed"===c.message)throw new Error(d.invalid_github_username);if("Bad credentials"===c.message)throw new Error(d.invalid_github_token);throw new Error(c.message)}const l=c.items;if(0===l.length){o=!0;break}if(n.push(...l),s++,s>10){o=!0;break}}return n.map((e=>({commitDate:e.commit.author.date,commitMessage:e.commit.message.split("\n")[0],commitId:e.html_url.split("commit/")[1],commitUrl:e.html_url,repository:e.repository.full_name,repositoryLink:`https://github.com/${e.repository.full_name}`,repositoryId:e.repository.id,repositoryName:e.repository.name,repositoryOwner:e.repository.owner.login,repositoryDescription:e.repository.description,isRepositoryPrivate:e.repository.private,isRepositoryFork:e.repository.fork})))}function w(e){const t={":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 i=e;for(const[e,n]of Object.entries(t))i=i.replace(e,n);return i}const E=()=>{var e;return null!==(e=Calendar.CalendarList.list({showHidden:!0}).items)&&void 0!==e?e:[]},C=e=>E().find((t=>t.summary===e)),x=e=>{const t=Calendar;if(t.CalendarList.list({showHidden:!0}).items.filter((e=>"owner"===e.accessRole)).map((e=>e.summary)).includes(e))throw new Error(`calendar ${e} already exists!`);const i=t.newCalendar();i.summary=e,i.timeZone=t.Settings.get("timezone").value;return t.Calendars.insert(i)};function R(e){return E().find((t=>t.summary===e))}function D(e){const t=e.reduce(((e,t)=>{const i=function(e){const t=Calendar.Events.list(e.id,{maxResults:o.MAX_GCAL_TASKS}).items.map((e=>function(e){var t,i,n,s,o;return{id:e.id,summary:e.summary,description:null!==(t=e.description)&&void 0!==t?t:"",htmlLink:e.htmlLink,attendees:null!==(i=e.attendees)&&void 0!==i?i:[],reminders:null!==(n=e.reminders)&&void 0!==n?n:{},visibility:null!==(s=e.visibility)&&void 0!==s?s:"default",start:e.start,end:e.end,created:e.created,updated:e.updated,colorId:e.colorId,extendedProperties:null!==(o=e.extendedProperties)&&void 0!==o?o:{}}}(e)));return t}(R(t));return[...e,...i]}),[]);return t}function N(e,t){return Calendar.Events.insert(t,e.id)}function A(e,t,i){const n=function(e,t){const i=Calendar.Events.get(e.id,t);return i}(e,t.id),s=Object.assign(Object.assign({},n),i);return Calendar.Events.update(s,e.id,t.id)}function O(e,t,i){P(e,i),Utilities.sleep(2e3);return N(t,i)}function P(e,t){Calendar.Events.remove(e.id,t.id)}function M(){b("github_commit_changes_count","0"),b("github_commits_tracked_to_be_added",[]),b("github_commits_tracked_to_be_deleted",[])}function U(e,t){return t.sort(((e,t)=>Number(new Date(t.commitDate))-Number(new Date(e.commitDate)))).filter((t=>t.repository.includes(e[_].username))).filter((t=>!1===e[_].commits_configs.ignored_repos.includes(t.repositoryName)))}function G(e){const t={githubCommits:I(e[_].username,e[_].personal_token),githubGcalCommits:D([e[_].commits_configs.commits_calendar])},n=f("github_commit_changes_count"),s=Number(n)+1;null===n&&M(),b("github_commit_changes_count",s.toString()),1===s?v.info(`checking commit changes: ${s}/${o.REQUIRED_GITHUB_VALIDATIONS_COUNT}`):s>1&&se.extendedProperties.private.repository===t.repository)).find((e=>e.extendedProperties.private.commitDate===t.commitDate&&w(e.extendedProperties.private.commitMessage)===w(t.commitMessage)))){const e=r?w(t.commitMessage):t.commitMessage,i={private:{commitMessage:e,commitDate:t.commitDate,repository:t.repository,repositoryName:t.repositoryName,repositoryLink:t.repositoryLink,commitId:t.commitId}},n={summary:`${t.repositoryName} - ${e}`,description:`repository: https://github.com/${t.repository}\ncommit: ${t.commitUrl}`,start:{dateTime:t.commitDate},end:{dateTime:t.commitDate},reminders:{useDefault:!1,overrides:[]},extendedProperties:i};a.commits_tracked_to_be_added.push(n)}}if(1===t)return b("github_commits_tracked_to_be_added",a.commits_tracked_to_be_added.map((e=>e))),a;const c=f("github_commits_tracked_to_be_added").map((e=>e.extendedProperties.private.commitId)),d=a.commits_tracked_to_be_added.map((e=>e.extendedProperties.private.commitId));if(i(c,d).length>0)return v.info("reset github commit properties due differences in added commits"),M(),a;if(t===o.REQUIRED_GITHUB_VALIDATIONS_COUNT&&a.commits_tracked_to_be_added.length>0){v.info(`adding ${a.commits_tracked_to_be_added.length} commits to gcal`);for(let e=0;e{const t=e.extendedProperties.private;s.filter((e=>e.repository===t.repository)).find((e=>e.commitDate===t.commitDate&&w(e.commitMessage)===w(t.commitMessage)))||r.commits_tracked_to_be_deleted.push(e)})),1===n)return b("github_commits_tracked_to_be_deleted",r.commits_tracked_to_be_deleted),r;const a=f("github_commits_tracked_to_be_deleted").map((e=>e.extendedProperties.private.commitId)),c=r.commits_tracked_to_be_deleted.map((e=>e.extendedProperties.private.commitId));if(i(a,c).length>0)return v.info("reset github commit properties due differences in deleted commits"),M(),r;if(n===o.REQUIRED_GITHUB_VALIDATIONS_COUNT&&r.commits_tracked_to_be_deleted.length>0){v.info(`deleting ${r.commits_tracked_to_be_deleted.length} commits on gcal`);for(let e=0;e{const n=e.slice(e.search(t)).replace(t,"");return n.slice(0,n.search(i))},L=(e,t)=>{const i=e.replace("webcal://","https://"),n=UrlFetchApp.fetch(i,{validateHttpsCertificates:!1,muteHttpExceptions:!0}),s=n.getContentText()||"";if(200!==n.getResponseCode())throw new Error(d.invalid_ics_calendar_link+i);if(-1===s.search("BEGIN:VCALENDAR"))throw new Error(d.incorrect_ics_calendar);const o=s.split("BEGIN:VEVENT\r\n").filter((e=>e.search("SUMMARY")>-1)),r=(s.search("SUMMARY:No task.")>0?[]:o.reduce(((e,t)=>{const i=t.split("BEGIN:VALARM\r\n");return[...e,{CALNAME:j(s,"X-WR-CALNAME:","\r\n"),DSTAMP:j(t,"DTSTAMP:","\r\n"),DTSTART:j(t,"DTSTART;","\r\n"),DTEND:j(t,"DTEND;","\r\n"),SUMMARY:j(t,"SUMMARY:","\r\n"),UID:j(t,"UID:","\r\n"),DESCRIPTION:j(t,"DESCRIPTION:","\r\n"),SEQUENCE:j(t,"SEQUENCE:","\r\n"),TZID:j(t,"TZID:","\r\n"),ALARM_TRIGGER:1===i.length?"":j(i[1],"TRIGGER:","\r\n"),ALARM_ACTION:1===i.length?"":j(i[1],"ACTION:","\r\n"),ALARM_DESCRIPTION:1===i.length?"":j(i[1],"DESCRIPTION:","\r\n")}]}),[])).map((e=>{const i=function(e,t,i,n){let s=e,o=t;if(s=s.slice(s.search(":")+1),o=o.slice(o.search(":")+1),""===o){const e=T(s),t=new Date(Date.UTC(Number(e.year),Number(e.month)-1,Number(e.day),0,0,0));t.setDate(t.getDate()+1),o={date:t.toISOString().split("T")[0]},s={date:`${e.year}-${e.month}-${e.day}`}}else{const e=T(s),t=T(o),r=(e=>0===e?"":`${e<0?"-":"+"}${String(Math.abs(e)).padStart(2,"0")}:00`)(n);s={dateTime:`${e.year}-${e.month}-${e.day}T${e.hours}:${e.minutes}:${e.seconds}${r}`,timeZone:i},o={dateTime:`${t.year}-${t.month}-${t.day}T${t.hours}:${t.minutes}:${t.seconds}${r}`,timeZone:i}}return{finalDtstart:s,finalDtend:o}}(e.DTSTART,e.DTEND,e.TZID,t);return{id:e.UID,name:e.SUMMARY,description:e.DESCRIPTION,tzid:e.TZID,start:i.finalDtstart,end:i.finalDtend}}));return r};function H(e){const t=e[l].ics_calendars,i={ticktickTasks:F(t,e.settings.timezone_correction),ticktickGcalTasks:D([...new Set(t.map((e=>e.gcal)))])},n=Object.assign(Object.assign({},function({ticktickGcalTasks:e,ticktickTasks:t}){const i={added_tasks:[],updated_tasks:[]};for(const n of t){const t=e.find((e=>e.extendedProperties.private.tickTaskId===n.id)),s=R(n.gcal);if(t){const e=s.summary!==t.extendedProperties.private.calendar,o=Q(n,t),r=R(n.gcal_done),a={private:{calendar:n.gcal,completedCalendar:n.gcal_done,tickTaskId:n.id}},c={summary:n.name,description:V(n),start:n.start,end:n.end,extendedProperties:a,colorId:(null==n?void 0:n.color)?null==n?void 0:n.color.toString():void 0};if(e){const e=O(s,r,Object.assign(Object.assign({},t),c));i.updated_tasks.push(e)}else if(o.length>0){const e=A(s,t,c);i.updated_tasks.push(e)}}else{const e=z(s,n);i.added_tasks.push(e)}}return i}(i)),function({ticktickGcalTasks:e,ticktickTasks:t}){const i={completed_tasks:[]},n=e.filter((e=>{var t,i;return null===(i=null===(t=e.extendedProperties)||void 0===t?void 0:t.private)||void 0===i?void 0:i.tickTaskId}));for(const e of n){if(!t.map((e=>e.id)).includes(e.extendedProperties.private.tickTaskId)){const t=O(R(e.extendedProperties.private.calendar),R(e.extendedProperties.private.completedCalendar),Object.assign(Object.assign({},e),{colorId:void 0}));i.completed_tasks.push(t)}}return i}(i));return n}const B=e=>{let t=e;return t=t.replace(/\\,/g,","),t=t.replace(/\\;/g,";"),t=t.replace(/\\"/g,'"'),t=t.replace(/\\\\/g,"\\"),t},V=e=>`task: https://ticktick.com/webapp/#q/all/tasks/${e.id.split("@")[0]}${e.description?"\n\n"+e.description.replace(/\\n/g,"\n"):""}`;function z(e,t){const i=function(e){const t={private:{calendar:e.gcal,completedCalendar:e.gcal_done,tickTaskId:e.id}},i=(null==e?void 0:e.color)?{colorId:e.color.toString()}:{};return Object.assign({summary:B(e.name),description:V(e),start:e.start,end:e.end,reminders:{useDefault:!0},extendedProperties:t},i)}(t);try{return N(e,i)}catch(e){throw e.message.search("API call to calendar.events.insert failed with error: Required")>-1?new Error(d.abusive_google_calendar_api_use):new Error(e.message)}}function Q(e,t){return[{hasChanged:B(e.name)!==t.summary,field:"name"},{hasChanged:Object.keys(e.start).length!==Object.keys(t.start).length,field:"date format"},{hasChanged:e.start.date!==t.start.date||e.start.dateTime!==t.start.dateTime,field:"initial date"},{hasChanged:e.end.date!==t.end.date||e.end.dateTime!==t.end.dateTime,field:"final date"},{hasChanged:(()=>{let i=!1;return i=void 0===(null==e?void 0:e.color)?void 0!==t.colorId:e.color.toString()!==t.colorId,i})(),field:"color"}].filter((e=>e.hasChanged)).map((e=>e.field))}function Y(e,i){const n=[];for(const t of e){const e=L(t.link,i).map((e=>Object.assign(Object.assign({},e),t)));n.push(e)}return t(n)}function F(e,t){const i=Y(e.filter((e=>e.tag)),t),n=Y(e.filter((e=>e.ignoredTags)),t).filter((e=>{const t=i.map((e=>`${e.tag}${e.id}`));return!1===e.ignoredTags.some((i=>t.includes(`${i}${e.id}`)))})),s=Y(e.filter((e=>!e.tag&&!e.ignoredTags)),t);return[...i,...n,...s]}function Z(e){return"object"==typeof e&&null!==e}function q(e,t){if(!Z(e))return!1;for(const i in t){if(!(i in e))return v.error(`Missing key: ${i}`),!1;const n=typeof t[i],s=typeof e[i];if(Z(t[i])){if(!Z(e[i])||!q(e[i],t[i]))return v.error(`Invalid nested structure or type mismatch at key: ${i}`),!1}else if(n!==s)return v.error(`Type mismatch at key: ${i}. Expected ${n}, found ${s}`),!1}return!0}function J(e,t){return q(e,t)}const X={settings:{sync_function:"",skip_mode:!1,timezone_correction:-3,update_frequency:4,per_day_emails:{time_to_send:"15:00",email_new_gcal_sync_release:!1,email_daily_summary:!1},per_sync_emails:{email_errors:!1,email_session:!1}}},K={gcal:"",gcal_done:"",link:""},W={should_sync:!1,ics_calendars:[]},ee={username:"",commits_configs:{should_sync:!1,commits_calendar:"",ignored_repos:[],parse_commit_emojis:!1},personal_token:""};return class{constructor(t){if(this.extended_configs={today_date:"",user_email:"",configs:{}},!function(e){if(!Z(e))return!1;const t={basic:!0,ticktick:!0,ticktickIcsItems:!0,github:!0,githubIgnoredRepos:!0};if(t.basic=J(e,X),t.github=J(e[_],ee),t.ticktick=J(e[l],W),"object"==typeof e[l]&&"ics_calendars"in e[l]&&Array.isArray(e[l].ics_calendars)){const i=e[l].ics_calendars.map((e=>J(e,K)));t.ticktickIcsItems=i.every((e=>!0===e))}if("object"==typeof e[_]&&"ignored_repos"in e[_]&&Array.isArray(e[_].ignored_repos)){const i=e[_].ignored_repos.map((e=>"string"==typeof e));t.githubIgnoredRepos=i.every((e=>!0===e))}return Object.values(t).every((e=>!0===e))}(t))throw new Error(d.invalid_configs);if("undefined"==typeof Calendar)throw new Error(d.production_only);this.extended_configs.user_email=Session.getActiveUser().getEmail(),this.extended_configs.today_date=$(t.settings.timezone_correction).toISOString().split("T")[0],this.extended_configs.configs=t,v.info(`${e.name} is running at version ${e.version}!`)}createMissingGASProperties(){const e=PropertiesService.getScriptProperties().getProperties();Object.keys(c).forEach((t=>{Object.keys(e).includes(t)||b(c[t],a[t])}))}createMissingGcalCalendars(){const{shouldSyncGithub:e,shouldSyncTicktick:t}=h(this.extended_configs);(e=>{let t=!1;e.forEach((e=>{C(e)||(x(e),v.info(`created google calendar: [${e}]`),t=!0)})),t&&Utilities.sleep(2e3)})([...new Set([].concat(e?[this.extended_configs.configs[_].commits_configs.commits_calendar]:[]).concat(t?[...this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal)),...this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal_done))]:[]))])}handleError(t){if(this.extended_configs.configs.settings.per_sync_emails.email_errors){const i="string"==typeof t?t:t instanceof Error?t.message:JSON.stringify(t);k(function(t,i){const n=`Hi!\n

\n an error recently occurred:

\n ${i}\n

\n Regards,\n your ${e.name} bot\n `;return{to:t,name:`${e.name}`,subject:`an error occurred - ${e.name}`,htmlBody:n}}(this.extended_configs.user_email,i))}else v.error(t)}getSessionLogs(){return v.logs}getTicktickTasks(){return F(this.extended_configs.configs[l].ics_calendars,this.extended_configs.configs.settings.timezone_correction)}getGoogleEvents(){return D([...new Set(this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal)))])}getGithubCommits(){const e=I(this.extended_configs.configs[_].username,this.extended_configs.configs[_].personal_token);return U(this.extended_configs.configs,e)}install(){var t,i;y(this.extended_configs.configs.settings.sync_function),t=this.extended_configs.configs.settings.sync_function,i=this.extended_configs.configs.settings.update_frequency,ScriptApp.newTrigger(t).timeBased().everyMinutes(i).create(),this.createMissingGASProperties(),v.info(`${e.name} was set to run function "${this.extended_configs.configs.settings.sync_function}" every ${this.extended_configs.configs.settings.update_frequency} minutes`)}uninstall(){y(this.extended_configs.configs.settings.sync_function),Object.keys(c).forEach((e=>{var t;t=c[e],PropertiesService.getScriptProperties().deleteProperty(t)})),v.info(`${e.name} automation was removed from appscript!`)}sync(){if(this.extended_configs.configs.settings.skip_mode)return v.info("skip_mode is set to true, skipping sync"),{};const{shouldSyncGithub:e,shouldSyncTicktick:t}=h(this.extended_configs);if(!e&&!t)return v.info("nothing to sync"),{};this.createMissingGcalCalendars(),this.createMissingGASProperties();const i=Object.assign(Object.assign(Object.assign({},{added_tasks:[],updated_tasks:[],completed_tasks:[],commits_added:[],commits_deleted:[],commits_tracked_to_be_added:[],commits_tracked_to_be_deleted:[]}),t&&H(this.extended_configs.configs)),e&&G(this.extended_configs.configs));return S(this.extended_configs,i)}}})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).GcalSync=t()}(this,(function(){"use strict";const e={name:"gcal-sync",github_repository:"lucasvtiradentes/gcal-sync",version:"1.10.0",build_date_time:"28/01/2024 18:56:48"},t=e=>e.reduce(((e,t)=>e.concat(t)),[]);function i(e,t){const i=e.filter((e=>!t.includes(e))),n=t.filter((t=>!e.includes(t)));return i.concat(n)}const n=(e,t,i)=>e.reduce(((e,n)=>{const s=n[t],o=n[i];return e[s]=o,e}),{});var s;const o={DEBUG_MODE:!0,MAX_GCAL_TASKS:2500,REQUIRED_GITHUB_VALIDATIONS_COUNT:3,IS_TEST_ENVIRONMENT:"object"==typeof process&&(null===(s=null===process||void 0===process?void 0:process.env)||void 0===s?void 0:s.NODE_ENV)},r=[{key:"today_ticktick_added_tasks",initial_value:[]},{key:"today_ticktick_updated_tasks",initial_value:[]},{key:"today_ticktick_completed_tasks",initial_value:[]},{key:"today_github_added_commits",initial_value:[]},{key:"today_github_deleted_commits",initial_value:[]},{key:"last_released_version_alerted",initial_value:""},{key:"last_released_version_sent_date",initial_value:""},{key:"last_daily_email_sent_date",initial_value:""},{key:"github_commits_tracked_to_be_added",initial_value:[]},{key:"github_commits_tracked_to_be_deleted",initial_value:[]},{key:"github_commit_changes_count",initial_value:""}],a=n(r,"key","initial_value"),c=n(r,"key","key"),d={invalid_configs:"schema invalid",production_only:"This method cannot run in non-production environments",incorrect_ics_calendar:"The link you provided is not a valid ICS calendar: ",abusive_google_calendar_api_use:"Due to the numerous operations in the last few hours, the google api is not responding.",invalid_ics_calendar_link:"You provided an invalid ICS calendar link: ",invalid_github_token:"You provided an invalid github token",invalid_github_username:"You provided an invalid github username"},l="ticktick_sync",m="github_sync";const _={tableStyle:'style="border: 1px solid #333; width: 90%"',tableRowStyle:'style="width: 100%"',tableRowColumnStyle:'style="border: 1px solid #333"'},u=e=>"date"in e?e.date:e.dateTime;function g(e){return e.added_tasks.length+e.updated_tasks.length+e.completed_tasks.length+e.commits_added.length+e.commits_deleted.length}function p(t){let i="";return i=`Hi!

there were ${g(t)} changes made to your google calendar:
\n`,i+=function(e){const t=e.added_tasks,i=e.updated_tasks,n=e.completed_tasks,s=e=>0===e.length?"":`${e.map((e=>{const t=[u(e.start).split("T")[0],e.extendedProperties.private.calendar,`${e.summary}`].map((e=>`  ${e}`)).join("\n");return`\n${t}\n`})).join("\n")}`,o=`\ndatecalendartask\n`;let r="";return r+=t.length>0?`
added ticktick events : ${t.length}

\n
\n\n${o}\n${s(t)}\n
\n
\n`:"",r+=i.length>0?`
updated ticktick events : ${i.length}

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

\n
\n\n${o}\n${s(n)}\n
\n
\n`:"",r}(t),i+=function(e){const t=e.commits_added,i=e.commits_deleted,n=e=>0===e.length?"":`${e.map((e=>{const{repositoryLink:t,commitMessage:i,repositoryName:n}=e.extendedProperties.private,s=[u(e.start).split("T")[0],`${n}`,`${i}`].map((e=>`  ${e}`)).join("\n");return`\n${s}\n`})).join("\n")}`,s=`\ndaterepositorycommit\n`;let o="";return o+=t.length>0?`
added commits events : ${t.length}

\n
\n\n${s}\n${n(t)}\n
\n
\n`:"",o+=i.length>0?`
removed commits events : ${i.length}

\n
\n\n${s}\n${n(i)}\n
\n
\n`:"",o}(t),i+=`
Regards,
your ${e.name} bot`,i}function h(e){return{shouldSyncGithub:e.configs[m].commits_configs.should_sync,shouldSyncTicktick:e.configs[l].should_sync}}function f(e){const t=PropertiesService.getScriptProperties().getProperty(e);let i;try{i=JSON.parse(t)}catch(e){i=t}return i}function b(e,t){const i="string"==typeof t?t:JSON.stringify(t);PropertiesService.getScriptProperties().setProperty(e,i)}function y(e){const t=ScriptApp.getProjectTriggers().find((t=>t.getHandlerFunction()===e));t&&ScriptApp.deleteTrigger(t)}function k(e){MailApp.sendEmail(e)}const v=new class{constructor(){this.logs=[]}info(e,...t){o.IS_TEST_ENVIRONMENT||(console.log(e,...t),this.logs.push(e))}error(e,...t){o.IS_TEST_ENVIRONMENT||(console.error(e,...t),this.logs.push(e))}};function $(e){const t=e.split("T");return{year:t[0].substring(0,4),month:t[0].substring(4,6),day:t[0].substring(6,8),hours:t[1]?t[1].substring(0,2):"00",minutes:t[1]?t[1].substring(2,4):"00",seconds:t[1]?t[1].substring(4,6):"00"}}function T(e,t){const i=function(e){const t=new Date;return t.setHours(t.getHours()+e),t}(t),n=60*Number(i.getHours())+Number(i.getMinutes()),s=e.split(":");return n>=60*Number(s[0])+Number(s[1])}function S(t,i){const{shouldSyncGithub:n,shouldSyncTicktick:s}=h(t),o=i.added_tasks.length+i.updated_tasks.length+i.completed_tasks.length;if(s&&o>0){const e=f(c.today_ticktick_added_tasks),t=f(c.today_ticktick_updated_tasks),n=f(c.today_ticktick_completed_tasks);b(c.today_ticktick_added_tasks,[...e,...i.added_tasks]),b(c.today_ticktick_updated_tasks,[...t,...i.updated_tasks]),b(c.today_ticktick_completed_tasks,[...n,...i.completed_tasks]),v.info(`added ${o} new ticktick items to today's stats`)}const r=i.commits_added.length+i.commits_deleted.length;if(n&&r>0){const e=f(c.today_github_added_commits),t=f(c.today_github_deleted_commits);b(c.today_github_added_commits,[...e,...i.commits_added]),b(c.today_github_deleted_commits,[...t,...i.commits_deleted]),v.info(`added ${r} new github items to today's stats`)}!function(t,i,n){var s;const o=t.user_email;if(t.configs.settings.per_sync_emails.email_session&&n>0){k(function(t,i){const n=p(i);return{to:t,name:`${e.name}`,subject:`session report - ${g(i)} modifications - ${e.name}`,htmlBody:n}}(o,i))}const r=T(t.configs.settings.per_day_emails.time_to_send,t.timezone_offset),a=t.today_date===f(c.last_daily_email_sent_date);if(r&&t.configs.settings.per_day_emails.email_daily_summary&&!a){b(c.last_daily_email_sent_date,t.today_date);k(function(t,i,n){const s=p(i);return{to:t,name:`${e.name}`,subject:`daily report for ${n} - ${g(i)} modifications - ${e.name}`,htmlBody:s}}(o,{added_tasks:f(c.today_ticktick_added_tasks),updated_tasks:f(c.today_ticktick_updated_tasks),completed_tasks:f(c.today_ticktick_completed_tasks),commits_added:f(c.today_github_added_commits),commits_deleted:f(c.today_github_deleted_commits)},t.today_date)),b(c.today_github_added_commits,[]),b(c.today_github_deleted_commits,[]),b(c.today_ticktick_added_tasks,[]),b(c.today_ticktick_completed_tasks,[]),b(c.today_ticktick_updated_tasks,[]),v.info("today stats were reseted!")}const d=t.today_date===f(c.last_released_version_sent_date),l=e=>Number(e.replace("v","").split(".").join("")),m=()=>{var t;const i=UrlFetchApp.fetch(`https://api.github.com/repos/${e.github_repository}/releases?per_page=1`);return null!==(t=JSON.parse(i.getContentText())[0])&&void 0!==t?t:{tag_name:e.version}};if(r&&t.configs.settings.per_day_emails.email_new_gcal_sync_release&&!d){b(c.last_released_version_sent_date,t.today_date);const i=m(),n=l(i.tag_name),r=l(e.version),a=null!==(s=f(c.last_released_version_alerted))&&void 0!==s?s:"";if(n>r&&n.toString()!=a){k(function(t,i){const n=`Hi!\n

\n a new ${e.name} version is available:
\n
    \n
  • new version: ${i.tag_name}
  • \n
  • published at: ${i.published_at}
  • \n
  • details: here
  • \n
\n to update, replace the old version number in your apps scripts gcal sync project to the new version: ${i.tag_name.replace("v","")}
\n and also check if you need to change the setup code in the installation section.\n

\n Regards,\n your ${e.name} bot\n `;return{to:t,name:`${e.name}`,subject:`new version [${i.tag_name}] was released - ${e.name}`,htmlBody:n}}(o,i)),b(c.last_released_version_alerted,n.toString())}}}(t,i,o+r);const{added_tasks:a,updated_tasks:d,completed_tasks:l,commits_added:m,commits_deleted:_,commits_tracked_to_be_added:u,commits_tracked_to_be_deleted:y}=i;return{added_tasks:a.length,updated_tasks:d.length,completed_tasks:l.length,commits_added:m.length,commits_deleted:_.length,commits_tracked_to_be_added:u.length,commits_tracked_to_be_deleted:y.length}}function w(e,t){var i;const n=[];let s=1,o=!1;for(;!1===o;){const r=`https://api.github.com/search/commits?q=author:${e}&page=${s}&sort=committer-date&per_page=100`;let a;a=""!==t?UrlFetchApp.fetch(r,{muteHttpExceptions:!0,headers:{Authorization:`Bearer ${t}`}}):UrlFetchApp.fetch(r,{muteHttpExceptions:!0});const c=null!==(i=JSON.parse(a.getContentText()))&&void 0!==i?i:{};if(200!==a.getResponseCode()){if("Validation Failed"===c.message)throw new Error(d.invalid_github_username);if("Bad credentials"===c.message)throw new Error(d.invalid_github_token);throw new Error(c.message)}const l=c.items;if(0===l.length){o=!0;break}if(n.push(...l),s++,s>10){o=!0;break}}return n.map((e=>({commitDate:e.commit.author.date,commitMessage:e.commit.message.split("\n")[0],commitId:e.html_url.split("commit/")[1],commitUrl:e.html_url,repository:e.repository.full_name,repositoryLink:`https://github.com/${e.repository.full_name}`,repositoryId:e.repository.id,repositoryName:e.repository.name,repositoryOwner:e.repository.owner.login,repositoryDescription:e.repository.description,isRepositoryPrivate:e.repository.private,isRepositoryFork:e.repository.fork})))}function I(e){const t={":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 i=e;for(const[e,n]of Object.entries(t))i=i.replace(e,n);return i}const E=()=>{var e;return null!==(e=Calendar.CalendarList.list({showHidden:!0}).items)&&void 0!==e?e:[]},C=e=>E().find((t=>t.summary===e)),D=e=>{const t=Calendar;if(t.CalendarList.list({showHidden:!0}).items.filter((e=>"owner"===e.accessRole)).map((e=>e.summary)).includes(e))throw new Error(`calendar ${e} already exists!`);const i=t.newCalendar();i.summary=e,i.timeZone=t.Settings.get("timezone").value;return t.Calendars.insert(i)};function x(e){return E().find((t=>t.summary===e))}function N(e){const t=e.reduce(((e,t)=>{const i=function(e){const t=Calendar.Events.list(e.id,{maxResults:o.MAX_GCAL_TASKS}).items.map((e=>function(e){var t,i,n,s,o;return{id:e.id,summary:e.summary,description:null!==(t=e.description)&&void 0!==t?t:"",htmlLink:e.htmlLink,attendees:null!==(i=e.attendees)&&void 0!==i?i:[],reminders:null!==(n=e.reminders)&&void 0!==n?n:{},visibility:null!==(s=e.visibility)&&void 0!==s?s:"default",start:e.start,end:e.end,created:e.created,updated:e.updated,colorId:e.colorId,extendedProperties:null!==(o=e.extendedProperties)&&void 0!==o?o:{}}}(e)));return t}(x(t));return[...e,...i]}),[]);return t}function R(e,t){return Calendar.Events.insert(t,e.id)}function A(e,t,i){const n=function(e,t){const i=Calendar.Events.get(e.id,t);return i}(e,t.id),s=Object.assign(Object.assign({},n),i);return Calendar.Events.update(s,e.id,t.id)}function O(e,t,i){P(e,i),Utilities.sleep(2e3);return R(t,i)}function P(e,t){Calendar.Events.remove(e.id,t.id)}function M(){b("github_commit_changes_count","0"),b("github_commits_tracked_to_be_added",[]),b("github_commits_tracked_to_be_deleted",[])}function U(e,t){return t.sort(((e,t)=>Number(new Date(t.commitDate))-Number(new Date(e.commitDate)))).filter((t=>t.repository.includes(e[m].username))).filter((t=>!1===e[m].commits_configs.ignored_repos.includes(t.repositoryName)))}function G(e){v.info("syncing github commits");const t={githubCommits:w(e[m].username,e[m].personal_token),githubGcalCommits:N([e[m].commits_configs.commits_calendar])},n=f("github_commit_changes_count"),s=Number(n)+1;null===n&&M(),b("github_commit_changes_count",s.toString()),1===s?v.info(`checking commit changes: ${s}/${o.REQUIRED_GITHUB_VALIDATIONS_COUNT}`):s>1&&se.extendedProperties.private.repository===t.repository)).find((e=>e.extendedProperties.private.commitDate===t.commitDate&&I(e.extendedProperties.private.commitMessage)===I(t.commitMessage)))){const e=r?I(t.commitMessage):t.commitMessage,i={private:{commitMessage:e,commitDate:t.commitDate,repository:t.repository,repositoryName:t.repositoryName,repositoryLink:t.repositoryLink,commitId:t.commitId}},n={summary:`${t.repositoryName} - ${e}`,description:`repository: https://github.com/${t.repository}\ncommit: ${t.commitUrl}`,start:{dateTime:t.commitDate},end:{dateTime:t.commitDate},reminders:{useDefault:!1,overrides:[]},extendedProperties:i};a.commits_tracked_to_be_added.push(n)}}if(1===t)return b("github_commits_tracked_to_be_added",a.commits_tracked_to_be_added.map((e=>e))),a;const c=f("github_commits_tracked_to_be_added").map((e=>e.extendedProperties.private.commitId)),d=a.commits_tracked_to_be_added.map((e=>e.extendedProperties.private.commitId));if(i(c,d).length>0)return v.info("reset github commit properties due differences in added commits"),M(),a;if(t===o.REQUIRED_GITHUB_VALIDATIONS_COUNT&&a.commits_tracked_to_be_added.length>0){v.info(`adding ${a.commits_tracked_to_be_added.length} commits to gcal`);for(let e=0;e{const t=e.extendedProperties.private;s.filter((e=>e.repository===t.repository)).find((e=>e.commitDate===t.commitDate&&I(e.commitMessage)===I(t.commitMessage)))||r.commits_tracked_to_be_deleted.push(e)})),1===n)return b("github_commits_tracked_to_be_deleted",r.commits_tracked_to_be_deleted),r;const a=f("github_commits_tracked_to_be_deleted").map((e=>e.extendedProperties.private.commitId)),c=r.commits_tracked_to_be_deleted.map((e=>e.extendedProperties.private.commitId));if(i(a,c).length>0)return v.info("reset github commit properties due differences in deleted commits"),M(),r;if(n===o.REQUIRED_GITHUB_VALIDATIONS_COUNT&&r.commits_tracked_to_be_deleted.length>0){v.info(`deleting ${r.commits_tracked_to_be_deleted.length} commits on gcal`);for(let e=0;e{const n=e.slice(e.search(t)).replace(t,"");return n.slice(0,n.search(i))},L=(e,t)=>{const i=e.replace("webcal://","https://"),n=UrlFetchApp.fetch(i,{validateHttpsCertificates:!1,muteHttpExceptions:!0}),s=n.getContentText()||"";if(200!==n.getResponseCode())throw new Error(d.invalid_ics_calendar_link+i);if(-1===s.search("BEGIN:VCALENDAR"))throw new Error(d.incorrect_ics_calendar);const o=s.split("BEGIN:VEVENT\r\n").filter((e=>e.search("SUMMARY")>-1)),r=(s.search("SUMMARY:No task.")>0?[]:o.reduce(((e,t)=>{const i=t.split("BEGIN:VALARM\r\n");return[...e,{CALNAME:j(s,"X-WR-CALNAME:","\r\n"),DSTAMP:j(t,"DTSTAMP:","\r\n"),DTSTART:j(t,"DTSTART;","\r\n"),DTEND:j(t,"DTEND;","\r\n"),SUMMARY:j(t,"SUMMARY:","\r\n"),UID:j(t,"UID:","\r\n"),DESCRIPTION:j(t,"DESCRIPTION:","\r\n"),SEQUENCE:j(t,"SEQUENCE:","\r\n"),TZID:j(t,"TZID:","\r\n"),ALARM_TRIGGER:1===i.length?"":j(i[1],"TRIGGER:","\r\n"),ALARM_ACTION:1===i.length?"":j(i[1],"ACTION:","\r\n"),ALARM_DESCRIPTION:1===i.length?"":j(i[1],"DESCRIPTION:","\r\n")}]}),[])).map((e=>{const i=function(e,t,i,n){let s=e,o=t;if(s=s.slice(s.search(":")+1),o=o.slice(o.search(":")+1),""===o){const e=$(s),t=new Date(Date.UTC(Number(e.year),Number(e.month)-1,Number(e.day),0,0,0));t.setDate(t.getDate()+1),o={date:t.toISOString().split("T")[0]},s={date:`${e.year}-${e.month}-${e.day}`}}else{const e=$(s),t=$(o),r=(e=>0===e?"":`${e<0?"-":"+"}${String(Math.abs(e)).padStart(2,"0")}:00`)(n);s={dateTime:`${e.year}-${e.month}-${e.day}T${e.hours}:${e.minutes}:${e.seconds}${r}`,timeZone:i},o={dateTime:`${t.year}-${t.month}-${t.day}T${t.hours}:${t.minutes}:${t.seconds}${r}`,timeZone:i}}return{finalDtstart:s,finalDtend:o}}(e.DTSTART,e.DTEND,e.TZID,t);return{id:e.UID,name:e.SUMMARY,description:e.DESCRIPTION,tzid:e.TZID,start:i.finalDtstart,end:i.finalDtend}}));return r};function H(e){v.info("syncing ticktick tasks");const t=e.configs[l].ics_calendars,i={ticktickTasks:F(t,e.timezone_offset),ticktickGcalTasks:N([...new Set(t.map((e=>e.gcal)))])},n=Object.assign(Object.assign({},function({ticktickGcalTasks:e,ticktickTasks:t}){const i={added_tasks:[],updated_tasks:[]};for(const n of t){const t=e.find((e=>e.extendedProperties.private.tickTaskId===n.id)),s=x(n.gcal);if(t){const e=s.summary!==t.extendedProperties.private.calendar,o=Q(n,t),r=x(n.gcal_done),a={private:{calendar:n.gcal,completedCalendar:n.gcal_done,tickTaskId:n.id}},c={summary:n.name,description:V(n),start:n.start,end:n.end,extendedProperties:a,colorId:(null==n?void 0:n.color)?null==n?void 0:n.color.toString():void 0};if(e){const e=O(s,r,Object.assign(Object.assign({},t),c));i.updated_tasks.push(e)}else if(o.length>0){const e=A(s,t,c);i.updated_tasks.push(e)}}else{const e=z(s,n);i.added_tasks.push(e)}}return i}(i)),function({ticktickGcalTasks:e,ticktickTasks:t}){const i={completed_tasks:[]},n=e.filter((e=>{var t,i;return null===(i=null===(t=e.extendedProperties)||void 0===t?void 0:t.private)||void 0===i?void 0:i.tickTaskId}));for(const e of n){if(!t.map((e=>e.id)).includes(e.extendedProperties.private.tickTaskId)){const t=O(x(e.extendedProperties.private.calendar),x(e.extendedProperties.private.completedCalendar),Object.assign(Object.assign({},e),{colorId:void 0}));i.completed_tasks.push(t)}}return i}(i));return n}const B=e=>{let t=e;return t=t.replace(/\\,/g,","),t=t.replace(/\\;/g,";"),t=t.replace(/\\"/g,'"'),t=t.replace(/\\\\/g,"\\"),t},V=e=>`task: https://ticktick.com/webapp/#q/all/tasks/${e.id.split("@")[0]}${e.description?"\n\n"+e.description.replace(/\\n/g,"\n"):""}`;function z(e,t){const i=function(e){const t={private:{calendar:e.gcal,completedCalendar:e.gcal_done,tickTaskId:e.id}},i=(null==e?void 0:e.color)?{colorId:e.color.toString()}:{};return Object.assign({summary:B(e.name),description:V(e),start:e.start,end:e.end,reminders:{useDefault:!0},extendedProperties:t},i)}(t);try{return R(e,i)}catch(e){throw e.message.search("API call to calendar.events.insert failed with error: Required")>-1?new Error(d.abusive_google_calendar_api_use):new Error(e.message)}}function Q(e,t){return[{hasChanged:B(e.name)!==t.summary,field:"name"},{hasChanged:Object.keys(e.start).length!==Object.keys(t.start).length,field:"date format"},{hasChanged:e.start.date!==t.start.date||e.start.dateTime!==t.start.dateTime,field:"initial date"},{hasChanged:e.end.date!==t.end.date||e.end.dateTime!==t.end.dateTime,field:"final date"},{hasChanged:(()=>{let i=!1;return i=void 0===(null==e?void 0:e.color)?void 0!==t.colorId:e.color.toString()!==t.colorId,i})(),field:"color"}].filter((e=>e.hasChanged)).map((e=>e.field))}function Z(e,i){const n=[];for(const t of e){const e=L(t.link,i).map((e=>Object.assign(Object.assign({},e),t)));n.push(e)}return t(n)}function F(e,t){const i=Z(e.filter((e=>e.tag)),t),n=Z(e.filter((e=>e.ignoredTags)),t).filter((e=>{const t=i.map((e=>`${e.tag}${e.id}`));return!1===e.ignoredTags.some((i=>t.includes(`${i}${e.id}`)))})),s=Z(e.filter((e=>!e.tag&&!e.ignoredTags)),t);return[...i,...n,...s]}function Y(e){return"object"==typeof e&&null!==e}function q(e,t){if(!Y(e))return!1;for(const i in t){if(!(i in e))return v.error(`Missing key: ${i}`),!1;const n=typeof t[i],s=typeof e[i];if(Y(t[i])){if(!Y(e[i])||!q(e[i],t[i]))return v.error(`Invalid nested structure or type mismatch at key: ${i}`),!1}else if(n!==s)return v.error(`Type mismatch at key: ${i}. Expected ${n}, found ${s}`),!1}return!0}function J(e,t){return q(e,t)}const X={settings:{sync_function:"",skip_mode:!1,timezone_offset_correction:0,update_frequency:4,per_day_emails:{time_to_send:"15:00",email_new_gcal_sync_release:!1,email_daily_summary:!1},per_sync_emails:{email_errors:!1,email_session:!1}}},K={gcal:"",gcal_done:"",link:""},W={should_sync:!1,ics_calendars:[]},ee={username:"",commits_configs:{should_sync:!1,commits_calendar:"",ignored_repos:[],parse_commit_emojis:!1},personal_token:""};return class{constructor(t){if(this.extended_configs={timezone:"",timezone_offset:0,today_date:"",user_email:"",configs:{}},!function(e){if(!Y(e))return!1;const t={basic:!0,ticktick:!0,ticktickIcsItems:!0,github:!0,githubIgnoredRepos:!0};if(t.basic=J(e,X),t.github=J(e[m],ee),t.ticktick=J(e[l],W),"object"==typeof e[l]&&"ics_calendars"in e[l]&&Array.isArray(e[l].ics_calendars)){const i=e[l].ics_calendars.map((e=>J(e,K)));t.ticktickIcsItems=i.every((e=>!0===e))}if("object"==typeof e[m]&&"ignored_repos"in e[m]&&Array.isArray(e[m].ignored_repos)){const i=e[m].ignored_repos.map((e=>"string"==typeof e));t.githubIgnoredRepos=i.every((e=>!0===e))}return Object.values(t).every((e=>!0===e))}(t))throw new Error(d.invalid_configs);if("undefined"==typeof Calendar)throw new Error(d.production_only);const i=CalendarApp.getDefaultCalendar().getTimeZone();this.extended_configs.timezone=i,this.extended_configs.timezone_offset=function(e){const t=new Date,i=new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes(),t.getSeconds())),n=new Date(t.toLocaleString("en-US",{timeZone:e}));return(Number(n)-Number(i))/36e5}(i)+-1*t.settings.timezone_offset_correction;const n=function(e){const t=new Date,i=new Intl.DateTimeFormat("en-CA",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(t),n=e=>i.find((t=>t.type===e)).value;return`${n("year")}-${n("month")}-${n("day")}T${n("hour")}:${n("minute")}:${n("second")}.000`}(i);this.extended_configs.today_date=n.split("T")[0],this.extended_configs.user_email=Session.getActiveUser().getEmail(),this.extended_configs.configs=t,v.info(`${e.name} is running at version ${e.version}!`)}createMissingGASProperties(){const e=PropertiesService.getScriptProperties().getProperties();Object.keys(c).forEach((t=>{Object.keys(e).includes(t)||b(c[t],a[t])}))}createMissingGcalCalendars(){const{shouldSyncGithub:e,shouldSyncTicktick:t}=h(this.extended_configs);(e=>{let t=!1;e.forEach((e=>{C(e)||(D(e),v.info(`created google calendar: [${e}]`),t=!0)})),t&&Utilities.sleep(2e3)})([...new Set([].concat(e?[this.extended_configs.configs[m].commits_configs.commits_calendar]:[]).concat(t?[...this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal)),...this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal_done))]:[]))])}handleError(t){if(this.extended_configs.configs.settings.per_sync_emails.email_errors){const i="string"==typeof t?t:t instanceof Error?t.message:JSON.stringify(t);k(function(t,i){const n=`Hi!\n

\n an error recently occurred:

\n ${i}\n

\n Regards,\n your ${e.name} bot\n `;return{to:t,name:`${e.name}`,subject:`an error occurred - ${e.name}`,htmlBody:n}}(this.extended_configs.user_email,i))}else v.error(t)}getSessionLogs(){return v.logs}getTicktickTasks(){return F(this.extended_configs.configs[l].ics_calendars,this.extended_configs.timezone_offset)}getGoogleEvents(){return N([...new Set(this.extended_configs.configs[l].ics_calendars.map((e=>e.gcal)))])}getGithubCommits(){const e=w(this.extended_configs.configs[m].username,this.extended_configs.configs[m].personal_token);return U(this.extended_configs.configs,e)}install(){var t,i;y(this.extended_configs.configs.settings.sync_function),t=this.extended_configs.configs.settings.sync_function,i=this.extended_configs.configs.settings.update_frequency,ScriptApp.newTrigger(t).timeBased().everyMinutes(i).create(),this.createMissingGASProperties(),v.info(`${e.name} was set to run function "${this.extended_configs.configs.settings.sync_function}" every ${this.extended_configs.configs.settings.update_frequency} minutes`)}uninstall(){y(this.extended_configs.configs.settings.sync_function),Object.keys(c).forEach((e=>{var t;t=c[e],PropertiesService.getScriptProperties().deleteProperty(t)})),v.info(`${e.name} automation was removed from appscript!`)}sync(){if(this.extended_configs.configs.settings.skip_mode)return v.info("skip_mode is set to true, skipping sync"),{};const{shouldSyncGithub:e,shouldSyncTicktick:t}=h(this.extended_configs);if(!e&&!t)return v.info("nothing to sync"),{};this.createMissingGcalCalendars(),this.createMissingGASProperties();const i=Object.assign(Object.assign(Object.assign({},{added_tasks:[],updated_tasks:[],completed_tasks:[],commits_added:[],commits_deleted:[],commits_tracked_to_be_added:[],commits_tracked_to_be_deleted:[]}),t&&H(this.extended_configs)),e&&G(this.extended_configs.configs));return S(this.extended_configs,i)}}})); diff --git a/dist/setup/gcalsync_dev.js b/dist/setup/gcalsync_dev.js index 85b047c..ae72765 100644 --- a/dist/setup/gcalsync_dev.js +++ b/dist/setup/gcalsync_dev.js @@ -4,7 +4,7 @@ function getGcalSyncDev(){ name: 'gcal-sync', github_repository: 'lucasvtiradentes/gcal-sync', version: '1.10.0', - build_date_time: '25/01/2024 22:36:15' + build_date_time: '28/01/2024 18:56:48' }; const mergeArraysOfArrays = (arr) => arr.reduce((acc, val) => acc.concat(val), []); @@ -314,6 +314,30 @@ function getGcalSyncDev(){ const specifiedStamp = Number(timeArr[0]) * 60 + Number(timeArr[1]); return curStamp >= specifiedStamp; } + function getCurrentDateInSpecifiedTimezone(timeZone) { + const date = new Date(); + const formatter = new Intl.DateTimeFormat('en-CA', { + timeZone: timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + const parts = formatter.formatToParts(date); + const findPart = (type) => parts.find((part) => part.type === type).value; + const isoDate = `${findPart('year')}-${findPart('month')}-${findPart('day')}T${findPart('hour')}:${findPart('minute')}:${findPart('second')}.000`; + return isoDate; + } + function getTimezoneOffset(timezone) { + const date = new Date(); + const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); + const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone })); + const offset = (Number(tzDate) - Number(utcDate)) / (1000 * 60 * 60); + return offset; + } function getTodayStats() { const todayStats = { @@ -375,7 +399,7 @@ function getGcalSyncDev(){ const sessionEmail = getSessionEmail(userEmail, sessionData); sendEmail(sessionEmail); } - const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.configs.settings.timezone_correction); + const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.timezone_offset); const alreadySentTodaySummaryEmail = extendedConfigs.today_date === getGASProperty(GAS_PROPERTIES_ENUM.last_daily_email_sent_date); if (isNowTimeAfterDailyEmails && extendedConfigs.configs.settings.per_day_emails.email_daily_summary && !alreadySentTodaySummaryEmail) { updateGASProperty(GAS_PROPERTIES_ENUM.last_daily_email_sent_date, extendedConfigs.today_date); @@ -545,6 +569,10 @@ function getGcalSyncDev(){ return curString; } + // ============================================================================= + const getCurrentTimezoneFromGoogleCalendar = () => { + return CalendarApp.getDefaultCalendar().getTimeZone(); + }; const createMissingCalendars = (allGcalendarsNames) => { let createdCalendar = false; allGcalendarsNames.forEach((calName) => { @@ -653,6 +681,7 @@ function getGcalSyncDev(){ return filteredRepos; } function syncGithub(configs) { + logger.info(`syncing github commits`); const info = { githubCommits: getAllGithubCommits(configs[githubConfigsKey].username, configs[githubConfigsKey].personal_token), githubGcalCommits: getTasksFromGoogleCalendars([configs[githubConfigsKey].commits_configs.commits_calendar]) @@ -798,7 +827,7 @@ function getGcalSyncDev(){ return newStr.slice(0, newStr.search(substr2)); }; - const getIcsCalendarTasks = (icsLink, timezoneCorrection) => { + const getIcsCalendarTasks = (icsLink, timezone_offset) => { const parsedLink = icsLink.replace('webcal://', 'https://'); const urlResponse = UrlFetchApp.fetch(parsedLink, { validateHttpsCertificates: false, muteHttpExceptions: true }); const data = urlResponse.getContentText() || ''; @@ -829,7 +858,7 @@ function getGcalSyncDev(){ return [...acc, eventObj]; }, []); const allEventsParsedArr = allEventsArr.map((item) => { - const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezoneCorrection); + const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezone_offset); return { id: item.UID, name: item.SUMMARY, @@ -841,7 +870,7 @@ function getGcalSyncDev(){ }); return allEventsParsedArr; }; - function getParsedIcsDatetimes(dtstart, dtend, timezone, timezoneCorrection) { + function getParsedIcsDatetimes(dtstart, dtend, timezone, timezone_offset) { let finalDtstart = dtstart; let finalDtend = dtend; finalDtstart = finalDtstart.slice(finalDtstart.search(':') + 1); @@ -862,7 +891,7 @@ function getGcalSyncDev(){ } return `${fixer < 0 ? '-' : '+'}${String(Math.abs(fixer)).padStart(2, '0')}:00`; }; - const timezoneFixedString = getTimeZoneFixedString(timezoneCorrection); + const timezoneFixedString = getTimeZoneFixedString(timezone_offset); finalDtstart = { dateTime: `${startDateObj.year}-${startDateObj.month}-${startDateObj.day}T${startDateObj.hours}:${startDateObj.minutes}:${startDateObj.seconds}${timezoneFixedString}`, timeZone: timezone @@ -878,10 +907,11 @@ function getGcalSyncDev(){ }; } - function syncTicktick(configs) { - const icsCalendarsConfigs = configs[ticktickConfigsKey].ics_calendars; + function syncTicktick(extendedConfigs) { + logger.info(`syncing ticktick tasks`); + const icsCalendarsConfigs = extendedConfigs.configs[ticktickConfigsKey].ics_calendars; const info = { - ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, configs.settings.timezone_correction), + ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, extendedConfigs.timezone_offset), ticktickGcalTasks: getTasksFromGoogleCalendars([...new Set(icsCalendarsConfigs.map((item) => item.gcal))]) }; const resultInfo = Object.assign(Object.assign({}, addAndUpdateTasksOnGcal(info)), moveCompletedTasksToDoneGcal(info)); @@ -948,23 +978,23 @@ function getGcalSyncDev(){ ]; return resultArr.filter((item) => item.hasChanged).map((item) => item.field); } - function getTicktickTasks(icsCalendarsArr, timezoneCorrection) { + function getTicktickTasks(icsCalendarsArr, timezone_offset) { const extendedTasks = []; for (const icsCal of icsCalendarsArr) { - const tasks = getIcsCalendarTasks(icsCal.link, timezoneCorrection); + const tasks = getIcsCalendarTasks(icsCal.link, timezone_offset); const extendedItem = tasks.map((item) => (Object.assign(Object.assign({}, item), icsCal))); extendedTasks.push(extendedItem); } return mergeArraysOfArrays(extendedTasks); } - function getAllTicktickTasks(icsCalendars, timezoneCorrection) { - const taggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.tag), timezoneCorrection); - const ignoredTaggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.ignoredTags), timezoneCorrection).filter((item) => { + function getAllTicktickTasks(icsCalendars, timezone_offset) { + const taggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.tag), timezone_offset); + const ignoredTaggedTasks = getTicktickTasks(icsCalendars.filter((icsCal) => icsCal.ignoredTags), timezone_offset).filter((item) => { const ignoredTasks = taggedTasks.map((it) => `${it.tag}${it.id}`); const shouldIgnoreTask = item.ignoredTags.some((ignoredTag) => ignoredTasks.includes(`${ignoredTag}${item.id}`)); return shouldIgnoreTask === false; }); - const commonTasks = getTicktickTasks(icsCalendars.filter((icsCal) => !icsCal.tag && !icsCal.ignoredTags), timezoneCorrection); + const commonTasks = getTicktickTasks(icsCalendars.filter((icsCal) => !icsCal.tag && !icsCal.ignoredTags), timezone_offset); return [...taggedTasks, ...ignoredTaggedTasks, ...commonTasks]; } function addAndUpdateTasksOnGcal({ ticktickGcalTasks, ticktickTasks }) { @@ -1063,7 +1093,7 @@ function getGcalSyncDev(){ settings: { sync_function: '', skip_mode: false, - timezone_correction: -3, + timezone_offset_correction: 0, update_frequency: 4, per_day_emails: { time_to_send: '15:00', @@ -1122,6 +1152,8 @@ function getGcalSyncDev(){ class GcalSync { constructor(configs) { this.extended_configs = { + timezone: '', + timezone_offset: 0, today_date: '', user_email: '', configs: {} @@ -1132,8 +1164,12 @@ function getGcalSyncDev(){ if (!isRunningOnGAS()) { throw new Error(ERRORS.production_only); } + const timezone = getCurrentTimezoneFromGoogleCalendar(); + this.extended_configs.timezone = timezone; + this.extended_configs.timezone_offset = getTimezoneOffset(timezone) + configs.settings.timezone_offset_correction * -1; + const todayFixedByTimezone = getCurrentDateInSpecifiedTimezone(timezone); + this.extended_configs.today_date = todayFixedByTimezone.split('T')[0]; this.extended_configs.user_email = getUserEmail(); - this.extended_configs.today_date = getDateFixedByTimezone(configs.settings.timezone_correction).toISOString().split('T')[0]; this.extended_configs.configs = configs; logger.info(`${APP_INFO.name} is running at version ${APP_INFO.version}!`); } @@ -1171,7 +1207,7 @@ function getGcalSyncDev(){ return logger.logs; } getTicktickTasks() { - return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.configs.settings.timezone_correction); + return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.timezone_offset); } getGoogleEvents() { return getTasksFromGoogleCalendars([...new Set(this.extended_configs.configs[ticktickConfigsKey].ics_calendars.map((item) => item.gcal))]); @@ -1215,7 +1251,7 @@ function getGcalSyncDev(){ commits_tracked_to_be_added: [], commits_tracked_to_be_deleted: [] }; - const sessionData = Object.assign(Object.assign(Object.assign({}, emptySessionData), (shouldSyncTicktick && syncTicktick(this.extended_configs.configs))), (shouldSyncGithub && syncGithub(this.extended_configs.configs))); + const sessionData = Object.assign(Object.assign(Object.assign({}, emptySessionData), (shouldSyncTicktick && syncTicktick(this.extended_configs))), (shouldSyncGithub && syncGithub(this.extended_configs.configs))); const parsedSessionData = handleSessionData(this.extended_configs, sessionData); return parsedSessionData; } diff --git a/dist/setup/setup.js b/dist/setup/setup.js index 1860e88..811d99c 100644 --- a/dist/setup/setup.js +++ b/dist/setup/setup.js @@ -2,7 +2,7 @@ function getConfigs() { const configs = { settings: { sync_function: 'sync', // function name to run every x minutes - timezone_correction: -3, // hour difference from your timezone to utc timezone | https://www.utctime.net/ + timezone_offset_correction: 0, // hour correction to match maybe a daylight saving difference (if you want the events 1 hour "before", then put -1) update_frequency: 5, // wait time between sync checks (must be multiple of 5: 10, 15, etc) skip_mode: false, // if set to true, it will skip every sync (useful for not messing up your data if any bug occurs repeatedly) per_day_emails: { @@ -61,8 +61,8 @@ function getGcalSync(){ const GcalSync = getGcalSyncDev() gcalSync = new GcalSync(configs); } else { - const version = "1.9.0" - const gcalSyncContent = UrlFetchApp.fetch(`https://cdn.jsdelivr.net/npm/gcal-sync@1.9.0`).getContentText(); + const version = "1.10.0" + const gcalSyncContent = UrlFetchApp.fetch(`https://cdn.jsdelivr.net/npm/gcal-sync@1.10.0`).getContentText(); eval(gcalSyncContent) gcalSync = new GcalSync(configs); } diff --git a/resources/configs.ts b/resources/configs.ts index 70375f0..ae817c4 100644 --- a/resources/configs.ts +++ b/resources/configs.ts @@ -4,7 +4,7 @@ import { TConfigs } from '../src/consts/types'; export const configs: TConfigs = { settings: { sync_function: 'sync', // function name to run every x minutes - timezone_correction: -3, // hour difference from your timezone to utc timezone | https://www.utctime.net/ + timezone_offset_correction: 0, // hour correction to match maybe a daylight saving difference (if you want the events 1 hour "before", then put -1) update_frequency: 5, // wait time between sync checks (must be multiple of 5: 10, 15, etc) skip_mode: false, // if set to true, it will skip every sync (useful for not messing up your data if any bug occurs repeatedly) per_day_emails: { diff --git a/src/consts/types.ts b/src/consts/types.ts index 7a36a12..c4afb6f 100644 --- a/src/consts/types.ts +++ b/src/consts/types.ts @@ -32,7 +32,7 @@ export type TGithubSync = { export type TBasicConfig = { settings: { - timezone_correction: number; + timezone_offset_correction: number; sync_function: string; update_frequency: number; skip_mode: boolean; @@ -54,6 +54,8 @@ export const githubConfigsKey = 'github_sync' as const; export type TConfigs = TBasicConfig & { [ticktickConfigsKey]: TTicktickSync } & { [githubConfigsKey]: TGithubSync }; export type TExtendedConfigs = { + timezone: string; + timezone_offset: number; today_date: string; user_email: string; configs: TConfigs; diff --git a/src/index.ts b/src/index.ts index 1d7d4d0..7a97f16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,14 +9,16 @@ import { getAllTicktickTasks, syncTicktick } from './methods/sync_ticktick'; import { validateConfigs } from './methods/validate_configs'; import { getAllGithubCommits } from './modules/Github'; import { addAppsScriptsTrigger, deleteGASProperty, isRunningOnGAS, listAllGASProperties, removeAppsScriptsTrigger, updateGASProperty } from './modules/GoogleAppsScript'; -import { createMissingCalendars, getTasksFromGoogleCalendars } from './modules/GoogleCalendar'; +import { createMissingCalendars, getCurrentTimezoneFromGoogleCalendar, getTasksFromGoogleCalendars } from './modules/GoogleCalendar'; import { getUserEmail, sendEmail } from './modules/GoogleEmail'; import { logger } from './utils/abstractions/logger'; import { checkIfShouldSync } from './utils/check_if_should_sync'; -import { getDateFixedByTimezone } from './utils/javascript/date_utils'; +import { getCurrentDateInSpecifiedTimezone, getTimezoneOffset } from './utils/javascript/date_utils'; class GcalSync { private extended_configs: TExtendedConfigs = { + timezone: '', + timezone_offset: 0, today_date: '', user_email: '', configs: {} as TConfigs @@ -31,9 +33,15 @@ class GcalSync { throw new Error(ERRORS.production_only); } + const timezone = getCurrentTimezoneFromGoogleCalendar(); + this.extended_configs.timezone = timezone; + this.extended_configs.timezone_offset = getTimezoneOffset(timezone) + configs.settings.timezone_offset_correction * -1; + + const todayFixedByTimezone = getCurrentDateInSpecifiedTimezone(timezone); + this.extended_configs.today_date = todayFixedByTimezone.split('T')[0]; this.extended_configs.user_email = getUserEmail(); - this.extended_configs.today_date = getDateFixedByTimezone(configs.settings.timezone_correction).toISOString().split('T')[0]; this.extended_configs.configs = configs; + logger.info(`${APP_INFO.name} is running at version ${APP_INFO.version}!`); } @@ -78,7 +86,7 @@ class GcalSync { } getTicktickTasks() { - return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.configs.settings.timezone_correction); + return getAllTicktickTasks(this.extended_configs.configs[ticktickConfigsKey].ics_calendars, this.extended_configs.timezone_offset); } getGoogleEvents() { @@ -139,7 +147,7 @@ class GcalSync { const sessionData: TExtendedSessionStats = { ...emptySessionData, - ...(shouldSyncTicktick && syncTicktick(this.extended_configs.configs)), + ...(shouldSyncTicktick && syncTicktick(this.extended_configs)), ...(shouldSyncGithub && syncGithub(this.extended_configs.configs)) }; diff --git a/src/methods/handle_session_data.ts b/src/methods/handle_session_data.ts index eefe810..94a4f24 100644 --- a/src/methods/handle_session_data.ts +++ b/src/methods/handle_session_data.ts @@ -83,7 +83,7 @@ function sendSessionEmails(extendedConfigs: TExtendedConfigs, sessionData: TExte sendEmail(sessionEmail); } - const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.configs.settings.timezone_correction); + const isNowTimeAfterDailyEmails = isCurrentTimeAfter(extendedConfigs.configs.settings.per_day_emails.time_to_send, extendedConfigs.timezone_offset); const alreadySentTodaySummaryEmail = extendedConfigs.today_date === getGASProperty(GAS_PROPERTIES_ENUM.last_daily_email_sent_date); if (isNowTimeAfterDailyEmails && extendedConfigs.configs.settings.per_day_emails.email_daily_summary && !alreadySentTodaySummaryEmail) { diff --git a/src/methods/sync_github.ts b/src/methods/sync_github.ts index 2c7d408..6f03b2e 100644 --- a/src/methods/sync_github.ts +++ b/src/methods/sync_github.ts @@ -37,6 +37,8 @@ export function getFilterGithubRepos(configs: TConfigs, commits: TParsedGithubCo } export function syncGithub(configs: TConfigs) { + logger.info(`syncing github commits`); + const info: TInfo = { githubCommits: getAllGithubCommits(configs[githubConfigsKey].username, configs[githubConfigsKey].personal_token), githubGcalCommits: getTasksFromGoogleCalendars([configs[githubConfigsKey].commits_configs.commits_calendar]) diff --git a/src/methods/sync_ticktick.ts b/src/methods/sync_ticktick.ts index a9bb85d..d203429 100644 --- a/src/methods/sync_ticktick.ts +++ b/src/methods/sync_ticktick.ts @@ -1,7 +1,8 @@ +import { ERRORS } from '../consts/errors'; +import { TExtendedConfigs, TIcsCalendar, ticktickConfigsKey } from '../consts/types'; import { TGcalPrivateTicktick, TGoogleCalendar, TGoogleEvent, TParsedGoogleEvent, addEventToCalendar, getCalendarByName, getTasksFromGoogleCalendars, moveEventToOtherCalendar, updateEventFromCalendar } from '../modules/GoogleCalendar'; import { TExtendedParsedTicktickTask, getIcsCalendarTasks } from '../modules/ICS'; -import { ERRORS } from '../consts/errors'; -import { TConfigs, TIcsCalendar, ticktickConfigsKey } from '../consts/types'; +import { logger } from '../utils/abstractions/logger'; import { mergeArraysOfArrays } from '../utils/javascript/array_utils'; type TInfo = { @@ -15,11 +16,13 @@ export type TTicktickSyncResultInfo = { completed_tasks: TParsedGoogleEvent[]; }; -export function syncTicktick(configs: TConfigs) { - const icsCalendarsConfigs = configs[ticktickConfigsKey].ics_calendars; +export function syncTicktick(extendedConfigs: TExtendedConfigs) { + logger.info(`syncing ticktick tasks`); + + const icsCalendarsConfigs = extendedConfigs.configs[ticktickConfigsKey].ics_calendars; const info: TInfo = { - ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, configs.settings.timezone_correction), + ticktickTasks: getAllTicktickTasks(icsCalendarsConfigs, extendedConfigs.timezone_offset), ticktickGcalTasks: getTasksFromGoogleCalendars([...new Set(icsCalendarsConfigs.map((item) => item.gcal))]) }; @@ -109,11 +112,11 @@ export function checkIfTicktickTaskInfoWasChanged(ticktickTask: TExtendedParsedT return resultArr.filter((item) => item.hasChanged).map((item) => item.field); } -export function getTicktickTasks(icsCalendarsArr: TIcsCalendar[], timezoneCorrection: number) { +export function getTicktickTasks(icsCalendarsArr: TIcsCalendar[], timezone_offset: number) { const extendedTasks: TExtendedParsedTicktickTask[][] = []; for (const icsCal of icsCalendarsArr) { - const tasks = getIcsCalendarTasks(icsCal.link, timezoneCorrection); + const tasks = getIcsCalendarTasks(icsCal.link, timezone_offset); const extendedItem = tasks.map((item) => ({ ...item, ...icsCal @@ -124,14 +127,14 @@ export function getTicktickTasks(icsCalendarsArr: TIcsCalendar[], timezoneCorrec return mergeArraysOfArrays(extendedTasks); } -export function getAllTicktickTasks(icsCalendars: TIcsCalendar[], timezoneCorrection: number) { +export function getAllTicktickTasks(icsCalendars: TIcsCalendar[], timezone_offset: number) { const taggedTasks = getTicktickTasks( icsCalendars.filter((icsCal) => icsCal.tag), - timezoneCorrection + timezone_offset ); const ignoredTaggedTasks = getTicktickTasks( icsCalendars.filter((icsCal) => icsCal.ignoredTags), - timezoneCorrection + timezone_offset ).filter((item) => { const ignoredTasks = taggedTasks.map((it) => `${it.tag}${it.id}`); const shouldIgnoreTask = item.ignoredTags.some((ignoredTag) => ignoredTasks.includes(`${ignoredTag}${item.id}`)); @@ -139,7 +142,7 @@ export function getAllTicktickTasks(icsCalendars: TIcsCalendar[], timezoneCorrec }); const commonTasks = getTicktickTasks( icsCalendars.filter((icsCal) => !icsCal.tag && !icsCal.ignoredTags), - timezoneCorrection + timezone_offset ); return [...taggedTasks, ...ignoredTaggedTasks, ...commonTasks]; diff --git a/src/methods/validate_configs.ts b/src/methods/validate_configs.ts index b9878f5..c34827d 100644 --- a/src/methods/validate_configs.ts +++ b/src/methods/validate_configs.ts @@ -6,7 +6,7 @@ const basicRequiredObjectShape: TBasicConfig = { settings: { sync_function: '', skip_mode: false, - timezone_correction: -3, + timezone_offset_correction: 0, update_frequency: 4, per_day_emails: { time_to_send: '15:00', diff --git a/src/modules/GoogleCalendar.ts b/src/modules/GoogleCalendar.ts index bdbd2d0..9a74c52 100644 --- a/src/modules/GoogleCalendar.ts +++ b/src/modules/GoogleCalendar.ts @@ -29,7 +29,7 @@ export type TParsedGoogleEvent = GcalCommon & { extendedProperties: TP // ============================================================================= -export const getCurrentTimezone = () => { +export const getCurrentTimezoneFromGoogleCalendar = () => { return CalendarApp.getDefaultCalendar().getTimeZone(); }; diff --git a/src/modules/ICS.ts b/src/modules/ICS.ts index 89d8158..c741b1f 100644 --- a/src/modules/ICS.ts +++ b/src/modules/ICS.ts @@ -16,7 +16,7 @@ export type TExtendedParsedTicktickTask = TParsedTicktickTask & TIcsCalendar; export type TDate = { date: string } | { dateTime: string; timeZone: string }; -export const getIcsCalendarTasks = (icsLink: string, timezoneCorrection: number) => { +export const getIcsCalendarTasks = (icsLink: string, timezone_offset: number) => { const parsedLink = icsLink.replace('webcal://', 'https://'); const urlResponse = UrlFetchApp.fetch(parsedLink, { validateHttpsCertificates: false, muteHttpExceptions: true }); const data = urlResponse.getContentText() || ''; @@ -52,7 +52,7 @@ export const getIcsCalendarTasks = (icsLink: string, timezoneCorrection: number) }, []); const allEventsParsedArr = allEventsArr.map((item) => { - const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezoneCorrection); + const parsedDateTime = getParsedIcsDatetimes(item.DTSTART, item.DTEND, item.TZID, timezone_offset); return { id: item.UID, name: item.SUMMARY, @@ -66,7 +66,7 @@ export const getIcsCalendarTasks = (icsLink: string, timezoneCorrection: number) return allEventsParsedArr as TParsedTicktickTask[]; }; -export function getParsedIcsDatetimes(dtstart: string, dtend: string, timezone: string, timezoneCorrection: number) { +export function getParsedIcsDatetimes(dtstart: string, dtend: string, timezone: string, timezone_offset: number) { let finalDtstart: TDate | string = dtstart; let finalDtend: TDate | string = dtend; @@ -89,7 +89,7 @@ export function getParsedIcsDatetimes(dtstart: string, dtend: string, timezone: } return `${fixer < 0 ? '-' : '+'}${String(Math.abs(fixer)).padStart(2, '0')}:00`; }; - const timezoneFixedString = getTimeZoneFixedString(timezoneCorrection); + const timezoneFixedString = getTimeZoneFixedString(timezone_offset); finalDtstart = { dateTime: `${startDateObj.year}-${startDateObj.month}-${startDateObj.day}T${startDateObj.hours}:${startDateObj.minutes}:${startDateObj.seconds}${timezoneFixedString}`,