OmniAICreator/Qiita-1.07M huggingface dataset:
【React.js&Next.js超入門 第2版】リスト6-8（ログイン認証 Firebase v9）

こちらの技術書はどうやらFirebaseのv８仕様で対応されているようで、v９では動かず3時間苦戦したので覚え書き。

研修の先生にご指導いただきつつ、
参考にさせて頂いたサイトはこちら。

https://qiita.com/sgigagaeru/items/b5a752f75986312fd6fb

本当に助かりました！！
ただ、私の場合は、プラス２つ追加設定が必要でしたので、同じく悶絶されている方はご参考まで。

<h1>ポップアップウィンドウの設定</h1>
アドレスバーのビックリマークをクリックすると「サイトの設定」がポップアップされるので、クリック。

「ポップアップとリダイレクト」を許可してポップアップ表示を予め有効にしておきましょう！でないと認証したくてもログインウィンドウが表示されなくなってしまいますので。

<h1>追加で必要だったコード<br>.catchエラー系の設定</h1>
先の記事に追加して欲しいコードがあります。
私の場合は先の記事のコードだけではエラーが表示されて
認証ログイン画面に遷移できませんでした。

なので、以下のように`useEffect`内に`.catch〜`を追加。
無事コードが動き、エラーで止まることなく、ログインできましたー😂

以下は先の記事にコードを追加したものです
マルっとコピペできるかと。

```pages/fire/index.js
import { useState, useEffect } from 'react';
import Layout from '../../components/layout';
import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth';

const auth = getAuth();
const provider = new GoogleAuthProvider();

export default function Home() {
  const [message, setMessage] = useState('wait...');

  useEffect(() => {
    signInWithPopup(auth, provider)
      .then(result => {
        setMessage('logined: ' + result.user.displayName);
      })
      .catch(error => {
        if (error.code === 'auth/cancelled-popup-request') {
          setMessage('Authentication request was cancelled by the user.');
        } else {
          setMessage('An error occurred during authentication: ' + error.message);
        }
      });
  }, []);

  return (
    <Layout header="Next.js" title="Top page.">
      <div className="alert alert-primary text-center">
        <h5 className="mb-4">{message}</h5>
        <p className="h6 text-left">
          uid: {auth.currentUser != null ? auth.currentUser.uid : ''}<br />
          displayName: {auth.currentUser != null ? auth.currentUser.displayName : ''}<br />
          email: {auth.currentUser != null ? auth.currentUser.email : ''}<br />
          phoneNumber: {auth.currentUser != null ? auth.currentUser.phoneNumber : ''}
        </p>
      </div>
    </Layout>
  );
}
```
現場からは以上です！！
WooCommerce Subscriptions で定期購入の注文を取得する方法

# 定期購入関数　wcs_get_subscriptions

定期購入の注文データを探すために　`wcs_get_subscriptions` という関数を使います。
使い方は以下のようにします。
例：顧客ID125のユーザーの持っている定期購入を抜き出す方法
```php5:サンプル
$arg = array( 'customer_id' => 125 );
$subscriptions = wcs_get_subscriptions( $arg );
```
## 設定できるパラメーターの説明

設定できるパラメーターは以下となります。()内はデフォルトの設定です。

+ customer_id: 顧客 ID (0)
+ product_id: 商品 ID (0)
+ variation_id: バリエーション ID (0)
+ subscription_status: 定期購入ステータス (array('any'))
+ order_id: 定期購入で作成された注文 ID　(0)
+ meta_query_relation: メタデータのクエリ ('AND')
+ subscriptions_per_page: 1ページでの抽出数 (10)
+ paged:　何ページ目か (1)
+ offset: (0)
+ orderby: 並び替えの基準 ('start_date')
+ order: 並び替え順番 ('DESC')

## 返答として返ってくるオブジェクト

返答として返ってくるオブジェクトは `WC_Subscription` となります。
オブジェクトの説明は以下を参考にしてください。英語ですが。。。翻訳欲しいですか？www

https://woocommerce.com/document/subscriptions/develop/functions/
成長した人と成長しなかった人の比較

# はじめに
2023年2月~6月の間、2人の非エンジニア友達からAndroidアプリ開発を教え欲しいと依頼があり、その過程で2人の成長スピードが対照的だったので、いくつかの要素を比較をしてみた。


# 🧑‍💻出席率
成長した人は出席率が高く(8割程度)、どうしても出席できない日は自ら振替日の提案をしてくるなど、学びに対して積極的な姿勢だった。
成長しなかった人は出席率が低く(3割程度)、自分の予定を優先して欠席することが多かった。

|成長した人|成長しなかった人|
|:---:|:---:|
|**高い** (8割程度)<br>出席できない日は自ら振替日を提案|**低い** (3割程度)<br>自分の予定を優先|

# 📚与えたれたタスクに対する姿勢
成長した人は与えられたタスク以上のことをしてくることが多かった。
成長しなかった人は与えられたタスクすらしてこないことが多かった。

|成長した人|成長しなかった人|
|:---:|:---:|
|与えられたタスク以上のことをしてくる|与えられたタスクすらしてこない|

# 🫤躓いた時の対処法
成長した人は、躓いたことがあるとGoogleやChat-GPTで調べたり自分で考えたりして、自分なりに解決していた。どうしても分からないことだけ私に質問していた。また、教えていた時間外でもチャットで質問をしていた。
成長しなかった人は、躓いたことがあるとすぐに私に質問していた。

|成長した人|成長しなかった人|
|:---:|:---:|
|自分で調べる or 考える<br>どうしても分からない箇所だけ質問|すぐに質問|

# 🤝指導終了後
Androidアプリ開発の指導を終了後もプログラミングを継続していたかどうかで比較。
成長した人は、指導終了後にiOSのアプリ開発を自ら始めており、App Storeにリリースしていた。
成長しなかった人は、指導終了後はプログラミングをしていなかった。
|成長した人|成長しなかった人|
|:---:|:---:|
|iOSアプリ開発に着手&App Storeにリリース|プログラミングをしていない|

# おわりに
* どちらの人物が良い悪いとかではない。2人とも友達ではあるが、成長した人にも短所はあり、成長しなかった人にも長所はある。
* 対照的な結果となった要因として、その人の向き不向きや意欲の違いがあると思う。
【前編】DB設計を担当した話


基本設計にあたりDBの設計を行いました！
ここで得た経験をまとめたいと思います。

# 1. インデックスについて

設計の中でどこにインデックスを貼るのかは記載をしなければなりません。
じゃあどこに貼ろうかなーと考えると分かりません。
まず調べてみましょう。

参考サイト:↓

https://products.sint.co.jp/siob/blog/index



>・SQLの検索条件で頻出する列や、結合条件に指定されている列
・テーブル内の全データの量に対して、取得対象のデータが少ない列
・Bツリーインデックスの場合はカーディナリティが高い列（格納されている値の種類が多い列）、ビットマップインデックスの場合はカーディナリティが低い列

読んでみてどこに貼ればいいのかはなんとなく分かりました。

ただ、、、

決定的な根拠をもってインデックスを貼ることができません。
それは自分の中でインデックスを色々試してみて検索の高速化を体感できていないからです。

まず上司に相談したところインデックスは難しいので一旦置いておくことにしました。
ここでそのままにするのではなく個人的に大量のデータを保持したテーブルを用意して色々試したいと思います。

インデックス勉強しよう。

# 2. ユーザのタイプ管理について

現在開発中のサービスですがユーザ毎にタイプ（役割のようなもの）を振り分けようと考えています。
※タイプとは一般ユーザ、管理ユーザなど
じゃあどうやって管理をしようかなーーという話です。

最初は種類ごとにテーブルを分けました。
もし4つの種類があったら4つのテーブルを作成しそれぞれ格納しようとする算段です。

利点としてテーブル同士のリレーションが分かりやすくなるというのがあります。
例えばAテーブルに「管理ユーザ」の列があるとします。その際「管理ユーザテーブル」と紐づけることができます。
もしユーザが一つのテーブルにまとまっている場合、「ユーザテーブル」と紐づけることになりテーブルの中身を見ても様々なユーザタイプが混在しているため分かりづらいなーーと考えました。

欠点はユーザの管理が大変、テーブルが多くなることです。
４つ別々のテーブルに同じユーザが登録される可能性があります。DB運用やアプリケーションによって変わってきますが、まあぱっと見煩雑ですよね。。。
テーブルが多くなることも↑と近しいですが、ユーザテーブルが４つ存在するのは煩雑です。

こちらでレビュー頂いたところ、「別に１個にまとめていい」とのことでした。
上記で挙げた利点ですが、結局のところ自分目線で見やすいことが優先されていて整合性がとれない危険性を見落としていました。
勉強になりました。

最終的には１つのテーブルにまとめ、タイプは「role」列に格納し判断するようにしました。
（テーブルまとめると見た目スッキリしたな。。。）


# 3. データの一意性を保つ

例を出しますと、商品テーブルが存在していて「税抜き価格」「消費税額」「税込み価格」列があるとします。
「税抜き価格」と「消費税額」を加算すると「税込み価格」が計算結果になります。
この場合「税込み価格」列は必要なのか？というお話です。

上記は例ですが、開発中のサービスでは合計を算出するための要素数が複数あり合計を出すために毎回計算すると面倒なので「合計」列を設けそこに格納する？というのが背景です。

結果的には「合計」列は必要ありません。
理由は簡単で普通に計算しろってことですね。計算してわかることをわざわざ列に格納するのはよくないです。
列は無駄に増えるし、もしかすると合計列と計算結果が一致しない可能性も発生します。

これも2.と近しく、自分目線で見やすいことが優先されていて無駄が発生しており、よくないです。


# 4. 終わりに
個人開発でDB設計を行ったことはあるのですが、自社開発になると考慮すべき点が変わってきますね。
特にインデックス周りについて詳しくないので勉強していきたいです。

# 5. 予告
11月3日に投稿予定です！
次回は「【後編】DB設計を担当した話」について投稿します！
# .NETのチュートリアルと.NETで書かれたPowerShellのコードリーディングを通して.NETを理解する

# .NETのチュートリアルと.NETで書かれたPowerShellのコードリーディングを通して.NETを理解する

.NETの勉強会に参加する機会があり、キャッチアップとして記事を書いてみました。

## .NETとは

.NETフレームワークは、ウィンドウズベースのアプリケーションを開発するための包括的なプラットフォームです。この記事では、.NETの基本的なチュートリアルと、.NETで書かれたPowerShellのコードリーディングを通じて、.NETの特性と機能を理解する方法に焦点を当てます。

## .NETのチュートリアル: タイマーアプリの作成

.NETを学ぶ基本的な方法の1つは、実際のプロジェクトを通じて実践的な経験を積むことです。ここでは、.NETでシンプルなタイマーアプリを作成する基本的なチュートリアルを紹介します。

1. **環境の準備**:
   - 最初に、[.NET SDK](https://dotnet.microsoft.com/download)をダウンロードしてインストールします。

2. **プロジェクトの作成**:
   - コマンドラインまたはターミナルを開き、以下のコマンドを実行して新しいプロジェクトを作成します。
     ```bash
     dotnet new console -o TimerApp
     ```

3. **コードの記述**:
   - `Program.cs` ファイルをエディタで開き、以下のコードを記述します。
     ```csharp
     using System;
     using System.Timers;

     class Program
     {
         private static Timer timer;

         static void Main(string[] args)
         {
             timer = new Timer(1000);
             timer.Elapsed += OnTimedEvent;
             timer.AutoReset = true;
             timer.Enabled = true;

             Console.WriteLine("Press 'q' to quit the sample.");
             while (Console.Read() != 'q') ;
         }

         private static void OnTimedEvent(Object source, ElapsedEventArgs e)
         {
             Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}", e.SignalTime);
         }
     }
     ```

4. **アプリの実行**:
   - ターミナルで以下のコマンドを実行してアプリを実行します。
     ```bash
     dotnet run --project TimerApp
     ```

このシンプルなチュートリアルを通じて、.NETの基本的な概念と、タイマーイベントの作成と処理の方法を学ぶことができます。

## PowerShellのコードリーディング
https://github.com/PowerShell/PowerShell

PowerShellは、Windows環境でスクリプティングとコマンドラインインタフェースを提供する強力なツールです。PowerShellは.NETフレームワーク上に構築されており、.NETのコードベースを理解するための優れたリソースとなっています。

ここでは、PowerShellの`Get-Help`コマンドがどのように機能するかを調査し、その実装を通じて.NETの概念を理解します。

1. **Get-Helpコマンドの解析**:
   - ユーザーが`Get-Help`コマンドを実行すると、PowerShellランタイムはコマンドを解析し、`HelpCommands.cs`ファイル内の関連するコードを呼び出します。

2. **コードの実行**:
   - `HelpCommands.cs`ファイル内のコードは、ヘルプ情報を取得し、ユーザーに表示する責任を持っています。

3. **コマンドレットの登録と解決**:
   - `powerShell.AddCommand("Get-Help");`コードスニペットは、`Get-Help`コマンドをランタイムに登録し、後で実行する準備をします。

このように、.NETのチュートリアルとPowerShellのコードリーディングは、.NETの機能と設計原則を理解する助けとなります。実際のプロジェクトと実際のコードを通じて、.NETの強力な機能とその適用方法を学ぶことができます。
【 TailwindCSS 】記事が一瞬で美麗になる "prose" クラスのすすめ

デザインセンスや知識を持ち合わせていないエンジニアにとって、`h1` や `h2` のフォントサイズをいくつにするか、行間隔をどうするかなどは非常に悩ましい問題です。

TailwindCSSには公式でそのような問題を解決してくれるプラグインが用意されているので紹介します。

## @tailwindcss/typography

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552712/15dcc19e-4a51-78e1-824f-12e35e165c65.png)

[@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)はTailwindCSSの公式プラグインです。[インストール、設定](https://tailwindcss.com/docs/typography-plugin#installation)したあと、`prose`（散文）というクラスが使用できるようになります。

`prose`クラスは次のように使用し、`<article>`の子要素に対し適用されます。

```html
<article class="prose lg:prose-xl">
  <h1>Garlic bread with cheese: What the science tells us</h1>
  <p>
    For years parents have espoused the health benefits of eating garlic bread with cheese to their
    children, with the food earning such an iconic status in our culture that kids will often dress
    up as warm, cheesy loaf for Halloween.
  </p>
  <p>
    But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
    springing up around the country.
  </p>
  <!-- ... -->
</article>
```

`prose`クラスが適用されるのは、マークダウン記法に対応したHTML要素、つまり`h1, h2, p, code, li, table, img...`などです。

文字色、スケールともに５パターン用意されていますが、微調整やカスタマイズも可能です。さらにダークモードにも対応しています。

```html
<article class="prose dark:prose-invert">
  {{ markdown }}
</article>
```

## 実例

実際に`prose`クラスを適用した実例をビフォー・アフターで見てみましょう。

BEFORE:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552712/683f32c7-f5d7-e387-da03-2c4fbe4752cb.png)

AFTER:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552712/2a7c2579-b34b-e591-caa6-61060bf303fe.png)

よくあるブログなどの記事ぽくなりました。

文字列の大きさやコントラストなどが変更され、記事も中央寄せになっており、読みやすくなったかと思います。このスタイリングが、クラス名に`prose`と打ち込むだけで完成します。

とても簡単に導入でき記事が美しくなるので、TailwindCSSをお使いであれば使ってみてください。

以上です。
CloudFormationで指定したパラメータを取得する

Cfnを実行するときのパラメータをファイルに保存しておきたいときに使用します。

```sh
aws cloudformation describe-stacks | jq '.Stacks[] | select(.StackName == "my-stack-name") | .Parameters'
```

次回更新時、パラメータの一部を変えて実行したいときとかに、あれ？ってなっちゃうんですよね。。
SwiftUI の MapKit で画面をタップした場所の緯度・経度を取得する (iOS17〜)


## 概要
iOS17 から [MapReader](https://developer.apple.com/documentation/mapkit/mapreader)  を使って、SwiftUI のマップで表示している画面上の位置を緯度・経度に変換することができるようになりました。


## 取得方法
SwiftUI の View 上でタップした位置の座標を、緯度・経度に変換するコードは下記の通りです。GeometoryReader で View を囲むのと同じように、 MapReader で Map を囲みます。あとは convert メソッドで画面の座標を、緯度経度の座標に変換します。

### サンプルコード

```:swift
MapReader { reader in
                    Map()
                        .onTapGesture(perform: { screenLocation in
                            guard let location = reader.convert(screenLocation, from: .local) else { return }
                            print("tapped point: ", location)
                        })
                }
```

かんたんですね！

## おわりに

iOS16 までは MapKit を SwiftUI で扱う場合に、マップ上の画面の座標を、緯度・経度に変換する標準の方法はありませんでした。そのため、UIKit の MapKit に頼らざるを得ない側面がありましたが、iOS17 になり、 SwiftUI 対応も進んできて実用領域に入った感じがして嬉しいですね！
【IT-DIY】既にある技術の仕組みを知るため、自作で動画の分析をしてみる。

# はじめに
※こちらの記事は活動記録になります。
弊社のスキルアップ活動の一環で、表題の活動を行いました。

# やりたいこと
最近フリーアドレスになったので、Teamsの社内のカメラで本社の映像から人を検出し、「リアルタイム空席状況」を表示してみたい。

# 既にある技術
表題にもある通り、人間を検知する仕組みはもう存在する。yoloというPythonライブラリを利用すると、どうやら簡単に動画内の人間を検知できるらしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950891/3a66d963-0f71-4e7d-ebcb-7cdf6ea20abc.png)

また、人間に限らず、「とにかく動いているもの」を検知するのであれば、高精度のものがネットにたくさん転がっている。

# 自作する人間検知技術
今回作成したのは、「前景セグメンテーション」という仕組みを用いた「リアルタイム空席状況」プログラム。(Python - openCV)
以下の手順で実施。
➀動画をフレーム単位に分割。
```
ret, frame = cap.read()
```
➁フレームを取得して一時保存
➂次のフレームを取得する。
➃前フレームと現フレームの差分を検出
```
cv2.accumulateWeighted(gray, avg, 0.7)
frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))
```
⑤差分の輪郭を抽出し、面積の大きいものを「人間」とみなす
⑥輪郭を抽出して得られる、線に囲まれた「領域」内の重心を算出。
```
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for i in range(len(contours)):
    contournow = contours[i]
    # 輪郭[i]の面積
    area = cv2.contourArea(contournow) 
    if area > 10:      
        # 輪郭[i]の重心
        m = cv2.moments(contournow) 

        mx,my= int(m["m10"]/m["m00"]) , int(m["m01"]/m["m00"])
        # 配列に詰めてマップした座席に渡す
        coordinates.append([mx, my+30])
```
⑦あらかじめ動画内の座席の座標をマッピングしておく。
⑧上記⑥で得られた重心から最も近い座席を紐づける。
⑨座席と紐づいた部分をハイライトさせた座席表を出力する。
⑩上記➁～⑨をループ

のちにノイズ低減のため、③、➃の間でグレースケール変換処理を追加
```
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
```

# 結果
※セキュリティの観点で社内の様子や座席配置はお見せ出来ません。

ある程度は検出できました。が、まだ実用的なラインではないようです。
検証として差分を検出する前に、２つのフレームにフィルタをかけてみました。
フィルタの歪みを重くすると、微量な変化は検知しなくなるので、モニタの軽度なちらつきや影などは検知しなくなりました。
しかし、オフィス内の人間も、毎フレーム大きな動作をしているわけではないので、検知しない人も出ました。
歪みを小さくすると、少しの動きでも人間を検知しますが、人間以外のものも過剰に検知するので、人間以外で検知した領域の面積が大きくなり、誤認識が発生しました。
また今回の動画の場合、座席同士の距離が近く、斜めから撮影しているため、一人の人間で二つの座席が認識してしまうこともしばしば起きる。

# まとめ
システムとして良いものが出来たわけではないですが、活動の目的である技術習得は大いに達成できたと思います。最初は天井のシーリングが毎フレーム反応してめちゃくちゃな座標が返ってきていましたが、検出したものがマップした座席と遠すぎると領域が大きくてもスキップする、などの工夫を加えていって、課題解決するたびに力がついたのが実感出来ました。
やってみて思うのですが、前景セグメンテーションは「人間」だけを抽出できるわけではないので、今回のシステムには向いてないかもですね。。。
莫大な時間と予算があれば、ディープラーニングで「人間」の写真を大量に学習させて、openCVで動画内でピンポイントで「人間」だけを検知できると、精度が高く、拡張性もあるシステムが出来そうです。
今回は技術習得が目的だったので、ここら辺にとどめておきます。

今後も社内で行った活動を発信していきます。
boost::fusion::make_fused_procedureが何者か調べるだけの記事

# boostのリファレンスより

>https://www.boost.org/doc/libs/1_81_0/libs/fusion/doc/html/fusion/functional/adapters/fused_procedure.html
>
> An unary Polymorphic Function Object adapter template for Callable Object target functions. It takes a Forward Sequence that contains the arguments for the target function.
>
>The result is discarded and the adapter's return type is void.
>
>The type of the target function is allowed to be const qualified or a reference. Const qualification is preserved and propagated appropriately (in other words, only const versions of operator() can be used for a target function object that is const or, if the target function object is held by value, the adapter is const - these semantics have nothing to do with the const qualification of a member function, which is referring to the type of object pointed to by this which is specified with the first element in the sequence passed to the adapter).
>
>If the target function is a pointer to a members function, the corresponding object can be specified as a reference, pointer, or smart pointer. In case of the latter, a freestanding get_pointer function must be defined (Boost provides this function for std::auto_ptr and boost::shared_ptr).
>
>The target function must not be a pointer to a member object (dereferencing such a pointer without returning anything does not make sense, so this case is not implemented). 

全然頭に入ってこない。

# 実験

こんなコードを書いてみる

```cpp
#include <iostream>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/combine.hpp>
#include <boost/bind/bind.hpp> 
#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/fusion/functional/generation/make_fused_procedure.hpp>

void f(int n, bool b)
{
    std::cout << n << ',' << b << std::endl;
}
int main()
{
    using namespace boost::placeholders;
    std::vector<int> v = { 2, 4, 7 };
    std::vector<bool> state = { true, true, false };
    boost::for_each(boost::combine(v, state), boost::fusion::make_fused_procedure(boost::bind(f, _1, _2)));
}
```


実行結果

```
2,1
4,1
7,0
```

https://wandbox.org/permlink/ijjzc7WZ4ckWMtbr

`boost::combine`の結果、for_eachから得られる要素型は概ね`boost::tuple<int, bool>`のようになっているはずです。

これに対して関数`f`の引数は`(int, bool)`です。

つまり`boost::fusion::make_fused_procedure`でラップすることでいい感じにtupleをsequenceとしてあつかい、展開して渡してくれるということなのでしょう。

# C++17において

もっとも今となっては構造化束縛を用いて明示的にこんなふうに書いたほうがわかりやすい気がします。

```cpp
boost::for_each(boost::combine(v, state), [](auto&& t) {
    auto&& [n, b] = t;
    return f(n, b);
});
```

あるいはもっと汎用的に書くなら`std::apply`でもいい気がします。

```cpp
boost::for_each(boost::combine(v, state), [](auto&& t) {
    return std::apply(f, t);
});
```

・・・と思ったんですけど、これは書きにくいという昔の記事を見かけたのでなるほど・・・？という気持ちです。

https://faithandbrave.hateblo.jp/entry/2014/03/12/151125
BizRobo!とKintone～今後のRPA活用方法について～

就業先では、RPAツールのBizRobo!を使用しています。BizRobo!開発で悩み、BizRobo!ナレッジベースで日々検索しています。

ServiceNowやSpendiaなど様々なシステムはバージョンアップを行います。
RPAは、各システムのバージョンアップに影響を受けます。セレクターやDOMXPath等の画面要素の変更により、今まで動いていたRPAが動かなくなってしまうことはよくあるケースです。

Kintoneが2023年9月頃にバージョンアップされました。Kintoneのバージョンアップによって、BizRobo!で開発されたRPAにどのような影響が生じたか現時点で判明している点を共有したいと思います。

※1　2023年10月時点での情報になります。記事を閲覧した時期によっては、状況が変わっているケースがございますので、ご注意ください。
※2　間違っている箇所や不適切な表現があるかもしれませんが、その時は都度修正いたしますので、ご指摘いただけると幸いです。

## Kintoneとは
サイボウズ株式会社が提供しているクラウド型の業務アプリ開発プラットフォームになります。
顧客管理や売上管理、FAQ、日報の集積、基幹システムとのデータ連携などプログラミングなしで開発できるツールとなっております。
ドラッグアンドドロップの操作だけでアプリを作成できるので、非IT部門の担当者でもアプリを作成することができます。
API連携や、JavaScriptやCSSを用いたプラグインなど拡張機能も充実しています。

詳細はキントーンのHPをご参考ください。
[キントーンが選ばれる理由](https://kintone.cybozu.co.jp/what_is_kintone/why/)

## Kintoneが関わる業務の自動化をBizRobo!で実現するには
就業先でもKintoneを使用しており、Kintoneに関わる業務の自動化をBizRobo!で対応しています。

Kintoneに関わる業務内容をサンプルで説明すると下記となります。

:::note info
株式会社TBOの法務部門のお話です。
法務部には色々な事務手続き申請が届きますが、紙ベースであり、データ化しておりませんでした。そのためKintoneで管理することにしました。
Kintoneには、事務手続きに必要なデータと承認者・参照者の情報が一覧化されています。
一覧から該当のステータス「承認待ち」を探し出し、承認者や参照者宛に登録されたことをメールするようにいたしました。
承認者や参照者に該当メールが届けば、Kintoneでレコードを確認し、ステータスを「承認完了」にするだけで業務を完了となります。
今までは、紙に判子を押下することで対応していたので、待ち時間を今までの5分の1ほど削減できました。
:::

仮の業務内容ですが、このような形でKintoneの業務自動化を実現しており、それをBizRobo!のRPAで対応しておりました。

Kintoneのバージョンアップが、2023年9月上旬にありました。そのバージョンアップによって、BizRobo!で開発されたRPAが動かなくなってしまいました。DSで開発したRPAとなります。
KintoneのURLを「Load Page」ステップにて読み込み、Kintone上にあるボタンを「クリック」ステップで押下する処理をRPAに仕込んでおりました。しかし、BizRobo!上でKintone画面を表示させることができず、ボタンをクリックすることができなくなってしまったのです。

BizRobo!バージョンは、10.3.0.7です。バージョンアップで解決されるのかどうかは試せていません。また、DAで対応できるかどうかも試せておりません。
ただ、BizRobo!でKintoneを読み込み、クリックなどのGUI操作を行うのは現時点ではできなくなってしまっていると想定しています。

Kintoneが関わる業務の自動化をBizRobo!で実現するには、別の手段を試す必要があります。

<strong>①Kintone APIを活用すること</strong>
BizRobo!、特にDSで開発されたRPAを改修するには、現時点でこの方法が最有力候補だと思っています。Kintone API仕様がHPで公開されているので、参考にしてください。
[kintone REST API](https://cybozu.dev/ja/kintone/docs/rest-api/)

<strong>②他のRPAツールを試すこと</strong>
BizRobo!ではできなくなっていますが、UiPathやWinActorなど他のRPAツールでもできないかはまだ試しておりません。お金がかかる手段ですが、GUI操作を実現できる可能性があるので、こちらの方が改修しやすいかなと考えております。

## 最後に
BizRobo!とKintoneの現時点での情報について、共有しました。

システムのバージョンアップでRPAが使えなくなってしまうケースは初めてだったので、最初はどうしたものかと悩みました。今回のケンをBizRobo! LAND Communityで調べた際、とても有意義な情報が見つかりました。こういう時にも、BizRobo! LAND Communityは役立つなと感じました。

今回の記事が、RPA開発で悩んでいる人の為になれば幸いです。
NVR500の電話機能をconfigで無効にする

前回のに引き継ぎ下記のconfigを削除する投稿です。

```config
analog supplementary-service pseudo call-waiting
analog extension dial prefix line
analog extension dial prefix sip prefix="9#"
```

前回はこれらについて触れていなかったので改めて精査します。

```config
analog supplementary-service pseudo call-waiting
```
→フレックスホン機能の使用パターンの設定
→→INS キャッチホン(コールウェイティング) 機能使用を示すキーワード

```config
analog extension dial prefix line
```
→TEL ポートにおける特定のプレフィックスによる発呼経路選択の設定
→→アナログ回線、ISDN 回線でかけるためのプレフィックス設定

```config
analog extension dial prefix sip prefix="9#"
```
→TEL ポートにおける特定のプレフィックスによる発呼経路選択の設定
→→sip を設定すると、sip による発呼を行う。
→→→先頭からの一部が重複するプレフィックス (例えば "9#" と "9#9") は異なるものとして扱われる。

上記ドキュメントにおいて、コマンドの行頭に「no 」を加えるとこれらのコンフィグは消去されるので試してみました。

```config
no analog supplementary-service pseudo call-waiting
no analog extension dial prefix line
no analog extension dial prefix sip prefix="9#"
```
それではNVR500管理画面で実施します。

![スクリーンショット (32).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/be0e40a4-48c2-6616-1c40-80d08b7f8fbe.png)

トップ画面より「詳細設定と情報」をクリック。

![スクリーンショット (52).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/c4eb6831-5e7f-4748-fec1-02917d036f60.png)

詳細設定と情報が表示され、下にスクロール。

![スクリーンショット (54).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/855fa305-bbad-2440-eeea-a859a5a54fcc.png)

レポートの作成・コマンド実行・初期化の
コマンドの実行の「実行」をクリック。

![スクリーンショット (55).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/23c12e16-63e4-d40a-a7bd-d1b2b03a3d5e.png)

コマンドの実行が表示され、テキストボックスに
```config
no analog supplementary-service pseudo call-waiting
no analog extension dial prefix line
no analog extension dial prefix sip prefix="9#"
```
を入力し「設定の確定」をクリック。

![スクリーンショット (56).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/477a8e36-7093-48ad-fee1-c69636e299e6.png)

結果が表示され対処されていることを確認し「戻る」をクリック。

![スクリーンショット (58).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/3c8f6aec-2259-dca3-6dd5-9f20a6ad1598.png)

再度、詳細設定と情報のレポートの作成・コマンド実行・初期化が表示され、
本製品の全設定(config)のレポート作成の「実行」をクリック。

![スクリーンショット (59).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/c5954cf6-7b9a-250d-5d50-92b80fad3fd0.png)

configファイルを取得します。

![スクリーンショット (60).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3581855/4b4069c0-4a66-7991-a433-3b7dac675ccc.png)

取得したconfig.txtの中身は

```config
# NVR500 Rev.11.00.43 (Wed Jul 20 11:22:59 2022)
# MAC Address : 00:a0:de:7f:95:6c, 00:a0:de:7f:95:6d
# Memory 64Mbytes, 2LAN, 1BRI
# main:  NVR500 ver=00 serial=S35066826 MAC-Address=00:a0:de:7f:95:6c MAC-Address=00:a0:de:7f:95:6d
# Reporting Date: Oct 23 10:50:31 2023
ip lan1 address 192.168.100.1/24
telnetd host lan
dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.100.2-192.168.100.191/24
dns private name setup.netvolante.jp
#
```
電話の機能が消えています。

【参考URL】

http://www.rtpro.yamaha.co.jp/RT/manual/nvr500/analog/analog_supplementary-service.html

http://www.rtpro.yamaha.co.jp/RT/manual/nvr500/voip/analog_extension_dial_prefix.html
Java のリフレクションでできること

# Java のリフレクション

Java のリフレクションとは、実行時にクラスやメソッドなどの情報を取得したり、実行したりする機能です。

## リフレクションを利用するメリット

リフレクションを利用することによって、以下のメリットがあります。

* クラスやメソッドの情報を取得したり、実行したりすることができるため、実行時にクラスやメソッドを動的に操作することができます。
* クラスやメソッドの内部構造を直接操作するため、柔軟なコードを作成することができます。
* フレームワークやライブラリの内部構造を解析することができます。

## リフレクションを利用する際の注意点

リフレクションを利用する際には、以下の点に注意が必要です。

* リフレクションは、実行時にクラスやメソッドの情報を得るため、パフォーマンスに影響を与える可能性があります。
* リフレクションは、クラスやメソッドの内部構造を直接操作するため、不正な操作を行うと、予期しない結果を招く可能性があります。

## リフレクションを利用する例

リフレクションは、以下の用途に利用できます。

* フレームワークやライブラリの内部構造を解析する
* ランタイム時にクラスやメソッドを拡張する
* プロキシパターンを実現する

## リフレクションを実際に使ってみる

以下のコードは、`com.example.MyClass` というクラスを取得し、そのクラスの情報を表示するコードです。

```java
// クラスを取得する
Class<?> clazz = Class.forName("com.example.MyClass");

// クラスの名前を取得する
System.out.println(clazz.getName()); // com.example.MyClass

// クラスのメソッドを取得する
Method[] methods = clazz.getMethods();

// メソッドの名前を取得する
for (Method method : methods) {
  System.out.println(method.getName());
} // sayHello, getAge

// メソッドを実行する
Object instance = clazz.newInstance();
Method sayHelloMethod = clazz.getMethod("sayHello");
sayHelloMethod.invoke(instance); // Hello!

// フィールドを取得する
Field ageField = clazz.getField("age");

// フィールドの値を取得する
Object age = ageField.get(instance);
System.out.println(age); // 20

// フィールドの値を設定
ageField.set(instance, 30);
age = ageField.get(instance);
System.out.println(age); // 30
```


このコードは、以下の処理を行っています。

* `Class.forName()` メソッドを使用して、`com.example.MyClass` というクラスを取得します。
* `clazz.getName()` メソッドを使用して、クラスの名前を取得します。
* `clazz.getMethods()` メソッドを使用して、クラスのすべてのメソッドを取得します。
* `method.getName()` メソッドを使用して、メソッドの名前を取得します。
* `clazz.newInstance()` メソッドを使用して、クラスのインスタンスを生成します。
* `clazz.getMethod()` メソッドを使用して、メソッドを取得します。
* `method.invoke()` メソッドを使用して、メソッドを実行します。
* `clazz.getField()` メソッドを使用して、フィールドを取得します。
* `ageField.get()` メソッドを使用して、フィールドの値を取得します。
* `ageField.set()` メソッドを使用して、フィールドの値を設定します。

このコードを実行すると、以下の出力結果が得られます。

```
com.example.MyClass
sayHello, getAge
Hello!
20
30
```

他にも下記の様に修飾子に関する取得処理などがあります、
* `Modifier.isPrivate(field.getModifiers())` そのフィールドがprivateかを取得します。
* `Modifier.isPrivate(method.getModifiers())` そのメソッドがprivateかを取得します。

## リフレクションを使いこなすためのポイント

リフレクションを使いこなすためには、以下のポイントを押さえておくとよいでしょう。

* リフレクションがどのような機能なのかを理解する。
* リフレクションを利用する際の注意点を理解する。
* リフレクションを実際に使ってみる。

リフレクションは、Java の高度な機能ですが、使いこなせるととても便利です。ぜひ、リフレクションを使いこなして、Java プログラミングをより楽しく、より便利にしましょう。
【Google Cloud】BigQueryとCloud Storageのストレージ料金

## BigQueryとCloud Storageのコストはどれくらい違うのか
BigQueryの履歴データをCloud Storageでアーカイブすべきか悩んでおり、Cloud Storageにどれくらいコストメリットがあるのかを調査した、

当初はアーカイブデータはGCSにエクスポートする想定だったが、BigQueryと大きくストレージ料金が変わらないのであればBigQueryに保存でもいい気がしたのでストレージコストを調べた。

結論から言えば、スタンダートなストレージ料金は同じ。ストレージクラスが多いぶん、Cloud Storageの方がコストメリットがあるとは言える。
ただ、BigQueryにも長期保存クラスはあるようなので、圧倒的にCloud　Storageが安いということもなさそう。

### BigQuery
![スクリーンショット 2023-10-23 11.34.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3580229/68feee96-016c-8601-9b57-372137645e11.png)
参考：https://cloud.google.com/bigquery/pricing?hl=ja

ちなみに、長期保存は連続して90日間アクセスがないテーブルに対して自動的に適用されるそう。

### Cloud Storage
![スクリーンショット 2023-10-23 11.36.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3580229/0c13bf90-5e85-68c3-b5fb-5877fc79b945.png)
参考：https://cloud.google.com/storage/pricing?hl=ja#asia
※無料枠は米国リージョンで5GB/月

今回のユースケースではあまり関係ないけど、無料枠はBigQueryより少なめ。
BigQueryよりストレージクラスが豊富だけど、現状だとアクセス頻度が読めないからライフサイクルポリシーを設定するのは難しそう。

## BigQueryのデータはBigQueryでアーカイブした方がいい？
公式ドキュメントによると、まずは、BigQueryのデータはBigQueryでアーカイブすることを検討した方がいい。理由はBigQuery→Cloud Storage間で料金が発生するから。（Cloud Storage→BigQueryは無料らしい）
また、過去データは集計して保存することによってストレージ料金が減らせないかも検討した方がいいと書いてある。（DWHの役割としては時系列データにいつでも（恒常的に）アクセスできるということもあるから、これはいかがなものかと感じた。）

それでもあまりにもアクセスされないようならCloud Storageに保存することを考えてもいいみたい。
参考：https://cloud.google.com/bigquery/docs/best-practices-storage?hl=ja


一旦はBigQuery内で保存して、アクセスがあまりにもないようであればCloud　Storageの活用も検討してみよう。
[flutter]Navigatorの仕組みについて

## はじめに

こんにちは。
今回は、flutterを使っている方にはお馴染みなNavigatorの中身がどのようになっているのか、どういう仕組みで、動いているのかコードやドキュメントを見ながら理解を深めていければと思います。
なおこの記事は、画面遷移の仕方を提供するものではありません。

## なぜ学ぶのか

正直にいうと、画面遷移の裏側でどのような挙動が起きているのかなんて**知らなくても十分に実装できます**。
~~まだブラウザバックしないでね。。~~

ですが、画面遷移時のロジックを知ることにより、
画面遷移時のバグを効率よく解決できたり、
**実装時に保守性の高く、
パフォーマンスを考慮できるコード**を書くことに繋がります。

何より普段何気なく使っているロジックがどのように動いているのかを勉強することはそれ自体が面白いですしね。
気になる方は以下に公式ドキュメント貼っておくので、
見てみる事をお勧めします。

https://api.flutter.dev/flutter/widgets/Navigator-class.html


## Navigatorの仕組み

```dart:画面遷移

  Navigator.of(context).push(
    MaterialPageRoute(
     builder: (context) => const SecondView(),

        ),);
```

よく使う画面遷移のロジックですが、このNavigatorクラスを実際に見ていきましょう。

![スクリーンショット 2023-10-21 15.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3158219/5496ff26-d78b-c19c-c5a0-ba65540ca03c.png)

注目してほしいのは、以下の引数です。
```dart
 this.pages = const <Page<dynamic>>[],
```

pagesという引数はPage型を要素にできるリストとして宣言されているようです。

ではこのPageとはどいうクラスなのか見ていきます。

```dart
///  * [Navigator.pages], which accepts a list of [Page]s and updates its routes
///    history.
abstract class Page<T> extends RouteSettings {
  /// Creates a page and initializes [key] for subclasses.
  ///
  /// The [arguments] argument must not be null.
  const Page({
    this.key,
    super.name,
    super.arguments,
    this.restorationId,
  });

```

どうやらRouteSetteingというクラスを継承している子クラスのようですね。

RouteSetteingがどんなクラスなのかというと、
遷移している画面のデータを保存しているクラスの様ですね。

整理するとつまり、Navigatorのpagesという引数は、
**現在遷移している画面をリストで管理している変数**と言えそうです。

もう少しわかりやすく話すと、

A,B,C,Dの画面があるとしましょう。

A画面はB画面に遷移することができ、BはCに遷移可能、CはDにと言った具合だと仮定しましょう。

初回ビルド時には、Aが表示されるものとすると、
Navigatorクラスのpagesは初回ビルド時に以下の様になります。

```dart
pages = [A]
///あくまでこれはイメージでです。
```

B画面に遷移した際は、
```dart
pages = [A,B]
///あくまでこれはイメージでです。
```
一個飛ばしてD画面まで遷移した際は、以下の様になります。
```dart
pages = [A,B,C,D]
///あくまでこれはイメージでです。
```

D画面からA画面までNavigator.popで戻った場合は、以下です。
```dart
pages = [A]
///あくまでこれはイメージでです。
```

また、ここで注目したいのが、
このpagesは、**Stack**で管理されているということです。

StackというWidgetを使ったことがある人がほとんどだと思います。

```dart
Stack(
 children: <Widget>[
     Container(
        width: 100,
        height: 100,
        color:Colors.green
         ),
     Container(
        width: 50,
        height: 80,
        color: Colors.orange,
         )
              ],
            )
```

Navigator.pagesの引数に変化があったときにその変化を検知し、Stack内のデータをリフレッシュしています。
このように、遷移している画面のデータを**積み重ねる**ことによって、
画面遷移のロジックを実現しているんですね。面白い。

## 参考記事

https://api.flutter.dev/flutter/widgets/Navigator-class.html

https://qiita.com/heavenosk/items/9e43298955a371221393

https://itome.team/blog/2019/12/flutter-advent-calendar-day10/




## 最後に

最後までご覧いただきありがとうございました。
間違いや、ご指摘等ありましたら、コメントいただけますと幸いです。
よろしくお願いします。
z/OSのSSH, SCP, SFTPのEBCDIC-ASCII変換について。SCPでEBCDIC-ASCII変換が行われないとき。

z/OS UNIX System Servicesと、OpenSSH_9.0以上のSCPクライアントでASCIIテキストファイルを送受信する場合は、`-O`オプションを使用しましょう。

以上。

## 解説

最近は、z/OSでも、セキュリティー上の理由でTELNET, FTPを禁止し、SSH, SCP, SFTPを使用するようになってきています。z/OSでこれらを使う場合のユーザーの最大の関心事は、EBCDICテキストの取り扱いだと思います。

まず初めに、SSHではISO/IEC 8859-1（日本ではA0からFFの領域を使うことが少ないのでASCIIとほぼ同義）しか扱えません。UNIX System Servicesのコンソールで日本語を使用することはあきらめましょう。ASCIIの範囲であれば、SSHはEBCDIC-ASCII変換をしてくれます。

https://www.ibm.com/docs/en/zos/3.1.0?topic=version-what-zos-openssh-does-not-support
> z/OS OpenSSH does not support multibyte locales. z/OS OpenSSH does not support running in multibyte locales. It currently only supports single-byte locales that are compatible with ASCII coded character set ISO/IEC 8859-1.

SCP, SFTPについては、バイナリ転送する場合はSFTP, ASCIIテキストファイルをEBCDIC-ASCII変換して転送する場合はSCPが使用できます。

https://www.ibm.com/docs/en/zos/3.1.0?topic=version-what-zos-openssh-supports
> sftp can treat files as binary or text. By default, sftp assumes that files are binary. 
> scp treats files as text. By default, scp performs ASCII/EBCDIC conversion on files.

ところで、最近SCPでファイル転送しているつもりなのに、なぜかEBCDIC-ASCII変換をしてくれないことはないでしょうか。

```shell-session
$ echo abcdefg > test.txt
$ scp -i ～ test.txt ～@～:.
test.txt                                                                                           100%    8     0.1KB/s   00:00
$ ssh -i ～ ～@～
> cat test.txt
/□□□□□Ŏ
> iconv -f utf8 -t ibm1047 test.txt
abcdefg
```

そういう時は、SCPクライアントのバージョン（OpenSSHのバージョン）を確認しましょう。

```shell-session
$ ssh.exe -V
OpenSSH_9.3p1, OpenSSL 3.1.1 30 May 2023
```

OpenSSH_9.0以上の場合、SCPは内部でSFTPプロトコルを使用するように変更となっています。

https://www.openssh.com/releasenotes.html
> OpenSSH 9.0/9.0p1 (2022-04-08)
> ~
> Changes since OpenSSH 8.9
> ~
> Potentially-incompatible changes
> ~
> This release switches scp(1) from using the legacy scp/rcp protocol
to using the SFTP protocol by default.

```shell-session
$ scp -v -i ～ test.txt ～@～:.
~
debug1: Sending subsystem: sftp
~
```

SCPでテキストファイルをEBCDIC-ASCII変換して転送したい場合は、`-O`オプションを使用してレガシーなSCPプロトコルを使いましょう。

> In case of incompatibility, the scp(1) client may be instructed to use
the legacy scp/rcp using the -O flag.

```shell-session
$ scp -O -v -i ～ test.txt ～@～:.
~
debug1: Sending command: scp -v -t .
~
$ ssh -i ～ ～@～
> cat test.txt
abcdefg
```

よきz/OSライフを。
AI-102試験対策のまとめ「Azure AI Language」

## はじめに
Azure AI Engineer Associate(AI-102)取得に向けた勉強でまとめた内容を本記事では投稿しています。
試験範囲の内容を私が勉強していく中で、
・忘れてしまっていた内容
・他の知識と絡めて理解した方が良い内容
・Microsoft Learnの読解に苦戦した内容
などをできるだけ分かりやすく簡潔にまとめることを意識しています。
Azureの勉強を一から始め、AI-102取得を目指している方の参考になれば幸いです。
※本記事の内容が必ず試験に出るわけではないこと、ご注意ください
※勉強しながら投稿しているため、追加で学んだことがあれば随時投稿内容を更新します。

## 試験対策のまとめ
## Azure AI Language
テキストを理解し分析するための自然言語処理(NLP)機能を提供  
※これまで利用できたAzure AIサービス(Text Analytics、QnA Maker、LUIS)を統合したもの  

### Azure AI Languageの機能
| 機能 | 説明 | 例 |
| --- | --- | --- |
| 名前付きエンティティの認識(NER) | テキスト内のエンティティ(語句)カテゴリを抽出 | 人・イベント・場所・日付など |
| カスタム固有表現認識(カスタムNER) | データに固有のモデルを使用してエンティティカテゴリを抽出 | 住宅ローンの申し込みデータの抽出など |
| 個人を特定できる情報(PII)の検出 | 機密性の高い情報を識別・分類・編集 | 電話番号・メールアドレス・各種形式のIDなど
| 言語検出 | 使用されている言語を検出 | 英語・フランス語など |
| 感情分析とオピニオンマイニング | テキストから正負の感情を判定 | POSITIVE・NEUTRAL・NEGATIVE |
| 概要(要約処理) | 要約を生成 | ドキュメント・会話の文字起こしなど |
| キーフレーズの抽出 | 主要なトピックと重要なフレーズを特定 | テキスト内の固有名詞など |
| エンティティリンク設定 | エンティティのリンクを返す | Wikipedia |
| Text Analytics for Health | 関連する医療情報を抽出してラベル付け | 医師のメモ・退院要約・臨床ドキュメント・電子健康記録など |
| カスタムテキスト分類 | ドキュメントを1つ以上のカテゴリに分類 | コンピュータサイエンスなどのカテゴリ |
| 会話言語理解(CLU) | ユーザー入力の意図を予測し重要な情報を抽出 | 自然言語によるIoTの操作など |
| 質問応答 | ユーザー入力に対して最も適切な回答を生成 | チャットボットなど |
| Custom text analytics for health | 作成したモデルで医療固有のエンティティを抽出 | 臨床メモ・レポートなど |
| オーケストレーションワークフロー | CLU・LUIS・質問応答からアプリを接続 | 大企業のエンタープライズチャットボットなど |

* 参考
    - https://learn.microsoft.com/ja-jp/azure/ai-services/language-service/overview

## 次の試験対策のまとめ
* **AI-102試験対策のまとめ「Azure AI Document Intelligence」**

https://qiita.com/fsd-jume/items/d831423c892a47819c56

## 学習内容をまとめたページの一覧

https://qiita.com/fsd-jume/items/65aba5d5514e023fd66d
JavaScriptで配列から重複しているIDを削除する

どうも！TABEです。

JavaScriptでデータを持っている配列の中で（IDや、Nameなどの値が入っている配列）
Idが重複している場合にES6以降であればfilterを使って重複を削除できるので、それをメモが代わりに書いておきます。


結論から言うとarrayという配列があった時に、重複しているデータを削除させたい場合は以下の様にします。
```
const arrayFilterResult = array.filter((v, i, a) => a.findIndex(t => (t.id === v.id)) === i);
```

関数は以下のパラメータを取ります：

v: 現在の配列要素
i: 現在の配列要素のインデックス
a: 配列自体
t: 配列の要素

`Array.prototype.findIndex`メソッドを使用しています。このメソッドは配列の各要素に対してテスト関数を実行し、テスト関数がtrueを返す最初の要素のインデックスを返します。もしテスト関数がどの要素に対してもtrueを返さない場合は、-1を返します。

`findIndex`メソッドで、現在の要素vと同じidプロパティを持つ最初の要素のインデックスを検索しています。もし現在の要素がそのidに対して最初の要素であれば、そのインデックスiは`findIndex`メソッドが返すインデックスと等しくなります。これはそのidを持つ要素が重複していないことを意味します。その結果、テスト関数はtrueを返し、要素は新しい配列`arrayFilterResult`に追加されます。

tは、`Array.prototype.findIndex`メソッド内で使われる関数のパラメータです。この関数は`array`配列の各要素をtとして受け取り、その要素が現在の要素vと同じidプロパティを持つかどうかをチェックします。

コードのこの部分：`a.findIndex(t => (t.id === v.id))`では、tはarray配列の各要素を表しています。この関数は、t.id === v.idという条件を使って、tのidプロパティが現在の要素vのidプロパティと同じかどうかをチェックしています。

以上です！
参考になれば幸いです。
Docker 概要まとめ

### Docker概要

Dockerとは
・Docker社が開発している、Linux をターゲットとするコンテナ管理基盤です。
・Windows版は「Docker Desktop」をダウンロード、インストールし、起動して使う。
（※起動しないと使えない）
Imageとは
・システムの最小構成になっているOS込みのファイル群
Containerとは（
・イメージを指定（マウント）して、OS（サービス）を起動、終了など操作を行える入れ物。
Docker Composeとは
・Dockerのイメージ作成からコンテナ立ち上げまで一連の動作を自動化してくれる指示書のこと。
・Docker Composeは複数のコンテナ（サービス）、ポートなどを一度に行い、各サービスが相互に通信できる状態にしてくれる
→例えば、Nginx、Mysql、Flaskなどのサービスを起動し、相互通信できるようにする（以下参照）
```yml
version: '3.8'
services:
  appseed-app:
    container_name: appseed_app
    restart: always
    env_file: .env
    build: .
    networks:
      - db_network
      - web_network
  nginx:
    container_name: nginx
    restart: always
    image: "nginx:latest"
    ports:
      - "5085:5085"
    volumes:
      - ./nginx:/etc/nginx/conf.d
    networks:
      - web_network
    depends_on: 
      - appseed-app
networks:
  db_network:
    driver: bridge
  web_network:
    driver: bridge
```

### Docker hubとは
Docker imageをダウンロード、アップロードすることができる
※Freeライセンスは制限がある
### Dockerコンテナの注意点
Dockerコンテナを削除した時点で、ファイルすべて消えてしまいます。

### Dockerコマンド
```terminal
# 名前を付けて実行する（--name:名前)
docker run --name "recommend_app" -it krs_y_test:ver1

# HostからDockerコンテナへコピーする
# from host to container
docker cp test_file.txt container_name:/app/data

# from container to host
docker cp container_name:/app/data/test_file.txt .
```
詳細は[こちら](https://www.tohoho-web.com/docker/docker_run.html)

### dockerイメージ作成
```
# イメージ作成
docker build -t my-app:latest .
# イメージ一覧表示
docker images
```
### docker イメージをExportする
```
docker save sample-image > sample-image.tar
```
### docker イメージをLoadする
```terminal
docker load < image.tar
# 実行終わったら自動的にDownするのは後で調べる
```
Dockerイメージからdockerコンテナを立ちあげる
（runコマンドはイメージからコンテナを作成する一度のみ実行する）
```terminal
docker run --name <container_name> <image_name>:<image_ver>
```
### docker containerを起動する
```terminal
# container_appというコンテナを実行する
$ docker start container_app

# コンテナ一覧の確認（停止している場合）
$ docker ps -a
# コンテナを削除する
$ docker rm <container_id>
```
TypeError: 'numpy._DTypeMeta' object is not subscriptable

Ubuntu22.04でOpenCV-pythonを使おうとして出てきたエラー。

参考の通り、numpyを再インストールしたら解決した。

```
sudo apt remove python3-numpy
pip install numpy
```

### 参考
* https://www.reddit.com/r/learnpython/comments/16rebv6/error_importing_cv2_typeerror_numpy_dtypemeta/?rdt=53922

* https://stackoverflow.com/questions/77120118/when-importing-opencv-typeerror-numpy-dtypemeta-object-is-not-subscriptable

* https://stackoverflow.com/questions/68916893/typeerror-numpy-dtypemeta-object-is-not-subscriptable
プルリクエストによるコードレビューを迅速に行うために実装者が意識した方がいいこと

## 自己紹介

株式会社 Signifi でエンジニアとして働いています。
昔はバックエンド専門でしたが、最近はモバイルアプリ、Webフロント、DevOpsなども担当しています。

## 背景

コードレビューをする時に、プルリクエストを見て「何をレビューしたらいいのか分からない！」ってことはありませんか？
ここでは、レビューワーがどんな情報を期待しているのか？実装者はどんな情報を記載すればいいのか？
その辺りで自分が普段気をつけている内容を記載しようと思っています。

## レビューとは？

まずは、よくあるそもそも「レビュー」とはなんぞやというところから記載します。
ちょっと google 検索をすると下記の内容が出てきます。

> 情報システムやソフトウェアの開発で、作成された仕様書やコードなどの成果物を開発者とは別の人が詳細に調べ、仕様や要求を満たす内容になっているか、誤りや不具合が無いか、資源の浪費や不必要な冗長さ、極端な低性能などの問題が無いかなどについて開発者にフィードバックする工程をレビューという。

[IT用語辞典 e-Words](https://e-words.jp/w/%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC.html)

とはいえ、この内容をレビューワーが愚直にやるのは負担が大きいため、厳しいかと思います。


## 実装者側で担保して欲しいこと

そこで自分は下記をなるべく明言するようにしています。

### 動作確認

コードが意図した通りに動作するかどうかは、単体テストで実装者が確認すべき事項だと思っています。
もちろん、間違いを見つけたら指摘はしますが、レビューで弾く想定だとレビューワーはかなり大変です。
最低限の動作保証は、実装者側の責任でお願いしています。

### 実装背景を明記

「なんでこんな回りくどいことしているんだろう？」...そう思った事はありませんか？
「なんでこんなことしているんですか？」...って聞いたら驚愕の事実を聞いたことはありませんか？
「最初からプルリクエスト文面に書いてよ！」...って思った経験はありませんか？

私はたくさん経験があります。
そこで、「実装をするときに気をつけなければならなかったこと」をきちんと書くようにしましょう。

例えば、下記のような事項です。
登録顧客の一覧を検索する API を作ったとして、下記のように書かれていればどこに何の修正を入れたか想像できませんか？

```
会員登録を介して登録された顧客一覧を名前から検索する機能を作りました。

会員数は最終的に500万人を見込んでおり、SQL の Like 文ではパフォーマンス的に対処できない想定のため全文検索を使って検索するようにしています。
このため、会員登録時でも検索をするための仕込みの処理を追加しています。
```

また、API 側であるデータが削除されるので、それらに関連するプルリクエストでは下記のように書いたこともあります。

```
○○ API の ××属性が削除されるため、該当データを参照するすべての内容を削除しています。

ただし、API 側の該当データ削除は、フロント修正後に行われるため API は現状態でも動作するように記載しています。
(※ 削除予定の必須パラメータにはダミー値を入れたりしている)

この部分に関しては、TODO コメントとして修正すべき内容を記載して、API 側で修正されたらフロント側も修正する想定です。
```

このように書いておくだけで、「何でこんなことをしているんだ？」という疑問の大半は解消されるはずです。


### コードの細かい説明は不要

「実装に関して説明を書いてください。」と言うと、一行一行実装内容を説明する人がいます。
これは、私は不要だと考えています。

なぜなら、プルリクエストに細かい挙動の説明をしないと、意図が理解できないソースコードはそれだけで危険なものです。
また、プルリクエストの説明はコードレビューでしか見る事はできません。
あとでその箇所を修正するとなったとき、理解できなかったら大変な事です。

どうしても分かり辛いことは、一行コメント・ドキュメンテーションコメントで対応するようにしましょう。
ただし、これはあくまで次善策であり、本来はソースコードだけで意図が伝わるようにするのが理想です。


### 不安なこと・課題点は明記する

時間は有限です。どうしても完璧に実装する事は難しいでしょう。
それはそれで許されるケースもあります。不安な事は不安として記載しましょう。
簡単に解決できるなら、レビューワーが指摘をくれるでしょう。

課題を解決したいけど、それが難しいケースもしくは優先度的に後回しでも問題なければ、
別タスクとして定義して TODO コメントをつけるなり対応できるはずです。
その選択肢で悩んでいる！と言う情報があると、レビューワーも読む時に理解してくれるはずです。


## まとめ

今回はコードレビューに関して、実装者側で意識した方がいいことを記載しました。

コードレビューはレビューワーの負担が大きく、最終的に頓挫してしまうケースも多いと思います。
しかし、コードレビューの本来の目的はソースコードを開発者間で共有意識を持ってメンテナンスしていくことです。
これを目指すのであれば、レビューワーだけでなく実装者側もできるだけレビューワーに優しいプルリクエストを書くことをお勧めします。
AWS-EC2のアクセスログをCloudWatch Logsで取得する

# 目的

EC2にCloudWatchのエージェントを導入することで以下が可能になるようです。

1. インスタンスやサービスログの蓄積
2. ログのモニタリング
3. ログのフィルタリング
4. ログデータのアーカイブ
5. ログデータの分析　(CloudWatch Logs Insights)
6. ログのアラート化

今回はCloudWatchを使い、EC2のApacheのアクセスログを収集、確認してみたいと思います。

# インスタンス準備
- OSはAmazonLinux2023
- sshでつながるec2を準備する。
- **インターネットから80番ポート** への接続はセキュリティグループで許可
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/899359cd-422f-40d9-9681-8dbecad332ea.png)



## Apacheの導入

- apacheインストール、起動
```
// インストール
sudo yum install httpd -y

// サービス起動
sudo systemctl start httpd
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/27b6eea9-3ec6-410b-f0d2-5acd4cd7bd33.png)


- アクセス確認
```
// HEADだけ確認
curl http://localhost | head
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/3a90c647-14e6-f540-2ff4-c8b801b90117.png)

- httpdログの出力があるか確認
```
cd /var/log/httpd
tail -f access_log -n 100
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/c0e4e2f0-c7a3-de4e-97aa-69ba43a45fc8.png)

## CloudWatchLogsポリシーの設定
- EC2に割り当てるIAMロール作成

```
1. マネジメントコンソールからIAMを選択
2. 信頼されたエンティティタイプ - AWS のサービス
3. ユースケース - EC2
4. 次へ
5. 検索窓に 「CloudWatchLogsFullAccess」 を入力、チェックオン
6. 次へ
7. ロール名には任意の名前を設定
8. ロールを作成
```

- IAMロールをEC2に設定
```
1. マネージメントコンソールからEC2を選択
2. 対象のEC2をチェックオン
3. アクション - セキュリティ - IAMロールを選択
4. IAMロールから、作成したロールを選択
5. IAMロールの更新
```
ロールをEC2に紐付けることで、CloudWatchLogsサービスの利用権限を保持するようになります。

## CloudWatchエージェントの導入
- CloudWatch Logsエージェントのインストール
```
$ sudo dnf install amazon-cloudwatch-agent
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/dd0085f5-a906-754a-9ac1-a8e0586bbeda.png)


- ウィザードを利用した監視設定
```
$ cd /opt/aws/amazon-cloudwatch-agent/bin
$ sudo ./amazon-cloudwatch-agent-config-wizard
```
基本的にdefault choiceですが、今回はhttpdのログ取得なので以下の設定を行います。
```
Log file path:
/var/log/httpd/access_log
```

instance_idでログストリームが作成されます。


- JSONファイルのコピー、リネーム
- /opt/aws/amazon-cloudwatch-agent/bin 配下に「config.json」が出来上がります。
しかし、このままでは読み込んでくれないため、起動に失敗します。
/opt/aws/amazon-cloudwatch-agent/etc/ 配下に「config.json」をコピーします。
```
#ファイル移動
$ sudo cp -p config.json ../etc/

#ディレクトリ移動、リネーム
$ cd ../etc/
$ sudo mv config.json ./amazon-cloudwatch-agent.json
```

- エージェントの起動、ステータス確認
```
// 起動
$ sudo systemctl restart amazon-cloudwatch-agent
// ステータス確認
$ sudo systemctl status amazon-cloudwatch-agent
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/a495b616-334c-e834-5286-e322d15669a7.png)

※OSがAM2023の場合、collectが入っていないので、うまくエージェントが起動してくれませんでした。以下を参考にcollectのインストール後、無事に起動しました。
参考URL：https://zenn.dev/techa_log/articles/d2f766d38c0407

- CloudWatchでログ収集確認
ロググループ - access_log - instanceIdを選択し、正常にログ出力されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186572/2376560f-a6ab-43ee-17e0-3788e9e89212.png)
Pipでインストールされている全てのPackageを一括UpgradeするためのOne-liner

# はじめに
PipでPython libraryを管理する場合、`pip list -o`などでUpgradeされたPackageを確認すると、思いのほか大量にある場合がある。一つ一つ手動でUpgradeしても良いが、大量にあると少々面倒だ。
そこで、一括でUpgradeできるOne-linerを書いてみた。
# One-liner
## 基本形
基本形は以下の通りとなる。
```
pip install -U `pip list -o | tail -\`expr \\\`pip list -o | wc -l\\\` - 2\` | awk '{print $1}' | sort -u`
```
### 仕様・解説
1. UpgradeされるPackage数を取得
`pip list -o | wc -l`で取得された行数から、Header行の`2`を引いている。
1. `pip list -o`の出力からHeaderを除いたPackage一覧を出力し、Package名が格納されている1列目を抜き出す。
1. 上記で作成したPackage一覧に対してUpgradeを実施。
## 一部のPackageを除外
UpgradeしたくないPackageや、PipでUpgradeしようとしても失敗するPackageは`grep -v`を用いて除外する。
```
pip install -U `pip list -o | tail -\`expr \\\`pip list -o | wc -l\\\` - 2\` | awk '{print $1}' | sort -u | grep -v "PyGObject\|pycairo"`
```
上記の例では`PyGObject`と`pycairo`を除外している。
## 特定バージョンのPythonや、InterpreterのPackageに対して実行
1台のマシンに複数バージョンのPythonがインストールされている場合や、`Pypy`などの別のPython実行環境がインストールされている場合、以下のようにして該当のPython実行環境に対してUpgradeを実施できる。
以下の例は`Pypy3.9`環境に対して実行した例。
```
pypy3.9 -m pip install -U `pypy3.9 -m pip list -o | tail -\`expr \\\`pypy3.9 -m pip list -o | wc -l\\\` - 3\` | awk '{print $1}' | sort -u`
```

これで、瞬時にPython packageの一括Upgradeを実施できるようになった。
誰かのお役に立てれば幸いである。
では、楽しいPython lifeを!
[GAS][Googleスライド]現在開いているページを選択する

### 現在開いているページを選択する

```js
var slide = SlidesApp.getActivePresentation().getSelection().getCurrentPage();
```

#### 使用例
[Gravatar](https://gravatar.com/)から取得したProfile Imageを現在開いているページに挿入する。

```js
function insertGravatar() {
  // get a profile image from Gravatar
  const email = 'YourEmailAddress@example.com';
  const hash  = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, email.trim().toLowerCase());
  const image = UrlFetchApp.fetch('https://secure.gravatar.com/avatar/' + hash);

  // insert the image
  const slide = SlidesApp.getActivePresentation().getSelection().getCurrentPage();
  slide.insertImage(image);
}
```

### 参考
- [Class Selection &nbsp;|&nbsp; Apps Script &nbsp;|&nbsp; Google for Developer](https://developers.google.com/apps-script/reference/slides/selection?hl=ja#getCurrentPage())
CDKでLambdaレイヤーに Klayers をarnで指定する

## 経緯

### 起こった問題
CDKでLambdaを構築していて、その際に必要なパッケージはrequirements.txtなどにまとめてローカルでzip化してレイヤーとして渡していた。
しかしpandasなどの一部のライブラリはOSによる環境依存の影響を受ける。
例えば私はMacOSを利用しているが、Macでpandasをインストールしても実際にそれを使いたいLambdaはAmazon LinuxをOSとしているため、互換性がなく動作しない。pandasみたいなこういうライブラリがたまにあるので対応が必要。

### 解決方法

解決方法としてはいくつかあり、
- Dockerを使ってLambda環境を立ち上げ、その中でライブラリをインストールしてレイヤーとして渡す
- すでに公開されているレイヤーを利用する

などが思いつく。
信頼できるレイヤーがすでに存在しているのであればこれを使うのが一番楽なので、今回は後者の方法でレイヤーを定義する。マネコンで設定する方法は記事がすでにいくつ公開されていたので、CDKで実装する方法を紹介する。

## 動作環境
- CDK 2.93.0
- Python 3.10

## LambdaレイヤーのARNを取得

今回は良く使われているKlayersのレイヤーを使わせていただきます。
[GitHubで公開されている](https://github.com/keithrozario/Klayers/tree/master/deployments)のでこちらにアクセスして、使いたいライブラリが入っているレイヤーのARNがあるかを確認する。

## CDKで定義

~~~typescript
import * as lambda from "aws-cdk-lib/aws-lambda";

...

// Lambdaの定義
const sampleFunction = new lambda.Function(this, "sampleFunction", {
  runtime: lambda.Runtime.PYTHON_3_10,
  code: lambda.Code.fromAsset(path.join(__dirname, "../lambda")),
  handler: "app.handler",
  layers: [
    customLayer, // ←他に定義するレイヤーがあれば
    // pandas2.1.1のレイヤーを追加 ここから
    lambda.LayerVersion.fromLayerVersionArn(
      this, "KlayersPandasLayer", "arn:aws:lambda:ap-northeast-1:xxxxxxxx:layer:Klayers-p310-pandas:6"
    )
    // ここまで
  ],
  vpc: props.vpc,
  securityGroups: [props.securityGroup],
  timeout: Duration.seconds(300),
  memorySize: props.envConfig.lambdaMemorySize,
});
~~~
fromLayerVersionArnの第３引数に先ほど確認したARNを入れるだけ。
上記のようにFunctionメソッドのlayersパラメーターの配列の中に直接入れてもいいし、先に定義してから追加してもいい。後者の方がスッキリして見やすいかも？

## 結論
Klayersありがとうございます
【javascript】初学者でもできた。LIFF.sendMessagesを使い、送るメッセージを条件によって変える方法

こんにちは。最近、liff.sendMessagesを使おうとしたけど、javascriptの変数の使い方が分からなくて困った人です。
そこで今回は、知っているとliff.sendMessagesがちょっとだけ便利に使える方法を紹介したいと思います。

# 目次
- [概要](#概要)
- [必要な前提知識](#必要な前提知識)
- [liff.sendMessagesメソッドの基本](#liff.sendMessagesメソッドの基本)
- [if文を使用した条件分岐](#if文を使用した条件分岐)
    - [メッセージを丸ごと分岐させるパターン](#メッセージを丸ごと分岐させるパターン)
    - [変数にメッセージを入力し、出力するパターン](#変数にメッセージを入力し、出力するパターン)
- [終わりに](#終わりに)
# 概要
LINE Front-end Framework（LIFF）は、LINEアプリ内で動作するWebアプリケーションを開発するためのツールです。LIFFを使用すると、LINEアプリ内でユーザーに対してメッセージを送信できる機能を実装することができます。しかし、メッセージの内容を条件に応じて変更したい場合、どのように実現すればよいでしょうか？この記事では、liff.sendMessagesを使用してメッセージ送信を条件分岐させる方法を詳しく説明します。

# 必要な前提知識
LIFFを使用する前に、以下の前提知識が必要です。

- LINEアカウントの作成
- LINE Developersコンソールへのアクセス
- LIFFアプリケーションの作成
# liff.sendMessagesメソッドの基本
liff.sendMessagesメソッドは、LINEアプリ内でメッセージを送信するために使用されます。基本的な使い方は以下の通りです。

```javascript
liff.sendMessages([
  {
    type: 'text',
    text: 'こんにちは、LINEユーザー！'
  }
]);
```
このコードは、ユーザーに対して「こんにちは、LINEユーザー！」というテキストメッセージを送信します。しかし、このメッセージを条件に応じて変更するためには、if文を使用する必要があります。

# if文を使用した条件分岐
#### メッセ―ジを丸ごと分岐させるパターン
メッセージの内容を条件に応じて変更するには、JavaScriptのif文を使用します。以下は、簡単な例です。

```javascript
if (条件) {
  liff.sendMessages([
    {
      type: 'text',
      text: '条件が真の場合のメッセージ'
    }
  ]);
} else {
  liff.sendMessages([
    {
      type: 'text',
      text: '条件が偽の場合のメッセージ'
    }
  ]);
}
```
このコードでは、指定した条件が真の場合と偽の場合で異なるメッセージを送信します。条件は、アプリのロジックに合わせてカスタマイズできます。
<br>
<br>

##### 実際の使用例
以下は、実際の使用例です。ユーザーが特定のコマンドを入力した場合に異なるメッセージを送信するLIFFアプリのコードです。

```javascript
const userCommand = getUserCommand(); // ユーザーの入力コマンドを取得する関数

if (userCommand === 'こんにちは') {
  liff.sendMessages([
    {
      type: 'text',
      text: 'こんにちは、LINEユーザー！'
    }
  ]);
} else if (userCommand === 'おやすみ') {
  liff.sendMessages([
    {
      type: 'text',
      text: 'おやすみなさい！'
    }
  ]);
} else {
  liff.sendMessages([
    {
      type: 'text',
      text: 'コマンドが理解できません。'
    }
  ]);
}
```
このコードでは、ユーザーが「こんにちは」と入力した場合には「こんにちは、LINEユーザー！」と返し、ユーザーが「おやすみ」と入力した場合には「おやすみなさい！」と返します。それ以外の場合には「コマンドが理解できません。」と返します。

#### 変数にメッセージを入力し、出力するパターン
メッセージの一部分のみ変えたい場合に役に立ちます。以下は簡単な例です。

```javascript
if (条件) {
    message = '条件が真の場合のメッセージ'
} else {
    message = '条件が偽の場合のメッセージ'

  liff.sendMessages([
    {
      type: 'text',
      text: message
    }
  ]);
} 
```
このコードでは、指定した条件が真の場合と偽の場合で異なるメッセージを変数に代入し、代入されたメッセージを送信します。条件は、アプリのロジックに合わせてカスタマイズできます。
<br>
<br>

##### 実際の使用例
以下は、実際の使用例です。ユーザーが特定のコマンドを入力した場合に異なるメッセージを送信するLIFFアプリのコードです。

```javascript
const userCommand = getUserCommand(); // ユーザーの入力コマンドを取得する関数

var message = '';//条件に合ったメッセージを入れるための変数

if (userCommand === 'こんにちは') {
    message = 'こんにちは'
} else if (userCommand === 'おやすみ') {
    message = 'おやすみなさい'
} else {
    message = 'コマンドが理解できません。'
}

  liff.sendMessages([
    {
      type: 'text',
      text: message + '、LINEユーザー'
    }
  ]);
} 
```
このコードでは、ユーザーが「こんにちは」と入力した場合には「こんにちは、LINEユーザー」と返し、ユーザーが「おやすみ」と入力した場合には「おやすみなさい、LINEユーザー」と返します。それ以外の場合には「コマンドが理解できません、LINEユーザー」と返します。

# 終わりに

LIFFを使用してLINEアプリ内でメッセージを送信する際、条件分岐を使用することで、ユーザーに対して適切なメッセージを送信できます。前提知識とJavaScriptのif文を駆使して、メッセージの内容を動的に変更し、ユーザーエクスペリエンスを向上させることができます。この記事を参考に、LIFFアプリ内でのメッセージ送信をカスタマイズしてみてください。
フォーク元の更新を組み込みたい時のメモ

Githubでフォークしたリポジトリを更新する方法を無限に調べているのでメモっておこうという意気込み。
fork後、フォーク元のリポジトリに追いつきたい時に使える。
```
$ git remote add upstream https://github.com/zzz/xxx

$ git remote -v
origin	https://github.com/aaa/xxx.git (fetch)
origin	https://github.com/aaa/xxx.git (push)
upstream	https://github.com/zzz/xxx.git (fetch)
upstream	https://github.com/zzz/xxx.git (push)

$ git fetch upstream
$ git merge upstream/master
```
※ mainブランチの場合もあるのでそこは注意