[{"data":1,"prerenderedAt":5144},["ShallowReactive",2],{"posts":3,"/contact":5103},[4,228,562,774,878,1454,1593,1777,1975,2243,4470,4568,4612],{"id":5,"title":6,"body":7,"categories":214,"cross_post":215,"date":216,"description":217,"extension":218,"image":20,"meta":219,"navigation":31,"note_id":215,"note_url":215,"path":220,"private":221,"qiita_id":215,"qiita_url":215,"seo":222,"stem":223,"tags":224,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":227},"posts/posts/1064.md","javascript 配列",{"type":8,"value":9,"toc":211},"minimal",[10,14,207],[11,12,13],"h2",{"id":13},"操作",[15,16,21],"pre",{"className":17,"code":18,"language":19,"meta":20,"style":20},"language-javascript shiki shiki-themes github-light github-dark","\nconst  arr = []\n\n// 先頭を削除\narr.shift() ;\n\n// 末尾を削除\narr.pop() ;\n\n// 配列合計\ndata.reduce((a, x) =>{return a + ((x || 0) - 0);}, 0);\n\n// 配列から新しい配列\nconst count = Array.from(line)\n\n// 配列を空,null undefined を削除\narr.filter((v) => v\n// 配列の要素すべてが条件を満たしているか\n  o = s.every(isUpperCase)\n//配列の要素一つでも条件を満たしているか\narr.some()\n//配列のkeyを取得\narr_key = Object.keys(arr)\n// 配列に引数の値が含まれているかどうか\narr_key.includes(x)\n//配列の順番を逆に\narr.reverse()\n\n// 配列の重複削除\nconst array1 = [1, 5, 3, 1, 5, 3];\nconst array2 = Array.from(new Set(array1))\n\n","javascript","",[22,23,24,33,39,44,50,56,61,67,73,78,84,90,95,101,107,112,118,124,130,136,142,148,154,160,166,172,178,184,189,195,201],"code",{"__ignoreMap":20},[25,26,29],"span",{"class":27,"line":28},"line",1,[25,30,32],{"emptyLinePlaceholder":31},true,"\n",[25,34,36],{"class":27,"line":35},2,[25,37,38],{},"const  arr = []\n",[25,40,42],{"class":27,"line":41},3,[25,43,32],{"emptyLinePlaceholder":31},[25,45,47],{"class":27,"line":46},4,[25,48,49],{},"// 先頭を削除\n",[25,51,53],{"class":27,"line":52},5,[25,54,55],{},"arr.shift() ;\n",[25,57,59],{"class":27,"line":58},6,[25,60,32],{"emptyLinePlaceholder":31},[25,62,64],{"class":27,"line":63},7,[25,65,66],{},"// 末尾を削除\n",[25,68,70],{"class":27,"line":69},8,[25,71,72],{},"arr.pop() ;\n",[25,74,76],{"class":27,"line":75},9,[25,77,32],{"emptyLinePlaceholder":31},[25,79,81],{"class":27,"line":80},10,[25,82,83],{},"// 配列合計\n",[25,85,87],{"class":27,"line":86},11,[25,88,89],{},"data.reduce((a, x) =>{return a + ((x || 0) - 0);}, 0);\n",[25,91,93],{"class":27,"line":92},12,[25,94,32],{"emptyLinePlaceholder":31},[25,96,98],{"class":27,"line":97},13,[25,99,100],{},"// 配列から新しい配列\n",[25,102,104],{"class":27,"line":103},14,[25,105,106],{},"const count = Array.from(line)\n",[25,108,110],{"class":27,"line":109},15,[25,111,32],{"emptyLinePlaceholder":31},[25,113,115],{"class":27,"line":114},16,[25,116,117],{},"// 配列を空,null undefined を削除\n",[25,119,121],{"class":27,"line":120},17,[25,122,123],{},"arr.filter((v) => v\n",[25,125,127],{"class":27,"line":126},18,[25,128,129],{},"// 配列の要素すべてが条件を満たしているか\n",[25,131,133],{"class":27,"line":132},19,[25,134,135],{},"  o = s.every(isUpperCase)\n",[25,137,139],{"class":27,"line":138},20,[25,140,141],{},"//配列の要素一つでも条件を満たしているか\n",[25,143,145],{"class":27,"line":144},21,[25,146,147],{},"arr.some()\n",[25,149,151],{"class":27,"line":150},22,[25,152,153],{},"//配列のkeyを取得\n",[25,155,157],{"class":27,"line":156},23,[25,158,159],{},"arr_key = Object.keys(arr)\n",[25,161,163],{"class":27,"line":162},24,[25,164,165],{},"// 配列に引数の値が含まれているかどうか\n",[25,167,169],{"class":27,"line":168},25,[25,170,171],{},"arr_key.includes(x)\n",[25,173,175],{"class":27,"line":174},26,[25,176,177],{},"//配列の順番を逆に\n",[25,179,181],{"class":27,"line":180},27,[25,182,183],{},"arr.reverse()\n",[25,185,187],{"class":27,"line":186},28,[25,188,32],{"emptyLinePlaceholder":31},[25,190,192],{"class":27,"line":191},29,[25,193,194],{},"// 配列の重複削除\n",[25,196,198],{"class":27,"line":197},30,[25,199,200],{},"const array1 = [1, 5, 3, 1, 5, 3];\n",[25,202,204],{"class":27,"line":203},31,[25,205,206],{},"const array2 = Array.from(new Set(array1))\n",[208,209,210],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":20,"searchDepth":35,"depth":35,"links":212},[213],{"id":13,"depth":35,"text":13},[19],null,"2021-04-24T00:00:00.000Z","JavaScriptの配列操作(shift/pop/reduce/filter/every/some/includes等)を使い方付きでまとめたメモ","md",{},"/posts/1064",false,{"title":6,"description":217},"posts/1064",[225,226],"javascprit","memo","rW_GaIDsBwJwl8o_eZz0yNuHwXpEQbgKszj5j66OS2g",{"id":229,"title":230,"body":231,"categories":550,"cross_post":215,"date":552,"description":553,"extension":218,"image":20,"meta":554,"navigation":31,"note_id":215,"note_url":215,"path":555,"private":221,"qiita_id":215,"qiita_url":215,"seo":556,"stem":557,"tags":558,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":561},"posts/posts/2024-review.md","2024年を振り返って",{"type":8,"value":232,"toc":543},[233,237,241,244,247,250,253,256,269,278,282,285,288,291,294,297,300,303,316,319,322,325,328,337,340,343,346,349,352,355,358,361,370,374,377,380,383,386,389,392,395,398,401,404,407,416,425,428,431,434,438,441,444,447,450,453,456,459,468,471,474,477,480,483,486,489,492,495,498,507,516,519,522,525,528,531,534,537,540],[11,234,236],{"id":235},"_13月","1~3月",[238,239,240],"p",{},"例年ながら、冬は寒いし日光なくてあんまり調子は出ないで",[238,242,243],{},"今年もローテンションで生活していました..",[238,245,246],{},"とはいえ",[238,248,249],{},"りんご農園さんの受注分析システムを作成したり",[238,251,252],{},"はじめて冬の戸隠神社に仲良くなったオーストラリア人でワーキングホリデーで日本に来た友達と",[238,254,255],{},"雪の中行ってみたりしました。",[238,257,258,261,267],{},[259,260],"br",{},[262,263],"iframe",{"src":264,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1ZNigyxjgJNA8ZBWw213p2JPtEG5pa9aJ&pid=explorer&efh=false&a=v&chrome=false&embedded=true","width:300px; height:300px;","0",[259,268],{},[238,270,271,273,276],{},[259,272],{},[262,274],{"src":275,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1pMSpEv11YoZqC_ctYznDY3MvTZdz3H_E&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,277],{},[11,279,281],{"id":280},"_46月","4~6月",[238,283,284],{},"4月5月で東京でSIerの新人研修のサブ講師をしたのは新しい経験でした。",[238,286,287],{},"今まで子供にプログラミングを教えたりとか、",[238,289,290],{},"オンラインのプログラミング教室の講師の経験をしてきましたが",[238,292,293],{},"やはり対面でメイン講師ではないとはいえ100名以上を前に喋ったり",[238,295,296],{},"サポートするのは今までとはちがった経験で仕事を受けた時は意識していませんでしたが",[238,298,299],{},"チャレンジであり新しい価値観とスキルを身につけた気がします。",[238,301,302],{},"また、登山もことしも行き始め",[304,305,306,310,313],"ul",{},[307,308,309],"li",{},"見晴岳・三方ヶ峰・篭ノ登山(東篭ノ登山)",[307,311,312],{},"飯綱山",[307,314,315],{},"蓼科山",[238,317,318],{},"に行きましたー。",[238,320,321],{},"ものすごい高い山ではないですが、",[238,323,324],{},"山頂でお湯を沸かしてカップラーメンとコーヒーは達成感あっていいものですね〜",[238,326,327],{},"帰りに、温泉に寄ってくるのも定番です！",[238,329,330,332,335],{},[259,331],{},[262,333],{"src":334,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1H0UBDszh3o_635R8-u5oAeRigbkx55dg&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,336],{},[238,338,339],{},"そして、今年の大きな旅行１つ目",[238,341,342],{},"台湾にも１人旅をしてきました。",[238,344,345],{},"今まで海外は誰かと一緒でしたので今回",[238,347,348],{},"全ての手配も一人でやり一人で台湾に行って帰ってきたのは",[238,350,351],{},"旅行の楽しさも凄くありましたが、",[238,353,354],{},"より色々なとこにフットワーク軽く移動できるようになった気がして嬉しかったです。",[238,356,357],{},"それから台湾は英語あまり通じなかったですが、同じホステルの人とかと頑張って喋ってみて",[238,359,360],{},"前よりも英語も上達している気がしたのも嬉しかったです。",[238,362,363,365,368],{},[259,364],{},[262,366],{"src":367,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1WlUr1YDksAX4nLRQmwuR9G3BUUcnGxCV&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,369],{},[11,371,373],{"id":372},"_79月","7~9月",[238,375,376],{},"暑すぎてあまり引きこもり気味でした…",[238,378,379],{},"それでも",[238,381,382],{},"松本の花火や穂高神社の花火に行ったりしました。",[238,384,385],{},"そして今年の大きな旅行２つ目",[238,387,388],{},"青春18きっぷで北海道まで行ったことです。",[238,390,391],{},"青春18きっぷが今年で無くなるのではないかとの噂を聞いて",[238,393,394],{},"今まで１回もチャレンジしたことなかったので",[238,396,397],{},"長野からどこまで行けるか調べてチャレンジしてみました。",[238,399,400],{},"フェリーに乗る必要があるとはいえ函館まで行きさらに札幌までいってきました！",[238,402,403],{},"じつはこのルート青春18きっぷの改訂でこの冬からは同じ手段ではいけなくなっていまいました。",[238,405,406],{},"ちゃんと夏のシーズンにいってよかったな思いました。",[238,408,409,411,414],{},[259,410],{},[262,412],{"src":413,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1d5_ZDcg1bq_BYWCNivb4XSqmRNA0f8s3&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,415],{},[238,417,418,420,423],{},[259,419],{},[262,421],{"src":422,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1kOoSs5Py_ln7tIOIQNXvv2fd12R1HXbS&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,424],{},[238,426,427],{},"山にも登りました、",[238,429,430],{},"9月の少し涼しくなってきたタイミングで",[238,432,433],{},"烏帽子岳と湯ノ丸山にいってきましたー",[11,435,437],{"id":436},"_1012月","10~12月",[238,439,440],{},"前半登山とキャンプ",[238,442,443],{},"根子岳と四阿山にいってきました。",[238,445,446],{},"キャンプは子安温泉キャンプ所で！",[238,448,449],{},"今回一緒に行ったメンバーが焚き火台を持ってきてくれて",[238,451,452],{},"初めてキャンプで焚き火を焚き火で魚や肉を焼いたりして",[238,454,455],{},"炎を眺めるのもいいですし、焚き火で作った料理は美味しいしで",[238,457,458],{},"僕も焚き火台が欲しくなりました…",[238,460,461,463,466],{},[259,462],{},[262,464],{"src":465,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1vR5tfYQ5BKvAPrOAMui4c24vvGe0yo44&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,467],{},[238,469,470],{},"そして今年３つ目の大きな旅行で",[238,472,473],{},"出雲へいってきました",[238,475,476],{},"東京からサンライズ出雲の寝台列車に乗ることができて嬉しかったです！",[238,478,479],{},"日本で唯一毎日運行されている寝台特急ということで",[238,481,482],{},"乗れるうちに乗ってみたかったのですが、今回その目標も叶いました〜",[238,484,485],{},"出雲で出雲大社と周辺の神社、それから松江で宿泊し、松江城や歴史館",[238,487,488],{},"そして世界遺産の石見銀山の構成遺産の温泉津に泊まって",[238,490,491],{},"翌日石見銀山へ",[238,493,494],{},"なかなか関西より西に行くことが少ないので色々回れておもしろかったし、",[238,496,497],{},"歴史も垣間見れる場所もいけてよかったです。",[238,499,500,502,505],{},[259,501],{},[262,503],{"src":504,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1HLBYL8UOTYv5efg5_m-Z6Stmnzg0fR9_&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,506],{},[238,508,509,511,514],{},[259,510],{},[262,512],{"src":513,"style":265,"frameBorder":266},"https://drive.google.com/viewer?srcid=1Bec0WqpRYjwK6Fvdt1JgXF9UHPhjq_7Y&pid=explorer&efh=false&a=v&chrome=false&embedded=true",[259,515],{},[11,517,518],{"id":518},"まとめ",[238,520,521],{},"今年は結構旅行など意外に外出が多かった年でした〜",[238,523,524],{},"仕事ではフリーランスになって１年目で変化が多かったですが",[238,526,527],{},"りんご農園さんのDX支援は講師をしてみたりで新たな自分の可能性を広げられたかなと思ってます。",[238,529,530],{},"あと、地味に夏からダイエットをしてるのです、12月末で7kg痩せていて",[238,532,533],{},"自分でもびっくりですが結構嬉しいです！",[238,535,536],{},"あと2kg痩せてそこから筋肉つけれる方向に向かっていければなって考えてますね〜",[238,538,539],{},"来年の目標は年始にまた考えますが、もうちょい痩せて、もうちょい英語喋れるようになるのは",[238,541,542],{},"頑張っていきたいところですね〜",{"title":20,"searchDepth":35,"depth":35,"links":544},[545,546,547,548,549],{"id":235,"depth":35,"text":236},{"id":280,"depth":35,"text":281},{"id":372,"depth":35,"text":373},{"id":436,"depth":35,"text":437},{"id":518,"depth":35,"text":518},[551],"日常","2024-12-31T00:00:00.000Z","2024年の振り返り。りんご農園の受注分析システム開発や戸隠神社への冬の旅など、この一年の出来事をまとめた",{},"/posts/2024-review",{"title":230,"description":553},"posts/2024-review",[559,560],"振り返り",2024,"hcqQECg5_b5lM3KrvHW_vEsywbW-6PwPztLfabEwl0E",{"id":563,"title":564,"body":565,"categories":764,"cross_post":215,"date":766,"description":767,"extension":218,"image":20,"meta":768,"navigation":31,"note_id":215,"note_url":215,"path":769,"private":221,"qiita_id":215,"qiita_url":215,"seo":770,"stem":771,"tags":772,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":773},"posts/posts/4456.md","firebase firestore データ取得",{"type":8,"value":566,"toc":761},[567,570,758],[11,568,569],{"id":569},"データ取得コレクション全部",[15,571,575],{"className":572,"code":573,"language":574,"meta":20,"style":20},"language-typescript shiki shiki-themes github-light github-dark","firebase.initializeApp(firebaseConfig)\n\nconst firestore = firebase.firestore()\n\nconst querySnapshot = await firestore.collection('p0st')\n\n// firebase.firestore.QuerySnapshotのインスタンスを取得\nconsole.log(querySnapshot.size)\nconsole.log(querySnapshot.empty)\nconsole.log(querySnapshot.docs.map((postDoc) => postDoc.id))\nquerySnapshot.forEach((postDoc) => {\n  console.log([postDoc.id, JSON.stringify(postDoc.data())])\n})\n","typescript",[22,576,577,590,594,616,620,648,652,658,669,678,706,725,753],{"__ignoreMap":20},[25,578,579,583,587],{"class":27,"line":28},[25,580,582],{"class":581},"sVt8B","firebase.",[25,584,586],{"class":585},"sScJk","initializeApp",[25,588,589],{"class":581},"(firebaseConfig)\n",[25,591,592],{"class":27,"line":35},[25,593,32],{"emptyLinePlaceholder":31},[25,595,596,600,604,607,610,613],{"class":27,"line":41},[25,597,599],{"class":598},"szBVR","const",[25,601,603],{"class":602},"sj4cs"," firestore",[25,605,606],{"class":598}," =",[25,608,609],{"class":581}," firebase.",[25,611,612],{"class":585},"firestore",[25,614,615],{"class":581},"()\n",[25,617,618],{"class":27,"line":46},[25,619,32],{"emptyLinePlaceholder":31},[25,621,622,624,627,629,632,635,638,641,645],{"class":27,"line":52},[25,623,599],{"class":598},[25,625,626],{"class":602}," querySnapshot",[25,628,606],{"class":598},[25,630,631],{"class":598}," await",[25,633,634],{"class":581}," firestore.",[25,636,637],{"class":585},"collection",[25,639,640],{"class":581},"(",[25,642,644],{"class":643},"sZZnC","'p0st'",[25,646,647],{"class":581},")\n",[25,649,650],{"class":27,"line":58},[25,651,32],{"emptyLinePlaceholder":31},[25,653,654],{"class":27,"line":63},[25,655,657],{"class":656},"sJ8bj","// firebase.firestore.QuerySnapshotのインスタンスを取得\n",[25,659,660,663,666],{"class":27,"line":69},[25,661,662],{"class":581},"console.",[25,664,665],{"class":585},"log",[25,667,668],{"class":581},"(querySnapshot.size)\n",[25,670,671,673,675],{"class":27,"line":75},[25,672,662],{"class":581},[25,674,665],{"class":585},[25,676,677],{"class":581},"(querySnapshot.empty)\n",[25,679,680,682,684,687,690,693,697,700,703],{"class":27,"line":80},[25,681,662],{"class":581},[25,683,665],{"class":585},[25,685,686],{"class":581},"(querySnapshot.docs.",[25,688,689],{"class":585},"map",[25,691,692],{"class":581},"((",[25,694,696],{"class":695},"s4XuR","postDoc",[25,698,699],{"class":581},") ",[25,701,702],{"class":598},"=>",[25,704,705],{"class":581}," postDoc.id))\n",[25,707,708,711,714,716,718,720,722],{"class":27,"line":86},[25,709,710],{"class":581},"querySnapshot.",[25,712,713],{"class":585},"forEach",[25,715,692],{"class":581},[25,717,696],{"class":695},[25,719,699],{"class":581},[25,721,702],{"class":598},[25,723,724],{"class":581}," {\n",[25,726,727,730,732,735,738,741,744,747,750],{"class":27,"line":92},[25,728,729],{"class":581},"  console.",[25,731,665],{"class":585},[25,733,734],{"class":581},"([postDoc.id, ",[25,736,737],{"class":602},"JSON",[25,739,740],{"class":581},".",[25,742,743],{"class":585},"stringify",[25,745,746],{"class":581},"(postDoc.",[25,748,749],{"class":585},"data",[25,751,752],{"class":581},"())])\n",[25,754,755],{"class":27,"line":97},[25,756,757],{"class":581},"})\n",[208,759,760],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":20,"searchDepth":35,"depth":35,"links":762},[763],{"id":569,"depth":35,"text":569},[765],"firebase","2021-04-20T00:00:00.000Z","FirebaseのFirestoreからコレクション内の全ドキュメントを取得するTypeScriptのサンプルコード",{},"/posts/4456",{"title":564,"description":767},"posts/4456",[765,612,574],"_6bC5HkhYk9U-Ihf-VZwIsYGtwpC6ubOa1j877FGMD0",{"id":775,"title":776,"body":777,"categories":869,"cross_post":215,"date":870,"description":871,"extension":218,"image":20,"meta":872,"navigation":31,"note_id":215,"note_url":215,"path":873,"private":221,"qiita_id":215,"qiita_url":215,"seo":874,"stem":875,"tags":876,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":877},"posts/posts/4621.md","javascript 年月の差分、日にちの差分",{"type":8,"value":778,"toc":865},[779,782,785,829,832,835,863],[11,780,781],{"id":781},"月の差分",[238,783,784],{},"何月前か後ろかを整数で返す",[15,786,788],{"className":17,"code":787,"language":19,"meta":20,"style":20},"//  js月の比較\nconst date1 = new Date(2021, 1, 1)\nconst date2 = new Date(2021, 12, 11)\n\nlet months\nmonths = (date2.getFullYear() - date1.getFullYear()) * 12\nmonths -= date1.getMonth() + 1\nmonths += date2.getMonth() + 1\n",[22,789,790,795,800,805,809,814,819,824],{"__ignoreMap":20},[25,791,792],{"class":27,"line":28},[25,793,794],{},"//  js月の比較\n",[25,796,797],{"class":27,"line":35},[25,798,799],{},"const date1 = new Date(2021, 1, 1)\n",[25,801,802],{"class":27,"line":41},[25,803,804],{},"const date2 = new Date(2021, 12, 11)\n",[25,806,807],{"class":27,"line":46},[25,808,32],{"emptyLinePlaceholder":31},[25,810,811],{"class":27,"line":52},[25,812,813],{},"let months\n",[25,815,816],{"class":27,"line":58},[25,817,818],{},"months = (date2.getFullYear() - date1.getFullYear()) * 12\n",[25,820,821],{"class":27,"line":63},[25,822,823],{},"months -= date1.getMonth() + 1\n",[25,825,826],{"class":27,"line":69},[25,827,828],{},"months += date2.getMonth() + 1\n",[11,830,831],{"id":831},"日の差分",[238,833,834],{},"単純に日数の差分を返す",[15,836,838],{"className":17,"code":837,"language":19,"meta":20,"style":20},"//  js日差分\nconst date1 = new Date(2021, 1, 1)\nconst date2 = new Date(2021, 12, 11)\nconst termDay = (date2 - date1) / 86400000\nconsole.log(termDay)\n",[22,839,840,845,849,853,858],{"__ignoreMap":20},[25,841,842],{"class":27,"line":28},[25,843,844],{},"//  js日差分\n",[25,846,847],{"class":27,"line":35},[25,848,799],{},[25,850,851],{"class":27,"line":41},[25,852,804],{},[25,854,855],{"class":27,"line":46},[25,856,857],{},"const termDay = (date2 - date1) / 86400000\n",[25,859,860],{"class":27,"line":52},[25,861,862],{},"console.log(termDay)\n",[208,864,210],{},{"title":20,"searchDepth":35,"depth":35,"links":866},[867,868],{"id":781,"depth":35,"text":781},{"id":831,"depth":35,"text":831},[19],"2021-04-28T00:00:00.000Z","JavaScriptで2つの日付から月の差分・日にちの差分を計算するコードのメモ",{},"/posts/4621",{"title":776,"description":871},"posts/4621",[19,226],"u8OAQB-t7QX_VA81OWKiOGTOI8qymwoavvyIXRZq27Q",{"id":879,"title":880,"body":881,"categories":1442,"cross_post":215,"date":1444,"description":1445,"extension":218,"image":20,"meta":1446,"navigation":31,"note_id":215,"note_url":215,"path":1447,"private":221,"qiita_id":215,"qiita_url":215,"seo":1448,"stem":1449,"tags":1450,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":1453},"posts/posts/gas-checkRakutenPayEmailsAndLogDaily.md","楽天payと楽天キャッシュの入出金を毎日GAS(google app script)で取得してみる",{"type":8,"value":882,"toc":1433},[883,888,891,894,897,900,908,911,914,918,929,1187,1189,1193,1203,1207,1388,1390,1394,1401,1420,1422,1425,1428,1431],[884,885,887],"h1",{"id":886},"gmailを自動解析楽天payと楽天キャッシュの利用情報をスプレッドシートに記録するスクリプト","Gmailを自動解析！楽天Payと楽天キャッシュの利用情報をスプレッドシートに記録するスクリプト",[11,889,890],{"id":890},"はじめに",[238,892,893],{},"日々の支出管理やキャッシュフローの可視化は大切ですよね？",[238,895,896],{},"僕は基本的に支払いは電子決済で済ませてるので大体の履歴はマネーフォワードに連携して",[238,898,899],{},"そこで見ているですが、実は楽天pay,PayPay,ANApayなどは連携していなく",[238,901,902,903,907],{},"アプリでしかみることができないので今回\n「楽天Pay」や「楽天キャッシュ」の利用履歴を自動で取得して、スプレッドシートに保存してBIで見れたら便利だと思いやってみました\n今回は ",[904,905,906],"strong",{},"Google Apps Script (GAS)"," を使って、Gmailから自動で利用情報を取得し、Googleスプレッドシートに記録するスクリプトを作成します。",[238,909,910],{},"また、基本的に楽天payの支払いを楽天キャッシュからしているので支払いとチャージ料方法を取得するコードもつくりました。",[912,913],"hr",{},[11,915,917],{"id":916},"_1-楽天pay版スクリプト","1. 楽天Pay版スクリプト",[304,919,920,923,926],{},[307,921,922],{},"Gmailの「楽天Payアプリご利用内容確認メール」を解析。",[307,924,925],{},"利用日時、店舗名、伝票番号、決済総額などを抽出。",[307,927,928],{},"データをGoogleスプレッドシートに自動で記録。",[15,930,932],{"className":17,"code":931,"language":19,"meta":20,"style":20},"\n\nfunction checkRakutenPayEmailsAndLogDaily() {\n  const sheetId = \"YOUR_SPREADSHEET_ID\"; // スプレッドシートIDを指定\n  const sheetName = \"楽天Payログ\"; // シート名を指定\n\n  const today = new Date();\n  today.setHours(0, 0, 0, 0);\n  const yesterday = new Date(today);\n  yesterday.setDate(today.getDate() - 1);\n  const startDate = Utilities.formatDate(yesterday, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n  const endDate = Utilities.formatDate(today, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n\n  const searchQuery = `from:no-reply@pay.rakuten.co.jp subject:\"楽天ペイアプリご利用内容確認メール\" after:${startDate} before:${endDate}`;\n  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);\n  if (!sheet) throw new Error(`シート \"${sheetName}\" が見つかりません`);\n\n  const threads = GmailApp.search(searchQuery);\n  if (threads.length === 0) {\n    console.log(\"前日分の対象メールはありません。\");\n    return;\n  }\n\n  threads.forEach(thread => {\n    const messages = thread.getMessages();\n    messages.forEach(message => {\n      const body = message.getPlainBody();\n      let date, receiptNumber, store, totalAmount, points, paymentMethod;\n\n      const dateMatch = body.match(/ご利用日時\\s+([\\d/]+\\(.\\)\\s+[\\d:]+)/);\n      const receiptMatch = body.match(/伝票番号\\s+([A-Z0-9-]+)/);\n      const storeMatch = body.match(/ご利用店舗\\s+(.+)/);\n      const totalAmountMatch = body.match(/決済総額\\s+¥([\\d,]+)/);\n      const pointsMatch = body.match(/ポイント\\s+([\\d,]+)/);\n      const paymentMatch = body.match(/お取引内容\\s+(.+)/);\n\n      date = dateMatch ? dateMatch[1] : \"不明\";\n      receiptNumber = receiptMatch ? receiptMatch[1] : \"不明\";\n      store = storeMatch ? storeMatch[1].trim() : \"不明\";\n      totalAmount = totalAmountMatch ? totalAmountMatch[1].replace(/,/g, '') : \"0\";\n      points = pointsMatch ? pointsMatch[1] : \"0\";\n      paymentMethod = paymentMatch ? paymentMatch[1].trim() : \"不明\";\n\n      sheet.appendRow([new Date(), date, receiptNumber, store, `¥${totalAmount}`, points, paymentMethod]);\n    });\n  });\n\n  console.log(\"前日分の楽天ペイのメール内容をスプレッドシートに記録しました。\");\n}\n\n",[22,933,934,938,942,947,952,957,961,966,971,976,981,986,991,995,1000,1005,1010,1014,1019,1024,1029,1034,1039,1043,1048,1053,1058,1063,1068,1072,1077,1082,1088,1094,1100,1106,1111,1117,1123,1129,1135,1141,1147,1152,1158,1164,1170,1175,1181],{"__ignoreMap":20},[25,935,936],{"class":27,"line":28},[25,937,32],{"emptyLinePlaceholder":31},[25,939,940],{"class":27,"line":35},[25,941,32],{"emptyLinePlaceholder":31},[25,943,944],{"class":27,"line":41},[25,945,946],{},"function checkRakutenPayEmailsAndLogDaily() {\n",[25,948,949],{"class":27,"line":46},[25,950,951],{},"  const sheetId = \"YOUR_SPREADSHEET_ID\"; // スプレッドシートIDを指定\n",[25,953,954],{"class":27,"line":52},[25,955,956],{},"  const sheetName = \"楽天Payログ\"; // シート名を指定\n",[25,958,959],{"class":27,"line":58},[25,960,32],{"emptyLinePlaceholder":31},[25,962,963],{"class":27,"line":63},[25,964,965],{},"  const today = new Date();\n",[25,967,968],{"class":27,"line":69},[25,969,970],{},"  today.setHours(0, 0, 0, 0);\n",[25,972,973],{"class":27,"line":75},[25,974,975],{},"  const yesterday = new Date(today);\n",[25,977,978],{"class":27,"line":80},[25,979,980],{},"  yesterday.setDate(today.getDate() - 1);\n",[25,982,983],{"class":27,"line":86},[25,984,985],{},"  const startDate = Utilities.formatDate(yesterday, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n",[25,987,988],{"class":27,"line":92},[25,989,990],{},"  const endDate = Utilities.formatDate(today, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n",[25,992,993],{"class":27,"line":97},[25,994,32],{"emptyLinePlaceholder":31},[25,996,997],{"class":27,"line":103},[25,998,999],{},"  const searchQuery = `from:no-reply@pay.rakuten.co.jp subject:\"楽天ペイアプリご利用内容確認メール\" after:${startDate} before:${endDate}`;\n",[25,1001,1002],{"class":27,"line":109},[25,1003,1004],{},"  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);\n",[25,1006,1007],{"class":27,"line":114},[25,1008,1009],{},"  if (!sheet) throw new Error(`シート \"${sheetName}\" が見つかりません`);\n",[25,1011,1012],{"class":27,"line":120},[25,1013,32],{"emptyLinePlaceholder":31},[25,1015,1016],{"class":27,"line":126},[25,1017,1018],{},"  const threads = GmailApp.search(searchQuery);\n",[25,1020,1021],{"class":27,"line":132},[25,1022,1023],{},"  if (threads.length === 0) {\n",[25,1025,1026],{"class":27,"line":138},[25,1027,1028],{},"    console.log(\"前日分の対象メールはありません。\");\n",[25,1030,1031],{"class":27,"line":144},[25,1032,1033],{},"    return;\n",[25,1035,1036],{"class":27,"line":150},[25,1037,1038],{},"  }\n",[25,1040,1041],{"class":27,"line":156},[25,1042,32],{"emptyLinePlaceholder":31},[25,1044,1045],{"class":27,"line":162},[25,1046,1047],{},"  threads.forEach(thread => {\n",[25,1049,1050],{"class":27,"line":168},[25,1051,1052],{},"    const messages = thread.getMessages();\n",[25,1054,1055],{"class":27,"line":174},[25,1056,1057],{},"    messages.forEach(message => {\n",[25,1059,1060],{"class":27,"line":180},[25,1061,1062],{},"      const body = message.getPlainBody();\n",[25,1064,1065],{"class":27,"line":186},[25,1066,1067],{},"      let date, receiptNumber, store, totalAmount, points, paymentMethod;\n",[25,1069,1070],{"class":27,"line":191},[25,1071,32],{"emptyLinePlaceholder":31},[25,1073,1074],{"class":27,"line":197},[25,1075,1076],{},"      const dateMatch = body.match(/ご利用日時\\s+([\\d/]+\\(.\\)\\s+[\\d:]+)/);\n",[25,1078,1079],{"class":27,"line":203},[25,1080,1081],{},"      const receiptMatch = body.match(/伝票番号\\s+([A-Z0-9-]+)/);\n",[25,1083,1085],{"class":27,"line":1084},32,[25,1086,1087],{},"      const storeMatch = body.match(/ご利用店舗\\s+(.+)/);\n",[25,1089,1091],{"class":27,"line":1090},33,[25,1092,1093],{},"      const totalAmountMatch = body.match(/決済総額\\s+¥([\\d,]+)/);\n",[25,1095,1097],{"class":27,"line":1096},34,[25,1098,1099],{},"      const pointsMatch = body.match(/ポイント\\s+([\\d,]+)/);\n",[25,1101,1103],{"class":27,"line":1102},35,[25,1104,1105],{},"      const paymentMatch = body.match(/お取引内容\\s+(.+)/);\n",[25,1107,1109],{"class":27,"line":1108},36,[25,1110,32],{"emptyLinePlaceholder":31},[25,1112,1114],{"class":27,"line":1113},37,[25,1115,1116],{},"      date = dateMatch ? dateMatch[1] : \"不明\";\n",[25,1118,1120],{"class":27,"line":1119},38,[25,1121,1122],{},"      receiptNumber = receiptMatch ? receiptMatch[1] : \"不明\";\n",[25,1124,1126],{"class":27,"line":1125},39,[25,1127,1128],{},"      store = storeMatch ? storeMatch[1].trim() : \"不明\";\n",[25,1130,1132],{"class":27,"line":1131},40,[25,1133,1134],{},"      totalAmount = totalAmountMatch ? totalAmountMatch[1].replace(/,/g, '') : \"0\";\n",[25,1136,1138],{"class":27,"line":1137},41,[25,1139,1140],{},"      points = pointsMatch ? pointsMatch[1] : \"0\";\n",[25,1142,1144],{"class":27,"line":1143},42,[25,1145,1146],{},"      paymentMethod = paymentMatch ? paymentMatch[1].trim() : \"不明\";\n",[25,1148,1150],{"class":27,"line":1149},43,[25,1151,32],{"emptyLinePlaceholder":31},[25,1153,1155],{"class":27,"line":1154},44,[25,1156,1157],{},"      sheet.appendRow([new Date(), date, receiptNumber, store, `¥${totalAmount}`, points, paymentMethod]);\n",[25,1159,1161],{"class":27,"line":1160},45,[25,1162,1163],{},"    });\n",[25,1165,1167],{"class":27,"line":1166},46,[25,1168,1169],{},"  });\n",[25,1171,1173],{"class":27,"line":1172},47,[25,1174,32],{"emptyLinePlaceholder":31},[25,1176,1178],{"class":27,"line":1177},48,[25,1179,1180],{},"  console.log(\"前日分の楽天ペイのメール内容をスプレッドシートに記録しました。\");\n",[25,1182,1184],{"class":27,"line":1183},49,[25,1185,1186],{},"}\n",[912,1188],{},[11,1190,1192],{"id":1191},"_2-楽天キャッシュ版スクリプト","2. 楽天キャッシュ版スクリプト",[304,1194,1195,1198,1201],{},[307,1196,1197],{},"Gmailの「楽天キャッシュチャージ完了メール」を解析。",[307,1199,1200],{},"取引日時、チャージ内容、金額を抽出。",[307,1202,928],{},[1204,1205,1206],"h3",{"id":1206},"スクリプトコード",[15,1208,1210],{"className":17,"code":1209,"language":19,"meta":20,"style":20},"function checkRakutenCashEmailsAndLogDaily() {\n  const sheetId = \"YOUR_SPREADSHEET_ID\"; // スプレッドシートIDを指定\n  const sheetName = \"楽天キャッシュログ\"; // シート名を指定\n\n  const today = new Date();\n  today.setHours(0, 0, 0, 0);\n  const yesterday = new Date(today);\n  yesterday.setDate(today.getDate() - 1);\n  const startDate = Utilities.formatDate(yesterday, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n  const endDate = Utilities.formatDate(today, Session.getScriptTimeZone(), \"yyyy/MM/dd\");\n\n  const searchQuery = `from:pointcharge@edy.rakuten.co.jp subject:\"【楽天キャッシュ】チャージ完了のお知らせ\" after:${startDate} before:${endDate}`;\n  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);\n  if (!sheet) throw new Error(`シート \"${sheetName}\" が見つかりません`);\n\n  const threads = GmailApp.search(searchQuery);\n  if (threads.length === 0) {\n    console.log(\"前日分の対象メールはありません。\");\n    return;\n  }\n\n  threads.forEach(thread => {\n    const messages = thread.getMessages();\n    messages.forEach(message => {\n      const body = message.getPlainBody();\n      let transactionDate, content, amount;\n\n      const dateMatch = body.match(/\\[取引日時\\]\\s+([\\d/]+\\s+[\\d:]+)/);\n      const contentMatch = body.match(/\\[内容\\]\\s+(.+)/);\n      const amountMatch = body.match(/\\[金額\\]\\s+([\\d,]+)円/);\n\n      transactionDate = dateMatch ? dateMatch[1] : \"不明\";\n      content = contentMatch ? contentMatch[1].trim() : \"不明\";\n      amount = amountMatch ? amountMatch[1].replace(/,/g, '') : \"0\";\n\n      sheet.appendRow([new Date(), transactionDate, content, `¥${amount}`]);\n    });\n  });\n\n  console.log(\"前日分の楽天キャッシュのメール内容をスプレッドシートに記録しました。\");\n}\n\n",[22,1211,1212,1217,1221,1226,1230,1234,1238,1242,1246,1250,1254,1258,1263,1267,1271,1275,1279,1283,1287,1291,1295,1299,1303,1307,1311,1315,1320,1324,1329,1334,1339,1343,1348,1353,1358,1362,1367,1371,1375,1379,1384],{"__ignoreMap":20},[25,1213,1214],{"class":27,"line":28},[25,1215,1216],{},"function checkRakutenCashEmailsAndLogDaily() {\n",[25,1218,1219],{"class":27,"line":35},[25,1220,951],{},[25,1222,1223],{"class":27,"line":41},[25,1224,1225],{},"  const sheetName = \"楽天キャッシュログ\"; // シート名を指定\n",[25,1227,1228],{"class":27,"line":46},[25,1229,32],{"emptyLinePlaceholder":31},[25,1231,1232],{"class":27,"line":52},[25,1233,965],{},[25,1235,1236],{"class":27,"line":58},[25,1237,970],{},[25,1239,1240],{"class":27,"line":63},[25,1241,975],{},[25,1243,1244],{"class":27,"line":69},[25,1245,980],{},[25,1247,1248],{"class":27,"line":75},[25,1249,985],{},[25,1251,1252],{"class":27,"line":80},[25,1253,990],{},[25,1255,1256],{"class":27,"line":86},[25,1257,32],{"emptyLinePlaceholder":31},[25,1259,1260],{"class":27,"line":92},[25,1261,1262],{},"  const searchQuery = `from:pointcharge@edy.rakuten.co.jp subject:\"【楽天キャッシュ】チャージ完了のお知らせ\" after:${startDate} before:${endDate}`;\n",[25,1264,1265],{"class":27,"line":97},[25,1266,1004],{},[25,1268,1269],{"class":27,"line":103},[25,1270,1009],{},[25,1272,1273],{"class":27,"line":109},[25,1274,32],{"emptyLinePlaceholder":31},[25,1276,1277],{"class":27,"line":114},[25,1278,1018],{},[25,1280,1281],{"class":27,"line":120},[25,1282,1023],{},[25,1284,1285],{"class":27,"line":126},[25,1286,1028],{},[25,1288,1289],{"class":27,"line":132},[25,1290,1033],{},[25,1292,1293],{"class":27,"line":138},[25,1294,1038],{},[25,1296,1297],{"class":27,"line":144},[25,1298,32],{"emptyLinePlaceholder":31},[25,1300,1301],{"class":27,"line":150},[25,1302,1047],{},[25,1304,1305],{"class":27,"line":156},[25,1306,1052],{},[25,1308,1309],{"class":27,"line":162},[25,1310,1057],{},[25,1312,1313],{"class":27,"line":168},[25,1314,1062],{},[25,1316,1317],{"class":27,"line":174},[25,1318,1319],{},"      let transactionDate, content, amount;\n",[25,1321,1322],{"class":27,"line":180},[25,1323,32],{"emptyLinePlaceholder":31},[25,1325,1326],{"class":27,"line":186},[25,1327,1328],{},"      const dateMatch = body.match(/\\[取引日時\\]\\s+([\\d/]+\\s+[\\d:]+)/);\n",[25,1330,1331],{"class":27,"line":191},[25,1332,1333],{},"      const contentMatch = body.match(/\\[内容\\]\\s+(.+)/);\n",[25,1335,1336],{"class":27,"line":197},[25,1337,1338],{},"      const amountMatch = body.match(/\\[金額\\]\\s+([\\d,]+)円/);\n",[25,1340,1341],{"class":27,"line":203},[25,1342,32],{"emptyLinePlaceholder":31},[25,1344,1345],{"class":27,"line":1084},[25,1346,1347],{},"      transactionDate = dateMatch ? dateMatch[1] : \"不明\";\n",[25,1349,1350],{"class":27,"line":1090},[25,1351,1352],{},"      content = contentMatch ? contentMatch[1].trim() : \"不明\";\n",[25,1354,1355],{"class":27,"line":1096},[25,1356,1357],{},"      amount = amountMatch ? amountMatch[1].replace(/,/g, '') : \"0\";\n",[25,1359,1360],{"class":27,"line":1102},[25,1361,32],{"emptyLinePlaceholder":31},[25,1363,1364],{"class":27,"line":1108},[25,1365,1366],{},"      sheet.appendRow([new Date(), transactionDate, content, `¥${amount}`]);\n",[25,1368,1369],{"class":27,"line":1113},[25,1370,1163],{},[25,1372,1373],{"class":27,"line":1119},[25,1374,1169],{},[25,1376,1377],{"class":27,"line":1125},[25,1378,32],{"emptyLinePlaceholder":31},[25,1380,1381],{"class":27,"line":1131},[25,1382,1383],{},"  console.log(\"前日分の楽天キャッシュのメール内容をスプレッドシートに記録しました。\");\n",[25,1385,1386],{"class":27,"line":1137},[25,1387,1186],{},[912,1389],{},[11,1391,1393],{"id":1392},"_3-トリガー設定","3. トリガー設定",[238,1395,1396,1397,1400],{},"どちらのスクリプトも毎日自動実行するには、Google Apps Script の ",[904,1398,1399],{},"トリガー機能"," を活用します。",[1402,1403,1404,1407,1410,1417],"ol",{},[307,1405,1406],{},"スクリプトエディタを開き、時計マーク（トリガー設定）をクリック。",[307,1408,1409],{},"「トリガーを追加」を選択。",[307,1411,1412,1413,1416],{},"必要なスクリプト関数（例: ",[22,1414,1415],{},"checkRakutenPayEmailsAndLogDaily","）を選択。",[307,1418,1419],{},"実行タイミングを「日付ベース」「午前1時」などに設定。",[912,1421],{},[11,1423,1424],{"id":1424},"終わりに",[238,1426,1427],{},"これで、毎日の支出やチャージ状況を取得できそうです。",[238,1429,1430],{},"これを元にBIなどでみるかもしくはマネーフォワードに自動的にスクレイピングのライブラリを使って登録するのもいいかもしれません。",[208,1432,210],{},{"title":20,"searchDepth":35,"depth":35,"links":1434},[1435,1436,1437,1440,1441],{"id":890,"depth":35,"text":890},{"id":916,"depth":35,"text":917},{"id":1191,"depth":35,"text":1192,"children":1438},[1439],{"id":1206,"depth":41,"text":1206},{"id":1392,"depth":35,"text":1393},{"id":1424,"depth":35,"text":1424},[1443],"IT","2024-12-11T00:00:00.000Z","Google Apps ScriptでGmailを自動解析し、楽天Pay・楽天キャッシュの利用履歴をスプレッドシートに毎日記録するスクリプトを作った話",{},"/posts/gas-checkrakutenpayemailsandlogdaily",{"title":880,"description":1445},"posts/gas-checkRakutenPayEmailsAndLogDaily",[1451,1452],"Javascript","Google App script","hgRysF3JRet6R-ag_dl2-4dbBzuEVnWAhRZhYcuLjeU",{"id":1455,"title":1456,"body":1457,"categories":1582,"cross_post":215,"date":1583,"description":1584,"extension":218,"image":20,"meta":1585,"navigation":31,"note_id":215,"note_url":215,"path":1586,"private":221,"qiita_id":215,"qiita_url":215,"seo":1587,"stem":1588,"tags":1589,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":1592},"posts/posts/gas-cloud-function-auth.md","Google App Script から Could Functions を実行するために",{"type":8,"value":1458,"toc":1578},[1459,1462,1465,1468,1471,1474,1485,1488,1541,1555,1562,1569,1576],[1204,1460,1461],{"id":1461},"経緯",[238,1463,1464],{},"Google App Scriptだけでは外部ライブラリを使ったりnpmを使ったりするのが難しいの",[238,1466,1467],{},"Cloud Functions で処理してデータを返してもらって作ろうと思った時の認証について",[238,1469,1470],{},"ひとまず簡易なやり方で",[11,1472,1473],{"id":1473},"結論",[304,1475,1476,1479,1482],{},[307,1477,1478],{},"GCPでOAuth 同意画面を作成",[307,1480,1481],{},"Google App Script側をOAuth 同意画面作ったGCPプロジェクト作成",[307,1483,1484],{},"appscript.jsonの編集してOAuthスコープの設定",[238,1486,1487],{},"あと下記のようなコードで認証できる",[15,1489,1491],{"className":17,"code":1490,"language":19,"meta":20,"style":20},"\nfunction myFunction() {\n  const url = \"https://xxxxxx.cloudfunctions.net/xxxxx\"\n  const res = UrlFetchApp.fetch(url, {\n    headers: {\n      Authorization: `Bearer ${ScriptApp.getIdentityToken()}`\n    }\n  })\n  console.log(res.getContentText()) // Fnctiionの結果\n}\n\n\n",[22,1492,1493,1497,1502,1507,1512,1517,1522,1527,1532,1537],{"__ignoreMap":20},[25,1494,1495],{"class":27,"line":28},[25,1496,32],{"emptyLinePlaceholder":31},[25,1498,1499],{"class":27,"line":35},[25,1500,1501],{},"function myFunction() {\n",[25,1503,1504],{"class":27,"line":41},[25,1505,1506],{},"  const url = \"https://xxxxxx.cloudfunctions.net/xxxxx\"\n",[25,1508,1509],{"class":27,"line":46},[25,1510,1511],{},"  const res = UrlFetchApp.fetch(url, {\n",[25,1513,1514],{"class":27,"line":52},[25,1515,1516],{},"    headers: {\n",[25,1518,1519],{"class":27,"line":58},[25,1520,1521],{},"      Authorization: `Bearer ${ScriptApp.getIdentityToken()}`\n",[25,1523,1524],{"class":27,"line":63},[25,1525,1526],{},"    }\n",[25,1528,1529],{"class":27,"line":69},[25,1530,1531],{},"  })\n",[25,1533,1534],{"class":27,"line":75},[25,1535,1536],{},"  console.log(res.getContentText()) // Fnctiionの結果\n",[25,1538,1539],{"class":27,"line":80},[25,1540,1186],{},[238,1542,1543,1550],{},[1544,1545,1549],"a",{"href":1546,"rel":1547},"https://qiita.com/venect_qiita/items/26634a32037e40a585089%C2%A5",[1548],"nofollow","GAS（GoogleAppsScript）から認証付きのCloud Functionsをサービスアカウントを使用して呼び出す #GoogleCloud - Qiita",[1544,1551,1554],{"href":1552,"rel":1553},"https://qiita.com/kyhei_0727/items/d6b1cb33ce90b99e8129#oauth%E5%90%8C%E6%84%8F%E7%94%BB%E9%9D%A2%E3%81%AE%E4%BD%9C%E6%88%909%C2%A5",[1548],"GASから認証付きのCloud Functionsを実行する。 #GoogleCloud - Qiita",[238,1556,1557],{},[1544,1558,1561],{"href":1559,"rel":1560},"https://kkuchima.wordpress.com/2020/12/30/google-apps-script-gas-%E3%81%8B%E3%82%89-cloud-functions-%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%99%E3%82%8B/9%C2%A5",[1548],"Google Apps Script (GAS) から Cloud Functions に認証付きでアクセスする – kkuchima memo",[238,1563,1564],{},[1544,1565,1568],{"href":1566,"rel":1567},"https://link-and-motivation.hatenablog.com/entry/2024/04/10/1200009%C2%A5",[1548],"Google Apps Script で書かれたアドオンのバックエンドを Cloud Run に移行した話 - Link and Motivation Developers' Blog",[238,1570,1571],{},[1544,1572,1575],{"href":1573,"rel":1574},"https://qiita.com/akira-yp/items/e3cd9d4eefb8f5238edb9%C2%A5",[1548],"GAS + CloudFunctionsでVisionAPIの処理結果をスプレッドシートに出力する #GoogleAppsScript - Qiita",[208,1577,210],{},{"title":20,"searchDepth":35,"depth":35,"links":1579},[1580,1581],{"id":1461,"depth":41,"text":1461},{"id":1473,"depth":35,"text":1473},[1443],"2024-11-01T00:00:00.000Z","Google Apps ScriptからCloud Functionsを呼び出す際のOAuth認証設定を簡易なやり方でまとめたメモ",{},"/posts/gas-cloud-function-auth",{"title":1456,"description":1584},"posts/gas-cloud-function-auth",[1443,1452,1590,1591],"GCP","Cloud Function","0JQdyDLpXE-kxWplSpRJa15yZd75sF2w8imvF-Li9EY",{"id":1594,"title":1595,"body":1596,"categories":1768,"cross_post":215,"date":1769,"description":1770,"extension":218,"image":20,"meta":1771,"navigation":31,"note_id":215,"note_url":215,"path":1772,"private":221,"qiita_id":215,"qiita_url":215,"seo":1773,"stem":1774,"tags":1775,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":1776},"posts/posts/google-drive-csv-get.md","google drive csv 取込結合 スプレッドシート書き込み",{"type":8,"value":1597,"toc":1766},[1598,1764],[15,1599,1601],{"className":17,"code":1600,"language":19,"meta":20,"style":20},"function importData() {\n\n  // 対象フォルダを指定\n  const ss = SpreadsheetApp.getActiveSpreadsheet();\n  const sheet = ss.getSheetByName(\"data\"); //シート名\n  const folder = DriveApp.getFolderById('xxxxx'); //フォルダパスを指定\n\n\n\n  sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).clear()\n\n  const files = folder.getFiles();\n  let sortFiles = []\n  while (files.hasNext()) {\n    sortFiles.push(files.next());\n  }\n  sortFiles.sort(function (a, b) { if (a.getName() > b.getName()) { return 1 } else { return -1 } })\n\n\n  let allSetData = []\n  for (const iterator of sortFiles) {\n    const file = iterator;\n    // 取込処理\n    const data = file.getBlob().getDataAsString(\"UTF-8\");\n    const csv = Utilities.parseCsv(data);\n    const setData = csv\n    setData.shift()\n    setData.reverse()\n    const lastRow = sheet.getLastRow();\n    Logger.log(setData)\n\n    allSetData = [...allSetData, ...setData]\n    // CSV書込\n    if (setData.length > 0) sheet.getRange(lastRow + 1, 1, setData.length, setData[0].length).setValues(setData);\n\n\n",[22,1602,1603,1608,1612,1617,1622,1627,1632,1636,1640,1644,1649,1653,1658,1663,1668,1673,1677,1682,1686,1690,1695,1700,1705,1710,1715,1720,1725,1730,1735,1740,1745,1749,1754,1759],{"__ignoreMap":20},[25,1604,1605],{"class":27,"line":28},[25,1606,1607],{},"function importData() {\n",[25,1609,1610],{"class":27,"line":35},[25,1611,32],{"emptyLinePlaceholder":31},[25,1613,1614],{"class":27,"line":41},[25,1615,1616],{},"  // 対象フォルダを指定\n",[25,1618,1619],{"class":27,"line":46},[25,1620,1621],{},"  const ss = SpreadsheetApp.getActiveSpreadsheet();\n",[25,1623,1624],{"class":27,"line":52},[25,1625,1626],{},"  const sheet = ss.getSheetByName(\"data\"); //シート名\n",[25,1628,1629],{"class":27,"line":58},[25,1630,1631],{},"  const folder = DriveApp.getFolderById('xxxxx'); //フォルダパスを指定\n",[25,1633,1634],{"class":27,"line":63},[25,1635,32],{"emptyLinePlaceholder":31},[25,1637,1638],{"class":27,"line":69},[25,1639,32],{"emptyLinePlaceholder":31},[25,1641,1642],{"class":27,"line":75},[25,1643,32],{"emptyLinePlaceholder":31},[25,1645,1646],{"class":27,"line":80},[25,1647,1648],{},"  sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).clear()\n",[25,1650,1651],{"class":27,"line":86},[25,1652,32],{"emptyLinePlaceholder":31},[25,1654,1655],{"class":27,"line":92},[25,1656,1657],{},"  const files = folder.getFiles();\n",[25,1659,1660],{"class":27,"line":97},[25,1661,1662],{},"  let sortFiles = []\n",[25,1664,1665],{"class":27,"line":103},[25,1666,1667],{},"  while (files.hasNext()) {\n",[25,1669,1670],{"class":27,"line":109},[25,1671,1672],{},"    sortFiles.push(files.next());\n",[25,1674,1675],{"class":27,"line":114},[25,1676,1038],{},[25,1678,1679],{"class":27,"line":120},[25,1680,1681],{},"  sortFiles.sort(function (a, b) { if (a.getName() > b.getName()) { return 1 } else { return -1 } })\n",[25,1683,1684],{"class":27,"line":126},[25,1685,32],{"emptyLinePlaceholder":31},[25,1687,1688],{"class":27,"line":132},[25,1689,32],{"emptyLinePlaceholder":31},[25,1691,1692],{"class":27,"line":138},[25,1693,1694],{},"  let allSetData = []\n",[25,1696,1697],{"class":27,"line":144},[25,1698,1699],{},"  for (const iterator of sortFiles) {\n",[25,1701,1702],{"class":27,"line":150},[25,1703,1704],{},"    const file = iterator;\n",[25,1706,1707],{"class":27,"line":156},[25,1708,1709],{},"    // 取込処理\n",[25,1711,1712],{"class":27,"line":162},[25,1713,1714],{},"    const data = file.getBlob().getDataAsString(\"UTF-8\");\n",[25,1716,1717],{"class":27,"line":168},[25,1718,1719],{},"    const csv = Utilities.parseCsv(data);\n",[25,1721,1722],{"class":27,"line":174},[25,1723,1724],{},"    const setData = csv\n",[25,1726,1727],{"class":27,"line":180},[25,1728,1729],{},"    setData.shift()\n",[25,1731,1732],{"class":27,"line":186},[25,1733,1734],{},"    setData.reverse()\n",[25,1736,1737],{"class":27,"line":191},[25,1738,1739],{},"    const lastRow = sheet.getLastRow();\n",[25,1741,1742],{"class":27,"line":197},[25,1743,1744],{},"    Logger.log(setData)\n",[25,1746,1747],{"class":27,"line":203},[25,1748,32],{"emptyLinePlaceholder":31},[25,1750,1751],{"class":27,"line":1084},[25,1752,1753],{},"    allSetData = [...allSetData, ...setData]\n",[25,1755,1756],{"class":27,"line":1090},[25,1757,1758],{},"    // CSV書込\n",[25,1760,1761],{"class":27,"line":1096},[25,1762,1763],{},"    if (setData.length > 0) sheet.getRange(lastRow + 1, 1, setData.length, setData[0].length).setValues(setData);\n",[208,1765,210],{},{"title":20,"searchDepth":35,"depth":35,"links":1767},[],[1443],"2024-08-08T00:00:00.000Z","Google Apps ScriptでGoogle Driveフォルダ内のCSVファイルをまとめて読み込み、スプレッドシートに書き込むコード",{},"/posts/google-drive-csv-get",{"title":1595,"description":1770},"posts/google-drive-csv-get",[1590,1443],"lKL4QytOBHjSBySDt3shNGbOx7gEOmzMXpzYV96Etes",{"id":1778,"title":1779,"body":1780,"categories":1966,"cross_post":215,"date":1967,"description":1968,"extension":218,"image":20,"meta":1969,"navigation":31,"note_id":215,"note_url":215,"path":1970,"private":221,"qiita_id":215,"qiita_url":215,"seo":1971,"stem":1972,"tags":1973,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":1974},"posts/posts/google-drive-csv-write.md","google drive csv　 オブジェクトからcsv ファイルがない場合保存",{"type":8,"value":1781,"toc":1964},[1782,1962],[15,1783,1785],{"className":17,"code":1784,"language":19,"meta":20,"style":20},"\nconst sample = { id:1 ,name:\"aa\"} // サンプル実際のデータに直して\n\nconst fileName = \"test.csv\"\n\n\n\nconst blob = createBlob(csv, fileName)\n\ndeleteAndWriteDrive(blob, folderId, fileName)\n\nfunction deleteAndWriteDrive(csvBlob, folderId,fileName) {\n  const drive = DriveApp.getFolderById(folderId);\n  const files = drive.getFiles();\n  let sortFiles = []\n  while (files.hasNext()) {\n    sortFiles.push(files.next());\n  }\n  sortFiles.sort(function (a, b) { if (a.getName() > b.getName()) { return 1 } else { return -1 } })\n\n  for (const iterator of sortFiles) {\n    const file = iterator;\n    if (file.getName() === fileName) {\n      hasFileName = file.getName()\n      console.log(`${hasFileName}フォルダが存在します。`)\n      file.setTrashed(true)\n      break\n    }\n  }\n  \n  drive.createFile(csvBlob);\n}\n\nfunction createBlob(csv, fileName) {\n  const contentType = 'text/csv';\n  const charset = 'utf-8';\n  const blob = Utilities.newBlob('', contentType, fileName).setDataFromString(csv, charset);\n  return blob;\n}\n\n",[22,1786,1787,1791,1796,1800,1805,1809,1813,1817,1822,1826,1831,1835,1840,1845,1850,1854,1858,1862,1866,1870,1874,1878,1882,1887,1892,1897,1902,1907,1911,1915,1920,1925,1929,1933,1938,1943,1948,1953,1958],{"__ignoreMap":20},[25,1788,1789],{"class":27,"line":28},[25,1790,32],{"emptyLinePlaceholder":31},[25,1792,1793],{"class":27,"line":35},[25,1794,1795],{},"const sample = { id:1 ,name:\"aa\"} // サンプル実際のデータに直して\n",[25,1797,1798],{"class":27,"line":41},[25,1799,32],{"emptyLinePlaceholder":31},[25,1801,1802],{"class":27,"line":46},[25,1803,1804],{},"const fileName = \"test.csv\"\n",[25,1806,1807],{"class":27,"line":52},[25,1808,32],{"emptyLinePlaceholder":31},[25,1810,1811],{"class":27,"line":58},[25,1812,32],{"emptyLinePlaceholder":31},[25,1814,1815],{"class":27,"line":63},[25,1816,32],{"emptyLinePlaceholder":31},[25,1818,1819],{"class":27,"line":69},[25,1820,1821],{},"const blob = createBlob(csv, fileName)\n",[25,1823,1824],{"class":27,"line":75},[25,1825,32],{"emptyLinePlaceholder":31},[25,1827,1828],{"class":27,"line":80},[25,1829,1830],{},"deleteAndWriteDrive(blob, folderId, fileName)\n",[25,1832,1833],{"class":27,"line":86},[25,1834,32],{"emptyLinePlaceholder":31},[25,1836,1837],{"class":27,"line":92},[25,1838,1839],{},"function deleteAndWriteDrive(csvBlob, folderId,fileName) {\n",[25,1841,1842],{"class":27,"line":97},[25,1843,1844],{},"  const drive = DriveApp.getFolderById(folderId);\n",[25,1846,1847],{"class":27,"line":103},[25,1848,1849],{},"  const files = drive.getFiles();\n",[25,1851,1852],{"class":27,"line":109},[25,1853,1662],{},[25,1855,1856],{"class":27,"line":114},[25,1857,1667],{},[25,1859,1860],{"class":27,"line":120},[25,1861,1672],{},[25,1863,1864],{"class":27,"line":126},[25,1865,1038],{},[25,1867,1868],{"class":27,"line":132},[25,1869,1681],{},[25,1871,1872],{"class":27,"line":138},[25,1873,32],{"emptyLinePlaceholder":31},[25,1875,1876],{"class":27,"line":144},[25,1877,1699],{},[25,1879,1880],{"class":27,"line":150},[25,1881,1704],{},[25,1883,1884],{"class":27,"line":156},[25,1885,1886],{},"    if (file.getName() === fileName) {\n",[25,1888,1889],{"class":27,"line":162},[25,1890,1891],{},"      hasFileName = file.getName()\n",[25,1893,1894],{"class":27,"line":168},[25,1895,1896],{},"      console.log(`${hasFileName}フォルダが存在します。`)\n",[25,1898,1899],{"class":27,"line":174},[25,1900,1901],{},"      file.setTrashed(true)\n",[25,1903,1904],{"class":27,"line":180},[25,1905,1906],{},"      break\n",[25,1908,1909],{"class":27,"line":186},[25,1910,1526],{},[25,1912,1913],{"class":27,"line":191},[25,1914,1038],{},[25,1916,1917],{"class":27,"line":197},[25,1918,1919],{},"  \n",[25,1921,1922],{"class":27,"line":203},[25,1923,1924],{},"  drive.createFile(csvBlob);\n",[25,1926,1927],{"class":27,"line":1084},[25,1928,1186],{},[25,1930,1931],{"class":27,"line":1090},[25,1932,32],{"emptyLinePlaceholder":31},[25,1934,1935],{"class":27,"line":1096},[25,1936,1937],{},"function createBlob(csv, fileName) {\n",[25,1939,1940],{"class":27,"line":1102},[25,1941,1942],{},"  const contentType = 'text/csv';\n",[25,1944,1945],{"class":27,"line":1108},[25,1946,1947],{},"  const charset = 'utf-8';\n",[25,1949,1950],{"class":27,"line":1113},[25,1951,1952],{},"  const blob = Utilities.newBlob('', contentType, fileName).setDataFromString(csv, charset);\n",[25,1954,1955],{"class":27,"line":1119},[25,1956,1957],{},"  return blob;\n",[25,1959,1960],{"class":27,"line":1125},[25,1961,1186],{},[208,1963,210],{},{"title":20,"searchDepth":35,"depth":35,"links":1965},[],[1443],"2024-03-06T00:00:00.000Z","Google Apps Scriptでオブジェクトから生成したCSVを、同名ファイルの重複を避けつつGoogle Driveへ保存するコード",{},"/posts/google-drive-csv-write",{"title":1779,"description":1968},"posts/google-drive-csv-write",[1590,1443],"N_OrY_EGpEkQ2rxYmXkgoEPWMZXuQhLL44XbPXGm3Ng",{"id":1976,"title":1977,"body":1978,"categories":2234,"cross_post":215,"date":2235,"description":2236,"extension":218,"image":20,"meta":2237,"navigation":31,"note_id":215,"note_url":215,"path":2238,"private":221,"qiita_id":215,"qiita_url":215,"seo":2239,"stem":2240,"tags":2241,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":2242},"posts/posts/js-array-count-max.md","javascript 配列 同じ要素数カウント、最大出力",{"type":8,"value":1979,"toc":2231},[1980,2213,2216,2222,2228],[15,1981,1983],{"className":572,"code":1982,"language":574,"meta":20,"style":20},"const array = [1, 1, 1, 2, 2, 3];\n\nconst map = array.reduce(\n  (acc, curr) => acc.set(curr, (acc.get(curr) || 0) + 1),\n  new Map()\n);\n\nconsole.log([...map.keys()]); // [1, 2, 3]\nconsole.log([...map.values()]); // [3, 2, 1]\nconsole.log([...map.entries()]); // [[1, 3], [2, 2], [3, 1]]\n\nconst arrayCountMax = Math.max(...map.values()) //3\n",[22,1984,1985,2026,2030,2048,2097,2107,2112,2116,2140,2160,2180,2184],{"__ignoreMap":20},[25,1986,1987,1989,1992,1994,1997,2000,2003,2005,2007,2009,2011,2014,2016,2018,2020,2023],{"class":27,"line":28},[25,1988,599],{"class":598},[25,1990,1991],{"class":602}," array",[25,1993,606],{"class":598},[25,1995,1996],{"class":581}," [",[25,1998,1999],{"class":602},"1",[25,2001,2002],{"class":581},", ",[25,2004,1999],{"class":602},[25,2006,2002],{"class":581},[25,2008,1999],{"class":602},[25,2010,2002],{"class":581},[25,2012,2013],{"class":602},"2",[25,2015,2002],{"class":581},[25,2017,2013],{"class":602},[25,2019,2002],{"class":581},[25,2021,2022],{"class":602},"3",[25,2024,2025],{"class":581},"];\n",[25,2027,2028],{"class":27,"line":35},[25,2029,32],{"emptyLinePlaceholder":31},[25,2031,2032,2034,2037,2039,2042,2045],{"class":27,"line":41},[25,2033,599],{"class":598},[25,2035,2036],{"class":602}," map",[25,2038,606],{"class":598},[25,2040,2041],{"class":581}," array.",[25,2043,2044],{"class":585},"reduce",[25,2046,2047],{"class":581},"(\n",[25,2049,2050,2053,2056,2058,2061,2063,2065,2068,2071,2074,2077,2080,2083,2086,2088,2091,2094],{"class":27,"line":46},[25,2051,2052],{"class":581},"  (",[25,2054,2055],{"class":695},"acc",[25,2057,2002],{"class":581},[25,2059,2060],{"class":695},"curr",[25,2062,699],{"class":581},[25,2064,702],{"class":598},[25,2066,2067],{"class":581}," acc.",[25,2069,2070],{"class":585},"set",[25,2072,2073],{"class":581},"(curr, (acc.",[25,2075,2076],{"class":585},"get",[25,2078,2079],{"class":581},"(curr) ",[25,2081,2082],{"class":598},"||",[25,2084,2085],{"class":602}," 0",[25,2087,699],{"class":581},[25,2089,2090],{"class":598},"+",[25,2092,2093],{"class":602}," 1",[25,2095,2096],{"class":581},"),\n",[25,2098,2099,2102,2105],{"class":27,"line":52},[25,2100,2101],{"class":598},"  new",[25,2103,2104],{"class":585}," Map",[25,2106,615],{"class":581},[25,2108,2109],{"class":27,"line":58},[25,2110,2111],{"class":581},");\n",[25,2113,2114],{"class":27,"line":63},[25,2115,32],{"emptyLinePlaceholder":31},[25,2117,2118,2120,2122,2125,2128,2131,2134,2137],{"class":27,"line":69},[25,2119,662],{"class":581},[25,2121,665],{"class":585},[25,2123,2124],{"class":581},"([",[25,2126,2127],{"class":598},"...",[25,2129,2130],{"class":581},"map.",[25,2132,2133],{"class":585},"keys",[25,2135,2136],{"class":581},"()]); ",[25,2138,2139],{"class":656},"// [1, 2, 3]\n",[25,2141,2142,2144,2146,2148,2150,2152,2155,2157],{"class":27,"line":75},[25,2143,662],{"class":581},[25,2145,665],{"class":585},[25,2147,2124],{"class":581},[25,2149,2127],{"class":598},[25,2151,2130],{"class":581},[25,2153,2154],{"class":585},"values",[25,2156,2136],{"class":581},[25,2158,2159],{"class":656},"// [3, 2, 1]\n",[25,2161,2162,2164,2166,2168,2170,2172,2175,2177],{"class":27,"line":80},[25,2163,662],{"class":581},[25,2165,665],{"class":585},[25,2167,2124],{"class":581},[25,2169,2127],{"class":598},[25,2171,2130],{"class":581},[25,2173,2174],{"class":585},"entries",[25,2176,2136],{"class":581},[25,2178,2179],{"class":656},"// [[1, 3], [2, 2], [3, 1]]\n",[25,2181,2182],{"class":27,"line":86},[25,2183,32],{"emptyLinePlaceholder":31},[25,2185,2186,2188,2191,2193,2196,2199,2201,2203,2205,2207,2210],{"class":27,"line":92},[25,2187,599],{"class":598},[25,2189,2190],{"class":602}," arrayCountMax",[25,2192,606],{"class":598},[25,2194,2195],{"class":581}," Math.",[25,2197,2198],{"class":585},"max",[25,2200,640],{"class":581},[25,2202,2127],{"class":598},[25,2204,2130],{"class":581},[25,2206,2154],{"class":585},[25,2208,2209],{"class":581},"()) ",[25,2211,2212],{"class":656},"//3\n",[1204,2214,2215],{"id":2215},"参考",[238,2217,2218],{},[1544,2219,2220],{"href":2220,"rel":2221},"https://qiita.com/saka212/items/408bb17dddefc09004c8",[1548],[238,2223,2224],{},[1544,2225,2226],{"href":2226,"rel":2227},"https://pote-chil.com/blog/count-duplicate-array-item",[1548],[208,2229,2230],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":20,"searchDepth":35,"depth":35,"links":2232},[2233],{"id":2215,"depth":41,"text":2215},[1443],"2023-11-28T00:00:00.000Z","JavaScriptで配列内の要素ごとの出現回数を数え、最も多く出現する回数を求めるコードのメモ",{},"/posts/js-array-count-max",{"title":1977,"description":2236},"posts/js-array-count-max",[1443,1451],"KvvHi7no7XgJ7mn4i4f3uPOK2IQfLzQU5WZcw6WQ_JM",{"id":2244,"title":2245,"body":2246,"categories":4459,"cross_post":215,"date":4460,"description":4461,"extension":218,"image":20,"meta":4462,"navigation":31,"note_id":215,"note_url":215,"path":4463,"private":221,"qiita_id":215,"qiita_url":215,"seo":4464,"stem":4465,"tags":4466,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":4469},"posts/posts/notion-to-google-calender-1103.md","NotionタスクとGoogleカレンダーの同期システムを作る",{"type":8,"value":2247,"toc":4432},[2248,2250,2253,2256,2259,2262,2276,2279,2293,2296,2300,2303,2343,2347,2350,2429,2432,2516,2520,2523,2590,2594,2597,2655,2659,2760,2763,2767,2778,2782,2793,2797,2864,2867,2870,2873,2887,2890,2953,4352,4355,4396,4398,4401,4404,4407,4430],[11,2249,890],{"id":890},[238,2251,2252],{},"NotionのタスクをGoogleカレンダーと自動で同期させるシステムの実装方法を紹介します。このシステムにより、Notionで管理しているタスクの期限日を自動的にGoogleカレンダーに反映させることができます。ただしGoogleカレンダーの変更をNotionに反映することは想定していません。",[238,2254,2255],{},"また、完了したタスクはGoogleカレンダータスクを同期しているカレンダーとは別のカレンダに移動させるようにしています。",[11,2257,2258],{"id":2258},"システムの概要",[1204,2260,2261],{"id":2261},"主な機能",[304,2263,2264,2267,2270,2273],{},[307,2265,2266],{},"Notionの新規タスクを自動的にGoogleカレンダーに追加",[307,2268,2269],{},"タスクの更新（タイトル、期限）をカレンダーに反映",[307,2271,2272],{},"完了したタスクのカレンダーイベントを自動削除",[307,2274,2275],{},"定期的に同期",[1204,2277,2278],{"id":2278},"必要な準備",[1402,2280,2281,2284,2287,2290],{},[307,2282,2283],{},"Notion API キー",[307,2285,2286],{},"Notionデータベースの設定",[307,2288,2289],{},"Googleカレンダー",[307,2291,2292],{},"Google Apps Script環境",[11,2294,2295],{"id":2295},"実装方法",[1204,2297,2299],{"id":2298},"_1-設定","1. 設定",[238,2301,2302],{},"必要な認証情報とIDを設定します：",[15,2304,2306],{"className":17,"code":2305,"language":19,"meta":20,"style":20},"const CONFIG = {\n  NOTION_API_KEY: 'your-notion-api-key',\n  NOTION_DATABASE_ID: 'your-database-id',\n  CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n  DONE_CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n  NOTION_VERSION: '2022-06-28'\n};\n",[22,2307,2308,2313,2318,2323,2328,2333,2338],{"__ignoreMap":20},[25,2309,2310],{"class":27,"line":28},[25,2311,2312],{},"const CONFIG = {\n",[25,2314,2315],{"class":27,"line":35},[25,2316,2317],{},"  NOTION_API_KEY: 'your-notion-api-key',\n",[25,2319,2320],{"class":27,"line":41},[25,2321,2322],{},"  NOTION_DATABASE_ID: 'your-database-id',\n",[25,2324,2325],{"class":27,"line":46},[25,2326,2327],{},"  CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n",[25,2329,2330],{"class":27,"line":52},[25,2331,2332],{},"  DONE_CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n",[25,2334,2335],{"class":27,"line":58},[25,2336,2337],{},"  NOTION_VERSION: '2022-06-28'\n",[25,2339,2340],{"class":27,"line":63},[25,2341,2342],{},"};\n",[1204,2344,2346],{"id":2345},"_2-notionとの連携","2. Notionとの連携",[1204,2348,2349],{"id":2349},"タスク取得",[15,2351,2353],{"className":17,"code":2352,"language":19,"meta":20,"style":20},"// Notionからタスク一覧を取得\nfunction getNotionTasks() {\n  const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`;\n  const response = UrlFetchApp.fetch(url, {\n    method: 'POST',\n    headers: NOTION_HEADERS,\n    payload: JSON.stringify({\n      sorts: [\n        {\n          property: 'DueDate', // 日付プロパティ名\n          direction: 'descending' // 昇順にソート（降順の場合は'descending'に変更）\n        }\n      ],\n    })\n  });\n\n",[22,2354,2355,2360,2365,2370,2375,2380,2385,2390,2395,2400,2405,2410,2415,2420,2425],{"__ignoreMap":20},[25,2356,2357],{"class":27,"line":28},[25,2358,2359],{},"// Notionからタスク一覧を取得\n",[25,2361,2362],{"class":27,"line":35},[25,2363,2364],{},"function getNotionTasks() {\n",[25,2366,2367],{"class":27,"line":41},[25,2368,2369],{},"  const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`;\n",[25,2371,2372],{"class":27,"line":46},[25,2373,2374],{},"  const response = UrlFetchApp.fetch(url, {\n",[25,2376,2377],{"class":27,"line":52},[25,2378,2379],{},"    method: 'POST',\n",[25,2381,2382],{"class":27,"line":58},[25,2383,2384],{},"    headers: NOTION_HEADERS,\n",[25,2386,2387],{"class":27,"line":63},[25,2388,2389],{},"    payload: JSON.stringify({\n",[25,2391,2392],{"class":27,"line":69},[25,2393,2394],{},"      sorts: [\n",[25,2396,2397],{"class":27,"line":75},[25,2398,2399],{},"        {\n",[25,2401,2402],{"class":27,"line":80},[25,2403,2404],{},"          property: 'DueDate', // 日付プロパティ名\n",[25,2406,2407],{"class":27,"line":86},[25,2408,2409],{},"          direction: 'descending' // 昇順にソート（降順の場合は'descending'に変更）\n",[25,2411,2412],{"class":27,"line":92},[25,2413,2414],{},"        }\n",[25,2416,2417],{"class":27,"line":97},[25,2418,2419],{},"      ],\n",[25,2421,2422],{"class":27,"line":103},[25,2423,2424],{},"    })\n",[25,2426,2427],{"class":27,"line":109},[25,2428,1169],{},[1204,2430,2431],{"id":2431},"タスク更新",[15,2433,2435],{"className":17,"code":2434,"language":19,"meta":20,"style":20},"// Notionのタスクを更新（カレンダーイベントIDの保存）\nfunction updateNotionTask(taskId, calendarEventId) {\n  const url = `https://api.notion.com/v1/pages/${taskId}`;\n  return UrlFetchApp.fetch(url, {\n    method: 'PATCH',\n    headers: NOTION_HEADERS,\n    payload: JSON.stringify({\n      properties: {\n        'Calendar Event ID': {\n          rich_text: [{\n            text: { content: calendarEventId }\n          }]\n        }\n      }\n    })\n  });\n}\n\n",[22,2436,2437,2442,2447,2452,2457,2462,2466,2470,2475,2480,2485,2490,2495,2499,2504,2508,2512],{"__ignoreMap":20},[25,2438,2439],{"class":27,"line":28},[25,2440,2441],{},"// Notionのタスクを更新（カレンダーイベントIDの保存）\n",[25,2443,2444],{"class":27,"line":35},[25,2445,2446],{},"function updateNotionTask(taskId, calendarEventId) {\n",[25,2448,2449],{"class":27,"line":41},[25,2450,2451],{},"  const url = `https://api.notion.com/v1/pages/${taskId}`;\n",[25,2453,2454],{"class":27,"line":46},[25,2455,2456],{},"  return UrlFetchApp.fetch(url, {\n",[25,2458,2459],{"class":27,"line":52},[25,2460,2461],{},"    method: 'PATCH',\n",[25,2463,2464],{"class":27,"line":58},[25,2465,2384],{},[25,2467,2468],{"class":27,"line":63},[25,2469,2389],{},[25,2471,2472],{"class":27,"line":69},[25,2473,2474],{},"      properties: {\n",[25,2476,2477],{"class":27,"line":75},[25,2478,2479],{},"        'Calendar Event ID': {\n",[25,2481,2482],{"class":27,"line":80},[25,2483,2484],{},"          rich_text: [{\n",[25,2486,2487],{"class":27,"line":86},[25,2488,2489],{},"            text: { content: calendarEventId }\n",[25,2491,2492],{"class":27,"line":92},[25,2493,2494],{},"          }]\n",[25,2496,2497],{"class":27,"line":97},[25,2498,2414],{},[25,2500,2501],{"class":27,"line":103},[25,2502,2503],{},"      }\n",[25,2505,2506],{"class":27,"line":109},[25,2507,2424],{},[25,2509,2510],{"class":27,"line":114},[25,2511,1169],{},[25,2513,2514],{"class":27,"line":120},[25,2515,1186],{},[1204,2517,2519],{"id":2518},"_3-googleカレンダーとの連携","3. Googleカレンダーとの連携",[1204,2521,2522],{"id":2522},"イベント作成",[15,2524,2526],{"className":17,"code":2525,"language":19,"meta":20,"style":20},"function createCalendarEvent(task) {\n  const calendar = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);\n  const title = task.properties.Name.title[0].plain_text;\n  const dueDate = new Date(task.properties.DueDate.date.start);\n\n  const event = calendar.createAllDayEvent(\n    title,\n    dueDate,\n    { description: `Notion Task ID: ${task.id}\\\\nURL: ${task.url}` }\n  );\n\n  return event.getId();\n}\n\n",[22,2527,2528,2533,2538,2543,2548,2552,2557,2562,2567,2572,2577,2581,2586],{"__ignoreMap":20},[25,2529,2530],{"class":27,"line":28},[25,2531,2532],{},"function createCalendarEvent(task) {\n",[25,2534,2535],{"class":27,"line":35},[25,2536,2537],{},"  const calendar = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);\n",[25,2539,2540],{"class":27,"line":41},[25,2541,2542],{},"  const title = task.properties.Name.title[0].plain_text;\n",[25,2544,2545],{"class":27,"line":46},[25,2546,2547],{},"  const dueDate = new Date(task.properties.DueDate.date.start);\n",[25,2549,2550],{"class":27,"line":52},[25,2551,32],{"emptyLinePlaceholder":31},[25,2553,2554],{"class":27,"line":58},[25,2555,2556],{},"  const event = calendar.createAllDayEvent(\n",[25,2558,2559],{"class":27,"line":63},[25,2560,2561],{},"    title,\n",[25,2563,2564],{"class":27,"line":69},[25,2565,2566],{},"    dueDate,\n",[25,2568,2569],{"class":27,"line":75},[25,2570,2571],{},"    { description: `Notion Task ID: ${task.id}\\\\nURL: ${task.url}` }\n",[25,2573,2574],{"class":27,"line":80},[25,2575,2576],{},"  );\n",[25,2578,2579],{"class":27,"line":86},[25,2580,32],{"emptyLinePlaceholder":31},[25,2582,2583],{"class":27,"line":92},[25,2584,2585],{},"  return event.getId();\n",[25,2587,2588],{"class":27,"line":97},[25,2589,1186],{},[1204,2591,2593],{"id":2592},"_4-状態管理","4. 状態管理",[238,2595,2596],{},"タスクの状態を管理するための機能：",[15,2598,2600],{"className":17,"code":2599,"language":19,"meta":20,"style":20},"\n// タスクの現在の状態を取得\nfunction getTaskState(task) {\n  return {\n    id: task.id,\n    title: task.properties.Name.title[0].plain_text,\n    dueDate: task.properties.DueDate.date?.start,\n    status: task.properties.Status.status ? task.properties.Status.status.name : \"None\",\n    calendarEventId: task.properties['Calendar Event ID']?.rich_text[0]?.text.content\n  };\n}\n\n",[22,2601,2602,2606,2611,2616,2621,2626,2631,2636,2641,2646,2651],{"__ignoreMap":20},[25,2603,2604],{"class":27,"line":28},[25,2605,32],{"emptyLinePlaceholder":31},[25,2607,2608],{"class":27,"line":35},[25,2609,2610],{},"// タスクの現在の状態を取得\n",[25,2612,2613],{"class":27,"line":41},[25,2614,2615],{},"function getTaskState(task) {\n",[25,2617,2618],{"class":27,"line":46},[25,2619,2620],{},"  return {\n",[25,2622,2623],{"class":27,"line":52},[25,2624,2625],{},"    id: task.id,\n",[25,2627,2628],{"class":27,"line":58},[25,2629,2630],{},"    title: task.properties.Name.title[0].plain_text,\n",[25,2632,2633],{"class":27,"line":63},[25,2634,2635],{},"    dueDate: task.properties.DueDate.date?.start,\n",[25,2637,2638],{"class":27,"line":69},[25,2639,2640],{},"    status: task.properties.Status.status ? task.properties.Status.status.name : \"None\",\n",[25,2642,2643],{"class":27,"line":75},[25,2644,2645],{},"    calendarEventId: task.properties['Calendar Event ID']?.rich_text[0]?.text.content\n",[25,2647,2648],{"class":27,"line":80},[25,2649,2650],{},"  };\n",[25,2652,2653],{"class":27,"line":86},[25,2654,1186],{},[1204,2656,2658],{"id":2657},"_5-メイン同期処理","5. メイン同期処理",[15,2660,2662],{"className":17,"code":2661,"language":19,"meta":20,"style":20},"function syncNotionWithCalendar() {\n  try {\n    const tasks = getNotionTasks();\n    const previousState = getPreviousState();\n    const currentState = {};\n\n    tasks.forEach(task => {\n      currentState[task.id] = getTaskState(task);\n    });\n\n    tasks.forEach(task => {\n      processTaskChanges(task, previousState, currentState);\n    });\n\n    processDeletedTasks(previousState, currentState);\n    savePreviousState(currentState);\n\n  } catch (error) {\n    logError(error, 'メイン同期処理');\n  }\n}\n\n",[22,2663,2664,2669,2674,2679,2684,2689,2693,2698,2703,2707,2711,2715,2720,2724,2728,2733,2738,2742,2747,2752,2756],{"__ignoreMap":20},[25,2665,2666],{"class":27,"line":28},[25,2667,2668],{},"function syncNotionWithCalendar() {\n",[25,2670,2671],{"class":27,"line":35},[25,2672,2673],{},"  try {\n",[25,2675,2676],{"class":27,"line":41},[25,2677,2678],{},"    const tasks = getNotionTasks();\n",[25,2680,2681],{"class":27,"line":46},[25,2682,2683],{},"    const previousState = getPreviousState();\n",[25,2685,2686],{"class":27,"line":52},[25,2687,2688],{},"    const currentState = {};\n",[25,2690,2691],{"class":27,"line":58},[25,2692,32],{"emptyLinePlaceholder":31},[25,2694,2695],{"class":27,"line":63},[25,2696,2697],{},"    tasks.forEach(task => {\n",[25,2699,2700],{"class":27,"line":69},[25,2701,2702],{},"      currentState[task.id] = getTaskState(task);\n",[25,2704,2705],{"class":27,"line":75},[25,2706,1163],{},[25,2708,2709],{"class":27,"line":80},[25,2710,32],{"emptyLinePlaceholder":31},[25,2712,2713],{"class":27,"line":86},[25,2714,2697],{},[25,2716,2717],{"class":27,"line":92},[25,2718,2719],{},"      processTaskChanges(task, previousState, currentState);\n",[25,2721,2722],{"class":27,"line":97},[25,2723,1163],{},[25,2725,2726],{"class":27,"line":103},[25,2727,32],{"emptyLinePlaceholder":31},[25,2729,2730],{"class":27,"line":109},[25,2731,2732],{},"    processDeletedTasks(previousState, currentState);\n",[25,2734,2735],{"class":27,"line":114},[25,2736,2737],{},"    savePreviousState(currentState);\n",[25,2739,2740],{"class":27,"line":120},[25,2741,32],{"emptyLinePlaceholder":31},[25,2743,2744],{"class":27,"line":126},[25,2745,2746],{},"  } catch (error) {\n",[25,2748,2749],{"class":27,"line":132},[25,2750,2751],{},"    logError(error, 'メイン同期処理');\n",[25,2753,2754],{"class":27,"line":138},[25,2755,1038],{},[25,2757,2758],{"class":27,"line":144},[25,2759,1186],{},[11,2761,2762],{"id":2762},"実装のポイント",[1204,2764,2766],{"id":2765},"_1-エラーハンドリング","1. エラーハンドリング",[304,2768,2769,2772,2775],{},[307,2770,2771],{},"各処理でtry-catchを使用",[307,2773,2774],{},"エラーログの記録",[307,2776,2777],{},"コンテキスト情報の保持",[1204,2779,2781],{"id":2780},"_2-キャッシュの活用","2. キャッシュの活用",[304,2783,2784,2787,2790],{},[307,2785,2786],{},"前回の状態をキャッシュに保存",[307,2788,2789],{},"6時間のキャッシュ有効期限",[307,2791,2792],{},"差分検出による効率的な更新",[1204,2794,2796],{"id":2795},"_3-定期実行の設定","3. 定期実行の設定",[15,2798,2800],{"className":17,"code":2799,"language":19,"meta":20,"style":20},"// 定期実行トリガーの設定\nfunction setUpTrigger() {\n  // 既存のトリガーを削除\n  ScriptApp.getProjectTriggers().forEach(trigger => {\n    ScriptApp.deleteTrigger(trigger);\n  });\n\n  // 新しいトリガーを作成（1時間ごとに実行）\n  ScriptApp.newTrigger('syncNotionWithCalendar')\n    .timeBased()\n    .everyHours(1)\n    .create();\n}\n\n",[22,2801,2802,2807,2812,2817,2822,2827,2831,2835,2840,2845,2850,2855,2860],{"__ignoreMap":20},[25,2803,2804],{"class":27,"line":28},[25,2805,2806],{},"// 定期実行トリガーの設定\n",[25,2808,2809],{"class":27,"line":35},[25,2810,2811],{},"function setUpTrigger() {\n",[25,2813,2814],{"class":27,"line":41},[25,2815,2816],{},"  // 既存のトリガーを削除\n",[25,2818,2819],{"class":27,"line":46},[25,2820,2821],{},"  ScriptApp.getProjectTriggers().forEach(trigger => {\n",[25,2823,2824],{"class":27,"line":52},[25,2825,2826],{},"    ScriptApp.deleteTrigger(trigger);\n",[25,2828,2829],{"class":27,"line":58},[25,2830,1169],{},[25,2832,2833],{"class":27,"line":63},[25,2834,32],{"emptyLinePlaceholder":31},[25,2836,2837],{"class":27,"line":69},[25,2838,2839],{},"  // 新しいトリガーを作成（1時間ごとに実行）\n",[25,2841,2842],{"class":27,"line":75},[25,2843,2844],{},"  ScriptApp.newTrigger('syncNotionWithCalendar')\n",[25,2846,2847],{"class":27,"line":80},[25,2848,2849],{},"    .timeBased()\n",[25,2851,2852],{"class":27,"line":86},[25,2853,2854],{},"    .everyHours(1)\n",[25,2856,2857],{"class":27,"line":92},[25,2858,2859],{},"    .create();\n",[25,2861,2862],{"class":27,"line":97},[25,2863,1186],{},[11,2865,2286],{"id":2866},"notionデータベースの設定",[238,2868,2869],{},"きちんとtextかstatusか日付か合わせる",[238,2871,2872],{},"必要な項目：",[1402,2874,2875,2878,2881,2884],{},[307,2876,2877],{},"Name（タイトル）",[307,2879,2880],{},"DueDate（期限日）",[307,2882,2883],{},"Status（ステータス）",[307,2885,2886],{},"Calendar Event ID（カレンダー連携用）",[11,2888,2889],{"id":2889},"セットアップ手順",[1402,2891,2892,2895,2909,2917,2932,2935,2943],{},[307,2893,2894],{},"Google Apps Scriptプロジェクトの作成",[307,2896,2897,2898],{},"Notion APIキーの取得\n",[1402,2899,2900],{},[307,2901,2902,2903,2908],{},"Notion APIキーは ",[1544,2904,2907],{"href":2905,"rel":2906},"https://www.notion.so/my-integrations",[1548],"Notion Developer"," から取得できます。",[307,2910,2911,2912],{},"Notionデータベースの準備\n",[1402,2913,2914],{},[307,2915,2916],{},"同期したいデータベースのURLから、IDを取得します。",[307,2918,2919,2922,2923],{},[904,2920,2921],{},"GoogleカレンダーID","を確認する。\n",[304,2924,2925],{},[307,2926,2927,2928,2931],{},"メインカレンダーの場合は",[22,2929,2930],{},"primary","、他のカレンダーの場合はカレンダー設定画面でIDを確認してください。",[307,2933,2934],{},"コードの貼り付けと設定",[307,2936,2937,2938],{},"トリガーの設定\n",[1402,2939,2940],{},[307,2941,2942],{},"setUpTriggerの実行 自分の頻度に合わせて",[307,2944,2945,2948],{},[904,2946,2947],{},"権限の設定",[304,2949,2950],{},[307,2951,2952],{},"初回実行時にGoogleカレンダーへのアクセス権限を求められますので、許可します。",[15,2954,2956],{"className":17,"code":2955,"language":19,"meta":20,"style":20},"// API キーやIDを管理する設定オブジェクト\nconst CONFIG = {\n  NOTION_API_KEY: 'your-notion-api-key',\n  NOTION_DATABASE_ID: 'your-database-id',\n  CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n  DONE_CALENDAR_ID: 'your-calendar-id@group.calendar.google.com',\n  NOTION_VERSION: '2022-06-28'\n};\n\n// Notionの共通ヘッダー設定\nconst NOTION_HEADERS = {\n  'Authorization': `Bearer ${CONFIG.NOTION_API_KEY}`,\n  'Notion-Version': CONFIG.NOTION_VERSION,\n  'Content-Type': 'application/json'\n};\n\n\n\n\n\n// メイン同期処理\nfunction syncNotionWithCalendar() {\n  try {\n    // タスク一覧の取得\n    const tasks = getNotionTasks();\n    const previousState = getPreviousState();\n    const currentState = {};\n\n    // 現在の状態を構築\n    console.log(\"現在の状態を構築\")\n    tasks.forEach(task => {\n\n      currentState[task.id] = getTaskState(task);\n    });\n\n    console.log(\"タスクの変更を処理\")\n    // タスクの変更を処理\n    tasks.forEach(task => {\n      console.log(\"currentState\", currentState[task.id])\n      console.log(\"previousState\", previousState[task.id])\n      processTaskChanges(task, previousState, currentState);\n    });\n\n    // 削除されたタスクを処理\n    console.log(\"削除されたタスクを処理\")\n    processDeletedTasks(previousState, currentState);\n\n    // 現在の状態を保存\n    console.log(\"現在の状態を保存\")\n    savePreviousState(currentState);\n\n  } catch (error) {\n    logError(error, 'メイン同期処理');\n  }\n}\n\n\n\n\n// Notionからタスク一覧を取得\nfunction getNotionTasks() {\n  const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`;\n  const response = UrlFetchApp.fetch(url, {\n    method: 'POST',\n    headers: NOTION_HEADERS,\n    payload: JSON.stringify({\n      sorts: [\n        {\n          property: 'DueDate', // 日付プロパティ名\n          direction: 'descending' // 昇順にソート（降順の場合は'descending'に変更）\n        }\n      ],\n    })\n  });\n\n  console.log(\"next_cursor\", JSON.parse(response.getContentText()).next_cursor)\n  return JSON.parse(response.getContentText()).results;\n}\n\n// Notionのタスクを更新（カレンダーイベントIDの保存）\nfunction updateNotionTask(taskId, calendarEventId) {\n  const url = `https://api.notion.com/v1/pages/${taskId}`;\n  return UrlFetchApp.fetch(url, {\n    method: 'PATCH',\n    headers: NOTION_HEADERS,\n    payload: JSON.stringify({\n      properties: {\n        'Calendar Event ID': {\n          rich_text: [{\n            text: { content: calendarEventId }\n          }]\n        }\n      }\n    })\n  });\n}\n\n// カレンダーイベントの新規作成\nfunction createCalendarEvent(task, CALENDAR_ID) {\n  const calendar = CalendarApp.getCalendarById(CALENDAR_ID);\n  const title = task.properties.Name.title[0].plain_text;\n  const dueDate = new Date(task.properties.DueDate.date.start);\n\n  if (!dueDate) return\n\n  const event = calendar.createAllDayEvent(\n    title,\n    dueDate,\n    { description: `Notion Task ID: ${task.id}\\nURL: ${task.url}` }\n  );\n\n  console.log(event)\n  return event.getId();\n}\n\n// カレンダーイベントの更新\nfunction updateCalendarEvent(eventId, task) {\n  const calendar = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);\n  const event = calendar.getEventById(eventId);\n  if (!event) return;\n\n  const title = task.properties.Name.title[0].plain_text;\n  const dueDate = new Date(task.properties.DueDate.date.start);\n\n  event.setTitle(title);\n  event.setAllDayDate(dueDate);\n  event.setDescription(`Notion Task ID: ${task.id}\\nURL: ${task.url}`);\n}\n\n// カレンダーイベントの削除\nfunction deleteCalendarEvent(eventId) {\n  const calendar = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);\n\n  try {\n    const event = calendar.getEventById(eventId);\n    if (event) {\n      event.deleteEvent();\n      return true\n    }\n  } catch (e) {\n    console.error(\"削除されている\", e)\n    return false\n\n  }\n\n  return false\n}\n\n// タスクの現在の状態を取得\nfunction getTaskState(task) {\n  return {\n    id: task.id,\n    title: task.properties.Name.title[0].plain_text,\n    dueDate: task.properties.DueDate.date?.start,\n    status: task.properties.Status.status ? task.properties.Status.status.name : \"None\",\n    calendarEventId: task.properties['Calendar Event ID']?.rich_text[0]?.text.content\n  };\n}\n\n// キャッシュから前回の状態を取得\nfunction getPreviousState() {\n  const cache = CacheService.getScriptCache();\n  const data = cache.get('previousState');\n\n  console.log(JSON.parse(data))\n  return data ? JSON.parse(data) : {};\n}\n\n// 現在の状態をキャッシュに保存（6時間有効）\nfunction savePreviousState(state) {\n  const cache = CacheService.getScriptCache();\n  cache.put('previousState', JSON.stringify(state), 21600);\n}\n\n// タスクの変更を検出して必要な処理を実行\nfunction processTaskChanges(task, previousState, currentState) {\n  const taskId = task.id;\n  const prevTask = previousState[taskId];\n  const currentTask = currentState[taskId];\n\n  try {\n    if (!prevTask && task.properties.DueDate?.date?.start) {\n      // 新規タスクの処理\n      console.log(\"新規タスクの処理\")\n      const calendarEventId = createCalendarEvent(task, CONFIG.CALENDAR_ID);\n      updateNotionTask(taskId, calendarEventId);\n    } else if (prevTask && prevTask.dueDate) {\n      // 既存タスクの処理\n      console.log(\"既存タスクの処理\")\n      const calendarEventId = prevTask.calendarEventId;\n      if (currentTask.status === 'Deleted') {\n        // 削除されたタスクの処理\n        console.log(\"削除されたタスクの処理\")\n        deleteCalendarEvent(calendarEventId);\n\n      } else if (\n        prevTask.title !== currentTask.title ||\n        prevTask.dueDate !== currentTask.dueDate\n      ) {\n        // タスクが更新された場合の処理\n        console.log(\"タスクが更新された場合の処理\")\n        updateCalendarEvent(calendarEventId, task);\n      } else if (currentTask.status === 'Done') {\n        // ステータスが完了タスクの処理\n        console.log(\"ステータスが完了タスクの処理\")\n        const reslut = deleteCalendarEvent(calendarEventId);\n        console.log(\"ステータスが完了タスクの処理終了 google カレンダー削除 \",reslut)\n\n        // 削除がない場合終了\n        if (!reslut) return\n\n        console.log(\"別カレンダーに完了を作成\")\n        // 別カレンダーに完了を作成\n        createCalendarEvent(task, CONFIG.DONE_CALENDAR_ID);\n        updateNotionTask(taskId, \"done\");\n        console.log(\"別カレンダーに完了を作成終了\")\n      }\n    }\n  } catch (error) {\n    logError(error, `タスク処理エラー - TaskID: ${taskId}`);\n  }\n}\n\n// 削除されたタスクの処理\nfunction processDeletedTasks(previousState, currentState) {\n  Object.keys(previousState).forEach(taskId => {\n    if (!currentState[taskId]) {\n      const calendarEventId = previousState[taskId].calendarEventId;\n      if (calendarEventId) {\n        try {\n          deleteCalendarEvent(calendarEventId);\n        } catch (error) {\n          logError(error, `削除済みタスクの処理エラー - TaskID: ${taskId}`);\n        }\n      }\n    }\n  });\n}\n\n// エラーログの記録\nfunction logError(error, context = '') {\n  console.error(`同期エラー ${context}:`, error);\n  // 必要に応じて追加のエラーハンドリング（Slack通知やメール送信など）\n\n\n}\n\n// 特定のキャッシュキーを削除する関数\nfunction clearPreviousStateCache() {\n  const cache = CacheService.getScriptCache();\n  cache.remove('previousState');\n  console.log(\"キャッシュ 'previousState' を削除しました\");\n}\n\n// 定期実行トリガーの設定\nfunction setUpTrigger() {\n  // 既存のトリガーを削除\n  ScriptApp.getProjectTriggers().forEach(trigger => {\n    ScriptApp.deleteTrigger(trigger);\n  });\n\n  // 新しいトリガーを作成（1時間ごとに実行）\n  ScriptApp.newTrigger('syncNotionWithCalendar')\n    .timeBased()\n    .everyHours(1)\n    .create();\n}\n",[22,2957,2958,2963,2967,2971,2975,2979,2983,2987,2991,2995,3000,3005,3010,3015,3020,3024,3028,3032,3036,3040,3044,3049,3053,3057,3062,3066,3070,3074,3078,3083,3088,3092,3096,3100,3104,3108,3113,3118,3122,3127,3132,3136,3140,3144,3149,3154,3158,3162,3167,3172,3177,3182,3187,3192,3197,3202,3207,3212,3217,3222,3227,3232,3237,3242,3247,3252,3257,3262,3267,3272,3277,3282,3287,3292,3297,3302,3308,3314,3319,3324,3329,3334,3339,3344,3349,3354,3359,3364,3369,3374,3379,3384,3389,3394,3399,3404,3409,3414,3420,3426,3432,3437,3442,3447,3453,3458,3463,3468,3473,3479,3484,3489,3495,3500,3505,3510,3516,3522,3527,3533,3539,3544,3549,3554,3559,3565,3571,3577,3582,3587,3593,3599,3604,3609,3614,3620,3626,3632,3638,3643,3649,3655,3661,3666,3671,3676,3682,3687,3692,3697,3702,3707,3712,3717,3722,3727,3732,3737,3742,3747,3753,3759,3765,3771,3776,3782,3788,3793,3798,3804,3810,3815,3821,3826,3831,3837,3843,3849,3855,3861,3866,3871,3877,3883,3889,3895,3901,3907,3913,3919,3925,3931,3937,3943,3949,3954,3960,3966,3972,3978,3984,3990,3996,4002,4008,4014,4020,4026,4031,4037,4043,4048,4054,4060,4066,4072,4078,4083,4088,4093,4099,4104,4109,4114,4120,4126,4132,4138,4144,4150,4156,4162,4168,4174,4179,4184,4189,4194,4199,4204,4210,4216,4222,4228,4233,4238,4243,4248,4254,4260,4265,4271,4277,4282,4287,4292,4297,4302,4307,4312,4317,4322,4327,4332,4337,4342,4347],{"__ignoreMap":20},[25,2959,2960],{"class":27,"line":28},[25,2961,2962],{},"// API キーやIDを管理する設定オブジェクト\n",[25,2964,2965],{"class":27,"line":35},[25,2966,2312],{},[25,2968,2969],{"class":27,"line":41},[25,2970,2317],{},[25,2972,2973],{"class":27,"line":46},[25,2974,2322],{},[25,2976,2977],{"class":27,"line":52},[25,2978,2327],{},[25,2980,2981],{"class":27,"line":58},[25,2982,2332],{},[25,2984,2985],{"class":27,"line":63},[25,2986,2337],{},[25,2988,2989],{"class":27,"line":69},[25,2990,2342],{},[25,2992,2993],{"class":27,"line":75},[25,2994,32],{"emptyLinePlaceholder":31},[25,2996,2997],{"class":27,"line":80},[25,2998,2999],{},"// Notionの共通ヘッダー設定\n",[25,3001,3002],{"class":27,"line":86},[25,3003,3004],{},"const NOTION_HEADERS = {\n",[25,3006,3007],{"class":27,"line":92},[25,3008,3009],{},"  'Authorization': `Bearer ${CONFIG.NOTION_API_KEY}`,\n",[25,3011,3012],{"class":27,"line":97},[25,3013,3014],{},"  'Notion-Version': CONFIG.NOTION_VERSION,\n",[25,3016,3017],{"class":27,"line":103},[25,3018,3019],{},"  'Content-Type': 'application/json'\n",[25,3021,3022],{"class":27,"line":109},[25,3023,2342],{},[25,3025,3026],{"class":27,"line":114},[25,3027,32],{"emptyLinePlaceholder":31},[25,3029,3030],{"class":27,"line":120},[25,3031,32],{"emptyLinePlaceholder":31},[25,3033,3034],{"class":27,"line":126},[25,3035,32],{"emptyLinePlaceholder":31},[25,3037,3038],{"class":27,"line":132},[25,3039,32],{"emptyLinePlaceholder":31},[25,3041,3042],{"class":27,"line":138},[25,3043,32],{"emptyLinePlaceholder":31},[25,3045,3046],{"class":27,"line":144},[25,3047,3048],{},"// メイン同期処理\n",[25,3050,3051],{"class":27,"line":150},[25,3052,2668],{},[25,3054,3055],{"class":27,"line":156},[25,3056,2673],{},[25,3058,3059],{"class":27,"line":162},[25,3060,3061],{},"    // タスク一覧の取得\n",[25,3063,3064],{"class":27,"line":168},[25,3065,2678],{},[25,3067,3068],{"class":27,"line":174},[25,3069,2683],{},[25,3071,3072],{"class":27,"line":180},[25,3073,2688],{},[25,3075,3076],{"class":27,"line":186},[25,3077,32],{"emptyLinePlaceholder":31},[25,3079,3080],{"class":27,"line":191},[25,3081,3082],{},"    // 現在の状態を構築\n",[25,3084,3085],{"class":27,"line":197},[25,3086,3087],{},"    console.log(\"現在の状態を構築\")\n",[25,3089,3090],{"class":27,"line":203},[25,3091,2697],{},[25,3093,3094],{"class":27,"line":1084},[25,3095,32],{"emptyLinePlaceholder":31},[25,3097,3098],{"class":27,"line":1090},[25,3099,2702],{},[25,3101,3102],{"class":27,"line":1096},[25,3103,1163],{},[25,3105,3106],{"class":27,"line":1102},[25,3107,32],{"emptyLinePlaceholder":31},[25,3109,3110],{"class":27,"line":1108},[25,3111,3112],{},"    console.log(\"タスクの変更を処理\")\n",[25,3114,3115],{"class":27,"line":1113},[25,3116,3117],{},"    // タスクの変更を処理\n",[25,3119,3120],{"class":27,"line":1119},[25,3121,2697],{},[25,3123,3124],{"class":27,"line":1125},[25,3125,3126],{},"      console.log(\"currentState\", currentState[task.id])\n",[25,3128,3129],{"class":27,"line":1131},[25,3130,3131],{},"      console.log(\"previousState\", previousState[task.id])\n",[25,3133,3134],{"class":27,"line":1137},[25,3135,2719],{},[25,3137,3138],{"class":27,"line":1143},[25,3139,1163],{},[25,3141,3142],{"class":27,"line":1149},[25,3143,32],{"emptyLinePlaceholder":31},[25,3145,3146],{"class":27,"line":1154},[25,3147,3148],{},"    // 削除されたタスクを処理\n",[25,3150,3151],{"class":27,"line":1160},[25,3152,3153],{},"    console.log(\"削除されたタスクを処理\")\n",[25,3155,3156],{"class":27,"line":1166},[25,3157,2732],{},[25,3159,3160],{"class":27,"line":1172},[25,3161,32],{"emptyLinePlaceholder":31},[25,3163,3164],{"class":27,"line":1177},[25,3165,3166],{},"    // 現在の状態を保存\n",[25,3168,3169],{"class":27,"line":1183},[25,3170,3171],{},"    console.log(\"現在の状態を保存\")\n",[25,3173,3175],{"class":27,"line":3174},50,[25,3176,2737],{},[25,3178,3180],{"class":27,"line":3179},51,[25,3181,32],{"emptyLinePlaceholder":31},[25,3183,3185],{"class":27,"line":3184},52,[25,3186,2746],{},[25,3188,3190],{"class":27,"line":3189},53,[25,3191,2751],{},[25,3193,3195],{"class":27,"line":3194},54,[25,3196,1038],{},[25,3198,3200],{"class":27,"line":3199},55,[25,3201,1186],{},[25,3203,3205],{"class":27,"line":3204},56,[25,3206,32],{"emptyLinePlaceholder":31},[25,3208,3210],{"class":27,"line":3209},57,[25,3211,32],{"emptyLinePlaceholder":31},[25,3213,3215],{"class":27,"line":3214},58,[25,3216,32],{"emptyLinePlaceholder":31},[25,3218,3220],{"class":27,"line":3219},59,[25,3221,32],{"emptyLinePlaceholder":31},[25,3223,3225],{"class":27,"line":3224},60,[25,3226,2359],{},[25,3228,3230],{"class":27,"line":3229},61,[25,3231,2364],{},[25,3233,3235],{"class":27,"line":3234},62,[25,3236,2369],{},[25,3238,3240],{"class":27,"line":3239},63,[25,3241,2374],{},[25,3243,3245],{"class":27,"line":3244},64,[25,3246,2379],{},[25,3248,3250],{"class":27,"line":3249},65,[25,3251,2384],{},[25,3253,3255],{"class":27,"line":3254},66,[25,3256,2389],{},[25,3258,3260],{"class":27,"line":3259},67,[25,3261,2394],{},[25,3263,3265],{"class":27,"line":3264},68,[25,3266,2399],{},[25,3268,3270],{"class":27,"line":3269},69,[25,3271,2404],{},[25,3273,3275],{"class":27,"line":3274},70,[25,3276,2409],{},[25,3278,3280],{"class":27,"line":3279},71,[25,3281,2414],{},[25,3283,3285],{"class":27,"line":3284},72,[25,3286,2419],{},[25,3288,3290],{"class":27,"line":3289},73,[25,3291,2424],{},[25,3293,3295],{"class":27,"line":3294},74,[25,3296,1169],{},[25,3298,3300],{"class":27,"line":3299},75,[25,3301,32],{"emptyLinePlaceholder":31},[25,3303,3305],{"class":27,"line":3304},76,[25,3306,3307],{},"  console.log(\"next_cursor\", JSON.parse(response.getContentText()).next_cursor)\n",[25,3309,3311],{"class":27,"line":3310},77,[25,3312,3313],{},"  return JSON.parse(response.getContentText()).results;\n",[25,3315,3317],{"class":27,"line":3316},78,[25,3318,1186],{},[25,3320,3322],{"class":27,"line":3321},79,[25,3323,32],{"emptyLinePlaceholder":31},[25,3325,3327],{"class":27,"line":3326},80,[25,3328,2441],{},[25,3330,3332],{"class":27,"line":3331},81,[25,3333,2446],{},[25,3335,3337],{"class":27,"line":3336},82,[25,3338,2451],{},[25,3340,3342],{"class":27,"line":3341},83,[25,3343,2456],{},[25,3345,3347],{"class":27,"line":3346},84,[25,3348,2461],{},[25,3350,3352],{"class":27,"line":3351},85,[25,3353,2384],{},[25,3355,3357],{"class":27,"line":3356},86,[25,3358,2389],{},[25,3360,3362],{"class":27,"line":3361},87,[25,3363,2474],{},[25,3365,3367],{"class":27,"line":3366},88,[25,3368,2479],{},[25,3370,3372],{"class":27,"line":3371},89,[25,3373,2484],{},[25,3375,3377],{"class":27,"line":3376},90,[25,3378,2489],{},[25,3380,3382],{"class":27,"line":3381},91,[25,3383,2494],{},[25,3385,3387],{"class":27,"line":3386},92,[25,3388,2414],{},[25,3390,3392],{"class":27,"line":3391},93,[25,3393,2503],{},[25,3395,3397],{"class":27,"line":3396},94,[25,3398,2424],{},[25,3400,3402],{"class":27,"line":3401},95,[25,3403,1169],{},[25,3405,3407],{"class":27,"line":3406},96,[25,3408,1186],{},[25,3410,3412],{"class":27,"line":3411},97,[25,3413,32],{"emptyLinePlaceholder":31},[25,3415,3417],{"class":27,"line":3416},98,[25,3418,3419],{},"// カレンダーイベントの新規作成\n",[25,3421,3423],{"class":27,"line":3422},99,[25,3424,3425],{},"function createCalendarEvent(task, CALENDAR_ID) {\n",[25,3427,3429],{"class":27,"line":3428},100,[25,3430,3431],{},"  const calendar = CalendarApp.getCalendarById(CALENDAR_ID);\n",[25,3433,3435],{"class":27,"line":3434},101,[25,3436,2542],{},[25,3438,3440],{"class":27,"line":3439},102,[25,3441,2547],{},[25,3443,3445],{"class":27,"line":3444},103,[25,3446,32],{"emptyLinePlaceholder":31},[25,3448,3450],{"class":27,"line":3449},104,[25,3451,3452],{},"  if (!dueDate) return\n",[25,3454,3456],{"class":27,"line":3455},105,[25,3457,32],{"emptyLinePlaceholder":31},[25,3459,3461],{"class":27,"line":3460},106,[25,3462,2556],{},[25,3464,3466],{"class":27,"line":3465},107,[25,3467,2561],{},[25,3469,3471],{"class":27,"line":3470},108,[25,3472,2566],{},[25,3474,3476],{"class":27,"line":3475},109,[25,3477,3478],{},"    { description: `Notion Task ID: ${task.id}\\nURL: ${task.url}` }\n",[25,3480,3482],{"class":27,"line":3481},110,[25,3483,2576],{},[25,3485,3487],{"class":27,"line":3486},111,[25,3488,32],{"emptyLinePlaceholder":31},[25,3490,3492],{"class":27,"line":3491},112,[25,3493,3494],{},"  console.log(event)\n",[25,3496,3498],{"class":27,"line":3497},113,[25,3499,2585],{},[25,3501,3503],{"class":27,"line":3502},114,[25,3504,1186],{},[25,3506,3508],{"class":27,"line":3507},115,[25,3509,32],{"emptyLinePlaceholder":31},[25,3511,3513],{"class":27,"line":3512},116,[25,3514,3515],{},"// カレンダーイベントの更新\n",[25,3517,3519],{"class":27,"line":3518},117,[25,3520,3521],{},"function updateCalendarEvent(eventId, task) {\n",[25,3523,3525],{"class":27,"line":3524},118,[25,3526,2537],{},[25,3528,3530],{"class":27,"line":3529},119,[25,3531,3532],{},"  const event = calendar.getEventById(eventId);\n",[25,3534,3536],{"class":27,"line":3535},120,[25,3537,3538],{},"  if (!event) return;\n",[25,3540,3542],{"class":27,"line":3541},121,[25,3543,32],{"emptyLinePlaceholder":31},[25,3545,3547],{"class":27,"line":3546},122,[25,3548,2542],{},[25,3550,3552],{"class":27,"line":3551},123,[25,3553,2547],{},[25,3555,3557],{"class":27,"line":3556},124,[25,3558,32],{"emptyLinePlaceholder":31},[25,3560,3562],{"class":27,"line":3561},125,[25,3563,3564],{},"  event.setTitle(title);\n",[25,3566,3568],{"class":27,"line":3567},126,[25,3569,3570],{},"  event.setAllDayDate(dueDate);\n",[25,3572,3574],{"class":27,"line":3573},127,[25,3575,3576],{},"  event.setDescription(`Notion Task ID: ${task.id}\\nURL: ${task.url}`);\n",[25,3578,3580],{"class":27,"line":3579},128,[25,3581,1186],{},[25,3583,3585],{"class":27,"line":3584},129,[25,3586,32],{"emptyLinePlaceholder":31},[25,3588,3590],{"class":27,"line":3589},130,[25,3591,3592],{},"// カレンダーイベントの削除\n",[25,3594,3596],{"class":27,"line":3595},131,[25,3597,3598],{},"function deleteCalendarEvent(eventId) {\n",[25,3600,3602],{"class":27,"line":3601},132,[25,3603,2537],{},[25,3605,3607],{"class":27,"line":3606},133,[25,3608,32],{"emptyLinePlaceholder":31},[25,3610,3612],{"class":27,"line":3611},134,[25,3613,2673],{},[25,3615,3617],{"class":27,"line":3616},135,[25,3618,3619],{},"    const event = calendar.getEventById(eventId);\n",[25,3621,3623],{"class":27,"line":3622},136,[25,3624,3625],{},"    if (event) {\n",[25,3627,3629],{"class":27,"line":3628},137,[25,3630,3631],{},"      event.deleteEvent();\n",[25,3633,3635],{"class":27,"line":3634},138,[25,3636,3637],{},"      return true\n",[25,3639,3641],{"class":27,"line":3640},139,[25,3642,1526],{},[25,3644,3646],{"class":27,"line":3645},140,[25,3647,3648],{},"  } catch (e) {\n",[25,3650,3652],{"class":27,"line":3651},141,[25,3653,3654],{},"    console.error(\"削除されている\", e)\n",[25,3656,3658],{"class":27,"line":3657},142,[25,3659,3660],{},"    return false\n",[25,3662,3664],{"class":27,"line":3663},143,[25,3665,32],{"emptyLinePlaceholder":31},[25,3667,3669],{"class":27,"line":3668},144,[25,3670,1038],{},[25,3672,3674],{"class":27,"line":3673},145,[25,3675,32],{"emptyLinePlaceholder":31},[25,3677,3679],{"class":27,"line":3678},146,[25,3680,3681],{},"  return false\n",[25,3683,3685],{"class":27,"line":3684},147,[25,3686,1186],{},[25,3688,3690],{"class":27,"line":3689},148,[25,3691,32],{"emptyLinePlaceholder":31},[25,3693,3695],{"class":27,"line":3694},149,[25,3696,2610],{},[25,3698,3700],{"class":27,"line":3699},150,[25,3701,2615],{},[25,3703,3705],{"class":27,"line":3704},151,[25,3706,2620],{},[25,3708,3710],{"class":27,"line":3709},152,[25,3711,2625],{},[25,3713,3715],{"class":27,"line":3714},153,[25,3716,2630],{},[25,3718,3720],{"class":27,"line":3719},154,[25,3721,2635],{},[25,3723,3725],{"class":27,"line":3724},155,[25,3726,2640],{},[25,3728,3730],{"class":27,"line":3729},156,[25,3731,2645],{},[25,3733,3735],{"class":27,"line":3734},157,[25,3736,2650],{},[25,3738,3740],{"class":27,"line":3739},158,[25,3741,1186],{},[25,3743,3745],{"class":27,"line":3744},159,[25,3746,32],{"emptyLinePlaceholder":31},[25,3748,3750],{"class":27,"line":3749},160,[25,3751,3752],{},"// キャッシュから前回の状態を取得\n",[25,3754,3756],{"class":27,"line":3755},161,[25,3757,3758],{},"function getPreviousState() {\n",[25,3760,3762],{"class":27,"line":3761},162,[25,3763,3764],{},"  const cache = CacheService.getScriptCache();\n",[25,3766,3768],{"class":27,"line":3767},163,[25,3769,3770],{},"  const data = cache.get('previousState');\n",[25,3772,3774],{"class":27,"line":3773},164,[25,3775,32],{"emptyLinePlaceholder":31},[25,3777,3779],{"class":27,"line":3778},165,[25,3780,3781],{},"  console.log(JSON.parse(data))\n",[25,3783,3785],{"class":27,"line":3784},166,[25,3786,3787],{},"  return data ? JSON.parse(data) : {};\n",[25,3789,3791],{"class":27,"line":3790},167,[25,3792,1186],{},[25,3794,3796],{"class":27,"line":3795},168,[25,3797,32],{"emptyLinePlaceholder":31},[25,3799,3801],{"class":27,"line":3800},169,[25,3802,3803],{},"// 現在の状態をキャッシュに保存（6時間有効）\n",[25,3805,3807],{"class":27,"line":3806},170,[25,3808,3809],{},"function savePreviousState(state) {\n",[25,3811,3813],{"class":27,"line":3812},171,[25,3814,3764],{},[25,3816,3818],{"class":27,"line":3817},172,[25,3819,3820],{},"  cache.put('previousState', JSON.stringify(state), 21600);\n",[25,3822,3824],{"class":27,"line":3823},173,[25,3825,1186],{},[25,3827,3829],{"class":27,"line":3828},174,[25,3830,32],{"emptyLinePlaceholder":31},[25,3832,3834],{"class":27,"line":3833},175,[25,3835,3836],{},"// タスクの変更を検出して必要な処理を実行\n",[25,3838,3840],{"class":27,"line":3839},176,[25,3841,3842],{},"function processTaskChanges(task, previousState, currentState) {\n",[25,3844,3846],{"class":27,"line":3845},177,[25,3847,3848],{},"  const taskId = task.id;\n",[25,3850,3852],{"class":27,"line":3851},178,[25,3853,3854],{},"  const prevTask = previousState[taskId];\n",[25,3856,3858],{"class":27,"line":3857},179,[25,3859,3860],{},"  const currentTask = currentState[taskId];\n",[25,3862,3864],{"class":27,"line":3863},180,[25,3865,32],{"emptyLinePlaceholder":31},[25,3867,3869],{"class":27,"line":3868},181,[25,3870,2673],{},[25,3872,3874],{"class":27,"line":3873},182,[25,3875,3876],{},"    if (!prevTask && task.properties.DueDate?.date?.start) {\n",[25,3878,3880],{"class":27,"line":3879},183,[25,3881,3882],{},"      // 新規タスクの処理\n",[25,3884,3886],{"class":27,"line":3885},184,[25,3887,3888],{},"      console.log(\"新規タスクの処理\")\n",[25,3890,3892],{"class":27,"line":3891},185,[25,3893,3894],{},"      const calendarEventId = createCalendarEvent(task, CONFIG.CALENDAR_ID);\n",[25,3896,3898],{"class":27,"line":3897},186,[25,3899,3900],{},"      updateNotionTask(taskId, calendarEventId);\n",[25,3902,3904],{"class":27,"line":3903},187,[25,3905,3906],{},"    } else if (prevTask && prevTask.dueDate) {\n",[25,3908,3910],{"class":27,"line":3909},188,[25,3911,3912],{},"      // 既存タスクの処理\n",[25,3914,3916],{"class":27,"line":3915},189,[25,3917,3918],{},"      console.log(\"既存タスクの処理\")\n",[25,3920,3922],{"class":27,"line":3921},190,[25,3923,3924],{},"      const calendarEventId = prevTask.calendarEventId;\n",[25,3926,3928],{"class":27,"line":3927},191,[25,3929,3930],{},"      if (currentTask.status === 'Deleted') {\n",[25,3932,3934],{"class":27,"line":3933},192,[25,3935,3936],{},"        // 削除されたタスクの処理\n",[25,3938,3940],{"class":27,"line":3939},193,[25,3941,3942],{},"        console.log(\"削除されたタスクの処理\")\n",[25,3944,3946],{"class":27,"line":3945},194,[25,3947,3948],{},"        deleteCalendarEvent(calendarEventId);\n",[25,3950,3952],{"class":27,"line":3951},195,[25,3953,32],{"emptyLinePlaceholder":31},[25,3955,3957],{"class":27,"line":3956},196,[25,3958,3959],{},"      } else if (\n",[25,3961,3963],{"class":27,"line":3962},197,[25,3964,3965],{},"        prevTask.title !== currentTask.title ||\n",[25,3967,3969],{"class":27,"line":3968},198,[25,3970,3971],{},"        prevTask.dueDate !== currentTask.dueDate\n",[25,3973,3975],{"class":27,"line":3974},199,[25,3976,3977],{},"      ) {\n",[25,3979,3981],{"class":27,"line":3980},200,[25,3982,3983],{},"        // タスクが更新された場合の処理\n",[25,3985,3987],{"class":27,"line":3986},201,[25,3988,3989],{},"        console.log(\"タスクが更新された場合の処理\")\n",[25,3991,3993],{"class":27,"line":3992},202,[25,3994,3995],{},"        updateCalendarEvent(calendarEventId, task);\n",[25,3997,3999],{"class":27,"line":3998},203,[25,4000,4001],{},"      } else if (currentTask.status === 'Done') {\n",[25,4003,4005],{"class":27,"line":4004},204,[25,4006,4007],{},"        // ステータスが完了タスクの処理\n",[25,4009,4011],{"class":27,"line":4010},205,[25,4012,4013],{},"        console.log(\"ステータスが完了タスクの処理\")\n",[25,4015,4017],{"class":27,"line":4016},206,[25,4018,4019],{},"        const reslut = deleteCalendarEvent(calendarEventId);\n",[25,4021,4023],{"class":27,"line":4022},207,[25,4024,4025],{},"        console.log(\"ステータスが完了タスクの処理終了 google カレンダー削除 \",reslut)\n",[25,4027,4029],{"class":27,"line":4028},208,[25,4030,32],{"emptyLinePlaceholder":31},[25,4032,4034],{"class":27,"line":4033},209,[25,4035,4036],{},"        // 削除がない場合終了\n",[25,4038,4040],{"class":27,"line":4039},210,[25,4041,4042],{},"        if (!reslut) return\n",[25,4044,4046],{"class":27,"line":4045},211,[25,4047,32],{"emptyLinePlaceholder":31},[25,4049,4051],{"class":27,"line":4050},212,[25,4052,4053],{},"        console.log(\"別カレンダーに完了を作成\")\n",[25,4055,4057],{"class":27,"line":4056},213,[25,4058,4059],{},"        // 別カレンダーに完了を作成\n",[25,4061,4063],{"class":27,"line":4062},214,[25,4064,4065],{},"        createCalendarEvent(task, CONFIG.DONE_CALENDAR_ID);\n",[25,4067,4069],{"class":27,"line":4068},215,[25,4070,4071],{},"        updateNotionTask(taskId, \"done\");\n",[25,4073,4075],{"class":27,"line":4074},216,[25,4076,4077],{},"        console.log(\"別カレンダーに完了を作成終了\")\n",[25,4079,4081],{"class":27,"line":4080},217,[25,4082,2503],{},[25,4084,4086],{"class":27,"line":4085},218,[25,4087,1526],{},[25,4089,4091],{"class":27,"line":4090},219,[25,4092,2746],{},[25,4094,4096],{"class":27,"line":4095},220,[25,4097,4098],{},"    logError(error, `タスク処理エラー - TaskID: ${taskId}`);\n",[25,4100,4102],{"class":27,"line":4101},221,[25,4103,1038],{},[25,4105,4107],{"class":27,"line":4106},222,[25,4108,1186],{},[25,4110,4112],{"class":27,"line":4111},223,[25,4113,32],{"emptyLinePlaceholder":31},[25,4115,4117],{"class":27,"line":4116},224,[25,4118,4119],{},"// 削除されたタスクの処理\n",[25,4121,4123],{"class":27,"line":4122},225,[25,4124,4125],{},"function processDeletedTasks(previousState, currentState) {\n",[25,4127,4129],{"class":27,"line":4128},226,[25,4130,4131],{},"  Object.keys(previousState).forEach(taskId => {\n",[25,4133,4135],{"class":27,"line":4134},227,[25,4136,4137],{},"    if (!currentState[taskId]) {\n",[25,4139,4141],{"class":27,"line":4140},228,[25,4142,4143],{},"      const calendarEventId = previousState[taskId].calendarEventId;\n",[25,4145,4147],{"class":27,"line":4146},229,[25,4148,4149],{},"      if (calendarEventId) {\n",[25,4151,4153],{"class":27,"line":4152},230,[25,4154,4155],{},"        try {\n",[25,4157,4159],{"class":27,"line":4158},231,[25,4160,4161],{},"          deleteCalendarEvent(calendarEventId);\n",[25,4163,4165],{"class":27,"line":4164},232,[25,4166,4167],{},"        } catch (error) {\n",[25,4169,4171],{"class":27,"line":4170},233,[25,4172,4173],{},"          logError(error, `削除済みタスクの処理エラー - TaskID: ${taskId}`);\n",[25,4175,4177],{"class":27,"line":4176},234,[25,4178,2414],{},[25,4180,4182],{"class":27,"line":4181},235,[25,4183,2503],{},[25,4185,4187],{"class":27,"line":4186},236,[25,4188,1526],{},[25,4190,4192],{"class":27,"line":4191},237,[25,4193,1169],{},[25,4195,4197],{"class":27,"line":4196},238,[25,4198,1186],{},[25,4200,4202],{"class":27,"line":4201},239,[25,4203,32],{"emptyLinePlaceholder":31},[25,4205,4207],{"class":27,"line":4206},240,[25,4208,4209],{},"// エラーログの記録\n",[25,4211,4213],{"class":27,"line":4212},241,[25,4214,4215],{},"function logError(error, context = '') {\n",[25,4217,4219],{"class":27,"line":4218},242,[25,4220,4221],{},"  console.error(`同期エラー ${context}:`, error);\n",[25,4223,4225],{"class":27,"line":4224},243,[25,4226,4227],{},"  // 必要に応じて追加のエラーハンドリング（Slack通知やメール送信など）\n",[25,4229,4231],{"class":27,"line":4230},244,[25,4232,32],{"emptyLinePlaceholder":31},[25,4234,4236],{"class":27,"line":4235},245,[25,4237,32],{"emptyLinePlaceholder":31},[25,4239,4241],{"class":27,"line":4240},246,[25,4242,1186],{},[25,4244,4246],{"class":27,"line":4245},247,[25,4247,32],{"emptyLinePlaceholder":31},[25,4249,4251],{"class":27,"line":4250},248,[25,4252,4253],{},"// 特定のキャッシュキーを削除する関数\n",[25,4255,4257],{"class":27,"line":4256},249,[25,4258,4259],{},"function clearPreviousStateCache() {\n",[25,4261,4263],{"class":27,"line":4262},250,[25,4264,3764],{},[25,4266,4268],{"class":27,"line":4267},251,[25,4269,4270],{},"  cache.remove('previousState');\n",[25,4272,4274],{"class":27,"line":4273},252,[25,4275,4276],{},"  console.log(\"キャッシュ 'previousState' を削除しました\");\n",[25,4278,4280],{"class":27,"line":4279},253,[25,4281,1186],{},[25,4283,4285],{"class":27,"line":4284},254,[25,4286,32],{"emptyLinePlaceholder":31},[25,4288,4290],{"class":27,"line":4289},255,[25,4291,2806],{},[25,4293,4295],{"class":27,"line":4294},256,[25,4296,2811],{},[25,4298,4300],{"class":27,"line":4299},257,[25,4301,2816],{},[25,4303,4305],{"class":27,"line":4304},258,[25,4306,2821],{},[25,4308,4310],{"class":27,"line":4309},259,[25,4311,2826],{},[25,4313,4315],{"class":27,"line":4314},260,[25,4316,1169],{},[25,4318,4320],{"class":27,"line":4319},261,[25,4321,32],{"emptyLinePlaceholder":31},[25,4323,4325],{"class":27,"line":4324},262,[25,4326,2839],{},[25,4328,4330],{"class":27,"line":4329},263,[25,4331,2844],{},[25,4333,4335],{"class":27,"line":4334},264,[25,4336,2849],{},[25,4338,4340],{"class":27,"line":4339},265,[25,4341,2854],{},[25,4343,4345],{"class":27,"line":4344},266,[25,4346,2859],{},[25,4348,4350],{"class":27,"line":4349},267,[25,4351,1186],{},[11,4353,4354],{"id":4354},"注意点",[1402,4356,4357,4368,4382],{},[307,4358,4359,4360],{},"APIキーの管理\n",[304,4361,4362,4365],{},[307,4363,4364],{},"重要な認証情報は適切に管理",[307,4366,4367],{},"プロジェクト設定での管理を推奨",[307,4369,4370,4371],{},"エラー処理\n",[304,4372,4373,4376,4379],{},[307,4374,4375],{},"ネットワークエラーへの対応",[307,4377,4378],{},"API制限の考慮→ noiton api (１回で100件のページしかとれない)とgasの制限に注意 (稼働時間、fetchのリクエスト回数など)",[307,4380,4381],{},"データ整合性の確保→手動編集する場合はnotinのみ編集する",[307,4383,4384,4385],{},"パフォーマンス\n",[304,4386,4387,4390,4393],{},[307,4388,4389],{},"不要な API 呼び出しの削減",[307,4391,4392],{},"キャッシュの活用",[307,4394,4395],{},"適切な実行間隔の設定",[11,4397,518],{"id":518},[238,4399,4400],{},"このシステムにより、NotionとGoogleカレンダーの連携が自動化され、タスク管理がより効率的になります。実装方法は目的や好みに応じて選択できます。",[238,4402,4403],{},"エラー処理やパフォーマンスの最適化など、実運用時の考慮点も含めて実装することで、より安定した運用が可能になります。",[11,4405,4406],{"id":4406},"参考文献",[304,4408,4409,4416,4423],{},[307,4410,4411],{},[1544,4412,4415],{"href":4413,"rel":4414},"https://developers.notion.com/",[1548],"Notion API Documentation",[307,4417,4418],{},[1544,4419,4422],{"href":4420,"rel":4421},"https://developers.google.com/calendar",[1548],"Google Calendar API Documentation",[307,4424,4425],{},[1544,4426,4429],{"href":4427,"rel":4428},"https://developers.google.com/apps-script",[1548],"Google Apps Script Documentation",[208,4431,210],{},{"title":20,"searchDepth":35,"depth":35,"links":4433},[4434,4435,4439,4449,4454,4455,4456,4457,4458],{"id":890,"depth":35,"text":890},{"id":2258,"depth":35,"text":2258,"children":4436},[4437,4438],{"id":2261,"depth":41,"text":2261},{"id":2278,"depth":41,"text":2278},{"id":2295,"depth":35,"text":2295,"children":4440},[4441,4442,4443,4444,4445,4446,4447,4448],{"id":2298,"depth":41,"text":2299},{"id":2345,"depth":41,"text":2346},{"id":2349,"depth":41,"text":2349},{"id":2431,"depth":41,"text":2431},{"id":2518,"depth":41,"text":2519},{"id":2522,"depth":41,"text":2522},{"id":2592,"depth":41,"text":2593},{"id":2657,"depth":41,"text":2658},{"id":2762,"depth":35,"text":2762,"children":4450},[4451,4452,4453],{"id":2765,"depth":41,"text":2766},{"id":2780,"depth":41,"text":2781},{"id":2795,"depth":41,"text":2796},{"id":2866,"depth":35,"text":2286},{"id":2889,"depth":35,"text":2889},{"id":4354,"depth":35,"text":4354},{"id":518,"depth":35,"text":518},{"id":4406,"depth":35,"text":4406},[1443],"2024-11-03T00:00:00.000Z","Notionのタスクの期限日をGoogleカレンダーへ自動同期し、完了タスクは別カレンダーへ移すシステムの実装方法",{},"/posts/notion-to-google-calender-1103",{"title":2245,"description":4461},"posts/notion-to-google-calender-1103",[1443,1452,4467,1451,4468],"Notion","Google Calender","UYNsU6I5BOAB7lrXRWuFN6DlO8thSRy133DJXQEh6-k",{"id":4471,"title":4472,"body":4473,"categories":4558,"cross_post":215,"date":4559,"description":4560,"extension":218,"image":20,"meta":4561,"navigation":31,"note_id":215,"note_url":215,"path":4562,"private":221,"qiita_id":215,"qiita_url":215,"seo":4563,"stem":4564,"tags":4565,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":4567},"posts/posts/nuxt-blog.md","nuxtを使ったblog",{"type":8,"value":4474,"toc":4553},[4475,4478,4484,4487,4490,4504,4507,4512,4515,4531,4534,4539,4542,4550],[238,4476,4477],{},"自分のブログを下記の構成でつくってみました。",[238,4479,4480],{},[1544,4481,4482],{"href":4482,"rel":4483},"https://tori-dev.com/",[1548],[11,4485,4486],{"id":4486},"機能",[238,4488,4489],{},"@nuxt/contentを使用したブログ作成のメイン機能について説明します。以下の機能が含まれます。",[304,4491,4492,4495,4498,4501],{},[307,4493,4494],{},"カテゴリー",[307,4496,4497],{},"タグ",[307,4499,4500],{},"関連記事",[307,4502,4503],{},"目次",[238,4505,4506],{},"また、アイキャッチの自動生成は現在のところ実装されていませんが、今後のアップデートで追加予定です。",[11,4508,4510],{"id":4509},"記事の作成方法",[904,4511,4509],{},[238,4513,4514],{},"ブログ記事の作成方法は以下のいずれかを選ぶことができます。",[1402,4516,4517,4528],{},[307,4518,4519,4520],{},"ローカルでダウンロードしてデプロイする方法\n",[304,4521,4522,4525],{},[307,4523,4524],{},"Notionを使用する場合",[307,4526,4527],{},"Markdownを使用する場合（Google Drive + Markdownツールなどを利用）",[307,4529,4530],{},"microCMS（ヘッドレスCMS）を使用する方法",[238,4532,4533],{},"それぞれの方法によって、使いやすさや環境に合わせて選択できます。",[11,4535,4537],{"id":4536},"デプロイ先",[904,4538,4536],{},[238,4540,4541],{},"作成したブログを公開するためのデプロイ先は、以下のオプションがあります。",[1402,4543,4544,4547],{},[307,4545,4546],{},"Firebase Hosting",[307,4548,4549],{},"Netlify",[238,4551,4552],{},"どちらのプラットフォームも高性能で信頼性があり、使いやすいインターフェースを提供しています。",{"title":20,"searchDepth":35,"depth":35,"links":4554},[4555,4556,4557],{"id":4486,"depth":35,"text":4486},{"id":4509,"depth":35,"text":4509},{"id":4536,"depth":35,"text":4536},[1443],"2023-06-13T00:00:00.000Z","@nuxt/contentでカテゴリー・タグ・関連記事・目次機能を備えた自分のブログを作った構成の紹介",{},"/posts/nuxt-blog",{"title":4472,"description":4560},"posts/nuxt-blog",[1443,4566],"nuxt","qHAf8eLhZLVjnD_Zd6RunYOK8TGmelSJtGlmSyL47zc",{"id":4569,"title":4570,"body":4571,"categories":4603,"cross_post":215,"date":4604,"description":4605,"extension":218,"image":20,"meta":4606,"navigation":31,"note_id":215,"note_url":215,"path":4607,"private":221,"qiita_id":215,"qiita_url":215,"seo":4608,"stem":4609,"tags":4610,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":4611},"posts/posts/nuxtLifeCycleMemo.md","nuxtライフサイクルmemo",{"type":8,"value":4572,"toc":4601},[4573],[304,4574,4575,4578,4589,4595],{},[307,4576,4577],{},"asyncDataはssaのときはクライアントでssgのときはgenerate時にしかできない",[307,4579,4580,4581],{},"以下はクライアントで動く\n",[304,4582,4583,4586],{},[307,4584,4585],{},"created：インスタンス初期化時、DOMが生成される前",[307,4587,4588],{},"mounted：インスタンス初期化時、DOMが生成された後",[307,4590,4591,4592],{},"computed： 算出プロパティ",[904,4593,4594],{},"getter,setterが使える",[307,4596,4597,4598],{},"watch：",[904,4599,4600],{},"データの変化をトリガにしたフック",{"title":20,"searchDepth":35,"depth":35,"links":4602},[],[1443],"2021-11-10T00:00:00.000Z","NuxtのasyncData・created・mounted・computed・watchなどライフサイクルの挙動を整理したメモ",{},"/posts/nuxtlifecyclememo",{"title":4570,"description":4605},"posts/nuxtLifeCycleMemo",[1443,4566],"istfZbodTDu1ykAFnNNP4fmGEzSjlnfGucXNM-WALyY",{"id":4613,"title":4614,"body":4615,"categories":5093,"cross_post":215,"date":4460,"description":5095,"extension":218,"image":20,"meta":5096,"navigation":31,"note_id":215,"note_url":215,"path":5097,"private":221,"qiita_id":215,"qiita_url":215,"seo":5098,"stem":5099,"tags":5100,"topics":215,"updated":215,"zenn_emoji":215,"zenn_slug":215,"zenn_type":215,"zenn_url":215,"__hash__":5102},"posts/posts/try-react-signature-canvas.md","reactでサイン機能をするためにreact-signature-canvasためしてみた",{"type":8,"value":4616,"toc":5087},[4617,4622,4625,4629,4643,4646,4649,4652,5069,5071,5078,5085],[11,4618,4620],{"id":4619},"react-signature-canvas",[904,4621,4619],{},[238,4623,4624],{},"react-signature-canvasは、Reactアプリケーションでサイン機能を簡単に実装できる人気のライブラリ",[1204,4626,4628],{"id":4627},"主な特徴","主な特徴:",[304,4630,4631,4634,4637,4640],{},[307,4632,4633],{},"キャンバスベースのシンプルなサイン機能",[307,4635,4636],{},"タッチデバイスとマウスの両方に対応",[307,4638,4639],{},"サインのクリア、保存、読み込みなどの基本機能を提供",[307,4641,4642],{},"カスタマイズ可能なスタイルとオプション",[1204,4644,4645],{"id":4645},"やってみて",[238,4647,4648],{},"公式を見ながらやったら意外といけた",[238,4650,4651],{},"useRefを使うのと保存したデータURLを取り込んだ時に画像が小さくなってしまうとこに少し困った。",[15,4653,4655],{"className":17,"code":4654,"language":19,"meta":20,"style":20},"\nimport SignatureCanvas from \"react-signature-canvas\";\nimport { FC, useState, useRef, useEffect } from \"react\";\n\n\n\n\n/**\n * サイン\n *\n * @returns サイン\n */\nconst Signature: FC\u003CProps> = () => {\n\n\n  const [trimmedDataURL, setTrimmedDataURL] = useState\u003Cstring>(\"\");\n  const padRef = useRef\u003CSignatureCanvas>(null);\n\n\n  /* -------------------------------- Methods ------------------------------- */\n\n  /**\n   * サインクリア\n   *\n   */\n  const clear = () => {\n    padRef.current?.clear();\n  };\n\n  /**\n   *サインURlに変換保存\n   *\n   */\n  const trim = () => {\n    const url = padRef.current?.getTrimmedCanvas().toDataURL(\"image/png\");\n    if (url) {\n      setTrimmedDataURL(url);\n\n      // どこかへ保存する\n    }\n  };\n\n  useEffect(() => {\n    /**\n     *初期実行\n     *\n     */\n    const init = async () => {\n\n                const url = \"ここで保存先から取得\"\n          \n          // fromDataURLでSignatureCanvasに\n          // 保存したデータURL取り込むきちんと幅と高さを合わせないと画像が小さくなったりする\n        padRef.current?.fromDataURL(url, {\n          width: 800,\n          height: 200\n        });\n        setTrimmedDataURL(url);\n      }\n    };\n\n    init();\n  }, []);\n\n\n  return (\n    \u003C>\n      \u003CSignatureCanvas\n        penColor=\"white\"\n        backgroundColor=\"gray\"\n        canvasProps={{ width: 800, height: 200 }}\n        ref={padRef}\n      />\n      \u003Cdiv>\n        {trimmedDataURL ? (\n          \u003Cimg alt=\"signature\" src={trimmedDataURL} width={80} height={20} />\n        ) : null}\n      \u003C/div>Ï\n          \u003CButton onClick={() => clear()} >\n            clear\n          \u003C/Button>\n          \u003CButton onClick={() => trim()} >\n               trim\n          \u003C/Button>\n\n    \u003C/>\n  );\n};\n\n\n\n\n",[22,4656,4657,4661,4666,4671,4675,4679,4683,4687,4692,4697,4702,4707,4712,4717,4721,4725,4730,4735,4739,4743,4748,4752,4757,4762,4767,4772,4777,4782,4786,4790,4794,4799,4803,4807,4812,4817,4822,4827,4831,4836,4840,4844,4848,4853,4858,4863,4868,4873,4878,4882,4887,4892,4897,4902,4907,4912,4917,4922,4927,4931,4936,4940,4945,4950,4954,4958,4963,4968,4973,4978,4983,4988,4993,4998,5003,5008,5013,5018,5023,5028,5033,5038,5043,5048,5052,5056,5061,5065],{"__ignoreMap":20},[25,4658,4659],{"class":27,"line":28},[25,4660,32],{"emptyLinePlaceholder":31},[25,4662,4663],{"class":27,"line":35},[25,4664,4665],{},"import SignatureCanvas from \"react-signature-canvas\";\n",[25,4667,4668],{"class":27,"line":41},[25,4669,4670],{},"import { FC, useState, useRef, useEffect } from \"react\";\n",[25,4672,4673],{"class":27,"line":46},[25,4674,32],{"emptyLinePlaceholder":31},[25,4676,4677],{"class":27,"line":52},[25,4678,32],{"emptyLinePlaceholder":31},[25,4680,4681],{"class":27,"line":58},[25,4682,32],{"emptyLinePlaceholder":31},[25,4684,4685],{"class":27,"line":63},[25,4686,32],{"emptyLinePlaceholder":31},[25,4688,4689],{"class":27,"line":69},[25,4690,4691],{},"/**\n",[25,4693,4694],{"class":27,"line":75},[25,4695,4696],{}," * サイン\n",[25,4698,4699],{"class":27,"line":80},[25,4700,4701],{}," *\n",[25,4703,4704],{"class":27,"line":86},[25,4705,4706],{}," * @returns サイン\n",[25,4708,4709],{"class":27,"line":92},[25,4710,4711],{}," */\n",[25,4713,4714],{"class":27,"line":97},[25,4715,4716],{},"const Signature: FC\u003CProps> = () => {\n",[25,4718,4719],{"class":27,"line":103},[25,4720,32],{"emptyLinePlaceholder":31},[25,4722,4723],{"class":27,"line":109},[25,4724,32],{"emptyLinePlaceholder":31},[25,4726,4727],{"class":27,"line":114},[25,4728,4729],{},"  const [trimmedDataURL, setTrimmedDataURL] = useState\u003Cstring>(\"\");\n",[25,4731,4732],{"class":27,"line":120},[25,4733,4734],{},"  const padRef = useRef\u003CSignatureCanvas>(null);\n",[25,4736,4737],{"class":27,"line":126},[25,4738,32],{"emptyLinePlaceholder":31},[25,4740,4741],{"class":27,"line":132},[25,4742,32],{"emptyLinePlaceholder":31},[25,4744,4745],{"class":27,"line":138},[25,4746,4747],{},"  /* -------------------------------- Methods ------------------------------- */\n",[25,4749,4750],{"class":27,"line":144},[25,4751,32],{"emptyLinePlaceholder":31},[25,4753,4754],{"class":27,"line":150},[25,4755,4756],{},"  /**\n",[25,4758,4759],{"class":27,"line":156},[25,4760,4761],{},"   * サインクリア\n",[25,4763,4764],{"class":27,"line":162},[25,4765,4766],{},"   *\n",[25,4768,4769],{"class":27,"line":168},[25,4770,4771],{},"   */\n",[25,4773,4774],{"class":27,"line":174},[25,4775,4776],{},"  const clear = () => {\n",[25,4778,4779],{"class":27,"line":180},[25,4780,4781],{},"    padRef.current?.clear();\n",[25,4783,4784],{"class":27,"line":186},[25,4785,2650],{},[25,4787,4788],{"class":27,"line":191},[25,4789,32],{"emptyLinePlaceholder":31},[25,4791,4792],{"class":27,"line":197},[25,4793,4756],{},[25,4795,4796],{"class":27,"line":203},[25,4797,4798],{},"   *サインURlに変換保存\n",[25,4800,4801],{"class":27,"line":1084},[25,4802,4766],{},[25,4804,4805],{"class":27,"line":1090},[25,4806,4771],{},[25,4808,4809],{"class":27,"line":1096},[25,4810,4811],{},"  const trim = () => {\n",[25,4813,4814],{"class":27,"line":1102},[25,4815,4816],{},"    const url = padRef.current?.getTrimmedCanvas().toDataURL(\"image/png\");\n",[25,4818,4819],{"class":27,"line":1108},[25,4820,4821],{},"    if (url) {\n",[25,4823,4824],{"class":27,"line":1113},[25,4825,4826],{},"      setTrimmedDataURL(url);\n",[25,4828,4829],{"class":27,"line":1119},[25,4830,32],{"emptyLinePlaceholder":31},[25,4832,4833],{"class":27,"line":1125},[25,4834,4835],{},"      // どこかへ保存する\n",[25,4837,4838],{"class":27,"line":1131},[25,4839,1526],{},[25,4841,4842],{"class":27,"line":1137},[25,4843,2650],{},[25,4845,4846],{"class":27,"line":1143},[25,4847,32],{"emptyLinePlaceholder":31},[25,4849,4850],{"class":27,"line":1149},[25,4851,4852],{},"  useEffect(() => {\n",[25,4854,4855],{"class":27,"line":1154},[25,4856,4857],{},"    /**\n",[25,4859,4860],{"class":27,"line":1160},[25,4861,4862],{},"     *初期実行\n",[25,4864,4865],{"class":27,"line":1166},[25,4866,4867],{},"     *\n",[25,4869,4870],{"class":27,"line":1172},[25,4871,4872],{},"     */\n",[25,4874,4875],{"class":27,"line":1177},[25,4876,4877],{},"    const init = async () => {\n",[25,4879,4880],{"class":27,"line":1183},[25,4881,32],{"emptyLinePlaceholder":31},[25,4883,4884],{"class":27,"line":3174},[25,4885,4886],{},"                const url = \"ここで保存先から取得\"\n",[25,4888,4889],{"class":27,"line":3179},[25,4890,4891],{},"          \n",[25,4893,4894],{"class":27,"line":3184},[25,4895,4896],{},"          // fromDataURLでSignatureCanvasに\n",[25,4898,4899],{"class":27,"line":3189},[25,4900,4901],{},"          // 保存したデータURL取り込むきちんと幅と高さを合わせないと画像が小さくなったりする\n",[25,4903,4904],{"class":27,"line":3194},[25,4905,4906],{},"        padRef.current?.fromDataURL(url, {\n",[25,4908,4909],{"class":27,"line":3199},[25,4910,4911],{},"          width: 800,\n",[25,4913,4914],{"class":27,"line":3204},[25,4915,4916],{},"          height: 200\n",[25,4918,4919],{"class":27,"line":3209},[25,4920,4921],{},"        });\n",[25,4923,4924],{"class":27,"line":3214},[25,4925,4926],{},"        setTrimmedDataURL(url);\n",[25,4928,4929],{"class":27,"line":3219},[25,4930,2503],{},[25,4932,4933],{"class":27,"line":3224},[25,4934,4935],{},"    };\n",[25,4937,4938],{"class":27,"line":3229},[25,4939,32],{"emptyLinePlaceholder":31},[25,4941,4942],{"class":27,"line":3234},[25,4943,4944],{},"    init();\n",[25,4946,4947],{"class":27,"line":3239},[25,4948,4949],{},"  }, []);\n",[25,4951,4952],{"class":27,"line":3244},[25,4953,32],{"emptyLinePlaceholder":31},[25,4955,4956],{"class":27,"line":3249},[25,4957,32],{"emptyLinePlaceholder":31},[25,4959,4960],{"class":27,"line":3254},[25,4961,4962],{},"  return (\n",[25,4964,4965],{"class":27,"line":3259},[25,4966,4967],{},"    \u003C>\n",[25,4969,4970],{"class":27,"line":3264},[25,4971,4972],{},"      \u003CSignatureCanvas\n",[25,4974,4975],{"class":27,"line":3269},[25,4976,4977],{},"        penColor=\"white\"\n",[25,4979,4980],{"class":27,"line":3274},[25,4981,4982],{},"        backgroundColor=\"gray\"\n",[25,4984,4985],{"class":27,"line":3279},[25,4986,4987],{},"        canvasProps={{ width: 800, height: 200 }}\n",[25,4989,4990],{"class":27,"line":3284},[25,4991,4992],{},"        ref={padRef}\n",[25,4994,4995],{"class":27,"line":3289},[25,4996,4997],{},"      />\n",[25,4999,5000],{"class":27,"line":3294},[25,5001,5002],{},"      \u003Cdiv>\n",[25,5004,5005],{"class":27,"line":3299},[25,5006,5007],{},"        {trimmedDataURL ? (\n",[25,5009,5010],{"class":27,"line":3304},[25,5011,5012],{},"          \u003Cimg alt=\"signature\" src={trimmedDataURL} width={80} height={20} />\n",[25,5014,5015],{"class":27,"line":3310},[25,5016,5017],{},"        ) : null}\n",[25,5019,5020],{"class":27,"line":3316},[25,5021,5022],{},"      \u003C/div>Ï\n",[25,5024,5025],{"class":27,"line":3321},[25,5026,5027],{},"          \u003CButton onClick={() => clear()} >\n",[25,5029,5030],{"class":27,"line":3326},[25,5031,5032],{},"            clear\n",[25,5034,5035],{"class":27,"line":3331},[25,5036,5037],{},"          \u003C/Button>\n",[25,5039,5040],{"class":27,"line":3336},[25,5041,5042],{},"          \u003CButton onClick={() => trim()} >\n",[25,5044,5045],{"class":27,"line":3341},[25,5046,5047],{},"               trim\n",[25,5049,5050],{"class":27,"line":3346},[25,5051,5037],{},[25,5053,5054],{"class":27,"line":3351},[25,5055,32],{"emptyLinePlaceholder":31},[25,5057,5058],{"class":27,"line":3356},[25,5059,5060],{},"    \u003C/>\n",[25,5062,5063],{"class":27,"line":3361},[25,5064,2576],{},[25,5066,5067],{"class":27,"line":3366},[25,5068,2342],{},[238,5070,2215],{},[238,5072,5073],{},[1544,5074,5077],{"href":5075,"rel":5076},"https://www.npmjs.com/package/react-signature-canvas",[1548],"react-signature-canvas - npm",[238,5079,5080],{},[1544,5081,5084],{"href":5082,"rel":5083},"https://github.com/agilgur5/react-signature-canvas",[1548],"agilgur5/react-signature-canvas: A React wrapper component around signature_pad (in \u003C 150 LoC). Unopinionated and heavily updated fork of react-signature-pad",[208,5086,210],{},{"title":20,"searchDepth":35,"depth":35,"links":5088},[5089],{"id":4619,"depth":35,"text":4619,"children":5090},[5091,5092],{"id":4627,"depth":41,"text":4628},{"id":4645,"depth":41,"text":4645},[5094],"React","Reactで手書きサイン機能を実装できるreact-signature-canvasを試し、実装時にハマった点をまとめた",{},"/posts/try-react-signature-canvas",{"title":4614,"description":5095},"posts/try-react-signature-canvas",[1443,1451,5094,5101],"Next.js","YwGSqRcXCDY7qRW5MqFK0ePKhXsCTVT3lSYN5KDYd1I",{"id":5104,"title":5105,"body":5106,"description":5110,"extension":218,"meta":5139,"navigation":31,"path":5140,"seo":5141,"stem":5142,"__hash__":5143},"sideContent/contact.md","Contact",{"type":8,"value":5107,"toc":5137},[5108,5111,5128,5130],[238,5109,5110],{},"お問い合わせは下記フォームまたはSNSからお願いいたします。",[304,5112,5113,5116,5119,5122,5125],{},[307,5114,5115],{},"ご返信に日数を要する場合があります。",[307,5117,5118],{},"当サイト管理者の判断により、ご返信を控えさせていただく場合があります。",[307,5120,5121],{},"何らかの理由でお問い合わせやそのご返信が届かない可能性もあります。",[307,5123,5124],{},"送信いただく情報の取り扱いについては、プライバシーポリシー等を参照願います。",[307,5126,5127],{},"特定の事業者様から短期間に同様の勧誘等を繰り返しいただいた場合、当サイト管理者の判断によりその事業者名を添えて注意喚起や所定の相談先等への連絡等をさせていただくことがあります。",[259,5129],{},[262,5131,5136],{"src":5132,"width":5133,"height":5134,"frameBorder":266,"marginHeight":5135,"marginWidth":5135},"https://docs.google.com/forms/d/e/1FAIpQLSesrT3kzyqZm9txcsnnoUe68tHpzJrwLOiXnnJLidkKWbyl9g/viewform?embedded=true","100%",800,0,"Loading…",{"title":20,"searchDepth":35,"depth":35,"links":5138},[],{},"/contact",{"title":5105,"description":5110},"contact","CU8dBymStvMltJVrJqPr2fpu-jlnyhvTaL3okxtlQhA",1788430702352]