カレンダーとタスク
カレンダーとタスク
 プレビュー
ダウンロード
 利用方法
    目次
  • ✨ 紹介
  • ✨ 導入方法
  • ✨ 使い方
  • ✨ 注意事項

基本的な機能

・予定を立てられる
・予定日までの期間を自動で計算して表示してくれる
・予定時刻の範囲も自動で計算して表示してくれる
・タスク(To do)を立てられる
・Googleカレンダーと相互同期できる

メリット

特殊記号による爆速作成

t, n, i, #, ##, ###, m, 月~日, +, -,
  • 記号のショートカットやテンプレートボタンを使うことで爆速で予定を立てられる!
  • 特殊記号の詳細はこちらで解説しています

    GoogleカレンダーにNotionのページをひもづけ

    NotionのページのリンクがGoogleカレンダーの説明欄に自動的に貼られる
    予定にNotionのページがひもづくから写真を載せたり日記を書いたり自由に使える。
    Googleカレンダーだけだとここまで自由に書き込めない。 ※使用例

    カレンダーでは扱いづらい無期限の予定も一緒に管理できる

    自動的に猶予ビューに振り分けられるから通常の予定も邪魔しない

    より扱いやすい通知機能

    マルチセレクトからお気に入りの通知次期を選ぶだけ!
    テンプレートやボタンにマルチセレクトの情報も登録しておけば、自分好みのリマインド時期を登録しておくこともできる

    直近ビューと直近ソート

    直近ビューには予定日まで7日を切った予定が表示される。
    また、予定日がより近いものから上に上がってくるから、絶対見落とさない!
    ホームのページに直近のリンクドビューを貼って見落とさないようにしておくことをおすすめします
    ※導入方法の項目でやり方を紹介しています

    そもそもなんでGoogleカレンダーと同期させるの?

    NotionのカレンダービューやNotionカレンダーでもカレンダーの機能は使えるのだから、あえて連携させる必要を感じない人もいると思います。

    ではなぜ連携させるのか。

    それは主にリマインダーのためです。
    Notionのfomulaプロパティで作成した予定日のカレンダーからは直接リマインドをかけられません。
    この場合、Notionカレンダーと連携しても同じくリマインドはできません。

    なので! Googleカレンダーに予定を普通に作っちゃってリマインドしようということなのです! GoogleカレンダーならNotionカレンダーと違い、リマインドのほかにもほかのカレンダーアプリと連携しやすい利点もあるですね!

    次の2つの条件にすべて当てはまる人はGoogleカレンダーを使う必要はないです

    ✅ 常にNotionを使い、最低でも1週間に1回はNotionを開く人。
    ※リンクドビューで自分のHomeのページに直近ビュー(期限が7日以下に迫った予定)を貼っていればリマインドとほぼ一緒。

    ✅ Googleカレンダーないしは他のサードパーティー製のカレンダーも使う予定がない人
    ※Notionカレンダー or Notionのデータベースのカレンダービューで十分だよね。

    前準備

    今回のツールでは

    • Notion API
    • Google Apps Script (GAS)
    を使います。

    初めてお使いになる人は導入を済ませておいてください。

    Googleカレンダーと同期せずに使う場合、前準備は不要です

    ※APIやGASの操作にはPCが必要です

    Notion

    テンプレートのダウンロード

    まずは自分のNotionに今回のテンプレートをダウンロードしてください
    このボタンを押してダウンロードします

    ダウンロード
    初めてダウンロードする人はこちらのページをご覧ください

    テンプレートの初期設定

    直近の予定をホームのページに貼り付ける!

    1. ダウンロードしたテンプレート(Calendar & Tasks)を開く。

    2. 画像のようなページが出てきたら、作成ボタンとなっているところを押す

    3. 直近で右クリックし、ビューのリンクをコピーする。

    ※スマホの場合は「元帳」→「直近の右の・・・」→「ビューのリンクをコピー」

    4. ホームのページを開く

    ホームのページを作ってない人はこちらの動画でどう
    いうものか説明していますので参考にしてください

    5. 貼り付けたい場所を選んでペースト

    6.「リンクドデータベース」を選択する

    7. C&T DBとなっている部分の右の「・・・」を押す

    8.「タイトルを非表示」を選択

    9.「+」→「+ 空のビュー」→「C&T DB」→「猶予」で新規ビュー追加

    10.「+」→「+ 空のビュー」→「C&T DB」→「直近完了」で新規ビュー追加

    お好みでビュー名を消したり、コールアウトに入れたりもできます

    Notion API の設定

    データベースと自作APIを連携させる

    1.「C&T DB」を開く

    2. 右上の「・・・」を押す。

    3.「コネクト/接続先」を押す。

    4. 自分のインテグレーションを選択する。

    GASの設定

    GASのファイル設定を完了する

    1. Google Apps Script を開く

    2. 新規プロジェクトを作成する(名前: Notion🔁Gカレンダー)

    3. 下のコードを「コード.gs」に貼り付ける

    const NOTION_TOKEN=PropertiesService.getScriptProperties().getProperty("NOTION_TOKEN"),NOTION_DATABASE_ID=PropertiesService.getScriptProperties().getProperty("NOTION_DATABASE_ID"),DBUrl=`https://api.notion.com/v1/databases/${NOTION_DATABASE_ID}/query`,Gcid="primary",NOTIFICATION_EMAIL=PropertiesService.getScriptProperties().getProperty("NOTIFICATION_EMAIL");function Try_main(){main()}function main(){let e=querySync(!1);if(e.length<=0){console.log("!Tempリストが空なので今回の同期はありません");return}e.forEach(e=>{let t=e.mark;if("from GC"===t){let n=e.eventID,r=e.remind,i=e.title,l=e.start.includes("T")?e.start:`${e.start}T00:00+0900`,a=e.end.includes("T")?e.end:`${e.end}T00:00+0900`,s=brakeDate(l,a),{ID:o,syncState:c,bell:$,Link:g}=writeNotion(t,e.ID,l,r,i,s,null,null);if("through"===c)return;writeGC(t,o,null,$,g,i,l,a,!1,c,n)}if("from Notion"===t){let u=e.pageID,p=e.URL,d=e.ID,m=e.content,f=e.junkDate,D=e.remind,h=e.make,_=e.pastDate,{startDate:T,endDate:I}=createDate(f,h),y=writeGC(t,d,_,D,p,m,T,I,!1!==_,null,null);writeNotion(t,d,null,null,null,null,y,u)}})}function Try_CD(){Try_main()}function createDate(e,t){let n=e,r,i,l,a,s="1000-01-00",o=1,c=new Date(t);t=new Date(t),n=(n=(n=n.replace(/\n/g,"【N】")).replace(/[\s/::;:/]/g,"")).replace(/0/g,"0").replace(/1/g,"1").replace(/2/g,"2").replace(/3/g,"3").replace(/4/g,"4").replace(/5/g,"5").replace(/6/g,"6").replace(/7/g,"7").replace(/8/g,"8").replace(/9/g,"9");let $=n.substring(0,1),g="曜日"===n.substring(1,3)?Number(n.substring(3,4)):"曜日"===n.substring(1,2)?Number(n.substring(2,3)):Number(n.substring(1,2)),u=!!"月火水木金土日".includes($);if(r=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=n.replace(/ま|m|m|M/g,"M")).replace(/月/g,"##?-5").replace(/火/g,"##?-4").replace(/水/g,"##?-3").replace(/木/g,"##?-2").replace(/金/g,"##?-1").replace(/土/g,"##?-0").replace(/日/g,"##?+1-0")).replace(/#/g,"#")).replace(/時|じ|た|t|t|T|※|\*/g,"T")).replace(/#T/g,"T#")).replace(/来|ら|n|n|N/g,"N")).replace(/今|い|i/g,"I")).replace(/間|あ|き|~|〜|a|A|k|k|K|K|x|x|X|X/g,"~")).replace(/~T/g,"~#0T")).replace(/####/g,"ERROR")).replace(/###/g,"【M】#")).replace(/##/g,"【W】#")).replace(/I/g,"I#")).replace(/I##/g,"I#")).replace(/~0/g,"~#0")).replace(/#0\./g,"【小数保持】")).replace(/\(-/g,"【負数保持】")).replace(/#000000000/g,"ERROR").replace(/#00000000/g,"#7").replace(/#0000000/g,"#6").replace(/#000000/g,"#5").replace(/#00000/g,"#4").replace(/#0000/g,"#3").replace(/#000/g,"#2").replace(/#00/g,"#1").replace(/#/g,"#0")).replace(/プ|足|+/g,"+")).replace(/マ|引|-|ー|—/g,"-")).replace(/\+/g,"【計算】+")).replace(/-/g,"【計算】-")).replace(/【小数保持】/g,"#0.")).replace(/【負数保持】/g,"-")).replace(/\(|\)/g,"")).replace(/か|カ|c|c|C/g,"C")).replace(/?/g,g)).replace(/T/g,"\nT").split("\n"),i=n.replace(/~/g,"\n~").split("\n"),a=n.replace(/-/g,"-\n").split("\n"),!0===u&&(l=null==r[1]?`${i[1]}`:`${r[1]}`,n=`${a[0]}`+`${a[1]}`.substring(0,1)+l),"M"===(n=`${n}`.replace(/undefined/g,"").replace(/NaN/g,""))||"MM"===n||"MMM"===n)return{startDate:s,endDate:o};if(0===(n=n.replace(/MMMM/g,"ERROR").replace(/MMM/g,"【M】#1【計算】+").replace(/MM/g,"【M】#0【計算】+").replace(/M/g,"【M】#(-1)【計算】+")).length)return o=8,{startDate:s,endDate:o};n=(r=n.split("【N】")).includes("コメント")||1===r.length?r[0]:`${r[0]}~${r[1]}`.replace(/~~/g,"~");let p=`${n.replace(/\d|#|【M】|【W】|T|N|I|~|\+|-|【計算】|\.|\(|\)|?/g,"")}`;if(0!==p.length&&!p.includes("undefined")||`${r[0]}`.length-`${r[0]}`.replace(/-/g,"").length>2||`${r[0]}`.length-`${r[0]}`.replace(/\+/g,"").length>2||`${r[1]}`.length-`${r[1]}`.replace(/-/g,"").length>1||`${r[1]}`.length-`${r[1]}`.replace(/\+/g,"").length>1)return console.log("無効なインプット"),{startDate:s,endDate:o};n.includes("I")&&(n=n.replace(/I#0T/,"I#0T#").replace(/I/,"I#").replace(/##/g,"#")),`${(r=n.split("~"))[0]}`.includes("【M】")&&`${r[0]}`.includes("【W】")&&(n=n.replace(/【W】/,"/【W】"));let d=n.split("/"),m;`${r[0]}`.includes("【M】")&&`${r[0]}`.includes("【W】")&&(n=d[1],m=!0);let f=`${d[0]}`;i=`${(r=n.split("~"))[0]}`.split("T");let D=`${i[1]}`.replace(/#/g,""),h=!!`${i[1]}`.includes("#");l=`${i[0]}`.replace(/N/g,"").split("【計算】");let _=`${l[0]}`,T=Number(l.filter(e=>e.includes("+")).join("").replace("+","")),I=Number(l.filter(e=>e.includes("-")).join("").replace("-",""));i=`${r[1]}`.split("T");let y=`${i[1]}`.replace(/#/g,""),M=!!`${i[1]}`.includes("#");l=`${i[0]}`.replace(/N/g,"").split("【計算】");let N=`${l[0]}`,b=Number(l.filter(e=>e.includes("+")).join("").replace("+","")),E=Number(l.filter(e=>e.includes("-")).join("").replace("-",""));n.includes("N")&&t.setFullYear(t.getFullYear()+1);let S=t.getHours(),w=t.getMinutes();r=_.replace(/#|【M】|【W】|I/g,"").split("-");let L=Number(r[0])-Number(r[1]||0);r=D.replace(/#|【M】|【W】|I/g,"").split("-");let R=Number(r[0])-Number(r[1]||0);r=N.replace(/#|【M】|【W】|I/g,"").split("-");let H=Number(r[0])-Number(r[1]||0);r=y.replace(/#|【M】|【W】|I/g,"").split("-");let O=Number(r[0])-Number(r[1]||0),A=["日","月","火","水","木","金","土"],v=A[t.getDay()],k=["土","日","月","火","水","木","金"],P=7-k.indexOf(v);"土"===v&&(P-=7),"日"===v&&(P-=6),i=(r=f.replace(/N/g,"").split("【計算】"))[0].replace(/【M】|#|I/g,"").split("-");let C=Number(i[0])-Number(i[1]||0),G=Number(r.filter(e=>e.includes("+")).join("").replace("+","")),x=Number(r.filter(e=>e.includes("-")).join("").replace("-","")),U=new Date(t.getFullYear(),t.getMonth(),1);U.setMonth(U.getMonth()+C+1),U.setDate(U.getDate()+(1-(G||0)+(-x||0)));let j=A[U.getDay()],F=7-k.indexOf(j);"土"===v&&(F-=7),"日"===v&&(F-=6);let B;switch(!0){case m:U.setDate(U.getDate()+F+7*L),B=U;break;case _.includes("【M】#"):(B=new Date(t.getFullYear(),t.getMonth(),1)).setMonth(B.getMonth()+L+1),B.setDate(B.getDate()-1);break;case _.includes("【W】#"):(B=t).setDate(B.getDate()+P+7*L);break;case _.includes("#"):(B=t).setDate(B.getDate()+L);break;case _.length<5:B=t.getFullYear()+_;break;default:B=_}_.includes("#")&&(B.setDate(B.getDate()+(T||0)+(-I||0)),B=Utilities.formatDate(B,Session.getScriptTimeZone(),"yyyyMdd"));let Z=B.substring(0,4),W=4===B.substring(4).length?B.substring(4,6):"0"+B.substring(4,5);r=B.substring(Z.length+W.replace(/0/,"").length);let q=1===r.length?"0"+r:r,Y=4===D.length?D.substring(0,2):"0"+D.substring(0,1);r=10>Number(Y)?D.substring(1):D.substring(2);let K=1===r.length?"0"+r:r,z=Y+":"+K,V=Number(D)&&!1===h?`${Z}-${W}-${q}T${z}+0900`:`${Z}-${W}-${q}T00:00+0900`;yotei=new Date(V),!0===u&&yotei<=c&&yotei.setDate(yotei.getDate()+7);let X=new Date(yotei);switch(!0){case N.includes("【M】#"):(X=new Date(yotei.getFullYear(),yotei.getMonth(),1)).setMonth(X.getMonth()+H+1),X.setDate(X.getDate()-1);break;case N.includes("【W】#"):X.setDate(X.getDate()+P+7*H);break;case N.includes("#"):X.setDate(X.getDate()+H);break;case N.length<5:X=yotei.getFullYear()+N;break;case""==!N||N:X=N;break;default:sendError("スイッチエラー")}(N.includes("#")||X===yotei)&&(X.setDate(X.getDate()+(b||0)+(-E||0)),X=Utilities.formatDate(X,Session.getScriptTimeZone(),"yyyyMdd"));let J=X.substring(0,4),Q=4===X.substring(4).length?X.substring(4,6):"0"+X.substring(4,5);r=X.substring(J.length+Q.replace(/0/,"").length);let ee=1===r.length?"0"+r:r,et=4===y.length?y.substring(0,2):"0"+y.substring(0,1);r=10>Number(et)?y.substring(1):y.substring(2);let en=1===r.length?"0"+r:r;r=!0===M?`${J}-${Q}-${ee}T00:00+0900`:Number(y)&&!1===M?`${J}-${Q}-${ee}T${et+":"+en}+0900`:X.includes("undefined")?`${V}`.replace(/T\d\d:\d\d/,"T23:59"):`${J}-${Q}-${ee}T23:59+0900`;let er=new Date(r);return _.includes("I")&&(yotei.setHours(yotei.getHours()+S),yotei.setMinutes(yotei.getMinutes()+w)),!0===h&&yotei.setMinutes(yotei.getMinutes()+60*R),!0===M&&(er.setHours(er.getHours()+yotei.getHours()),er.setMinutes(er.getMinutes()+yotei.getMinutes()),er.setMinutes(er.getMinutes()+60*O)),yotei-er==51846e4&&er.setDate(yotei.getDate()),r=new Date(er+"+0900"),yotei>r.setDate(r.getDate()+1)&&er.setFullYear(er.getFullYear()+1),(yotei.toDateString()===er.toDateString()&&0===yotei.getHours()&&0===yotei.getMinutes()&&23===er.getHours()&&59===er.getMinutes()||!(er instanceof Date)||isNaN(er))&&(er=yotei),yotei instanceof Date&&(s=yotei,o=er),{startDate:s,endDate:o}}function brakeDate(e,t){e=new Date(e),t=new Date(t);let n=t-e==864e5,r;return(e=Utilities.formatDate(e,Session.getScriptTimeZone(),"yyyyMddTHmm"))===(t=Utilities.formatDate(t,Session.getScriptTimeZone(),"yyyyMddTHmm"))||n?`${e}`:`${e}~${t}`}function Try_fN(){Try_main()}function getNotion(){let e=toNotion(DBUrl,{property:"Recent",checkbox:{equals:!0}},null,"");if(!e||"function"!=typeof e.getContentText){console.log("!1時間以内に更新された予定がなかったためスキップします");return}let t=JSON.parse(e.getContentText()),n=t.results.map(e=>{let t=e.properties.ID||"",n=e.properties.remind.multi_select;return{pageID:e.id,URL:`https://www.notion.so/${e.id.replace(/-/g,"")}`,ID:`${t.unique_id?.prefix}-${t.unique_id?.number}`,content:e.properties["内容"].title[0]?.text.content||void 0,junkDate:e.properties.input?.rich_text[0]?.text.content||"",remind:""==`${n}`?"# デフォルト":n.map(e=>e.name).join("【*M】"),make:e.properties["作成日"]?.created_time||void 0,update:e.properties["更新"]?.last_edited_time||void 0}});return n}function writeGC(e,t,n,r,i,l,a,s,o,c,$){let g=CalendarApp.getCalendarById(Gcid);if("from GC"===e){if(!1!==$){Calendar.Events.remove(Gcid,$),writeGC(e="third sync",t,null,r,i,l,a,s,!1,c,$);return}let u=new Date(a),p,d;if(r.includes("リマインドなし"))d=`${t} ${i} ==================== ${c}`;else{r=createRemind(u,r);let m=r.includes("メール通知"),f=!r.includes("only");d=`${t} ${i} ${r=formatDes_R(u,r,m,f)}==================== ${c}`}let D=g.getEventsForDay(u);D.forEach(e=>{let n=e.getDescription();n.includes(t)&&(e.setDescription(d),p=e.getLastUpdated(),console.log(`${t}の再同期が正常に完了しました`),saveList(t,p,!1,c))});return}let h=new Date(a),_=new Date(s),T=!0===o?new Date(n):null,I=6e4>=Math.abs((_-h)%864e5-864e5)&&0===h.getHours()&&(23===_.getHours()&&59==_.getMinutes()||0==_.getHours()&&0==_.getHours()),y=r.length-r.replace(/#/g,"").length,M="false"==r?0:(r.length-r.replace(/【\*M】/g,"").length)/4+1,N=M-y,b=r.includes("メール通知"),E=!r.includes("only"),S=!r.includes("リマインドなし"),w="false"==r||0===N&&!0==S||r.includes("デフォルト");if(!0===S&&N>0?(r=createRemind(h,r),S=!0):S=!1,!1===o){let L=`🌠${t} 新規予定を作成します🌠`;return c=createEvent(I,L,t,i,e,g,h,_,l,r,S,w,b,E,c)}let R=g.getEventsForDay(T);if(R.length<=0){let H=`🌠${t} 保存されている日付でイベントを見つけられなかったため再作成します🌠`;return c=createEvent(I,H,t,i,e,g,h,_,l,r,S,w,b,E,c)}let O=R.map(n=>{let a=n.getDescription();return!!a.includes(t)&&(c=writeEvent(I,n,t,i,e,g,h,_,l,r,S,w,b,E,c),!0)});if(!`${O}`.includes("true")){let A=`🌠${t} 保存されているIDのイベントをが存在しなかったため再作成します🌠`;c=createEvent(I,A,t,i,e,g,h,_,l,r,S,w,b,E,c)}return c}function createEvent(e,t,n,r,i,l,a,s,o,c,$,g,u,p,d){let m=formatDes_R(a,c,u,p),f=d,D;if(option={get description(){return`${n} ${r} ${m}==================== ${f}`}},!0===e){console.log(`${t}(終日)`),"from Notion"===i&&(f=createSync());let h=l.createAllDayEvent(o,a,s,option);cotrolRemind(h,c,$,g,u,p),D=h.getLastUpdated()}else{console.log(`${t}(時間指定)`),"from Notion"===i&&(f=createSync());let _=l.createEvent(o,a,s,option);cotrolRemind(_,c,$,g,u,p),D=_.getLastUpdated()}return saveList(n,D,a,f),f}function writeEvent(e,t,n,r,i,l,a,s,o,c,$,g,u,p,d){let m=formatDes_R(a,c,u,p),f=d,D;option={get description(){return`${n} ${r} ${m}==================== ${f}`}};let h=t.isAllDayEvent();if(e!==h){t.deleteEvent();let _=`🌠${n} 予定の形式が変わったため、既存の予定を削除して新たな予定を作成します🌠`;return createEvent(_,n,r,i,l,a,s,o,c,$,g,u,p)}return!0===e&&(t.setTitle(o),s.setDate(s.getDate()+1),t.setAllDayDates(a,s),"from Notion"===i&&(f=createSync()),t.setDescription(option.description),cotrolRemind(t,c,$,g,u,p),D=t.getLastUpdated(),console.log(`🌠${n} 既存の予定(終日) を上書きしました🌠`)),!1===e&&(t.setTitle(o),t.setTime(a,s),"from Notion"===i&&(f=createSync()),t.setDescription(option.description),cotrolRemind(t,c,$,g,u,p),D=t.getLastUpdated(),console.log(`🌠${n} 既存の予定(時間指定) を上書きしました🌠`)),saveList(n,D,a,f),f}function createRemind(e,t){return t=(t=(t=t.replace(/(【\*M】)|[^a-zA-Z0-9一-龠ぁ-んァ-ンー0-9#:]/g,(e,t)=>t||"")).split("【*M】").filter(e=>!e.includes("#"))).map(t=>{let n=0;if(!t.includes(":")){let r=[...t.matchAll(/(\d+)(分|時間|日)/g)];for(let i of r){let l=Number(i[1]),a=i[2];"分"===a&&(n+=l),"時間"===a&&(n+=60*l),"日"===a&&(n+=1440*l)}}if(t.includes(":")){t=t.replace(/当日/,"0日前").replace(/前日/,"1日前");let s=t.match(/(\d+)(日前)(\d{1,2}):(\d{2})/);if(s){let o=Number(s[1])-1,c=24-Number(s[3]),$=60-Number(s[4]),g=new Date(e),u=new Date(e);g.setMinutes(g.getMinutes()+1440*o+60*c+$),g.setMinutes(g.getMinutes()+60*u.getHours()+u.getMinutes()),n=(g-u)/6e4,n-=60}}return n})}function formatDes_R(e,t,n,r){if(`${t}`.includes("リマインドなし")||t&&t.length<1)return"";if(n=!0===n?"\uD83D\uDC8Cあり\n":"",!1===r&&(n=n.replace(/あり/,"のみ")),"# デフォルト"==`${t}`)return`==================== ${n}🔔デフォルト通知 `;t.sort((e,t)=>t-e);let i=["日","月","火","水","木","金","土"],l=t.map(t=>{let n=new Date(e);n.setMinutes(n.getMinutes()-t);let r=i[n.getDay()];return Utilities.formatDate(n,Session.getScriptTimeZone(),`🔔M月dd日(${r}) HH:mm`)});return`==================== ${n}${l=`${l}`.replace(/,/g,"\n")} `}function cotrolRemind(e,t,n,r,i,l){e.removeAllReminders(),!0===r&&(console.log("デフォルトのリマインドを追加します"),e.addPopupReminder(30),!0===i&&(console.log("デフォルトのリマインドを追加します(メール通知型)"),e.addEmailReminder(30))),!1!==n&&t.forEach(t=>{(!0!==i||(console.log(`メール通知のリマインド (${t}分前) を追加します`),e.addEmailReminder(t),!1!==l))&&(console.log(`ポップアップ通知のリマインド (${t}分前) を追加します`),e.addPopupReminder(t))})}function Try_tN(){Try_main()}function getGC(){let e={timeMin:new Date().toISOString(),singleEvents:!0,orderBy:"updated"},t=Calendar.Events.list(Gcid,e),n=t.items.map(e=>({eventID:e.id,reminders:e.reminders.overrides||!1,summary:e.summary||"無題",start:e.start.dateTime||e.start.date||"不明",end:e.end.dateTime||e.end.date||"不明",description:e.description||"なし",update:e.updated}));return n}function writeNotion(e,t,n,r,i,l,a,s){let o=!1!==t?Number(t.replace(/ID-/g,"")):0;if("from Notion"===e){let c={properties:{同期:{rich_text:[{text:{content:a}}]}}};syncToNotion(t,c,s),console.log(`${t}の再同期が正常に完了しました`);return}let $;r=formatRemind(n,r);let g="あああ",u={parent:{database_id:NOTION_DATABASE_ID},properties:{内容:{title:[{text:{content:i}}]},input:{rich_text:[{text:{content:l}}]},remind:{multi_select:r},get 同期(){return{rich_text:[{text:{content:g}}]}}}};if(!1===t){g=a=createSync();let{Bangou:p,URL:d,update:m,bell:f}=syncToNotion(t,u,"");t=p,g=m,r=f,$=d,console.log(`🌠Notionに新規ページを作成しました(${t})🌠`)}else{let D=toNotion(DBUrl,{property:"ID",number:{equals:o}},null,""),h=JSON.parse(D.getContentText());if(0==h.results.length)return console.log(`🌠保存されているIDのページが見つかりませんでしたので無視します(${t})🌠`),throughList(t),a="through",$=null,{syncState:a,bell:null,Link:$};{s=h.results[0].id,delete u.parent,g=a=createSync();let{Bangou:_,URL:T,update:I,bell:y}=syncToNotion(t,u,s);t=_,g=I,r=y,$=T,console.log(`🌠Notionの既存ページ(${t})を上書きしました🌠`)}}saveList(t,g,n,a);let M=r;return{ID:t,syncState:a,bell:M,Link:$}}function formatRemind(e,t){if(!1===t)return t=[{name:"# リマインドなし"}];{if(!Array.isArray(t))return[{name:t}];let n=[];t.forEach(t=>{t.method.includes("email")&&n.push({name:"# contain: メール通知"}),n.push({name:brakeRemind(e,t.minutes)})}),t=n}let r=`${t.map(e=>e.name)}`,i=r.length-r.replace(/#/g,"").length,l=t.length-i;return l>2&&r.includes("contain")&&(t.push({name:"# only: メール通知"}),t=t.filter(e=>!e.name.includes("contain"))),t}function brakeRemind(e,t){let n=new Date(e),r=new Date(e);r.setMinutes(r.getMinutes()-t);let i=n.getDate()-r.getDate(),l=Math.floor(t%1440/60),a=Math.floor(t%60),s=!1;return(r.getMinutes()%15==0||r.getMinutes()%10==0)&&(s=!0),!0===s&&(e.includes("T00:00")||t>180&&t%360!=0)?((t=` ${i} 日前 ${r.getHours()}:${r.getMinutes()}`.replace(/1 日前/,"前日")).includes(":00")||(t=t.replace(/:0/,":00")),t.includes(":60")&&(t=t.replace(/:6/,":0")),t=t.includes(" 0 日前 ")?t.replace(/ 0 日前 /,"当日 "):t.replace(/ /,"")):((t=` ${i} 日 ${l} 時間 ${a} 分 前`).includes(" 0 日 ")&&(t=t.replace(/ 0 日/,"")),t.includes(" 0 時間 ")&&(t=t.replace(/ 0 時間/,"")),t.includes(" 0 分 ")&&(t=t.replace(/ 0 分/,"")),t=t.replace(/ 前/,"前").replace(/ /,"当日 00:00")),e.includes("T00:00")&&""===t&&(t="0 分前"),t}function syncToNotion(e,t,n){let r=toNotion(DBUrl,null,t,n),i=JSON.parse(r.getContentText());e=i.properties.ID||"",e=`${e.unique_id?.prefix}-${e.unique_id?.number}`;let l=`https://www.notion.so/${i.id.replace(/-/g,"")}`,a=i.properties["更新"]?.last_edited_time||void 0,s=i.properties.remind.multi_select,o=""==`${s}`?"# デフォルト":s.map(e=>e.name).join("【*M】");return{Bangou:e,URL:l,update:a,bell:o}}function Try_c(){Try_main()}function saveList(e,t,n,r){e=`${e}`,t=new Date(t),!1!==n&&(n=new Date(n));let i=loadList(),l=i.find(t=>t.ID===e);l?(l.update=t,!1!==n&&(l.startDate=n),l.syncStatus=r):i.push({ID:e,update:t,startDate:n,syncStatus:r});let a=JSON.stringify(i);return PropertiesService.getScriptProperties().setProperty("ID_LIST",a),!!l&&l}function loadList(){let e=PropertiesService.getScriptProperties().getProperty("ID_LIST"),t=e?JSON.parse(e):[];return t}function putOutList(e){let t=loadList(),n=t.filter(t=>t.ID!==e),r=JSON.stringify(n);PropertiesService.getScriptProperties().setProperty("ID_LIST",r)}function cleanList(){let e=loadList(),t=e.filter(e=>/^ID-\d+$/.test(e.ID)),n=JSON.stringify(t);PropertiesService.getScriptProperties().setProperty("ID_LIST",n)}function pastDelList(){let e=loadList(),t=e.filter(e=>new Date(e.startDate)>new Date),n=JSON.stringify(t);PropertiesService.getScriptProperties().setProperty("ID_LIST",n);let r=throughList(0),i=r.filter(e=>new Date(e.end)>new Date),l=JSON.stringify(i);PropertiesService.getScriptProperties().setProperty("THROUGH_LIST",l)}function throughList(e,t){let n=PropertiesService.getScriptProperties().getProperty("THROUGH_LIST"),r=n?JSON.parse(n):[];if(0===e)return r;r.includes(e)||r.push({ID:e,end:t});let i=JSON.stringify(r);PropertiesService.getScriptProperties().setProperty("THROUGH_LIST",i)}function hateAdd(e,t){let n=e.find(e=>e.ID===t);return!!n&&n}function Try_mt(){Try_main()}function querySync(e){let t=loadList(),n=throughList(0),r=[],i=[],l=getGC();0===l.length?console.log("!Googleカレンダーに今より未来の予定がなかったのでスキップします"):l.forEach(e=>{let n=e.description?.match(/ID-\d+/)?.[0]||!1,i=!!n&&hateAdd(t,n);if(i&&e.update<=i.update){console.log("!Googleカレンダーの更新がなかったためスキップします");return}r.push({mark:"from GC",eventID:!1===n&&e.eventID,ID:n,pastDate:!1!==i&&i.startDate,remind:e.reminders,title:e.summary,start:e.start,end:e.end})});let a=getNotion();return a.forEach(a=>{if(!a.content||!a.junkDate)return console.log("日付(input)または内容が空です! どちらも埋める必要があります!"),null;let s=hateAdd(t,a.ID),o=new Date(a.update),c=new Date(s.update);if(c.setSeconds(0,0),s&&oe.ID===s.ID)||!1;if($){if(n.some(e=>e.ID===a.ID)){console.log(`${a.ID} はスルーリストにより省かれました`);return}if(!0===e){let{startDate:g,endDate:u}=createDate(a.junkDate,a.make),p=l.map(e=>e.description?.match(/ID-\d+/)?.[0]==a.ID?e:null);p=p.filter(e=>null!==e);let d={title:p[0].summary,start:p[0].start,end:p[0].end,remind:p[0].reminders,update:p[0].update,ID:a.ID,URL:a.URL,content:a.content,NSDate:g,NEDate:u,Nremind:a.remind,Nupdate:a.update};i.push(d)}}else{let m={mark:"from Notion",pageID:a.pageID,URL:a.URL,ID:a.ID,pastDate:!1!==s&&s.startDate,remind:a.remind,content:a.content,junkDate:a.junkDate,make:a.make};r.push(m)}}),!0===e&&i.length>0&&hateAlert(i),r=r.filter(e=>!n.some(t=>t.ID===e.ID))}function hateAlert(e){e.forEach(e=>{let{title:t,start:n,end:r,remind:i,update:l,ID:a,URL:s,content:o,NSDate:c,NEDate:$,Nremind:g,Nupdate:u}=e,p=new Date($);c===$&&p.setDate(p.getDate()+1);let d=n.includes("T")?n:`${n}T00:00+0900`,m=r.includes("T")?r:`${r}T00:00+0900`,f=Utilities.formatDate(new Date(d),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),D=Utilities.formatDate(new Date(m),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),h=Utilities.formatDate(new Date(l),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),_=Utilities.formatDate(new Date(c),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),T=Utilities.formatDate(new Date(p),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),I=Utilities.formatDate(new Date(u),Session.getScriptTimeZone(),"M月dd日(EEE) HH:mm"),y=i,M=createRemind(n,g);if(M.length<=0&&(M="なし"),!1===i&&(y="なし"),f===_&&D===T&&t===o&&y===M){console.log(`${a}の同期重複がありましたが、主要プロパティ「タイトル」「通知」「日付」に変更が見られなかったのでthroughリストに追加されませんでした`);return}console.log(`${a}の同期重複がありました。主要なデータが上書きされることが懸念されるので、これ以降「${a}」同期を停止します`),throughList(a,new Date(p));let N=`==========
    ${a}
    同期の重複が見つかったので同期を停止します。
    解除するにはTHROUGH_LISTから「${a}」を削除するか、同一の予定を複製してください

    🟢Googleカレンダーの内容
    タイトル: ${t}
    通知: ${y}
    日付: ${f} ~ ${D}
    最終更新: ${h}

    ⚪️Notionの内容
    タイトル: ${o}
    通知: ${M}
    日付: ${_} ~ ${T}
    最終更新: ${I}
    ==========`;MailApp.sendEmail(NOTIFICATION_EMAIL,"GAS: カレンダー同期の重複エラー","",{htmlBody:N})})}function createSync(){let e=new Date,t=Utilities.formatDate(e,Session.getScriptTimeZone(),"M-dd HH:mm (ss")+"s)",n=e.getTime(),r=(n*Math.floor(1e3*Math.random())%1e6).toString().padStart(6,"0"),i=`同期時刻: ${t} Code: ${r.substring(0,3)}-${r.substring(3)} ====================`;return i}function toNotion(e,t,n,r){let i="post",l;t&&(l={method:i,headers:{Authorization:`Bearer ${NOTION_TOKEN}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},payload:JSON.stringify({filter:t})}),""!==r&&(r="/"+r,i="patch"),n&&(e=`https://api.notion.com/v1/pages${r}`,l={method:i,headers:{Authorization:`Bearer ${NOTION_TOKEN}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},payload:JSON.stringify(n)});let a=UrlFetchApp.fetch(e,l);return a}function sendError(e){GmailApp.sendEmail(NOTIFICATION_EMAIL,`【Notion🔁Gカレンダー】(${e})`,"何らかのエラーが発生しました。お手数だけど自分で確認する")}

    4. 環境変数を以下のように設定する
    プロパティ
    値

    NOTIFICATION_EMAIL

    自分のメールアドレス

    NOTION_TOKEN

    ※必須
    Notion APIのインテグレーショントークン

    NOTION_DATABASE_ID

    ※必須
    データベースのID

    同期テスト

    テスト & GASのトリガー設定を終える

    1. Notionで「Calendar & Tasks」のテンプレートを開く

    2. テスト用の予定を作成する
    ~例~
    input: #1
    内容: テスト予定 リマインド: 前日 9:00

    3.「Googleカレンダーと同期する」ボタンを押す

    4. Google Apps Script から先ほどのプロジェクト開く

    5. エディターを開き「▶実行」を押す

    6. Googleカレンダーを開いて指定日に入力した予定内容が記されているか確認する

    7. Notionの同期したデータの同期コードと、Googleカレンダーに同期した予定に書かれている同期コードが一致しているか確かめる

    同期コードが一緒なら全ての設定は完了です!!

    作成方法

    予定の作成方法は3つ! 用途によって使い分けてね!

    ① ショートカットボタンから作成
    ② 分類ボタンから作成
    ③ データベースから作成

    ショートカットボタンから作成

    ショートカットボタンとは現在日時からの相対的な日付を使って予定を立てられるボタンです
    最初から汎用的なショートカットボタンがボタンが用意されているのですぐに使えます。
    例:「今週の金曜」や「週末・月末」など

    1. 好きなボタンを押す

    2. タイトル部分に内容を書き込む

    3. お好みで「リマインド」や「分類」をつける

    分類ボタンから作成

    分類ボタンは作成と同時に予定に分類を付けられるボタンです。
    Googleカレンダーに分類の内容は同期されないため、Notionでのフィルターに使われるだけです。
    不要に感じる人は使う必要はありません。
    削除しても問題ないです。

    1. 好きなボタンを押す

    2. タイトル部分に内容を書き込む

    3.「input」を書き込む

    4. お好みで「リマインド」をつける

    inputは以下のような形式で書き込みます
    年(4桁) + 月(1桁or2桁) + 日(2桁)
    ※今年の場合、年は省略できます

    ~例~
    1/4: 104 or 1/4
    12/4: 1204 or 12/4
    2025 12/4: 20251204 or 2025 12/4

    データベースから作成

    まとめて予定を作成するときにはデータベースから予定を作成するのがおすすめ。

    1. 作成ボタンとなっているところを押す

    2.「🔨作成」ビューを開く

    3.「+ 新規ページ」を押す

    4.「内容」と「input」を書き込む

    5. お好みで「リマインド」や「分類」をつける

    input プロパティ

    予定に時間を追加する

    1.「日付」を書く

    2.「日付」の後ろに「t」を書く

    3.「t」の後ろに「3桁 or 4桁 の数字」を書く

    ~例~
    12/4の9時: 1204t900

    予定に期間を追加する

    1.「1つ目の日付」を書く

    2.「1つ目の日付」の後ろに「~」を書く

    3.「~」の後ろに「2つ目の日付」を書く

    ~例~
    1/4の14時~17時: 104t1400~t1700
    1/4の14時~1/5の10時: 104t1400~105t1000

    これ以降に紹介する記号を使った予定にも時間や期間を同様の方法で追加できます。

    特殊記号 n の活用

     n を使うことで来年を省略できます

    1.「2025」などの代わりに「n」を書くだけ

    特殊記号 # の活用

     # を使うことで現在日からの予定を作成できます

    1.「#」を書く

    2.「#」の後ろに「数字」を書く

    「今日から(数字)日後」の予定が作成されます

    以下のように時間自体に#を追加することもできます
    ① #3#t12
    ② #3t1200
    ※「i」も同様の操作ができます

    特殊記号 i の活用

     i を使うことで現在時刻からの予定を作成できます

    1.「i」を書く

    2.「i」の後ろに「数字」を書く

    「今から(数字)日後」の予定が作成されます

    特殊記号 ## の活用

     ## を使うことで現在日から〇週末の予定を作成できます

    1.「##」を書く

    2.「##」の後ろに「数字」を書く

    「(数字)週末土曜日」の予定が作成されます
    ※0なら今週末。1なら来週末のように

    特殊記号 ### の活用

     ### を使うことで現在日から〇月末の予定を作成できます

    1.「###」を書く

    2.「###」の後ろに「数字」を書く

    「(数字)月末」の予定が作成されます

    付加記号 +- の活用

     +- を「##」や「###」と組み合わせて使うことで完全に自由な相対日時を設定できます

    例えば
    「##0-1」とすれば「今週の金曜」の予定
    「###1+5」とすれば「来月5日」の予定

    「###000+1##0+1t1200」
    こんな風に書けば「再々来月最初の日曜日」というこみいった注文も対応できます。
    ※実際に使うかは謎

    「###(-1)+12」
    さらにこのように()で囲めば負の数も指定できます
    ※「###0」が月末なので「###(-1)」は先月末を示しています
    ※つまりこれは「今月12日」ってこと。+の値を増やせば以外に普通に使い道ある

    便利記号 m の活用

     m は今月であることを示します

    「###(-1)+」と同じ意味
    今月の予定を速攻で立てたいときに便利

    便利記号 月~日 の活用

     月, 火, 水, 木, 金, 土, 日 は直近のその曜日を示します

    「##数-曜日インデックス」とほとんど同じ意味
    指定曜日の予定を速攻で立てたいときに便利

    その他のプロパティ

    ※()内はプロパティの構造名

    プロパティ名
    内容
    ✓ (チェックボックス)
    完了したら✓をつけることで予定をアーカイブできます
    内容 (タイトル)
    予定の内容を書き込めます
    input (テキスト)
    文字列を予定日-Cに渡して日付に変換してもらいます
    分類 (セレクト)
    予定を好きなカテゴリで分類できます
    remind (マルチセレクト)
    Googleカレンダーのリマインドを追加できます
    See (formula.text)
    予定日、予定期間、予定までの期間 が表示されます
    同期ボタン (ボタン)
    同期プロパティにCを追加し、Recentプロパティにtrueを付与します
    ID (ID)
    一意のIDは同期時の重複作成を防ぎ、上書き保存を可能とします
    SyncState (formula-text)
    同期情報を見やすいデザインで提供します
    同期 (テキスト)
    Googleカレンダーから同期情報が送られます
    予定日-C (formula.date)
    予定日をUTC形式にフォーマットします。
    これにより、Notion上でもカレンダービューで予定を確認できるようになる
    作成日 (作成日時)
    更新 (最終更新日時)
    Recent (formula.boolean)
    同期待機状態を監視
    🍐 (テキスト)
    1列看板を作成するためのダミープロパティ