From 7cb580e80dc32e359a9732a0b9c4c5d1cf5d2af8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 28 Jan 2024 22:00:42 +0000 Subject: [PATCH] :bookmark: tags: new version release 1.11.0 [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [1.11.0](https://github.com/lucasvtiradentes/gcal-sync/compare/v1.10.0...v1.11.0) (2024-01-28) ### โœจ feature: * get user timezone from google calendar ([#111](https://github.com/lucasvtiradentes/gcal-sync/issues/111)) ([aa0346d](https://github.com/lucasvtiradentes/gcal-sync/commit/aa0346df85e50a8db0aa4baa141f3d40565af4ce)) --- README.md | 4 ++-- dist/index.js | 4 ++-- dist/index.min.js | 2 +- dist/setup/gcalsync_dev.js | 4 ++-- docs/CHANGELOG.MD | 7 +++++++ package.json | 2 +- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 51b6843..b48e948 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,8 @@ function getGcalSync(){ const GcalSync = getGcalSyncDev() gcalSync = new GcalSync(configs); } else { - const version = "1.10.0" - const gcalSyncContent = UrlFetchApp.fetch(`https://cdn.jsdelivr.net/npm/gcal-sync@1.10.0`).getContentText(); + const version = "1.11.0" + const gcalSyncContent = UrlFetchApp.fetch(`https://cdn.jsdelivr.net/npm/gcal-sync@1.11.0`).getContentText(); eval(gcalSyncContent) gcalSync = new GcalSync(configs); } diff --git a/dist/index.js b/dist/index.js index d130fd9..6e28811 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7,8 +7,8 @@ const APP_INFO = { name: 'gcal-sync', github_repository: 'lucasvtiradentes/gcal-sync', - version: '1.10.0', - build_date_time: '28/01/2024 18:56:48' + version: '1.11.0', + build_date_time: '28/01/2024 19:00:37' }; const mergeArraysOfArrays = (arr) => arr.reduce((acc, val) => acc.concat(val), []); diff --git a/dist/index.min.js b/dist/index.min.js index ff397c7..dfe00af 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:"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 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)}}})); +!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.11.0",build_date_time:"28/01/2024 19:00:37"},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 ae72765..61a46de 100644 --- a/dist/setup/gcalsync_dev.js +++ b/dist/setup/gcalsync_dev.js @@ -3,8 +3,8 @@ function getGcalSyncDev(){ const APP_INFO = { name: 'gcal-sync', github_repository: 'lucasvtiradentes/gcal-sync', - version: '1.10.0', - build_date_time: '28/01/2024 18:56:48' + version: '1.11.0', + build_date_time: '28/01/2024 19:00:37' }; const mergeArraysOfArrays = (arr) => arr.reduce((acc, val) => acc.concat(val), []); diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 8048053..b344c5a 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -1,5 +1,12 @@ # CHANGELOG HISTORY +## [1.11.0](https://github.com/lucasvtiradentes/gcal-sync/compare/v1.10.0...v1.11.0) (2024-01-28) + + +### โœจ feature: + +* get user timezone from google calendar ([#111](https://github.com/lucasvtiradentes/gcal-sync/issues/111)) ([aa0346d](https://github.com/lucasvtiradentes/gcal-sync/commit/aa0346df85e50a8db0aa4baa141f3d40565af4ce)) + ## [1.10.0](https://github.com/lucasvtiradentes/gcal-sync/compare/v1.9.0...v1.10.0) (2024-01-26) diff --git a/package.json b/package.json index 38a495b..1c7a597 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gcal-sync", "description": "๐Ÿ”„ add an one way synchronization from ticktick tasks and github commits to google calendar and track your progress effortlessly.", - "version": "1.10.0", + "version": "1.11.0", "keywords": [ "google-calendar", "github",