OmniAICreator/Qiita-1.07M huggingface dataset:
[Tip] デバッグ効率向上：Chromiumロボでエラー発生した時にスクショを自動取得する方法！

## はじめに

BizRobo! の特徴として「ロボットをバックグラウンドで実行できる」ことはある程度有名だと思います。

この仕様はロボットの処理密度や効率を高めることができる一方で、ロボットの処理に画面を必要としないが故に、ロボットが処理に失敗した際には「その時画面に何が表示されていたのか？」を直接確認することができないデメリットもあります。

今回はそんな場合に素早く状況を把握し、原因特定をスムーズに進めるための **エラー発生時のスクショ取得方法** Tip をサンプルロボットを使って紹介します。

:::note
本記事は、BizRobo! v11.3.0.2 の利用を前提に解説します。
:::

## サンプルロボットの説明

それではサンプルロボットの中身を、ポイントを絞って説明します。

以下が `ベーシックエンジン ロボット` の全体像です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/0f122707-5d64-20e5-de92-187515089b20.png)
利用する人の環境に合わせて実行できるように「（Windows）ログインユーザ名取得」ステップで実行ユーザの名前を取得し、後続の「ファイル出力パス設定」で実行ユーザのプロファイル配下にファイルパスを設定。最後に「スナップ取得」`ロボット` を呼び出します。

`ロボット` の全体像は以下の通り。今回ポイントとなる部分は `Try-Catch` アクション内の下段です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/05fff057-fc44-f7ec-2a29-321d956ebc45.png)
上段部分には通常の処理を並べ、`Try-Catch` アクションの範囲内で `TimeOutError` が発生した場合に下段のエラー処理エリアに移動し、スナップショットの取得処理（`スクショ` ステップ）を実行します。

:::note
ある範囲で発生したエラーを全部まとめて一か所で対応したい!!といった場合に `Try-Catch` は便利です。
- 上段のメインエリアにはロボットの処理を設定します。
- 下段のエラー処理エリアには補足するエラー毎の処理を設定します。
- 右側のFinallyエリアにはエラーが発生してもしなくても実行したい処理を設定します。**アプリケーションを閉じるなどの終了処理はここに設定しておくと便利です**。
:::

`スクショ` ステップの中身は以下の通り。エラーが発生した時点でロボットが操作していた画面を、ウィンドウまるごと画像保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/8ad1f2c9-b4ac-10e4-aec7-96c928d4ca25.png)
画面と組み合わせて見ると以下の通りです。BizRobo!では常に処理のフロー（アクション）と画面の表示が連動していることもユニークな点ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/9af66335-0d93-6e66-1150-7db3fe6b6e73.png)
画像の保存先は「ファイル出力パス設定」で指定したディレクトリの配下で `ベーシックエンジン ロボット` のロボットファイル名でフォルダを作成し、その直下にタイムスタンプをファイル名としたスクショ画像を `.png` 形式で保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/44543fd6-05ab-005c-6bab-739ef0e9f86c.png)


## サンプルロボット

上で紹介したロボットのソースファイルを GitHub に登録してますのでよろしければご利用ください。(v11.3.0.2で作ってあります）

https://github.com/kiban-unyo/Tips_TakeSnapInChromium

## まとめ

本番運用中のロボットに対してスナップショット取得を埋め込む場合には `%Temp%` にファイルを出力するのがいいでしょう。

一時データフォルダ（`%Temp%`）の配下であれば、ディスク容量が足りなくなればOS側で自動的にファイル削除もしてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/2df2fc5e-2ba2-5b74-307b-9d21979f27a0.png)
「技術書」の読書術

「技術書」の読書術を読んだので、備忘録として記事を書く。

### 雑誌を定期購読する
webエンジニアとして活動しているので、以下の雑誌が定期購読に向いてそう
- Software Design（技術評論社）
  - ITエンジニア向けの開発技術
- WEB+DB PRESS（技術評論社）
  - Webアプリ開発者向け
- WebDesign（マイナビ出版）
  - Webの運用・マーケティング

技術書は本でのみ読んだことがあったが、雑誌のほうが最新の情報を得られるようなので、上記の雑誌を一度読んでみて、どれを定期購読するか決める。

### 英語の技術書をよむ
技術書の英語は、言い回しがさほど難しくないとのことなので読んでみる。英語の公式ドキュメントは読むことがあるが、自分の単語量が不足していて結構苦戦する。英語の技術書を定期的に読むことで、英語ドキュメント慣れするかもしれないので実行したい

### 3の発想
- 同じ分野の入門書・専門書・逆引きの３パターンを読む
- 入門系の本を三冊揃え読み比べる


### 同じ本を何度も読む
- 一度目はタイトルや太文字をざっと読む
- 二度目は、プログラミング系の本であれば実際にサンプルコードを入力するなど手を動かして読み進める
- 三度目はノートにまとめたりブログ投稿するなど、アウトプットを意識して読み進める

まずはざっと読むのは、何度か聞いたことがあるがやってるうちに、しっかり読んでしまうフシがこれまであった。タイトルと太字だけ読むというルールを設定することで、ざっと概要だけ読むという感覚を得られた気がする。
また、アウトプットを意識しないと自分の中に定着しないようなので、Qiitaなどに備忘録としてこれからも投稿しようと思う。
Laravel10でtailwindcssが効かない

## 状況
普段何気なくLaravelに触れていたが、今一度復習をするためにLaravel10で簡単なチャットアプリを作ろうとしていた。breezeをinstallして、デフォルトで用意してくれているtailwindでページを作成している時、一向にページが更新されないことに気づいた。例えば以下のように書いても一切ボーダーが見えない。
```
<div class="border-8">
    test
</div>
```
色の設定が必要かと思ったがそういうわけでもなかった。

## 結論

ものすごくしょうもない結論だが、以下のコマンドを忘れていた。
```
npm run dev
```
要するに、CSSやJSなどの重要なアセットが必要となる状況において、Viteを立ち上げてなかったという結末。**php artisan serve**でログイン機能まで動いているため、全然気づかなかった。フレームワークや各種ライブラリを使う時は、仕組みから理解することが重要だと気づいた。
【C#/NUnit】privateメソッドをテストコード側で呼び出しテストする

結論、リフレクションを使用してテストします。

# リフレクションとは
リフレクションはアセンブリからメタデータを取得できる機能です。

※アセンブリは.NETアプリケーションを構成する.exe・.dllファイルを指します
※メタデータはプログラムのバイナリ情報でアセンブリや型に関する情報が含まれます。

# コード例
## 環境
- C# 8.0
- NUnit 3.13.3
- VisualStudio 2022 17.4.1

## プロダクションコード
```.cs
public class Calc
{
    private int Add(int a, int b)
    {
        return a + b;
    }
}
```
## テストコード
```.cs
public class CalcTest
{
    [Test]
    public void AddTest()
    {
        var calcTest = new Calc();
        // CalCクラスの指定したメソッド情報の取得(インスタンスのメソッド且つパブリック以外のメソッド)
        var methodInfo = typeof(Calc).GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic);

        var param = new object[] { 1, 2 };

        // Calcクラスに引数を指定して実行
        var result = methodInfo.Invoke(calcTest, param);
        Assert.AreEqual(3, result);
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/91f182bf-dc79-bbe0-15ab-b8599dce6aa1.png)


# 参考
- [.NETのリフレクション](https://learn.microsoft.com/ja-jp/dotnet/framework/reflection-and-codedom/reflection)
- [.NETのアセンブリ](https://learn.microsoft.com/ja-jp/dotnet/standard/assembly/)
- [メタデータと自己言及的なコンポーネント](https://learn.microsoft.com/ja-jp/dotnet/standard/metadata-and-self-describing-components)
ABC310回答メモ

0.はじめに
　相性の問題か、Bの難易度が高くCは簡単に感じました。
　Dは、一応取り組みましたが、回答のきっかけさえつかめず
　解説を読んでもいまいちよくわかりませんでした‥。
　相性が悪いペアを同じチームにしないことが
　本当にPeacefulと言えるのか、と、質問しようかと思いましたが
　思いとどまりました。

　C問題までしか解けなかった割には、順位は高く
　レートは+32でした。


1.A - Order Something Else
　Aにしてはややこしい感じではありますが
　実装はそれほど悩まず、リスト内で最小の値＋Qと
　Pのうち、小さい方を表示することでACできました。

　https://atcoder.jp/contests/abc310/submissions/43584547

2.B - Strictly Superior
　ほんとに200点の問題？？と疑いたくなるような難易度でした。
　解説を見てもそこそこ難しい解答方法でした。

　愚直に、全組み合わせのiとjについて
　iの値段>jの値段の場合
　　iの機能がすべてjの機能にあるかチェック
　　　→あればYesを出力してプログラム終了
　iの値段=jの値段の場合
　　iの機能がすべてjの機能にあるかチェック
　　　→あればｊの機能のうちｉにない機能があるかチェック
　　　　→あれば、Yesを出力してプログラム終了
　すべての組み合わせを終えてプログラムが継続していたら
　（条件を満たすiとjがなければ）
　Noを出力してプログラム終了

　と、考えは単純ですが、実装はてこずりました。

　https://atcoder.jp/contests/abc310/submissions/43608108

3.C - Reversible
　打って変わって簡単な問題。
　解説を見るとsetを使うのが普通のようですが
　慣れ親しんだ辞書を使って回答しました。

　空辞書dを準備して、文字列Sを取得し
　文字列Sか、文字列Sを逆にした文字列RS(RS=[::-1])が
　辞書になかったらSを辞書に登録
　Sをすべて取得したあと
　len(d)を出力して終了

　https://atcoder.jp/contests/abc310/submissions/43611297

以上
【Docker + mysql5.7】DBに日本語をINSERTできない問題の解決法

# はじめに
dockerでよくあるphp:apatchとmysql5.7で簡単な環境を作って、
CakePHPのブログチュートリアルをやっているときに
日本語の記事テキストがDBへ登録できずエラーになったので解決法を載せようと思いました。

# もともとのdocker-compose.yml
``` docker-compose.yml
version: "3.7"
services:
  cakeapp:
    build:
      context: ./docker
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    tty: true
    stdin_open: true
    volumes:
      - ./html:/var/www/html

  db:
    image: mysql:5.7
    volumes:
      - "./db:/docker-entrypoint-initdb.d"
    environment:
      MYSQL_DATABASE: hogehoge
      MYSQL_USER: hogehoge
      MYSQL_PASSWORD: hogehoge
      MYSQL_ROOT_PASSWORD: hogehoge
```

# DBへ日本語登録できるdocker-compose.yml

db定義に一行追加するのみ
```docker-compose.yml
version: "3.7"
services:
  cakeapp:
    build:
      context: ./docker
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    tty: true
    stdin_open: true
    volumes:
      - ./html:/var/www/html

  db:
    image: mysql:5.7
# ここを追加
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - "./db:/docker-entrypoint-initdb.d"
    environment:
      MYSQL_DATABASE: hogehoge
      MYSQL_USER: hogehoge
      MYSQL_PASSWORD: hogehoge
      MYSQL_ROOT_PASSWORD: hogehoge
```

以上
GAS×ChatGPT×LINE公式　AIチャットbotの作り方

AI Chatbot: LINE ID 
https://liff.line.me/1645278921-kWRPP32q/?accountId=854dadlt

![S__3072011.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220659/6958405a-c523-5770-059a-e83cb7668b28.jpeg)


![S__3072013.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220659/1026c02d-e245-0431-3c6a-87fc044921a2.jpeg)


①ChatGPTの準備：
OpenAIのAPIを使用してChatGPTと連携します。これにはAPIキーが必要で、OpenAIのウェブサイトで取得できます。

②LINE公式アカウントの作成：
LINE Developersのウェブサイトにアクセスし、新しいプロバイダーとチャンネルを作成します。この過程で、チャネルアクセストークンとチャネルシークレットが生成され、これらは後で必要になります。

③GoogleAppsScript(GAS)プロジェクトの作成：
新規GASプロジェクトを作成し、以下の関数を含むスクリプトを作成します。
doPost：
LINEからのwebhookイベントを受け取ります。メッセージイベントの場合、ユーザーからのメッセージを取得し、それをOpenAIのChatGPTに送信します。ChatGPTからの応答を受け取ったら、それをLINEのユーザーに返します。
④ChatGPTへのリクエスト：
この関数はOpenAIのAPIにリクエストを送信し、GPTからの応答を受け取ります。
LINEとGASの連携：LINE Developersコンソールでwebhook URLを設定します。これはGASプロジェクトの公開URLになります。

```
const GPT_TOKEN = 'chatGPT APIキー'; //ChatGPTのAPIキーを入れてください
const LINE_TOKEN = 'LINEMessengerAPI キー';    // LINEのAPIキーを入れてください
const LINE_ENDPOINT = "https://api.line.me/v2/bot/message/reply";
const GPT_ENDPOINT = 'https://api.openai.com/v1/chat/completions';
const MODEL_NAME = 'gpt-3.5-turbo';
const MODEL_TEMP = 0.5;
const MAX_TOKENS = 256;

// LINEからPOSTリクエストが渡されてきたときに実行される処理
function doPost(e) {

  // LINEからPOSTされるJSON形式のデータをGASで扱える形式(JSオブジェクト)に変換
  const json = JSON.parse(e.postData.contents);
  // LINE側へ応答するためのトークンを作成(LINEからのリクエストに入っているので、それを取得する)
  const reply_token = json.events[0].replyToken;
  if (typeof reply_token === 'undefined') {
    return;
  }

  // LINEから送られてきたメッセージを取得
  const user_message = json.events[0].message.text;
  // 改行で区切って配列にする
  const user_msgarray = user_message.split(/\r\n|\n/);
  // LINEのメッセージをChatGPTに投げるメッセージにセットする
  const messages = [{ "role": "user", "content": user_msgarray[0] }]

  const headers = {
    'Authorization': 'Bearer ' + GPT_TOKEN,
    'Content-type': 'application/json',
  };
  // リクエストオプション
  const options = {
    'method': 'POST',
    'headers': headers,
    'payload': JSON.stringify({
      'model': MODEL_NAME,        // 使用するGPTモデル
      'max_tokens': MAX_TOKENS,   // レスポンストークンの最大値(最大4,096)
      'temperature': MODEL_TEMP,  // 応答の多様性(0-1)※数値が大きいほどランダムな応答になる
      'messages': messages
    })
  };
  // HTTPリクエストでChatGPTのAPIを呼び出す
  const res = JSON.parse(UrlFetchApp.fetch(GPT_ENDPOINT, options).getContentText());

  // ChatGPTから返却されたメッセージを応答メッセージとしてLINEに返す
  lineReply(json, res.choices[0].message.content.trimStart());
}



// LINEへの応答
function lineReply(json, replyText) {

  // 応答用のメッセージを作成
  const message = {
    "replyToken": json.events[0].replyToken,
    "messages": [{
      "type": "text",         // メッセージのタイプ(画像、テキストなど)
      "text": replyText
    }] // メッセージの内容
  };
  // LINE側へデータを返す際に必要となる情報
  options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json; charset=UTF-8",  // JSON形式を指定、LINEの文字コードはUTF-8
      "Authorization": "Bearer " + LINE_TOKEN           // 認証タイプはBearer(トークン利用)、アクセストークン
    },
    "payload": JSON.stringify(message)                    // 応答文のメッセージをJSON形式に変換する
  };
  // LINEへ応答メッセージを返す
  UrlFetchApp.fetch(LINE_ENDPOINT, options);
}
```
gitユーザーがSubversionに感じたいい点と困った点

# 経緯

新規立ち上げプロジェクトがひと段落して暇そうにしてるところを先輩に見つかり、レガシーコードを排除するプロジェクトに参加しています。
そこでバージョン管理として使われていたのがgitではなく、Subversion(略称SVN)。
gitは分散型、svnは集中型ということは知識としては知っていましたが違いを実感はしていなかったので、3ヶ月経って感じたいい点と困った点をまとめました。
PCがWindowsOSなのでtortoiseSVNを入れていました。

# SVNのいい点

## 子フォルダを親フォルダと別リビジョンにできる

gitだと子フォルダに移動してリビジョンを変えると、全体のリビジョンが変わりますよね。
tortoiseSVNなら子フォルダに移動して、ログを表示させてから右クリックから「項目をこのリビジョンへ更新」で子フォルダのみリビジョンを移動できます。
複数プロジェクトが含まれたフォルダはこれができると本当に便利でした。

## コピー元の変更履歴を引き継げる

同じリポジトリ内からファイルをコピーして貼り付ける時に、totoiseSVNメニューから「貼り付け」を選択してペーストすれば元ファイルの変更履歴も引き継げちゃいます。
この機能を先輩に教えてもらった時は思わず「へー！！！すごい！」って声が出ていました。
すごく便利じゃないですか？

# SVNの困った点

## SVNのコミットがgitのpush

gitユーザーからするとコミットというとローカルリポジトリの変更を記録するイメージですが、SVNは集中型なのでコミットすると全リポジトリに変更が反映されてしまいます。
SVNとgitでは同じ単語がいくつかありますが、効果が違うので気をつけています。
紛らわしい。

「gitのコミットは事後報告、SVNのコミットは事前連絡」とTwitterで見ましたが、たしかにそうだと思います。覚えておいた方がいいです。

# あとがき

今やバージョン管理といったらgitでSVNなんて使わないと思っていたので、いい経験をしています。
gitユーザーがSVNを使うことになったら日本語は通じるけど文化が違う異世界に来たと思うくらいの方がいいかもしれません。
chocoZAPの認証の仕組みを考えてみた

エンジニアであれば居酒屋やファミレスの注文用タブレットを使ってみて「API定義どうなってるんだろう」とか「メニュー表示されるの遅いなぁ、計測してないけど頻繁に変わるものじゃないんだから端末で長めにキャッシュさせておけば良いのに」などと考えたことのある方もいるでしょう。

さて、読者の8割がブラウザバックしたところで、今回は最近通っているchocoZAPというお手頃価格のジムでの利用者認証の仕組みが気になったので自分なりの仮説をChatGPTで検証してみたプロセスを書いていきたいと思います。

# 流れ

ここでいう認証システムについて説明しておきます。chocoZAPでは各店舗（ジム）に受付などの従業員は一切存在しません。利用者は専用のchocoZAPアプリをスマホにダウンロードしており、アプリに入館証であるQRコードを表示させます。そのQRコードを入口にある読み取り装置にかざすと認証が行われ、成功すれば解錠され入館できるという仕組みです。

# 仮説

実際にどのような認証システムの設計であれば良さそうでしょうか。ここでは以下のポイントを重視して考えます。

1. chocoZAPアプリからの認証リクエストであることを担保する。QRコードは本当に正規のアプリで表示されているのか？
2. chocoZAPの会員であることを担保する。これは当然。
3. QRコードが漏洩しても問題ない設計になっているかどうか。これも大切ですね。

#

これを満たすには以下の手順が良いのかなと思いました。

1. 利用者は予めchocoZAPアプリにログインしておく。以降、アプリは端末にユーザー情報を保持する。
2. chocoZAPアプリで現在時刻とユーザー情報をもとにトークン（文字列）を生成。トークンをQRコードに埋め込む。
3. 読み取り装置はQRコードを読み取ったら、認証サーバにトークンを送付する
4. 認証サーバはトークンをデコードし、ユーザー認証と時刻が直近であるかどうかを確認し結果を返す
5. 読み取り装置はその結果に従い、扉を解錠する。

要するにアプリ側でトークンを発行するということですね。ポイント2と3はクリアしています。しかしこれだとポイント1の「chocoZAPアプリからの認証リクエストである」かどうか分かりません。

ですが本当にchocoZAPアプリからの認証かどうか確かめる必要があるのでしょうか？私はよく分からなくなってきました。また、トークン生成のロジックをアプリ側に持たせるのも何だか怖い気がします。

# ChatGPTで答え合わせ

***Q.*** Chocozapというジムでは利用者が専用のアプリからQRコードをジムの入り口にある専用の読み取り装置に読み取らせて認証されると入場できます。この時、アプリとサーバの間で行われている具体的な認証の仕組みを提案してください。

***A.***

1. アプリの認証: 利用者が初めてアプリを使用するときに、自分の身分を証明するために必要な情報（例えばメールアドレス、パスワード、あるいはソーシャルログインなど）を用いて認証を行います。アプリはサーバと通信し、利用者の認証情報を検証します。

2. QRコードの生成: 認証が成功すると、アプリはサーバから一意のトークン（通常は暗号化されていて、ユーザーの情報や現在の日時などを含む）を受け取ります。その後、アプリはこのトークンを基にQRコードを生成します。このQRコードは、利用者がアプリを開くたび、または定期的に（例えば毎分、毎時間）新しく生成されます。

3. QRコードのスキャン: 利用者はアプリを開き、生成されたQRコードをジムの入り口にある専用の読み取り装置に提示します。この装置はQRコードをスキャンし、含まれる情報（一意のトークン）をデコードします。

4. サーバへの認証リクエスト: QRコードの読み取り装置は、デコードされた情報をサーバに送信します。サーバはこの情報（トークン）を用いて、利用者が認証されたかどうかを検証します。

5. 認証の結果: サーバは利用者の認証を検証した後、その結果を読み取り装置に送信します。読み取り装置はこれに基づいて行動します。もし認証が成功していれば、利用者をジムに入場させます。認証が失敗した場合は、入場を拒否します。

このアプローチにより、セキュリティを保ちつつ利用者の利便性を向上させることができます。利用者は毎回新しいQRコードが生成されるため、QRコードが盗まれても次回からは無効になります。また、サーバで全ての認証を行うため、読み取り装置がハッキングされても利用者の情報は安全です。

---

なるほど、トークンの生成をサーバ側に任せるとアプリ側に重要なロジックを持たなくて良いので安心ですね。加えてchocoZAPアプリ以外からのリクエストをケアしなくて良いのか聞いてみました。

***A.***
ここで、利用者がアプリを通じて生成したQRコード以外のリクエストを識別・排除するために、以下の追加的な検証手順を導入します：

リクエストの元の確認: サーバは、リクエストが本当にアプリから来たものかを確認します。これは、リクエストに含まれる特殊なヘッダーや署名などを確認することで行われます。

トークンの有効性確認: トークンは一定の時間後に無効になるように設定されています（例：数分、数時間など）。これにより、もしトークンが何らかの理由で漏洩しても、その有効期限が切れた時点で利用できなくなります。

---

なるほど、アプリが特殊なヘッダーを生成するようにするのが一番根本的な対策のように思えます。実際に実装する場合はQRコード内にヘッダーとなる文字列をトークンとは別に埋め込むような実装になるでしょうね。

トークン生成などの重要なロジックはサーバに置く。なるほどという感じでした。
データ分析 フラグの立て方

# --- 内容 ---
1. よく使うフラグの立て方
2. 祝日のフラグの立て方(日本)


#### --- よく使うフラグの立て方 ---
- maskメソッドを利用します。whereメソッドを利用した方法やfor文とif文を使って強引にやる方法もあるのですが、whereはFalseの時に実行を行うので分かりにくく、for文とif文で強引に行く方法はデータフレームを結合(concat)した際に欠損値が生じるケースがあるので使いにくいです。
したがってmaskを利用する方が個人的には圧倒的にやりやすかったです。
[参考]
- [where](https://deepage.net/features/pandas-where.html)
- [whereとmaskのQiita記事](https://qiita.com/KHTTakuya/items/1cd543cd929e1a2863b6)
- [concat時の不整合](https://teratail.com/questions/104363)

#### 方法
方法は簡単で、初めに全行が0の新しいカラムを作る ➡ 条件に合致する箇所を1に変えるの順番になります。
maskの使い方は

```python
df['column_name'] = 0
df['column_name'].mask(条件式, 代入する値, inplace=True)
```

代入する値はフラグを立てるだけなので1でいいでしょう。
inplaceはTrueでもFalseでも大丈夫です。(元のdfを変えるかどうかなので)
条件式はand, or, notではなく&, |, ~を使う点に注意してください。


#### --- 祝日フラグの立て方(日本) ---
- jpholidayライブラリを使用します。.is_holidayは日付データを与えると祝日かどうかを判定してくれます。
先ほど同様に全行が0のカラムを新しく作った後にfor文をぶん回して、at関数で代入してあげれば完了です。
今回は下の様なテーブルを作ったとします。

```python
df['holiday'] = 0
df.head(2)
```
【出力結果】
||datetime|holiday|
|:--|:-:|--:|
|1|2000-01-01|0|
|2|2000-01-02|0|

あとはjpholidayをimportして、for文で行数分だけ代入するだけです。

```python
import jpholiday
for i in range(df.shape[0]):
    df.at[i,'holiday'] = jpholiday.is_holiday(df["datetime"].iloc[i])
```

【出力結果】
||datetime|holiday|
|:--|:-:|--:|
|1|2000-01-01|True|
|2|2000-01-02|True|

jpholidayでは元旦も祝日扱いとなります。
[jpholiday](https://pypi.org/project/jpholiday/)
このままでは扱いずらいので、1をかけてあげれば0,1の形式に変わります。
(私の環境だけかもしれませんが、*1をしただけだとobject型のままなのでastypeでint型に直しています。)

```python
df['holiday'] = (df['holiday'] *1).astype(int)
```

以上です。
GASでコピーしたデータをスプレッドシートに貼り付ける方法

①コピーするスプレッドシートをフォルダに保存
②貼り付けたいスプレッドシートのIDを設定
③ ②のシート内の貼り付けたいシートのタブのIDを設定
④Sheet.getRangeでコピーしたいデータの範囲を指定
⑤SetValuesで貼り付け

基本的には、
コピーしたいファイルが保存されているフォルダのIDとタブの指定
コピーするファイルのIDとタブの指定
貼り付ける範囲の指定
で実行できます。
ただ、このままだと複数回実行した際に同じデータが上書きされずに
複数保存されてしますのでfor分とif分で条件を指定して保存したいデータだけ
貼り付けされるようにして下さい。

```
function test1() {
  // コピー
  var sheetMain = SpreadsheetApp.getActiveSheet();
  var files = DriveApp.getFolderById('元データが格納されているフォルダのID').getFiles();
  while (files.hasNext()) {
    var file = files.next();
    var sheet = SpreadsheetApp.open(file).getSheets()[2];
  }
  Logger.log("コピーが完了しました。");

  // 貼り付け
  var spreadsheet = SpreadsheetApp.openById('貼り付けたいシートのID'); // シートの指定
  var sheet2 = spreadsheet.getSheetByName('tabの名前'); // タブの指定

  aaa(sheet, sheet2);
}

function aaa(sheet, sheet2) {
  // コピー
  var dat = sheet.getDataRange().getValues();
  var numRow = dat.length;
  var copyRowCount = 0;
  for (var i = 10; i < numRow; i++) {
    if (dat[i][1] == "") {
      break;
    }
    copyRowCount++;
  }

  if (copyRowCount == 0) {
    return; // If there are no rows to copy, exit the function early
  }

  range = sheet.getRange(11, 1, copyRowCount, 32).getValues();
  Logger.log("コピーした範囲は " + sheet.getName() + "!" + sheet.getRange(11, 1, copyRowCount, 32).getA1Notation() + " です。");

  // 貼り付け
  var dat2 = sheet2.getDataRange().getValues();
  var currentDate = new Date();
  var month = currentDate.getMonth() + 1;
  var year = currentDate.getFullYear();
  var dateStrings = [];

  for (var i = 0; i < 4; i++) {
    var newDate = new Date(year, month - 1, i + 1, 16, 0, 0, 0);
    dateStrings.push(newDate.toLocaleDateString("en-US")); // スプレッドシート上の書式に合わせてロケールを指定
  }

  var numRow2 = dat2.length;
  for (var n = 1; n < numRow2; n++) {
    if (dateStrings.includes(sheet2.getRange(n, 38).getDisplayValue())) { // スプレッドシート上の表示値を取得して比較
      break;
    } else if (sheet2.getRange(n, 38).getDisplayValue() == "") { // スプレッドシート上の表示値を取得して比較
      break;
    }
  }
  range2 = sheet2.getRange(2, 38, copyRowCount, 32).setValues(range); // 範囲
  Logger.log("貼り付け範囲");
  Logger.log(range2.getValues());
}

```
VSCodeでのVite/Vue3アプリのデバック

Viteで起動したVue3アプリのデバックで苦戦したので備忘録を残す

## 1. (macのみ)setting.jsonの設定

macの場合、vsc内のターミナルにうまく環境変数が引き継がれなく、
yarnやpnpmのコマンドが使えないので、ターミナルの設定が必要。
setting.jsonに以下の設定を追加する。

.vscode/setting.json
```json
{
 "terminal.integrated.profiles.osx": {
        "zsh": {
            "path": "zsh",
            "args": [
                "-i"
            ]
        }
    },
    "terminal.integrated.defaultProfile.osx": "zsh",
    "typescript.tsdk": "node_modules/typescript/lib",
}
```

## 2.  launch.jsonの設定

launch.jsonに以下の設定を追加。
webRootとurlの設定が重要。

.vscode/launch.json
```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "msedge",
            "request": "launch",
            "name": "Launch Edge against localhost",
            "url": "http://localhost:4001",
            "webRoot": "${workspaceFolder}/${モノレポ構成の場合、プロジェクトのrootディレクトリ}",
        }
    ]
}
```

* type : edgeで起動する場合はmsedge
* request : launchを選択
* name : 任意の名前
* url : viteで起動する際のportと合わせる
* webRoot : viteで起動する本体が置いてあるディレクトリ。

## 3  viteでのvueアプリ起動

webRootで設定したディレクトリのvueアプリを起動させる。

```sh
$ vite --port 4001 --config vite.config.ts
```

## 4 デバックの起動

VSCのデバックボタンからデバックを起動させる。
開いたedgeのvueアプリはbreakポイントで止まるようになる。
弾丸のバウンドに合わせて音を出す【UnrealC++】

## 概要
弾丸（ProjectileMovement）が物に当たった時に呼び出されるデリゲートを使って、弾丸がバウンドする度に音を出す処理を実装します。

:::note warn
・FPSテンプレートを使用します。
・プロジェクト名はBounceTestとします。
・UE5.1.1を使用します。
:::

***

## プロジェクトを作って開く
FPSテンプレートでc++を選択後プロジェクトを作成します。名前はBounceTestとします。
プロジェクトが開いたらC++ ClassesフォルダからBounceTestProjectileをダブルクリックします。
![スクリーンショット 2023-07-16 150710.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1110651/1e11350e-9da3-dc4e-9da7-8f7eef24a3ab.png)
VisualStudioが立ち上がります。

## .h（ヘッダー）に以下を追記

```c++:SampleCharacter.cpp
protected:
    // OnBounceを呼び出す為のBeginPlay(コンストラクタで呼び出すとコンパイルエラーになる)
	virtual void BeginPlay() override;

	UFUNCTION()
	void OnBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity);

private:
	UPROPERTY(EditAnywhere)
	class USoundCue* BounceSound;

```
<br>

## .cpp（ソース）に以下を追記
```c++:SampleCharacter.cpp
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"

void ABounceTestProjectile::BeginPlay()
{
	AActor::BeginPlay();

	ProjectileMovement->OnProjectileBounce.AddDynamic(this, &ABounceTestProjectile::OnBounce);
}

void ABounceTestProjectile::OnBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
	if (BounceSound)
	{
		UGameplayStatics::PlaySoundAtLocation(
			this,
			BounceSound,
			GetActorLocation()
		);
	}
}

```
ここまで書いたらコンパイルします。(UE5はCtrl+F5, UE4はCtrl+Alt+F11)
<br>

## エディターで音の設定
BP_FirstPersonProjectileを開きます。
![スクリーンショット 2023-07-16 153038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1110651/10246bc5-b7bd-e5fd-b623-048b16ba7f6c.png)

BP_FirstPersonProjectile(Self)を選択し、DetailsよりBounce Soundを検索します。![スクリーンショット 2023-07-16 153341.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1110651/f5e27d5c-fba0-64bc-b151-18468a128930.png)

好きな音を設定します。
![スクリーンショット 2023-07-16 153442.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1110651/0dbe956a-5ab7-3ae7-9e73-191cf7534ec9.png)


最後にプレイして確認します。黄色の弾が壁や床に触れると音が鳴ります。
以上です。
***
JSの用語について

# 変数の宣言
JavaScriptでは変数の宣言はvar,let,constで行う
ただし、varについては以下の理由で使用が控えられている
###### 1.再宣言が可能
一度宣言した変数と同じ変数名で再度宣言することが許容されている
以下のコードではコンソールには0,1と表示される
```diff_javascript
var a = 0;
console.log(a);
var a = 1;
console.log(a);
```
###### 2.参照が巻き上げられる
変数を宣言する前に代入することが許容されている
以下のコードではコンソールには0と表示される
```diff_javascript
a = 0;
console.log(a);
var a;
```
##### 3.スコープ単位が関数である
関数やifのような制御構文のブロックをすり抜けて変数の参照をすることが許容されている

# コールスタック
実行中のコンテキストの積み重ねのこと
「LIFO」の形で消滅していく

# ホイスティング
コンテキスト内で宣言した変数、関数をコード実行前にメモリに配置すること
関数の宣言より前に実行している以下のような処理でもコンソールにログが表示される

```diff_javascript
a();
function a() {
  console.log('b');
}
```
エラー対処方法

実務での僕なりのエラー対処手順を言語化してみました。
1. エラー発生
2. エラー文を読む
3. エラー文から原因が特定できた場合
   - エラー文に従い対処法を調査、考える
   - エラーを対処する<strong>（終了）</strong>
4. エラー文から原因を特定できない場合（エラー文が出ない、エラー文の内容が理解できない場合を含む）
   - 動作済み環境と動作していない環境の差分を調査
   - 差分から原因を特定できた場合
     - 差分を埋めるための方法を調査、考える
     - 差分を埋める<strong>（終了）</strong>
   - 差分から原因を特定できない場合（差分が調査出来ない、差分が無い場合を含む）
     - エラー文がある場合
       - エラー文をそのままググり原因を特定
         - 個人ブログ
         - 企業のサイト
         - github issue　
         - stackoverflow, teratail　
         - 等
       - 対処<strong>（終了）</strong>　　実務ではここの繰り返しが多い印象
     - エラー文が無い場合
       - 起きた現象を言語化できる場合
         - 言語化したものをググる
         - 対処<strong>（終了）</strong>
       - 言語化出来ない場合
         - そのままソースコードを質問サイトに貼る
         - （注意）実行環境、やりたいこと、起きた症状（確立した言語でなくても少しでも多く書く）、やったこと等の詳細をしっかり書くこと
         - 返信内容を試す<strong>（終了）</strong>
GASでスプレッドシートをPDFに変換する

①スプレッドを開く
②コードを実行する
③ドライブにPDFが保存される

```
function savePdf() {
  //PDFの保存先
  //★★★フォルダーIDを入力してください★★★
  let folderId = "フォルダID";

  //アクティブなスプレッドシートを取得する
  let ss = SpreadsheetApp.getActiveSpreadsheet();

  //スプレッドシートIDを取得する
  let ssId = ss.getId();

  //シートIDを取得する
  let shId = ss.getActiveSheet().getSheetId();

  //★★★PDFのファイル名を入力してください★★★
  //※ポイント: ファイル名が重複しないようにしましょう
  let fileName = "pdfファイル名";

  //関数createPdfを実行し、PDFを作成して保存する
  createPdf(folderId, ssId, shId, fileName);
}

//PDFを作成し指定したフォルダーに保存する関数
//以下4つの引数を指定する必要がある
//1: フォルダーID (folderId)
//2: スプレッドシートID (ssId)
//3: シートID (shId)
//4: ファイル名 (fileName)
function createPdf(folderId, ssId, shId, fileName) {
  //PDFを作成するためのベースとなるURL
  let baseUrl = "https://docs.google.com/spreadsheets/d/"
    + ssId
    + "/export?gid="
    + shId;

  //★★★自由にカスタマイズしてください★★★
  //PDFのオプションを指定
  let pdfOptions = "&exportFormat=pdf&format=pdf"
    + "&size=A4" //用紙サイズ (A4)
    + "&portrait=false"  //用紙の向き true: 縦向き / false: 横向き
    + "&fitw=true"  //ページ幅を用紙にフィットさせるか true: フィットさせる / false: 原寸大
    + "&top_margin=0.50" //上の余白
    + "&right_margin=0.50" //右の余白
    + "&bottom_margin=0.50" //下の余白
    + "&left_margin=0.50" //左の余白
    + "&horizontal_alignment=CENTER" //水平方向の位置
    + "&vertical_alignment=TOP" //垂直方向の位置
    + "&printtitle=false" //スプレッドシート名の表示有無
    + "&sheetnames=false" //シート名の表示有無
    + "&gridlines=false" //グリッドラインの表示有無
    + "&fzr=false" //固定行の表示有無
    + "&fzc=false" //固定列の表示有無;

  //PDFを作成するためのURL
  let url = baseUrl + pdfOptions;

  //アクセストークンを取得する
  let token = ScriptApp.getOAuthToken();

  //headersにアクセストークンを格納する
  let options = {
    headers: {
      'Authorization': 'Bearer ' + token
    }
  };

  //PDFを作成する
  let blob = UrlFetchApp.fetch(url, options).getBlob().setName(fileName + '.pdf');

  //PDFの保存先フォルダー
  //フォルダーIDは引数のfolderIdを使用します
  let folder = DriveApp.getFolderById(folderId);

  //PDFを指定したフォルダに保存する
  folder.createFile(blob);

}

```
micro:bitで360度回転サーボ(ローテーションサーボ)SG90-HVを使ってみた

今まで0度～180度の間で角度を指定するサーボモーターSG90は使ったことがあったのですが、一度360度回転サーボを使ってみたいと思い、今回動作検証をしてみました。



# 購入
みんな大好き秋月電子で360度回転サーボ：SG90-HVが580円で販売されています。

https://akizukidenshi.com/catalog/g/gM-14382/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71746/f2809c43-add8-38b8-ca63-a519f2206d82.png)

# micro:bitでのサーボモーター使い方
参考までに、0度～180度の間で角度を指定するサーボモーターSG90を利用する場合は
高度なブロック＞サーボ　にある下記のブロックを使い、角度を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71746/60366fb1-87e9-0513-b9a6-e922945975e4.png)

続いて今回のメインである、360度回転サーボ(ローテーションサーボ)SG90-HVの利用方法です。

使うブロックは通常のサーボモーターと同じ！

ただし、ブロックの角度の数字により回転のスピードが変わります。

0度～90度未満：時計回りに回転、0度が最大スピード。90度に近づくと遅くなっていきます。
90度：静止
90度より上～180度：反時計回りに回転、180度が最大スピード。、90度に近づくと遅くなっていきます。

# サンプルコード
Aボタンを押すと時計回りのスピードアップ(角度の変数angleにマイナス10)、Bボタンを押すと反時計回りのスピードアップ(角度の変数angleにプラス10)
A+Bボタンを押すと角度の変数angleがリセット(90度が指定)され、静止します。
随時、変数angleに角度の値が更新され、「ずっとブロック」の中でサーボに角度が出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71746/adeeb003-716a-8be1-5a4a-e7ae1611fdad.png)

こちらからサンプルコードを利用可能です。

https://makecode.microbit.org/S04997-79750-02929-27335

## 動作ビデオ
<iframe width="1051" height="591" src="https://www.youtube.com/embed/DfhgBbDFQQ4" title="micro:bitで360度回転サーボモーターを使ってみた" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
GASでDrive内のファイルのフォルダ移動を行う

①ファイルをフォルダに保存
②コード実行
③指定したファイルを指定フォルダへ保存

```
function test() {
  /folder移動/
  var filesAndFolders = [
    { sourceFile: "保存されているファイル名1", destFolder: "移動先フォルダ名1" },
    { sourceFile: "保存されているファイル名2", destFolder: "移動先フォルダ名2" },

  ];

  for (var i = 0; i < filesAndFolders.length; i++) {
    moving(filesAndFolders[i].sourceFile, filesAndFolders[i].destFolder);
  }
}

function moving(sourcefile, destfolder) {
  var destFolders = DriveApp.getFoldersByName(destfolder);
  var sourceFiles = DriveApp.getFilesByName(sourcefile);

  if (destFolders.hasNext() && sourceFiles.hasNext()) {
    var destFolder = destFolders.next();
    var file = sourceFiles.next();
    var sourceFolder = file.getParents().next();
    destFolder.addFile(file);
    sourceFolder.removeFile(file);
  } else {
    console.log("エラー: ファイルが見つかりません: " + sourcefile);
  }
}
```
GASでエクセルをスプレッドシートに変換する

①変換したい複数のエクセルファイルをファイルに保存
②コード実行
③保存先のファイルにスプレッドシートが格納される。

※保存ファイルにPASSが設定されていると実行されないので
事前にPythonなどでPASSの解除が必要


```
function change() {
  /入金表/
  var dest_folder = DriveApp.getFolderById('エクセルファイルが保存されているフォルダのID');
  var source_folder = DriveApp.getFolderById('変換されたスプレッドシートを保存するフォルダのID');
  var excel_files = source_folder.getFiles();

  while (excel_files.hasNext()) {
    var file = excel_files.next();
    var options = {
      title: file.getName(),
      mimeType: MimeType.GOOGLE_SHEETS,
      parents: [{ id: dest_folder.getId() }]
    };
    Drive.Files.insert(options, file.getBlob());
  }
}
```
RubyのPolarsでデータフレームのすべての行を表示する

## はじめに

:::note warn
この記事は2023年7月16日時点の記事であり、半年後や1年後にはあまり役に立たない可能性が高いです。あくまで現時点での回避策だと思ってください。
:::

みなさん、[Polars](https://github.com/pola-rs/polars)はご存じですか？

Pandasを置き換えるかもしれないと言われている、Rustで実装された新しい高速なデータフレームライブラリです。PolarsにはRubyのバインディングもあって、Ankaneさんが開発・メンテナンスを行っています。

https://github.com/ankane/polars-ruby

このPolarsでCSVファイルを読み込むと次のようになります。

```ruby
require "csv"

df = Polars.read_csv("iris.csv")
```

```
=>
shape: (150, 5)
┌──────────────┬─────────────┬──────────────┬─────────────┬───────────┐
│ sepal.length ┆ sepal.width ┆ petal.length ┆ petal.width ┆ variety   │
│ ---          ┆ ---         ┆ ---          ┆ ---         ┆ ---       │
│ f64          ┆ f64         ┆ f64          ┆ f64         ┆ str       │
╞══════════════╪═════════════╪══════════════╪═════════════╪═══════════╡
│ 5.1          ┆ 3.5         ┆ 1.4          ┆ 0.2         ┆ Setosa    │
│ 4.9          ┆ 3.0         ┆ 1.4          ┆ 0.2         ┆ Setosa    │
│ 4.7          ┆ 3.2         ┆ 1.3          ┆ 0.2         ┆ Setosa    │
│ 4.6          ┆ 3.1         ┆ 1.5          ┆ 0.2         ┆ Setosa    │
│ …            ┆ …           ┆ …            ┆ …           ┆ …         │
│ 6.3          ┆ 2.5         ┆ 5.0          ┆ 1.9         ┆ Virginica │
│ 6.5          ┆ 3.0         ┆ 5.2          ┆ 2.0         ┆ Virginica │
│ 6.2          ┆ 3.4         ┆ 5.4          ┆ 2.3         ┆ Virginica │
│ 5.9          ┆ 3.0         ┆ 5.1          ┆ 1.8         ┆ Virginica │
└──────────────┴─────────────┴──────────────┴─────────────┴───────────┘
```

このように、すべての行が表示されず、先頭から4行、末尾から4行が表示されます。
つまり、合計で最大で8程度のレコードしか表示されないということになります。これでは少し不便ではないでしょうか？

（余談ですが、平均的な知能の人間では、頭の中で考えられることは7個前後が限界だと言われています。そういう意味ではこのデザインは人間の認知に沿った使いやすいデザインとなっていると言えるかもしれません）

## すべての列を表示するにはどうすればよいか？

Pythonでは、`set_tbl_rows` というメソッドが用意されていますが、その実装を見ると、[環境変数を設定](https://github.com/pola-rs/polars/blob/fd871ebcab4caed85ec63c9276b23d69d92b243b/py-polars/polars/config.py#L723-L755)しています。

```
ENV["POLARS_FMT_MAX_ROWS"] = "10000"
```

上のように設定すれば、10000行表示してくれるはずです。この環境変数は、`irb` の実行中であっても変更することが可能です。将来的にはRubyのPolarsバインディングにも Config モジュールが用意される予定だそうです。

## 参考文献

https://github.com/ankane/polars-ruby/issues/26

この記事は以上です。
SwiftでBoidsをやってみる

[『HUMANITY®』中村勇吾＆水口哲也スペシャルインタビュー！ 吉田修平が訊く“体験として新しいゲーム” – PlayStation.Blog 日本語](https://blog.ja.playstation.com/?p=230545)

今このコンテキストがどれだけ理解してもらえるのか不安ではありますが、自分はかつてFlasherで、「中村勇吾」という名前を見た瞬間に上記記事を開かずにはいられませんでした。

中村勇吾氏は今も興味深いことをしていますね。

上記記事で特に興味深かったのは

> 「ボイド（Boids）」という群れのシミュレーションプログラム

という部分です。

Boidsに関しては今回初めて知りましたが、調べてみるとやはり興味深いものでしたので、自分でも何かやってみたいという気になりました。

## 成果物

![画面収録 2023-07-16 10.38.51.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2763088/f64cf5ee-02b4-9067-7363-ca4da26aad94.gif)

https://github.com/HonmaMasaru/Boids

##  参考にしたもの

[【解説】動物の動きを再現する「Boids Algorithm」ってなぁに？？ | cloud.config Tech Blog](https://tech-blog.cloud-config.jp/2022-04-07-boids-algorithm)

アルゴリズムがどんなものかについては、検索でトップに来ていた上記ページがやはり1番わかりやすいです。

そして身もふたもない話ですが、2014年にSwiftでBoidsを実装している記事がQiitaにありました。

[SwiftとSprite Kitでボイド - Qiita](https://qiita.com/tnantoka/items/5fd493300c39430443ff)

こちらソースがSwift4時代のものですが、アルゴリズムをコードでどう実装しているかの解説が記載されています。

Boidsアルゴリズムの実装は色々なものがありましたが、1番動きがそれっぽく、コードもシンプルな以下をそのままポーティングさせていただきました。

https://github.com/beneater/boids

## 今回の学び

### 1. SwiftUIのアニメーションの向き不向き

当初はSwiftUIのカスタムアニメーションを使ってやってみようと思っていました。

しかしながら、カスタムアニメーションは「始点と終点があり、その間の動きを補完する」という用途のためのもので、今回のように「フレームごとにパラメータが変わりながらエンドレスにアニメを実行する」というのは無理そうでした。

それに実装できたとしてもパフォーマンス上の問題もあると思われます。

今後、始点と終点がないアニメに関しては素直にSpriteKitを使うことにします。

### 2. SpliteKitとSwift Playgroundは相性が悪い

最初コーディングはSwift Playgroundで始め、SpliteKitの簡単な動きを試してみたのですが、動きがガクガクでした。

FPSは分数になっていたと思われます。

おそらくデバッグ関係の出力が色々重そうで、Playgroundにオフにする何らかの設定があるものかと思ったのですが、特に見つからず、そういうものだと諦めてXcodeのプロジェクトでやることにしました。

### 3. SpliteKitのフレームの更新について

SpriteKitシーンのフレーム更新時の処理は以下のようになっています。

```swift
    /// 更新処理
    /// - Parameter currentTime: 現在時間
    override func update(_ currentTime: TimeInterval) {
        super.update(currentTime)
        view?.scene?.removeAllChildren()
        boids.forEach {
            $0.update(boids)
            view?.scene?.addChild($0)
        }
    }
```

`$0.update(boids)`は要素の位置情報の更新が行われるのですが、当初はこれだけで勝手に動くものと思っていました。

```swift
    /// 更新処理
    /// - Parameter currentTime: 現在時間
    override func update(_ currentTime: TimeInterval) {
        super.update(currentTime)
        boids.forEach {
            $0.update(boids)
        }
    }
```

SpriteKitの物理エンジンを利用した際はノードを追加すれば勝手に動くのでその感覚でした。

実際にはフレームごとにノードを全削除し、位置情報を更新し、ノードを再描画する、という手順が必要のようです。

------------

今回UIとしては実装しませんでしたが、下記パラメータの数値をいじるといろんな動きが表現できます。

```swift
/// 各種設定
let visualRange: CGFloat = 75
let centeringFactor: CGFloat = 0.005 // adjust velocity by this %
let minDistance: CGFloat = 20 // The distance to stay away from other boids
let avoidFactor: CGFloat = 0.05 // Adjust velocity by this %
let matchingFactor: CGFloat = 0.05 // Adjust by this % of average velocity
let speedLimit: CGFloat = 15
let margin: CGFloat = 200
let turnFactor: CGFloat = 1
```

![画面収録 2023-07-16 12.34.50.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2763088/69246035-d028-9b73-035f-ba25e104fa13.gif)  
visualRange: 150, minDistance: 100

本当に興味深いアルゴリズムです。
GASでGoogleDriveの不要なファイルを削除する。

GoogleDriveで同名のファイルを保存すると
URLのIDだけ変更され同名のままファイルが2つ存在してしまいます。

GASを実行する際にファイル名でシートを呼び出す際に不便なので
ファイルの削除を実行するコードを添付します。


```
function gomi() {
  /ゴミ箱/
  var fileNames = [
    "削除したいシートのファイル名1",
    "削除したいシートのファイル名2",
  ];

  for (var i = 0; i < fileNames.length; i++) {
    var files = DriveApp.getFilesByName(fileNames[i]);
    if (files.hasNext()) {
      files.next().setTrashed(true);
    } else {
      console.log("エラー: ファイルが見つかりません: " + fileNames[i]);
    }
  }
}
```
ngrokでローカル環境に立ち上げたアプリを外部に公開する

# この記事は何？
ngrokというサービスを使って、ローカル環境に立ち上げたWEBアプリを外部公開する手順のメモです。

# 環境
* Windows10 pro
* WSL2
* Docker 20.10.20
* Docker Compose v2.12.0

# ngrokとは？
ngrokは、ローカルで動作しているサーバーをインターネットに公開するための便利なツールです。一般に、ウェブ開発やアプリケーション開発などで、ローカル環境で開発したものをテストしたりデモンストレーションしたりする際に使用されます。

ngrokの仕組みは以下のようになっています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164843/8d2c673b-ac7c-b93f-57bd-be843daab9b7.png)

## 1. ローカルサーバの起動
開発者は自身のマシン（PCなど）上でウェブサーバーを起動します。これにより、ローカルマシンで動作するウェブアプリケーションが生成されます。

## 2. ngrokの起動
開発者は同じマシン上でngrokを起動します。ngrokは指定されたポート（例えば、ウェブアプリケーションが起動されているポート）でリッスンを開始します。

## 3. トンネルの確立
ngrokはngrokのサーバー（ngrokのインフラストラクチャに存在）と開発者のマシンの間に安全なトンネルを確立します。これにより、ローカルマシン上のアプリケーションが、ngrokの公開URLからアクセス可能になります。

## 4. インターネットからのアクセス
開発者はこのngrokの公開URLを他の人と共有することで、インターネット上から自身のローカルマシン上のアプリケーションにアクセスできます。これにより、自身が開発したアプリケーションのテストやデモが可能になります。



# 準備
事前に ngrok のアカウントを作成し、Authtoken を発行しておきます。

https://ngrok.com/


# 環境構築
dockerを使用します。
ここでは nginx サーバーをwebアプリに見立て、これを外部に公開してみます。

## docker-compose.yml

```yaml
version: "3"
services:
  nginx:
    image: nginx:latest
    container_name: nginx
    expose:
      - 80

  ngrok:
    container_name: ngrok
    image: ngrok/ngrok:latest
    restart: unless-stopped
    command: ["http", "nginx:80"]
    ports:
      - 4040:4040
    depends_on:
      - nginx
    environment:
      - NGROK_AUTHTOKEN
```

docker-compose.yml と同フォルダに .env ファイルを用意し、アカウント作成で発行された NGROK_AUTHTOKEN を記述しておきます。

```
NGROK_AUTHTOKEN=<発行したトークン>
```

# 起動
```
docker-compose up -d
```

# アクセス確認

localhost:4040 にアクセスすると、以下のように ngrokのアクセス用URLが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164843/8d8dbed5-bab2-7b1c-d57e-e832bc68a615.png)

このURLに別の端末からアクセスしてみると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164843/4aaf9f31-ad8e-1082-2e88-f795fe5bfa74.png)

ローカルに立ち上げたnginxサーバーにアクセスすることができます。
docker-compose のログを確認してみると、確かにアクセスされていることが確認できます。

```bash
docker-compose logs -f nginx
```

# まとめ
デプロイしなくてもアプリを外部公開できるのは便利ですね！
ローカルで作ったアプリをお試しで触ってもらうときなどに活躍しそうです。
[Javascript/Web]iOSでSVG画像がぼやける不具合の対策

# はじめに
こんにちは。また久しぶりの投稿ですね。
WebアプリでSVG画像を出すのは、よくあることですね。しかし、SVG画像にシャドーをかけると、iOSのブラウザで開てみたら、画像がぼやけます。
これは困ります。
おそらくiOSのバグですが、いつ直すかわからない以上、直すまで待つのもいけないです。
代わりにPNG画像を出すのは解決できるが、ズームインしてもぼやけないために、大きいPNGを置かないといかないので、画像のサイズが増えます。
では、SVGを使う前提で、ぼやけるを防ぐ方法があるのでしょうか？ここで一つの解決策を紹介させていただきます。

# 問題点
上記通り、シャドーをかけているSVGをそのままwebに出すと、iOS上でぼやけます。
SVGは下記のようです。
```xml
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="paper-plane" class="svg-inline--fa fa-paper-plane fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <filter id="f1">
    <feDropShadow dx="0" dy="2" stdDeviation="15" flood-color="black" />
  </filter>
  <path fill="currentColor" d="M476 3.2L12.5 270.6c-18.1 10.4-15.8 35.6 2.2 43.2L121 358.4l287.3-253.2c5.5-4.9 13.3 2.6 8.6 8.3L176 407v80.5c0 23.6 28.5 32.9 42.5 15.8L282 426l124.6 52.2c14.2 6 30.4-2.9 33-18.2l72-432C515 7.8 493.3-6.8 476 3.2z" filter="url(#f1)"></path>
</svg>
```

そして画像を出すソースコード（React Native）は下記のようです。
```jsx
<View style={styles.imgContainer}>
    <Image source={images.sample} tintColor={"#897654"} style={styles.img} />
</View>
// ...
imgContainer: {
    width: 50,
    height: 50,
    marginVertical: 6
},
img: {
    width: 50,
    height: 50
}
```

# 解決策
SVG画像のサイズを何倍にして、スタイルに`transform`をつけて元のサイズに戻せば良い。
ソースコードを見てみましょう。
```tsx
<Image source={images.sample} tintColor={"#897654"} style={styles.img} />
// ...
img: {
    width: 200,
    height: 200,
    transform: [{scale: 0.25}]
}
```
上記の例で、50x50の画像が欲しいですが、4倍まで上げて200x200にして、また`transform`で1/4にして結局50x50にできました。
もちろん画像は200x200の領域を占めるが、これに対する処理も簡単です。
```tsx
<View style={styles.imgContainer}>
    <Image source={images.sample} tintColor={"#897654"} style={styles.img} />
</View>
// ...
imgContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    width: 50,
    height: 50,
    marginVertical: 6,
    overflow: 'hidden'
}
```
上記のように、50x50のコンテナーを用意すれば、問題がないです。画像を真ん中に揃え、オーバーフローを隠せば、うまく表示されます。

# 改善結果
下記1つ目の画像はそのまま50x50で出し、iOS Safariで見た結果です。一方で、2つ目の画像は200x200かつ`transform`をつけている画像です。この方法でぼやけることをちゃんと防ぎます。
![demo1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252959/b394bf0a-e122-faf5-313d-42480c8150c7.png)

# 参考資料
https://stackoverflow.com/questions/43546754/svg-icons-in-safari-are-blurred ←2017年ごろの質問なのに、今も参考になれるとは。iOS何とかしなさいよ。
【Heroku CLI】Heroku loginで“IP address mismatch”と出てログインできないとき！

Heroku CLIコマンドで、herokuにログインしようとした際のエラー解消方法についてです。
ググった記事で解消できなかったので、私が解消した方法を紹介します。

## 問題
下記のコマンドをした後に、

```terminal
heroku login
```

ブラウザが立ち上がり、
「Login」ボタンを押したときに、
「IP address mismatch」と表示され、
ログインできないことがあります。

## 解消方法
下記のコマンドをすると、
ターミナル上でメールアドレスとパスワードの入力を求められるので、それでログインできます。

```terminal
heroku login -i
```

## それでもエラーになる場合
上記のコマンドでも、
二要素認証を設定している場合はログインできません。

その時は、
パスワードの代わりに、APIキーを入力します。

APIキーは、HEROKUブラウザから確認できます。

![スクリーンショット 2023-07-16 14.22.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258821/8f472ffd-a235-5430-e2a6-f5e2b3165350.png)


エラー解消に役立てれば幸いです！🙆‍♂️
Rubyでのoptparseについて



# optparseとは
標準ライブラリのひとつ
コマンドラインのオプションを取り扱うためのライブラリ

# 使い方

### 基本的なパターン
1. `optparse`をrequireする
1. `OptionParser`オブジェクトを生成する
1. `on`メソッドを用いて、オプションを取り扱うためのブロックを登録する
1. `parse`メソッドを用いて、与えられたargvをパースする


```ruby:sample.rb
require 'optparse'
opt = OptionParser.new

opt.on('-a') {|v| p v }
opt.on('-b') {|v| p v }

opt.parse(ARGV)
p ARGV
```
```bash:実行結果
$ ruby sample.rb 
#=> []

$ ruby sample.rb -a foo -b bar
#=> true
#=> true
#=> ["-a", "foo", "-b", "baz"]
```

### ロングオプション・説明
`on`メソッドでショートオプションに追加して、ロングオプション・説明を渡せばよい
```ruby:sample.rb
require 'optparse'
opt = OptionParser.new

opt.on('-a', '--a_long_option', 'a_description') {|v| p v }

opt.parse(ARGV)
p ARGV
```

### 引数の必須かどうかの記述
* 引数が必須の場合
    * `on`メソッドでオプションの末尾に文字列を追加する
    * 下記例だと、`value`を追加している
* 引数が省略可能な場合
    * `on`メソッドでオプションの末尾に`[]`で囲った文字列を追加する
    * 下記例だと、`[value]`を追加している
```ruby:sample.rb
require 'optparse'
opt = OptionParser.new

# 通常の記述
opt.on('-a') {|v| p v }
# 引数が必須
opt.on('-b value') {|v| p v }
# 引数は省略可能
opt.on('-c [value]') {|v| p v }

opt.parse(ARGV)
p ARGV
```

### 引数の型指定
`on`メソッドで型を追記する
```ruby:sample.rb
require 'optparse'
opt = OptionParser.new

opt.on('-a', '--a_long A', Integer) {|v| p v }

opt.parse(ARGV)
p ARGV
```

### ARGV
ARGVにコマンドラインから渡された引数が配列として渡される。
ARGVでオプションを定義することができる。
複雑なオプションが必要ない場合はARGVを使うのがおすすめ。
オプションの指定方法は`getopts`メソッドでオプションを指定するだけでOK。


- ショートネームのオプション
    - ショートネームのオプションを文字列で指定する。
    - オプションが引数をとる場合は直後に`:`を付ける。
        - aという引数をとるオプションを追加する場合：`a:`
        - aとbという引数をとるオプションを追加する場合：`a:b:`
        - aという引数をとらないオプションとbという引数をとるオプションを追加する場合：`ab:`
- ロングネームのオプション
    - ショートネームのオプションを文字列で指定する。
    - オプションが引数をとる場合は直後に`:`を付ける。
    - デフォルト値は `:` の直後に指定する。
        - bufsizeという引数をとらないオプションを追加する場合：`bufsize`
        - bufsizeという引数をとるオプションを追加する場合：`bufsize:`
        - デフォルト値1024のbufsizeという引数をとるオプションを追加する場合：`bufsize:1024`

```ruby:sample.rb
require 'optparse'

params = ARGV.getopts("a:b:")
p params
```
```bash:実行結果
$ ruby sample.rb -a foo -b bar
#=> {"a"=>"foo", "b"=>"bar"}
```


# 参考文献
https://docs.ruby-lang.org/ja/latest/library/optparse.html

https://docs.ruby-lang.org/ja/latest/class/OptionParser.html
いいコード悪いコードまとめ４章不変の活用

変数の値を変更することができることを可変（ミュータブル）と呼び、その反対を不変（イミュータブル）と呼ぶ。

変数がミュータブルだとこの値になるはずと意図して実装したにも関わらず、意図しないうちに値が変わってしまうという事態につながってしまう。

# 再代入していないか
以下のようにイミュータブルにしていないと上書きができてしまう。
```
x = 10
y = 20

x = y

puts x # 20
puts y # 20
```

# 不変と可変の取り扱い方針
## デフォルトは不変にしているか
定数の意味が変化しなくなるので混乱を避けられる
挙動が安定し、結果を予測しやすくなる
コードの影響範囲が限定的になり保守が容易になる

## 可変にしてもよいとき
不変が望ましくない場合は
* パフォーマンスに問題が生じるケース
例えば大量のデータの高速処理や画像処理リソース制約の厳しい組み込みソフトウェアなどで可変が必要なこともある。

* スコープが局所的なケース
ループ処理のスコープでしか使われないことが確実がローカル変数ならば可変にしてもよい

# まとめ
基本的に変数は不変にすること。ただし、一部例外については可変でも可能
Health Tech Hackathon [Fast Doctor × Qiita]　体験談

**1.簡単な自己紹介**
初めまして。私は今回「Health Tech Hackathon」に参加させていただきましたタクミと申します。現在はアメリカの大学でComputer Scienceを専攻しており、今秋からはUCLAでMathematics of Computationを専攻予定です。これまではHackathonなどの参加経験はなく、以前クラスで偶然見つけた日本人の友達２人で今回のhackathonに参加させていただきました。

**2.今回のHackathonについて**
今回のhackathonのテーマは「ChatGPT × 医療」でした。最近話題のChatGPTを用いて医療の未来を切り開くことを目的に主催されました。参加者たちは、自身のチームで協力し、ChatGPTを使って医療領域の課題に取り組みました。チーム一丸となりChatGPTの応用可能性を探求し、新たな発見やイノベーションを生み出すために、熱心に取り組みました。

**3.アイディアソン**
計2日で行われた今回のhackathonのうち、1日目の午前中はアイディアソンでした。私たちのグループでは初めは良いアイディアが出ず苦戦していた中で、現役ドクターへヒアリングを行いそこからはアイディアが山のように出てきました（この時は技術面、時間のことを考えていなかった。。。）。アイディアソンの発表では我らのチームのリーダが周りを魅了するプレゼンテーションを行い、良いスタートを切ることができました。

**4.いざ開発スタート！**
アイディアソンを終えいざ開発を始めるとなった時に私たちは一度手が止まってしまいました。なぜならこれまで開発経験、ハッカソン出場経験がある人が一人もいなかったからです。そこで簡単なアプリケーションならflutterで作ろうとなったのですが、環境構築に時間を費やしてしまい、これ以上は時間を使えないと思いPythonで開発を行うことにしました。この時点で1日目の日程は終了してしまいました。
Pythonを開発を始めてからはスムーズに進み、chatGPTのAPIを用いて「自己問診カルテの自動生成」を行うコードを書くことに挑戦しました。簡単に説明すると、ヒアリングを受けた際に医者の皆さんは「カルテの作成に時間を取られる」とおっしゃっていたため、病院を訪れる前に自己問診を行いある程度情報を診断前に医者に見せれるようにしておくと言うのが目的でした。自己問診には個人情報、疾患、症状などに加えchatGPTが病院を訪れる必要の有無などの情報も載せるようにしました。

**5.Hackathonを終えてみて**
各チームの発表を聞き社会人の技術の凄さを目の当たりにしました。私たちのグループは学生のみでしたがこれから勉強に熱心に取り組まないといけないなと感じました。
私も含め他のメンバーも今回のhackathonのおかげでチーム開発の楽しさ、hackathonの楽しさを認識することができました。またhackathon後の懇親会でも他のチームの方とお話をすることができてとても貴重な体験となりました。
第2回のhealth tech hackatonにもぜひ参加したいと思います。ここまで読んでくださりありがとうございました。
詳しい内容は私たちのリーダーが[投稿](https://qiita.com/shibbonstar)

しているので、気になった方は目を通してみてください！
GIMPでのモザイク処理【備忘録】

# Gimpでのモザイク処理

1. モザイクをかけたい部分を選択(モザイクをかける範囲を選択（矩形など))
1. 右クリックをし、[フィルター]→[ぼかし]→[モザイク処理]
1. [Block width]及び[Block height]を設定し、モザイクの粗さを設定。(ex:3以上〜?)

---
以下を参考。
https://pasokon-labo.com/gimp-image-blur/
モダンアクティビティを使ってみた(Excel編Part10～シートの保護解除...できるよね？～)

## はじめに
①本記事は、2023年7月開催の「UiPathブログ 発信チャレンジサマー」の16日目のブログです。
ブログの詳細、参加方法については以下のブログを参照ください。

https://note.com/shumpei_w/n/nd082bdf28853

②このブログ情報は、2023/7時点の情報です。
③UiPathはCommunity Edition、Versionは2023.6.1を使用しています。

## シートの保護を解除アクティビティとは
「シートの保護を解除」アクティビティは、保護をかけたシートの解除を行えるアクティビティです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/ccfed620-dd73-3796-ba23-2a9a1eae8644.png)
プロパティは、基本パスワード関連になりますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/02b6247e-8f90-e805-0338-f0961c1c911d.png)
【入力】
シート：パスワードを解除する対象のシートです。
パスワード：String型で、解除するために保護をかけた時のパスワードを設定します(でもおすすめしません)
パスワード(SecureString)：SecureString型で、解除するために保護をかけた時のパスワードを設定します。

## やってみよう
以下、Sheet3のシート保護を解除してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/8e0a6106-17ec-c2cc-3a6d-4cdebd20cefa.png)
①まずは、パスワードを解除する対象のシートを設定します。
(今回は、"Sheet3"を設定します)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/36b0132c-c3e9-9d28-143b-41f01801f843.png)
②シートを設定したら、パスワードの変数を設定します。(今回はSecureString型の変数sssにパスワードを設定していますので、こちらを使用します)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/be8ab863-3da1-c939-d47d-b0fbf702c4af.png)

結果は...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502614/7b8555a7-578d-313c-8d75-3388c5c354d5.png)
おお、編集できるようになってますね！

## まとめ

シートの保護⇔解除を行えるアクティビティをうまく活用して、Excelの自動化をさらに進めたいですね！
モダンアクティビティで可能になったExcelの操作は増えているので、使える状況にある方はぜひ使用の検討&使ってみましょう！
最後までお読みいただきありがとうございました。:relaxed:(´∀｀*)ﾎﾟｯ
Windows 11 memo帳 Ctrl H を backspace にショートカット

# 背景
Windows 11 になってから memo帳の中で Ctrl+H が backspace にならなくなってしまった。
なんか他のキーにショートカットされているようだ。
Ctrl+H で backspace、Ctrl+M で改行に慣れている身としては不便の極み。

# 目的
powertoys を install してショートカットを設定すれば目的を達成できたのでレポートする

# 内容
- install
   Windows store から、もしくは github から install

- 操作
  Powertoys -> Keyboard manager -> ショートカットの再マップ


 ![key_mgr2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99131/1a306542-8769-5e71-14bb-094047e1fbf3.png)

- 以下のように、key  - shortcut  -- app を登録してみた。それぞれ問題なく動作している

![key_mgr1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99131/e69001ef-8177-febc-cb72-e81b327e7e0c.png)

以上。
aka.ms/commandsとcmd.ms

# Microsoft関連の短縮URL
 aka.ms（also known as、よく知られている）は、マイクロソフトで使われる短縮URLで、ほぼ恒久的に使われるもの、イベントで一時的なものなどいろいろあります。前者について、Merill さん（ https://twitter.com/merill ）がまとめてくれています。
# aka.ms/commands

 https://aka.ms/commands

 主に各種管理ポータルへのリンクについて、カテゴリー分けと分かりやく短いキーワードで分かりやすいですね。
ブラウザのアドレスバーで呼び出すTipsもあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419691/a904ac26-c1a4-b120-58ba-a5acbbaf707c.png)

# cmd.ms

こちらは、xxx.cmd.ms でのアクセス方法です。

https://cmd.ms/

the Microsoft Cloud command line! ということでかなりのメニューをサポートします。
エイリアスもいろいろあるものですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419691/8d9ed36a-068d-c2ef-6829-913574fbd52f.png)
WSLでUbuntuを入れようとしたらエラーコード0×80004002が出た話

# まず最初に
くそ初心者がつまずいたところなので、ある程度知識がある人にとっては役立たないです(多分)。
以下のサイトを参考にAtcoderの環境構築をしようとしました。
https://qiita.com/Kujira_Box/items/0a1392d1842971146889
この最中に起きたエラーを備忘録として書き残します。

## Ubuntu 22.04.2 LTSインストール時
エラーコード出ました。インストールができません。
Error:0×80004002
### 解決方法
「設定」> アプリ > 「プログラムと機能」 > 左の「Windowsの機能の有効化または無効化」 > 「Linux用のWindowsサブシステム」と「仮想マシンプラットホーム」を有効化

## 最後に
意外にググっても出なかったんですよね。
常識すぎるかググり方が悪いんでしょうね。
AIをフル活用した「名言Bot」

## はじめに

昔から読書が好きで、偉人達の言葉によく感銘を受けていたのですが、なかなか普段本を読む時間が取れないという友人も多く、日常生活の中にもっと偉人達の言葉を取り込めないかなと考えていました。

UIとしてはやはりLINEが最も使い勝手が良いと思い、昨今のAIの進化も踏まえ、LINEから誰でも気軽に使える、AIを活用した名言Botを作りました。

定期的な名言の通知機能に加え、ChatGPTによる名言の選定、リップシンクによる名言の発話もあります。

■名言のスケジュール通知
任意の通知時刻を選択可能
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/4a89446c-3323-eb3e-8c46-66e450748ae4.png)

■ChatGPTによる名言の応答
任意の問いかけに対し、ChatGPTが応答する名言を選定、選定理由とともに応答

![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/956f64df-ce0e-730f-d5d5-0dee28744c0a.png)

■リップシンクによる名言の発話
名言のテキストと偉人の画像をインプットに、TextToSpeechとリップシンクで名言の発話動画を生成

<img width=250 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/1d32f8f9-f92d-7e0d-c40b-ce628b1c7884.gif>

アルバート・アインシュタイン

<img width=250 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/833bc5e2-1b4d-edc8-529e-924868ff1710.gif>

レオナルド・ダ・ヴィンチ

<img width=250 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/dc52ba09-e96b-df5d-b16e-acf4150115ba.gif>

アリストテレス

※実際は音声あり

## アーキテクチャ

全体アーキテクチャの概要は以下です。
![画像6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431306/32218293-4238-c6bf-3f04-de03c1158758.png)
名言のテキストデータをまずDynamoDBに登録し、Lambdaから取得できるようにしておきます。

リップシンク動画を作るために、名言のテキストと偉人の画像を用意し、まずTextToSpeechで名言を音声に変換し、生成した音声と画像からリップシンク動画を作成します(この動画をS3へ格納)。

名言のスケジュール通知のためにEventBridgeで定期的にLambdaを起動し、各ユーザーの名言通知の指定時刻に合致する場合は、名言をDynamoDBからランダムに一つ取り出します。

そして、対象の名言にリップシンク動画がある場合は、S3から動画を取得し、ユーザーに名言のテキストとリップシンク動画を通知します。

上記の図には記載していませんが、ランダム通知の際に、同じ名言が繰り返されてしまわないよう、ユーザー毎に通知済みの名言を保持しておき、名言の重複通知を回避しています。

ChatGPTについては、ユーザーの任意のチャットを受け、DynamoDBから取得した名言の一覧と合わせてChatGPTに送り、適切な名言を1つ、その選択理由とともに返すようプロンプトで指示を出し、応答結果をユーザーに返しています。

ちなみに、実際に試してみるとわかりますが、ChatGPTに「名言を一つ教えて」と聞いてみると、ハルシネーション(嘘の応答)の問題が発生し、多くの場合、それっぽいものの実際には存在しない名言が返ってきます。

今回は、DynamoDBに登録済みの名言の一覧の中から、適切な物を選ばせるという形を取ることによって、上記のハルシネーションの問題を回避しています。

## 利用方法

サービス登録等は特に不要で、以下のLineの友達追加から誰でも利用できます。

[Lineへの友達追加用リンク](https://line.me/R/ti/p/@550vvliy)

名言は、時代や国を問わず現在300近くあり、リップシンクは160近くあります。リップシンクを繋げた動画はYouTubeでも公開しているので、こちらも良かったら参考に見てみて下さい。

[リップシンク動画(YouTube)へのリンク](https://youtu.be/o596pUzEdCI)

上記で紹介した機能に加え、お気に入りの名言の登録や名言のランダム通知機能もあるので、まだまだプロト段階ですが、忙しい日常生活の中に、偉人達の名言を取り入れる一つの機会として、利用してみて頂ければと思います。

<登録済みの名言の抜粋>

"あなたがこの世で見たいと願う変化に、あなた自身がなりなさい。"
マハトマ・ガンディー

"私は決して障害に屈しはしない。いかなる障害も、私の中に強い決意を生み出すまでだ。"
レオナルド・ダ・ヴィンチ

"何かをもらうために与えるのではありません。与える事自体がこの世で一番の喜びなのです。"
エーリッヒ・フロム

"躍動する暮らしに飛び込め、そしてやりたい事をやれ。世の中は興味ある事で満ち溢れている。"
ゲーテ

"われわれの一番大きい資源は、子供の心である。"
ウォルト・ディズニー