[{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"2:1-4:42\"\u003e弊社(株式会社コードユニット)は、技術記事投稿の文化を根付けるためにQiita Organizationを始めました。\u003cbr\u003e\nそこで、Organization内のユーザーが投稿したら弊社Teamsに通知をすることにより、社員同士の投稿を確認しやすくなるのでは・・・？\u003cbr\u003e\nと思い、BOTを作ってみました。\u003c/p\u003e\n\u003cp data-sourcepos=\"6:1-6:111\"\u003e内々で使用するBOTなので、仕組みはちょっと甘めですが、参考になれば幸いです！\u003c/p\u003e\n\u003ch2 data-sourcepos=\"8:1-8:15\"\u003e\n\u003cspan id=\"使用技術\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e使用技術\u003c/h2\u003e\n\u003cul data-sourcepos=\"9:1-12:0\"\u003e\n\u003cli data-sourcepos=\"9:1-9:25\"\u003eTeams Incomming Webhook\u003c/li\u003e\n\u003cli data-sourcepos=\"10:1-10:5\"\u003eGAS\u003c/li\u003e\n\u003cli data-sourcepos=\"11:1-12:0\"\u003eQiita API\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"13:1-13:12\"\u003e\n\u003cspan id=\"作り方\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BD%9C%E3%82%8A%E6%96%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e作り方\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"14:1-14:44\"\u003e\n\u003cspan id=\"teamsのincomming-webhookを設定する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#teams%E3%81%AEincomming-webhook%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eTeamsのIncomming Webhookを設定する\u003c/h3\u003e\n\u003cp data-sourcepos=\"15:1-20:65\"\u003eTeamsで任意のチャンネルを右クリック\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fb67cc40a-7f48-fbe1-6b07-b5507c1fdd0e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=b348218f25563b35a3ae405801716c82\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fb67cc40a-7f48-fbe1-6b07-b5507c1fdd0e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=b348218f25563b35a3ae405801716c82\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fb67cc40a-7f48-fbe1-6b07-b5507c1fdd0e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=f636264097030a47ab663cc2355b0652 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/b67cc40a-7f48-fbe1-6b07-b5507c1fdd0e.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003cbr\u003e\nこちらの \u003ccode\u003eチャネルの管理\u003c/code\u003eを押下し、\u003ccode\u003eコネクタの編集\u003c/code\u003eを押下します。\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2F7c2fc8a2-4cf6-bd13-1037-2a434a5ea017.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=cf5c6a41b769ae3cec622c73f2954bfa\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2F7c2fc8a2-4cf6-bd13-1037-2a434a5ea017.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=cf5c6a41b769ae3cec622c73f2954bfa\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2F7c2fc8a2-4cf6-bd13-1037-2a434a5ea017.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6b6e91b98ee90a9fac7d6870c05f5689 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/7c2fc8a2-4cf6-bd13-1037-2a434a5ea017.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003cbr\u003e\nIncomming Webhook の構成を押下し、各情報を入力します。\u003cbr\u003e\n払い出されたWebhook URLをメモしておきましょう。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"22:1-22:40\"\u003e\n\u003cspan id=\"gasで定期実行の処理を書く\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#gas%E3%81%A7%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E6%9B%B8%E3%81%8F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eGASで定期実行の処理を書く\u003c/h3\u003e\n\u003cp data-sourcepos=\"23:1-24:56\"\u003e処理内容としては以下のようになります。\u003cbr\u003e\n今回はGASで10分毎の定期実行としました。\u003c/p\u003e\n\u003ch4 data-sourcepos=\"26:1-26:26\"\u003e\n\u003cspan id=\"qiita-apiをコール\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#qiita-api%E3%82%92%E3%82%B3%E3%83%BC%E3%83%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eQiita APIをコール\u003c/h4\u003e\n\u003cp data-sourcepos=\"27:1-28:50\"\u003e\u003ccode\u003ehttps://qiita.com/api/v2/items\u003c/code\u003e\u003cbr\u003e\nでQiitaの全ての投稿が取得できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"30:1-33:108\"\u003e\u003ca href=\"https://qiita.com/api/v2/docs\" class=\"autolink\"\u003ehttps://qiita.com/api/v2/docs\u003c/a\u003e\u003cbr\u003e\nこちらによると、クエリパラメータで query=${}で、\u003cbr\u003e\n\u003ca href=\"https://help.qiita.com/ja/articles/qiita-search-options\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://help.qiita.com/ja/articles/qiita-search-options\u003c/a\u003e\u003cbr\u003e\nの検索オプションが使えそうということがわかりましたので、試してみました。\u003c/p\u003e\n\u003cp data-sourcepos=\"35:1-35:156\"\u003e結果、 \u003ca href=\"https://qiita.com/api/v2/items?query=org:codeunit\" class=\"autolink\"\u003ehttps://qiita.com/api/v2/items?query=org:codeunit\u003c/a\u003e のようにOrganization名でも検索ができましたので、こちらを採用します。\u003c/p\u003e\n\u003cp data-sourcepos=\"37:1-38:96\"\u003e\u003ca href=\"https://qiita.com/fkooo/items/9f8e566c027bb833c29e\" class=\"autolink\" id=\"reference-26299c95c66e7e182f74\"\u003ehttps://qiita.com/fkooo/items/9f8e566c027bb833c29e\u003c/a\u003e\u003cbr\u003e\nこちらを参考に、失敗回避のため、以下のオプションを追記しました。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"39:1-43:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eheaders: {\n  'Accept-Encoding': 'identity',\n},\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch4 data-sourcepos=\"45:1-45:35\"\u003e\n\u003cspan id=\"キャッシュに保存する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%AB%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eキャッシュに保存する\u003c/h4\u003e\n\u003cp data-sourcepos=\"46:1-48:75\"\u003e今回は、処理を定期実行するにあたって、APIから取得した前回の値と、今回取得した値に変更があった場合に処理をしたいと考えました。\u003cbr\u003e\n\u003ca href=\"https://qiita.com/golyat/items/ba5d9ce38ec3308d3757\" class=\"autolink\" id=\"reference-4c037e0a9ec2a5eb6d99\"\u003ehttps://qiita.com/golyat/items/ba5d9ce38ec3308d3757\u003c/a\u003e\u003cbr\u003e\n上記を参考に、100分間キャッシュすることにしました。\u003c/p\u003e\n\u003ch4 data-sourcepos=\"50:1-50:11\"\u003e\n\u003cspan id=\"比較\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%AF%94%E8%BC%83\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e比較\u003c/h4\u003e\n\u003cp data-sourcepos=\"51:1-52:51\"\u003eキャッシュデータと今回取得したデータを比較して、\u003cbr\u003e\n追加のあった記事のみを取得します。\u003c/p\u003e\n\u003ch4 data-sourcepos=\"54:1-54:19\"\u003e\n\u003cspan id=\"teamsに通知\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#teams%E3%81%AB%E9%80%9A%E7%9F%A5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eTeamsに通知\u003c/h4\u003e\n\u003cp data-sourcepos=\"55:1-56:87\"\u003e追加のあった記事の内容を\u003cbr\u003e\n先ほど取得したWebhook URLに対してPOSTでメッセージを送信します。\u003c/p\u003e\n\u003ch4 data-sourcepos=\"58:1-58:29\"\u003e\n\u003cspan id=\"ソースコード全文\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E5%85%A8%E6%96%87\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eソースコード全文\u003c/h4\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"59:1-134:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003efunction makeCache() {\n  const cache = CacheService.getScriptCache();\n  return {\n    get: function (key) {\n      const value = cache.get(key);\n      return value ? JSON.parse(value) : null;\n    },\n    put: function (key, value, sec) {\n      cache.put(key, JSON.stringify(value), sec || 6000);\n      return value;\n    }\n  };\n}\n\n// Qiita APIのエンドポイント\nconst qiitaApiUrl = 'https://qiita.com/api/v2/items?query=org:codeunit';\n\n// Teams WebhookのURL\nconst teamsWebhookUrl = 'ここに先ほどコピーしたWebhook URLを書きます';\n// キャッシュを作成\nconst cache = makeCache();\n\n// Qiita APIを呼び出してデータを取得\nfunction fetchData() {\n  const options = {\n    headers: {\n      'Accept-Encoding': 'identity',\n    },\n  };\n  const response = UrlFetchApp.fetch(qiitaApiUrl, options);\n  const data = JSON.parse(response.getContentText());\n  return data.map(item =\u0026gt; ({\n    title: item.title,\n    url: item.url,\n    user: {\n      id: item.user.id,\n    },\n  }));\n}\n\n// 前回のデータと比較して新しい記事を通知する\nfunction notifyNewArticles() {\n  const currentData = fetchData();\n  const previousData = cache.get('qiitaData') || [];\n\n  // 前回のデータと比較して新しい記事を取得\n  const newArticles = currentData.filter(item =\u0026gt; !previousData.some(prevItem =\u0026gt; prevItem.url === item.url));\n\n  // 新しい記事がある場合、Teamsに通知する\n  if (newArticles.length \u0026gt; 0) {\n    newArticles.forEach(article =\u0026gt; {\n      const message = `${article.user.id} さんが新しい記事を投稿したよ \u0026lt;br\u0026gt; タイトル: ${article.title} \u0026lt;br\u0026gt; URL: ${article.url}`;\n      postToTeams(message);\n    });\n\n    // 新しいデータをキャッシュとして保存\n    cache.put('qiitaData', currentData);\n  }\n}\n\n// Teamsにメッセージを投稿する\nfunction postToTeams(message) {\n  const payload = {\n    text: message,\n  };\n\n  const options = {\n    method: 'post',\n    contentType: 'application/json',\n    payload: JSON.stringify(payload),\n  };\n\n  UrlFetchApp.fetch(teamsWebhookUrl, options);\n}\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"136:1-136:9\"\u003e\n\u003cspan id=\"結果\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B5%90%E6%9E%9C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e結果\u003c/h2\u003e\n\u003cp data-sourcepos=\"137:1-138:54\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fc41ca18f-f986-6b32-e304-d6a3a82ee233.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=ff3ca418eef06bb67cc44626816887c5\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fc41ca18f-f986-6b32-e304-d6a3a82ee233.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=ff3ca418eef06bb67cc44626816887c5\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1178540%2Fc41ca18f-f986-6b32-e304-d6a3a82ee233.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=0663b45ee8980c32c5551b53feaadef6 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/c41ca18f-f986-6b32-e304-d6a3a82ee233.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003cbr\u003e\nこんな感じで通知するBOTが出来ました！\u003c/p\u003e\n\u003ch2 data-sourcepos=\"140:1-140:9\"\u003e\n\u003cspan id=\"追伸\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%BF%BD%E4%BC%B8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e追伸\u003c/h2\u003e\n\u003cp data-sourcepos=\"141:1-143:54\"\u003e株式会社コードユニットでは北海道札幌市で一緒に働けるメンバーを絶賛募集中です！\u003cbr\u003e\n起業したばかりのベンチャーですが、和気あいあいと楽しく仕事をしています！\u003cbr\u003e\nご興味のある方は一度ご連絡ください！\u003c/p\u003e\n","body":"## はじめに\n弊社(株式会社コードユニット)は、技術記事投稿の文化を根付けるためにQiita Organizationを始めました。\nそこで、Organization内のユーザーが投稿したら弊社Teamsに通知をすることにより、社員同士の投稿を確認しやすくなるのでは・・・？\nと思い、BOTを作ってみました。\n\n内々で使用するBOTなので、仕組みはちょっと甘めですが、参考になれば幸いです！\n\n## 使用技術\n- Teams Incomming Webhook\n- GAS\n- Qiita API\n\n## 作り方\n### TeamsのIncomming Webhookを設定する\nTeamsで任意のチャンネルを右クリック\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/b67cc40a-7f48-fbe1-6b07-b5507c1fdd0e.png)\nこちらの `チャネルの管理`を押下し、`コネクタの編集`を押下します。\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/7c2fc8a2-4cf6-bd13-1037-2a434a5ea017.png)\nIncomming Webhook の構成を押下し、各情報を入力します。\n払い出されたWebhook URLをメモしておきましょう。\n\n### GASで定期実行の処理を書く\n処理内容としては以下のようになります。\n今回はGASで10分毎の定期実行としました。\n\n#### Qiita APIをコール\n`https://qiita.com/api/v2/items`\nでQiitaの全ての投稿が取得できます。\n\nhttps://qiita.com/api/v2/docs\nこちらによると、クエリパラメータで query=${}で、\nhttps://help.qiita.com/ja/articles/qiita-search-options\nの検索オプションが使えそうということがわかりましたので、試してみました。\n\n結果、 https://qiita.com/api/v2/items?query=org:codeunit のようにOrganization名でも検索ができましたので、こちらを採用します。\n\nhttps://qiita.com/fkooo/items/9f8e566c027bb833c29e\nこちらを参考に、失敗回避のため、以下のオプションを追記しました。\n```\nheaders: {\n  'Accept-Encoding': 'identity',\n},\n```\n\n#### キャッシュに保存する\n今回は、処理を定期実行するにあたって、APIから取得した前回の値と、今回取得した値に変更があった場合に処理をしたいと考えました。\nhttps://qiita.com/golyat/items/ba5d9ce38ec3308d3757\n上記を参考に、100分間キャッシュすることにしました。\n\n#### 比較\nキャッシュデータと今回取得したデータを比較して、\n追加のあった記事のみを取得します。\n\n#### Teamsに通知\n追加のあった記事の内容を\n先ほど取得したWebhook URLに対してPOSTでメッセージを送信します。\n\n#### ソースコード全文\n```\nfunction makeCache() {\n  const cache = CacheService.getScriptCache();\n  return {\n    get: function (key) {\n      const value = cache.get(key);\n      return value ? JSON.parse(value) : null;\n    },\n    put: function (key, value, sec) {\n      cache.put(key, JSON.stringify(value), sec || 6000);\n      return value;\n    }\n  };\n}\n\n// Qiita APIのエンドポイント\nconst qiitaApiUrl = 'https://qiita.com/api/v2/items?query=org:codeunit';\n\n// Teams WebhookのURL\nconst teamsWebhookUrl = 'ここに先ほどコピーしたWebhook URLを書きます';\n// キャッシュを作成\nconst cache = makeCache();\n\n// Qiita APIを呼び出してデータを取得\nfunction fetchData() {\n  const options = {\n    headers: {\n      'Accept-Encoding': 'identity',\n    },\n  };\n  const response = UrlFetchApp.fetch(qiitaApiUrl, options);\n  const data = JSON.parse(response.getContentText());\n  return data.map(item =\u003e ({\n    title: item.title,\n    url: item.url,\n    user: {\n      id: item.user.id,\n    },\n  }));\n}\n\n// 前回のデータと比較して新しい記事を通知する\nfunction notifyNewArticles() {\n  const currentData = fetchData();\n  const previousData = cache.get('qiitaData') || [];\n\n  // 前回のデータと比較して新しい記事を取得\n  const newArticles = currentData.filter(item =\u003e !previousData.some(prevItem =\u003e prevItem.url === item.url));\n\n  // 新しい記事がある場合、Teamsに通知する\n  if (newArticles.length \u003e 0) {\n    newArticles.forEach(article =\u003e {\n      const message = `${article.user.id} さんが新しい記事を投稿したよ \u003cbr\u003e タイトル: ${article.title} \u003cbr\u003e URL: ${article.url}`;\n      postToTeams(message);\n    });\n\n    // 新しいデータをキャッシュとして保存\n    cache.put('qiitaData', currentData);\n  }\n}\n\n// Teamsにメッセージを投稿する\nfunction postToTeams(message) {\n  const payload = {\n    text: message,\n  };\n\n  const options = {\n    method: 'post',\n    contentType: 'application/json',\n    payload: JSON.stringify(payload),\n  };\n\n  UrlFetchApp.fetch(teamsWebhookUrl, options);\n}\n```\n\n## 結果\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178540/c41ca18f-f986-6b32-e304-d6a3a82ee233.png)\nこんな感じで通知するBOTが出来ました！\n\n## 追伸\n株式会社コードユニットでは北海道札幌市で一緒に働けるメンバーを絶賛募集中です！\n起業したばかりのベンチャーですが、和気あいあいと楽しく仕事をしています！\nご興味のある方は一度ご連絡ください！\n","coediting":false,"comments_count":0,"created_at":"2023-12-13T16:01:23+09:00","group":null,"id":"68e688f5ee66018314fa","likes_count":2,"private":false,"reactions_count":0,"stocks_count":1,"tags":[{"name":"GAS","versions":[]},{"name":"Teams","versions":[]}],"title":"QiitaのOrganization内のユーザーが記事を投稿したらGASでTeamsに通知するBOTを作ってみた","updated_at":"2023-12-13T16:01:23+09:00","url":"https://qiita.com/suzuki1108/items/68e688f5ee66018314fa","user":{"description":"北海道札幌市の株式会社コードユニットというシステム開発会社の代表です。\r\nどちらかというとバックエンドよりのエンジニア。","facebook_id":"","followees_count":2,"followers_count":2,"github_login_name":"suzuki1108","id":"suzuki1108","items_count":3,"linkedin_id":"","location":"北海道札幌市","name":"","organization":"株式会社コードユニット","permanent_id":1178540,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/1178540/bfd5371dad47b78b228706ef2ed891fe1d741dce/large.png?1683606037","team_only":false,"twitter_screen_name":null,"website_url":"https://codeunit.co.jp"},"page_views_count":null,"team_membership":null,"organization_url_name":"codeunit","slide":false,"posting_campaign_uuid":null},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"2:1-2:181\"\u003eこの記事は、Vue3系でDOM構築後に出現したinput要素に対してfocusを当てることでnextTick() で同期的にDOM要素を操作する方法を説明します。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"3:1-3:15\"\u003e\n\u003cspan id=\"使用技術\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e使用技術\u003c/h2\u003e\n\u003cul data-sourcepos=\"4:1-7:0\"\u003e\n\u003cli data-sourcepos=\"4:1-4:6\"\u003eVue3\u003c/li\u003e\n\u003cli data-sourcepos=\"5:1-5:16\"\u003eCompositionAPI\u003c/li\u003e\n\u003cli data-sourcepos=\"6:1-7:0\"\u003escript setup\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"8:1-8:21\"\u003e\n\u003cspan id=\"やりたいこと\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eやりたいこと\u003c/h2\u003e\n\u003cp data-sourcepos=\"9:1-9:67\"\u003eDOM構築後に出現したinput要素に対してfocusを当てる\u003c/p\u003e\n\u003ch2 data-sourcepos=\"11:1-11:9\"\u003e\n\u003cspan id=\"結論\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B5%90%E8%AB%96\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e結論\u003c/h2\u003e\n\u003cp data-sourcepos=\"12:1-12:94\"\u003e\u003ccode\u003eawait nextTick();\u003c/code\u003eでコンポーネントのデータ変更がDOMに到達するのを待つ\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"vue\" data-sourcepos=\"13:1-31:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eApp.vue\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nt\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"nt\"\u003e\u0026lt;button\u003c/span\u003e \u003cspan class=\"err\"\u003e@\u003c/span\u003e\u003cspan class=\"na\"\u003eclick=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"handleClick\"\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003ebutton\u003cspan class=\"nt\"\u003e\u0026lt;/button\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"nt\"\u003e\u0026lt;input\u003c/span\u003e \u003cspan class=\"na\"\u003ev-if=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"show\"\u003c/span\u003e \u003cspan class=\"na\"\u003eref=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"input\"\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003cspan class=\"nt\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\n\u003cspan class=\"nt\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003escript\u003c/span\u003e \u003cspan class=\"na\"\u003esetup\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eonMounted\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003enextTick\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e\"\u003c/span\u003e\u003cspan class=\"s2\"\u003evue\u003c/span\u003e\u003cspan class=\"dl\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003einput\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eshow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandleClick\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003easync \u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"nx\"\u003eshow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003eshow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"nf\"\u003enextTick\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"nf\"\u003efocus\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003cspan class=\"nt\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"k\"\u003escript\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003ch2 data-sourcepos=\"33:1-33:9\"\u003e\n\u003cspan id=\"解説\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A7%A3%E8%AA%AC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e解説\u003c/h2\u003e\n\u003cp data-sourcepos=\"34:1-34:60\"\u003e上記コードにコメントベースで解説します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"vue\" data-sourcepos=\"35:1-65:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eApp.vue\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nt\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"nt\"\u003e\u0026lt;button\u003c/span\u003e \u003cspan class=\"err\"\u003e@\u003c/span\u003e\u003cspan class=\"na\"\u003eclick=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"handleClick\"\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003ebutton\u003cspan class=\"nt\"\u003e\u0026lt;/button\u0026gt;\u003c/span\u003e\n  \n  \u003cspan class=\"c\"\u003e\u0026lt;!-- DOMにアクセスするため、refを使用してテンプレート参照を行う --\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"nt\"\u003e\u0026lt;input\u003c/span\u003e \u003cspan class=\"na\"\u003ev-if=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"show\"\u003c/span\u003e \u003cspan class=\"na\"\u003eref=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"input\"\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003cspan class=\"nt\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\n\u003cspan class=\"nt\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003escript\u003c/span\u003e \u003cspan class=\"na\"\u003esetup\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eonMounted\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003enextTick\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e\"\u003c/span\u003e\u003cspan class=\"s2\"\u003evue\u003c/span\u003e\u003cspan class=\"dl\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// inputタグで ref=\"input\" としたため、 変数名を合わせ、refの引数を空にすることでDOMの内容を変数に受け取る\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003einput\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// inputタグの表示非表示を管理する変数\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eshow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003cspan class=\"cm\"\u003e/**\n * buttonクリックイベント\n * inputタグの表示制御、focus制御を行う\n */\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandleClick\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003easync \u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// inputタグの表示制御\u003c/span\u003e\n  \u003cspan class=\"nx\"\u003eshow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003eshow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// nextTickが引数なしで呼ばれた場合、コンポーネントのデータ変更がDOMに到達した時に解決されるPromiseを返します。\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// つまり、inputタグが実際に出現するまで処理を待ってくれます。\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"nf\"\u003enextTick\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// フォーカス\u003c/span\u003e\n  \u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"nf\"\u003efocus\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003cspan class=\"nt\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"k\"\u003escript\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n","body":"## はじめに\nこの記事は、Vue3系でDOM構築後に出現したinput要素に対してfocusを当てることでnextTick() で同期的にDOM要素を操作する方法を説明します。\n## 使用技術\n- Vue3\n- CompositionAPI\n- script setup\n\n## やりたいこと\nDOM構築後に出現したinput要素に対してfocusを当てる\n\n## 結論\n`await nextTick();`でコンポーネントのデータ変更がDOMに到達するのを待つ\n```vue:App.vue\n\u003ctemplate\u003e\n  \u003cbutton @click=\"handleClick\"\u003ebutton\u003c/button\u003e\n  \u003cinput v-if=\"show\" ref=\"input\" /\u003e\n\u003c/template\u003e\n\n\u003cscript setup\u003e\nimport { ref, onMounted, nextTick } from \"vue\";\n\nconst input = ref();\nconst show = ref(false);\n\nconst handleClick = async () =\u003e {\n  show.value = !show.value;\n  await nextTick();\n  input.value?.focus();\n};\n\u003c/script\u003e\n```\n\n## 解説\n上記コードにコメントベースで解説します。\n```vue:App.vue\n\u003ctemplate\u003e\n  \u003cbutton @click=\"handleClick\"\u003ebutton\u003c/button\u003e\n  \n  \u003c!-- DOMにアクセスするため、refを使用してテンプレート参照を行う --\u003e\n  \u003cinput v-if=\"show\" ref=\"input\" /\u003e\n\u003c/template\u003e\n\n\u003cscript setup\u003e\nimport { ref, onMounted, nextTick } from \"vue\";\n\n// inputタグで ref=\"input\" としたため、 変数名を合わせ、refの引数を空にすることでDOMの内容を変数に受け取る\nconst input = ref();\n// inputタグの表示非表示を管理する変数\nconst show = ref(false);\n\n/**\n * buttonクリックイベント\n * inputタグの表示制御、focus制御を行う\n */\nconst handleClick = async () =\u003e {\n  // inputタグの表示制御\n  show.value = !show.value;\n  // nextTickが引数なしで呼ばれた場合、コンポーネントのデータ変更がDOMに到達した時に解決されるPromiseを返します。\n  // つまり、inputタグが実際に出現するまで処理を待ってくれます。\n  await nextTick();\n  // フォーカス\n  input.value?.focus();\n};\n\u003c/script\u003e\n```\n","coediting":false,"comments_count":0,"created_at":"2023-05-12T18:39:08+09:00","group":null,"id":"5610f12542f14ecef33f","likes_count":0,"private":false,"reactions_count":0,"stocks_count":1,"tags":[{"name":"Vue.js","versions":[]},{"name":"Composition-API","versions":[]},{"name":"Vue3","versions":[]}],"title":"[Vue3 CompositionAPI] nextTick() で同期的にDOM要素を操作する~DOM構築後に出現したinput要素に対してfocusを当てる~","updated_at":"2023-05-12T18:39:08+09:00","url":"https://qiita.com/suzuki1108/items/5610f12542f14ecef33f","user":{"description":"北海道札幌市の株式会社コードユニットというシステム開発会社の代表です。\r\nどちらかというとバックエンドよりのエンジニア。","facebook_id":"","followees_count":2,"followers_count":2,"github_login_name":"suzuki1108","id":"suzuki1108","items_count":3,"linkedin_id":"","location":"北海道札幌市","name":"","organization":"株式会社コードユニット","permanent_id":1178540,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/1178540/bfd5371dad47b78b228706ef2ed891fe1d741dce/large.png?1683606037","team_only":false,"twitter_screen_name":null,"website_url":"https://codeunit.co.jp"},"page_views_count":null,"team_membership":null,"organization_url_name":"codeunit","slide":false,"posting_campaign_uuid":null}]