OmniAICreator/Qiita-1.07M huggingface dataset:
Vector searchのハイブリッド検索とフィルタリング

Mosaic AI Vector Searchは、Databricksのデータインテリジェンスプラットフォームに組み込まれたベクトルデータベースです。

# Hybrid search
Databricks Vector searchは、ベクトルベースの埋め込み検索(query_typeパラメータをannとして設定、既定値)とハイブリッド検索(ベクトルベースの埋め込み検索と従来のキーワードベース検索の組み合わせ、query_typeパラメータをHybridとして設定)の２つクエリタイプをサポートしています。
- ベクトルベースの埋め込み検索にHNSW（Hierarchical Navigable Small World）アルゴリズムを使用し、埋め込みベクトルの類似度を測定するためにL2距離メトリクスを使用します。（注：返される結果の数の上限は10,000行/クエリになります。）
- ハイブリッド検索では、クエリー内の単語とExact Matchだけでなく、概念的に類似したドキュメントも検索し、より包括的で関連性の高い検索結果を提供します。類似性検索の計算は[ドキュメント](ドキュメント)をご参照ください。インデクスにある全てのStringタイプの列はキーワード検索の対象となります。現時点すべての文字列を同じ重みで扱うとしています。（注：現時点、返される結果の数の上限は50行/クエリになります。）

```Sample
# Delta Sync Index with embeddings computed by Databricks
results = vsc.get_index(VECTOR_SEARCH_ENDPOINT_NAME, index_name).similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    num_results=2
    )

# Delta Sync Index using hybrid search, with embeddings computed by Databricks
results3 = vsc.get_index(VECTOR_SEARCH_ENDPOINT_NAME, index_name).similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    num_results=2,
    query_type="hybrid"
    )
```

# Filter
クエリでは、インデックス・テーブル内の任意の列に基づくフィルターを設定することができます。 similarity_search は、指定されたフィルターに一致する行のみを返します。 サポートしているフィルター演算子は[こちら](https://learn.microsoft.com/ja-jp/azure/databricks/generative-ai/create-query-vector-search#filters)に記載されています。
* 注）LIKEの挙動が少しわかりにくい状態です。より理解しやすいものに改良中ですが、現状SQLのような部分一致ではなく、「トークン」一致（大まかには単語と同等）しか対応していません。

```sample
# Match rows where `title` exactly matches `Athena` or `Ares`
results = index.similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    filters={"title": ["Ares", "Athena"]},
    num_results=2
    )

# Match rows where `title` or `id` exactly matches `Athena` or `Ares`
results = index.similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    filters={"title OR id": ["Ares", "Athena"]},
    num_results=2
    )

# Match only rows where `title` is not `Hercules`
results = index.similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    filters={"title NOT": "Hercules"},
    num_results=2
    )
```

ハイブリッド検索とフィルターを組み合わせて使用することもできます。
```sample
# Match rows where `title` or `id` exactly matches `Athena` or `Ares`
results = index.similarity_search(
    query_text="Greek myths",
    columns=["id", "text"],
    filters={"title OR id": ["Ares", "Athena"]},
    num_results=2,
    query_type="hybrid"
    )
```
【報連相】これさえあれば迷わない。疲れた時に使うメッセージのテンプレート

# 概要
忙しかったり疲れたりした時はメッセージを返すのが億劫になるもの
そんな時、自分はテンプレートに沿って書くことでハードルを下げています

未来の自分のためにもまとめておきます

# テンプレート
## 挨拶
#### 汎用
```
お疲れ様です
```

#### 始業
```
おはようございます
```

#### 就業
```
お疲れ様でした
```


## 感謝
#### 汎用
```
ありがとうございます :bow:

(過去)ありがとうございました :bow: 
```
> ありがとうございます :bow:

#### 活用
 - 接頭
日をまたがない過去: `先ほどは`
日をまたぐ過去: `先日は`
複数人: `みなさん`

 - 接尾
強調: `ありがとうございます :bow: :bow: :bow:`
柔らかく: `ありがとうございました！`

## 説明
<b>抽象度を徐々に低める</b>よう話を進める

抽象度が高い部分は記憶に残るから丁寧に
抽象度が低い部分はその場の説得材料か、備忘録ぐらいの役割にしておく

文中の語句については、ある程度攻めて大丈夫
ただ、語句の説明は文中になるべく挟まないようにする

```
相手「Roll a ballってなんですか？」

自分「Unityのチュートリアルです。

プレイヤーであるボールが地面の上を転がるゲームを作りながら、Unityの使い方を学べます。

プロジェクトの作成からプレイヤーの移動、物理演算、カメラの移動など、基礎的な操作が揃っており、
初学者にはうってつけのチュートリアルです。
以前はC#スクリプティングも学べましたが、最近はビジュアルスクリプティングに置き換わっています。
```

## 依頼する時
```
{::挨拶}
{依頼概要}

{依頼背景}

{依頼内容}
```

依頼内容を最後に持ってくる
メッセージなんて最初と最後しか印象に残らない

依頼背景 = [{現状}, {問題}, {軽い依頼内容}].shuffle.join

```:例
お疲れ様です
タイトルロゴ画像修正の依頼です

タイトルロゴとしていただいた画像について、画像サイズが8100*2700となっています
ビルド時のデータサイズを小さくするため、表示に十分な大きさまで縮小していただきたいです

画像サイズを45*15に修正していただいてもよろしいでしょうか
```


## 依頼された時
#### すぐ承諾
```
承知しました
```
情報そろってる？
 - 期限
 - 提出内容or形式
 - 提出先
 - 作業フロー

次の報告タイミングを自分の中で決めとく
カレンダーに入れろ！！！
絶対忘れる

#### ちょっと考える
```
〇〇の部分について、コードを確認してから再度返信させていただきます

--

ぱっと工数感が浮かばないので、少し調査してみます

--

リアクション :eyes:
```
ちょっとだからな！！！！！
別のことするな！！！！

#### 無理
```
工数がかなり重そうですが、優先度はどのぐらいでしょうか

--

現在進行中の〇〇とは、並行しての作業が難しい内容です

--

データの構造上、現状だと難しいです
```

## 調査依頼
```
{事象(なるべく客観的に)}

{発見経緯}

{自分で調査した内容や予想}
```
いい感じに並び替えて、長すぎるなら２メッセージに分ける

```:例
設定画面を開いた時、エラーが発生しているとCSにお問い合わせが急増しています。

初回は10:30で、そこから30件ほど着信しています
また、300件ほどのエラーも検知しています

こちら調査していただいてもよろしいでしょうか

--- 2メッセージ目
zettai_error.pyのerror_ni_nattyauメソッドの引数をlowercaseにしているところが怪しそうに思います
{コードとかURLとか}
```
Spresense HDRカメラ用のズームフォーカスレンズのモーターの仕様を確認する

Spresense HDRカメラのズームフォーカスレンズのフォルダを作りましたが、データシートなどがなく肝心のモーターの動かし方がわからない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/82473164-3def-f682-8645-5baf0e6bd246.png)


AliExpressなど、いろいろと粗探しをしていたところ、なんとなくそれらしい資料をみつけました。こちらがそれ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/fb8b90c9-0812-1606-df7c-2796cc071fb8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/cfc1d353-9767-588b-06ac-dc09ca12a91c.png)

主要な仕様を簡単にまとめると次のような感じです。

| 項目         | 仕様 |
| ----------- | ---- |
| FOV 1/3"（水平） | 37.4°(W) ～ 15.24°(T) |       
| FOV 1/3"（垂直） | 27.6°(W) ～ 11.4°(T) |       
| FOV 1/3"（対角） | 47.4°(W) ～ 19°(T) |       
| 近接距離 | 0.5m |       
| モーター相数  | 2相  |
| 電圧範囲     | 3.3-5V |
| 最大電流     | 125mA/Phase |
| 1ステップ当たりの角度 | 0.224度/Step |
| ズームステップ数 | 2342 steps (0.002882mm/step) |
| フォーカスステップ数 | 2342 steps (0.004867mm/step)|
| 推奨動作条件 | 4.2V 2-2相 300-800PPS |

HDRカメラのイメージセンサは1/3.8インチなので、ちょっと数値が合わないですが、近似値をもってくると次のようになります。

| 項目         | 仕様 |
| ----------- | ---- |
| FOV 1/3.8"（水平） | 32.9°(W) ～ 13.4°(T) |       
| FOV 1/3.8"（垂直） | 23.5°(W) ～ 10.5°(T) |       
| FOV 1/3.8"（対角） | 40.5°(W) ～ 16.2°(T) |       

Driving speed acceleration conditions　の 1PLUS は何をいっているのかよくわからないのが、ちょっと気になりますが、なんとなくステッピングモーターとして制御できそうです。で一番気になるのがコネクタの仕様。そちらも次のように記載されています。

|  色   |  信号線  |
| ----- | ------- |
| 橙 | ZOOM A+ |
| 緑 | ZOOM A- |
| 黒 | FOCUS A+ |
| 青 | FOCUS A- |
| 赤 | FOCUS B+ |
| 白 | FOCUS B- |
| 黄 | ZOOM B+ |
| 紫 | ZOOM B- |

ピン番号は私の手元のレンズと違いますが、同様の色のハーネスが出ています。

![DSC02601.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/9154056c-f070-354e-92e9-a7563ff19730.jpeg)

他に情報がないので、これを信じて動かしてみるしかないですね。次はいよいよズームフォーカスレンズを動かしてみます！ちなみにコネクタの大きさも書いてあって、1.25mmピッチのようですね。


関連記事
[Spresense HDRカメラのズームフォーカスレンズ用のフォルダを作る](https://qiita.com/TaroYoshino/items/d0abd58370d2c42f6fbb)
[Spresense HDRカメラ用のズームフォーカスレンズのモーターを制御する](https://qiita.com/TaroYoshino/items/342d6180e4ca6aa3b439)
[Spresense ズームレンズ対応HDRカメラを動作させてみた](https://qiita.com/TaroYoshino/items/98f5ab07975a0fb59ebc)
業務をちょっとだけ便利にさせてくれる拡張機能の紹介

# どんな記事?
- タイトルの通り、業務を少しだけ便利にしてくれるChromeの拡張機能を紹介！    
    - 人によっては、全然役に立たないかも……

## こんな方におすすめ
- リモートワークでMeetやSlackのハドルなど、複数の部屋に同時に参加する必要がある方 


## 「音量をそれぞれ調整できないかなー」と思った事はありませんか??
自分ごとではありますが、Slackのハドルを繋げつつMeetやZoomを行うことがあります。
そんな時に頼れる機能！
その名も **「Volume Master」** という拡張機能です。
各タブの音量を個別にコントロールすることが可能になります。

### Volume Masterについて
- タブごとに音量を調整できる
- 無料
- インストールは一瞬

### Volume Masterの始め方と使い方
#### 始め方
1. [ここ](https://chromewebstore.google.com/detail/volume-master-%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9/jghecgabfgfdldnmbfkhmffcabddioke)をクリック
1. 「chromeに追加」をクリック
![Volume-Master-ボリュームコントローラー-Chrome-ウェブストア-12-21-2024_12_21_AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1578844/a64fdd74-0af7-2ab0-0b5d-92ac8b6c5c28.png)
1. おしまい
#### 使い方
1. Volume Masterのアイコンをクリックすると調整画面が出てくる
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1578844/e0461164-f14d-2efe-d63d-3805909e2de7.png)
1. 青いバーのところで音量を良い感じに調整する
1. おしまい

##### 補足
- 音の効果を変える機能もあります。興味がある方は、お試しください。
    - Voice Boost：人の声
    - Bass Boost：低音
ディレクトリトラバーサルの脆弱性をあえて作り込んでみる

## これは何?

Nginxを使ってディレクトリトラバーサルを試せる環境を作成しました。
[GitHub Repository](https://github.com/RyosukeDTomita/security-assessment-handson/tree/main/directory_traversal)

---

## Nginx Alias Traversalとは

- [Gigazine](https://gigazine.net/news/20230708-nginx-alias-traversal/): 日本語記事
- [Hunting for Nginx Alias Traversals in the woid](https://labs.hakaioffsec.com/nginx-alias-traversal/)

Nginxは設定ミスによりディレクトリトラバーサルの脆弱性が発生します。

> In the pattern where this location is used in combination with alias, a critical vulnerability arises when two conditions are met:
> 1. no trailing slash at the end of the URL specified in the location
> 2. a trailing slash at the end of the path specified in the alias

```
# correct nginx.conf settings
location /good/ {
  alias /var/img/;
}
```

```
# vulnerble nginx.conf settings

location /bad {
  alias /var/img/;
}
```
locationで指定するパスが`/`で終わっておらず，aliasで指定するパスが/で終わる条件が満たされるとディレクトリトラバーサルが可能になります。

---

## デモを動かす

1. [GitHub Repository](https://github.com/RyosukeDTomita/security-assessment-handson)をクローンします。
2. Dockerコンテナを起動します。
    ```shell
    cd directory_traversal/
    docker compose up
    ```
3. [http://localhost:82](http://localhost:82)にアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/884ceb04-512c-a99a-7cbe-a951fc8eeb19.png)

4. 上のリンクをクリックする。これは正しく設定されており，宇宙猫が表示されます。
![good-settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/034af901-5cc3-d26f-4fdd-2603cc97a831.png)
5. 下のリンクをクリックすると本来ユーザからはアクセスできないはずのaccess.logのサンプルにアクセスでき，ダウンロードされます。

![Screenshot from 2024-12-21 01-01-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/fe98724f-294a-4a6a-9e9a-c33fd9924b33.png)
SplunkのSPLからcurlを実行する

## はじめに

さくっとWebページの情報取ってきたりREST API叩きたい・・・！
Splunkを使っていたら一度は思ったことがあるはずです。

そんなあなたはラッキーです。
2024年12月現在、神ツールがあります。

Webtools Add-on
https://splunkbase.splunk.com/app/4146

なんと、SPL内でcurlを叩けます。
なんと、Splunk Cloudにも対応しています。

早速やってみましょう。

## インストール

普通にSHにインストールしてください。
特に設定も不要です。

## curlを叩いてみる

### ターゲット

例えばAttlassianが提供するStatuspageは各社で使われています。
Splunk Observability Cloudのステータスもこちらで公開しています。

https://status.jp0.signalfx.com/

Statuspageでは`/api/v2/status.json`を付けることでJSONで結果を返してくれます。

https://status.jp0.signalfx.com/api/v2/status.json

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/e4fbe117-27bd-1adf-bf70-50f64ad13353.png)

これを叩いてみましょう。

### curl実行

これだけ。

```
| makeresults 
| curl method=get uri="https://status.jp0.signalfx.com/api/v2/status.json"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/8c763e75-b47c-f090-340a-1eba3a1d3c89.png)

あとはフィールド抽出するなりダッシュボード化するなりアラート定義するなり。

フィールドの値を読み取ることもできます。

```
| makeresults 
| eval urls=split("https://status.jp0.signalfx.com/api/v2/status.json,https://status.zoom.us/api/v2/status.json",",")
| stats count by urls
| curl method=get urifield=urls
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/7316e7b7-6eb1-98ac-8027-c8698d868520.png)


もちろん通常のWebページにも実行でき、その場合はHTMLが返ります。
頑張ってこれを処理するのでも。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/869924ba-868d-c750-bb8c-71bdb6acdee2.png)

### オプション

このコマンドの使用方法はSplunkbaseのDetailsに細かく書いてあります。
例えば以下のようなことができます。

- POST
- BASIC認証
- カスタムヘッダー

注意点として、このcurlはhttpsに対してのみ実行できます。
httpに実行しようとするとエラーが発生します。
（Splunk Cloud対応する際のセキュリティ制約のため）

### 値の保存

結果はメモリ上にしか残っていないので、必要に応じてスケジュール化し`| outputlookup`や`| collect`で出力しましょう。


## おわりに

便利な時代になりましたね。
Happy Splunking!
MeePet のバッテリー残量を MQTT で送受信する

## 全画面表示でもバッテリー残量を確認したい
MeePet のような、バッテリーの入った機器で電源をつなぎっ放しにするのはバッテリーの負担になるので、定期的にある放電作業をしておきたい。
その際バッテリー残量を確認したいのだが、 MeePet はステータスバーが無く、また Slideshow で全画面表示をしている間はバッテリー残量の表示がほぼ不可能である。

Termux:API を使うとコマンドラインからバッテリー残量を確認でき、さらにその情報を MQTTで送受信すれば PC などからバッテリー残量が確認できて便利と考え、設定することにした。

## Termux:API のインストール
Termux から Android の情報を取得するには、 Termux:API を Android 側、 Termux 側の双方にインストールする必要がある。

Android 側は F-Droid から取得してイントール。
Termux 側は `pkg install termux-api` でインストールできる。

必要に応じて Android 側で権限の設定をしておく（今回はバッテリー残量の確認だけなので、Android 側の設定で許可すべき項目は無かった）。

API の一覧・コマンドラインからの実行は以下の記事を参考にした。

https://matoken.org/blog/2018/06/21/try-termux-api-which-can-manipulate-the-android-terminal-with-shell/

## cron のインストールと設定
コマンドラインでバッテリー残量の確認を定期的に実行させるため、 cron を用いる。
Termux には cron がインストールされていないため、 cron のインストールと有効化、 crontab の編集が必要になる。

https://www.lisz-works.com/entry/termux-cron

```bash: crontab
*/10 * * * * termux-battery-status | mosquitto_pub -t /battery --stdin-file
```
`*/10 * * * *` で10分毎実行するよう設定した。

MQTT Broker には mosquitto ではなく Aedes MQTT broker を用いたが、自機に立てている場合は特にサーバー名の指定をしなくてもよい。

また、 mosquitto_pub は `--stdin-file` で標準入力をメッセージに使えるため、パイプで処理させることができた。

https://mosquitto.org/man/mosquitto_pub-1.html

MQTT Explorer で受信確認。
![mqtt_battery1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/3d0caac7-9e34-93cc-72b1-d9282d219b40.png)

## そもそも cron を使う必要はないのでは？
以上が正攻法の解決策ではあるが、この MeePet は Apache Web サーバーが起動しており PHP が動く環境を有している。
実は cron を使わずとも、 PHP と Node-Red の合わせ技で全く同じことが実現できるのでそちらも解説したい。

PHP から Linux シェルのコマンドを動かすにはバッククォートを使えばよい。

```php: battery.php
<?php
echo `termux-battery-status`
?>
```

この PHP ファイルを Web サーバー上に置き、 Node-Red 側でこの URL を指定、繰り返し間隔と mqtt out 部だけ繋いでやればよい。

![mqtt_battery2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/e8591ceb-6c35-1403-c313-4ee6b941e5e7.png)
![mqtt_battery3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/65961dce-97bf-eb01-297d-582e5bcc5d83.png)
![mqtt_battery4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/4b4a362b-8bc9-178e-6e53-8cbb202ebe67.png)
![mqtt_battery5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/b94341da-9268-872b-ffeb-781a55325919.png)

うまくいった（そもそもこの方法なら MQTT を配信するまでもなく、ブラウザから直接 PHP ファイルを見に行けばよかったかもしれない）。
![mqtt_battery6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/df89c792-b644-1c7d-bcce-a55215d7b882.png)
Ubuntuで接続しているBluetoothデバイスの名前を変更する(エイリアスを張る)

参考: https://superuser.com/questions/778660/changing-bluetooth-device-name
ほぼ↑の記事の内容そのまんまです。

ROSでロボット開発をしているような場合、Bluetooth接続の無線コントローラを使うことがあるかと思いますが、特に複数のコントローラをシステムに登録するとどれがどれかわからなくなりますよね。
特に同じ型番のコントローラを複数繋いだことがあり、うまく接続できないから一度デバイスを削除して再度ペアリングしようにもどれがどれか分からない！みたいなことがあります(少なくとも自分は経験しています)。

それでなくとも「Wireless Controller」としか表示されなかったりして不便ではありませんか?

それ、変更できます。ニックネームをつけましょう。

以下、Ubuntu22.04で確認しました。

## 準備

`bluez-tools`が必要なので入れます。

```bash
sudo apt install -y bluez-tools
```

## デバイスのMACアドレスの特定

```bash
bt-device -l
```

めちゃくちゃたくさんでてくると思うので`bt-device -l | grep -i controller`みたいな感じで特定してください。
で、MACアドレス(`ab:cd:ef:01:23:45`みたいなの)をコピーしておきます。

## エイリアスの指定

```bash
bt-device --set macaddress|name Alias "New Name"
```

みたいな感じで変更できるので、以下のようにします。
以下の場合は`ab:cd:ef:01:23:45`のMACアドレスを持つデバイスに`DualSense (Red)`という名前を付けています。
MACアドレス部分と名称部分を自前の環境に合わせて適当に変更してください。

```bash
sudo bt-device --set ab:cd:ef:01:23:45 Alias "DualSense (Red)"
```

(`sudo`いらないかも)

この設定を行うと、システム設定の画面でもここで設定した名前で表示されるようになります。
(再起動後も有効かを確認し忘れました)

ただ、`jstest-gtk`では最初にシステムで認識された名称が出てくるようです(エイリアスが効かない)。
画像認識とスプレッドシートでカレンダー登録を効率化

これは **"satto | [生成AIエージェント Advent Calendar 2024](https://qiita.com/advent-calendar/2024/satto)"** の21日目の記事です。

こんにちは。「学びの探究者」です。普段はnoteで活動しています。

https://note.com/lovely_hawk768

普段は、エンジニアではありません。
そんな私でも「satto」を使えば自分の好きなスキルができちゃうんだーということでサッとご紹介できればと思います。

この記事では、生成AIエージェント「satto」を使って、日常で役立つ簡単なスキルを作る方法を紹介します。生成AIを使ったアプリケーション開発に興味がある方や、非エンジニアでも手軽に生成AIを試してみたい方におすすめです。

---

# スキルの概要

## 導入

- **背景**:
    - 学校やイベントの予定が紙ベースで届くことが多い。
    - 手作業でカレンダー登録するのが大変なので、自動化を目指した。

## 技術手順

- **使用ツール**:
  - satto
  - Googleスプレッドシート

---

# Sattoの実装例

## フロー図

```mermaid
graph TD
   A[登録したい年度とカレンダー画像をsattoで登録] --> B[AIノード①: 画像を解析しテキスト化]
   B --> C[AIノード②: 解析した日付と行事を紐づける]
   C --> D[AIノード③: スプレッドシート連携用のbody文を生成]
   D --> E[アプリノード: Googleスプレッドシートにデータを書き込み]
```

## AIノード①のプロンプト
```text
# 命令
[対象画像]を見てその中にある文字情報を、そのままの構造を保持して文字起こししてください。

# 対象画像
{コンテキスト.画像}
```

## AIノード②のプロンプト
```text
あなたは、{インプット.年度} 年度の行事を整理するアシスタントです。

# システムプロンプト

1. {AIノード①.output}} からテキストデータを解析します。

2. テキスト内の日付を識別し、各行事名と紐づけて整理してください。

3.YYYYは以下のように定義します。

- 4月1日〜12月31日は、 {インプット.年度} 

- 1月1日〜3月31日は、 {インプット.年度}  +1

3. データは以下の形式で出力します。
{
"date": "YYYY-MM-DD",
"event": "行事名"
}

4. 上記のプロセスをステップバイステップで実行します。
- テキストデータを行単位で分割します。
- 各行に含まれる日付を解析します。
- 各行の行事名を識別し、該当する日付と共に整理します。

# 補足
- 指示の復唱はしないでください。
- 自己評価はしないでください。
- 結論やまとめは書かないでください。
- 最終成果物以外は出力しないでください。
- 出力結果をダブルクォーテーション("")で囲わないでください。
```

## AIノード③のプロンプト
```text
あなたはAIアシスタントです。ユーザーが提供するデータをもとに、GoogleスプレッドシートにPOSTするためのbody文を生成してください。以下のステップに従って作業を進めてください：

1. {AIノード②.output} からデータを受け取ります。
2. データを適切な形式に変換します。
3. GoogleスプレッドシートにPOSTするためのbody文を生成します。このbody文はJSON形式で出力してださい。

例: 
{ "values": [ ["date", "event"] ] }

補足：
- 指示の復唱はしないでください。
- 自己評価はしないでください。
- 結論やまとめは書かないでください。
- 最終成果物以外は出力しないでください。
- 出力結果をダブルクォーテーション("")で囲わないでください。
- ユーザーから提供されるデータは{コンテキスト.テキスト}と仮置きして使用します。
```

## アプリノードの設定
テンプレートがあるので簡単に説明します

1.アプリ「Googleスプレッドシート」を選択
2.テンプレートから「シートの最後の行に値を追加」を選択
3.SPREADSHEET_IDの項目に登録したいスプレッドシートのIDを入力
4.Bodyの項目に、AIノード③の実行結果を挿入

# 最後に

Googleカレンダーまで自動化を目指しましたが、以下の理由で止めました。

- **紙からの画像認識で誤認識があった場合**、修正はスプレッドシート上で行う方が早い。
- **GoogleカレンダーAPI連携**はGASでできなくはないけど、非エンジニアをテーマにしていると少しやり過ぎたかも。

ですが、「Zapier」や「Make」などのツールを使えば簡単に連携可能だと思うので、トライしたい方はぜひ挑戦してみてください。

この記事が役に立った場合は、ぜひコメントやいいねをお願いします！

noteでは、スキル作成の背景や学び、さらに応用アイデアについて詳しく解説しています！
リンクはこちら👉[お子さんの学校行事どうやって管理してますか？sattoでサッとGoogleカレンダーに入れよう！](https://note.com/lovely_hawk768/n/ne0d2a3499ddd)
SQL Injectionの脆弱性をあえて作り込んでみた

## これは何?

SQL Injectionについて知るため，サンプルリポジトリを作成しました。

[GitHub Repository](https://github.com/RyosukeDTomita/security-assessment-handson/tree/main/sqli)
この記事ではSQL Injectionについて解説します。

---

## SQL Injectionとは

わかりやすい説明が書いてあるサイトを貼っておきます。

- [UB Secureのサイト](https://www.ubsecure.jp/blog/sql-injection)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/8bf6b283-c22b-e19c-7db9-174f1d8c8ccc.png)

- [IPA安全なWebアプリケーションの作り方](https://www.ipa.go.jp/security/vuln/websecurity/sql.html)

> データベースと連携したウェブアプリケーションの多くは、利用者からの入力情報を基にSQL文（データベースへの命令文）を組み立てています。ここで、SQL文の組み立て方法に問題がある場合、攻撃によってデータベースの不正利用をまねく可能性があります。このような問題を「SQLインジェクションの脆弱性」と呼び、問題を悪用した攻撃を、「SQLインジェクション攻撃」と呼びます。 

### 仕組み

例えばこのようなコードがあるとします。

```python3
query = f"SELECT * FROM users WHERE user_id = '{user_id}' AND password = '{password}'"
```

この際にuser_idに`john`，passwordに`' OR '1'='1`を入れるとSQL文は以下のようになります。

```sql
SELECT * FROM users WHERE user_id = 'john' AND password = '' OR '1'='1'
```

SQLの場合`AND`が`OR`より優先されます。そのため，SQL文は次のように解釈されます。

```sql
SELECT * FROM users WHERE (user_id = 'john' AND password = '') OR '1'='1'
```

つまり，ORにより`(user_id = 'john' AND password = '')` もしくは`'1'='1'のどちらかがTRUEになればよいです。'1'='1'は常に成り立つのですべてのデータがSELECTされます。

---

## POCを作ってみる

### 構成

- MySQLにユーザデータを入れておく
- FlaskでMySQLに接続し，ログイン処理を行う

---

### SQL Injectionを使う

[GitHub Repository](https://github.com/RyosukeDTomita/security-assessment-handson/tree/main/sqli)からソースをダウンロードしてDockerコンテナを起動します。

```shell
cd sqli
docker compose up -d
```

- [localhost:8000](http://localhost:8000)にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/097600c3-6707-a2b6-a5e1-d833e1954727.png)

- john/johnpasswordが正しいクレデンシャルなのですが，john/`' OR '1'='`1を入力します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/d1d62038-63d2-2d34-5606-87c38721a67a.png)

- SQLの条件がTrueになり，全ユーザのデータが出力されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3718390/b6a14047-65c5-364c-1c74-703c5200d151.png)
21日目: 64AryTreeを実装する

[競プロ用ライブラリを作る Advent Calendar 2024](https://qiita.com/advent-calendar/2024/yukkku-ribitol)の21日目です.

https://qiita.com/advent-calendar/2024/yukkku-ribitol

# 64AryTreeって?

$0$以上$N$未満の整数が入る集合として

- 指定した要素の追加
- 指定した要素の削除
- 指定した要素を含むか判定
- 指定された区間と集合が共通部分を持つか判定する
- 集合の最小値を探す

などをとても定数倍が軽い$O(\log N)$で行えるデータ構造です.

# 仕組み

「64」はワードサイズです. JavaScriptみたいな「32bit整数の方が扱いやすい」みたいな環境だったりした場合は臨機応変に変えましょう. 以下はワードサイズが64bitだった場合の説明をします.

SegmentTreeをよく知っている方へ: boolの最大値を求めるSegmentTreeで, 64分木にしてbitsetで持つと演算が少ない命令でできてお得, という話です

64AryTreeは集合をいくつかのbitsetで表現します. 各bitsetは64bit整数の配列を内部で持っています.
```rs
// self.0[i] は64*i以上64*(i+1)未満の整数の情報を持ちます
struct SampleBitSet(Vec<u64>);
impl SampleBitSet {
    fn add(&mut self, index: usize) {
        self.0[index >> 6] |= 1 << (index & 63);
    }
    fn delete(&mut self, index: usize) {
        self.0[index >> 6] &= !(1 << (index & 63));
    }
    fn has(&self, index: usize) -> bool {
        (self.0[index >> 6] >> (index & 63)) & 1 == 1
    }
}
```

64AryTreeが持つ1つのbitsetはそのまま集合を表現します.

2つ目のbitsetは「1つ目のbitsetの各64bit整数の情報」を持ちます.
どういう情報を載せるかによって処理できるクエリを変えられますが, とりあえずここでは「各64bit整数が0かどうか」を載せることにします. 要素を含んでいたら対応するビットが1になるのでこれは「64bit整数が管理する値に集合に含まれるものが存在するか」を見ていることになります.

3つ目のbitsetは「2つ目のbitsetの各64bit整数の情報」を持ちます. 2つ目のときと同様です.
4つ目のbitsetは「3つ目のbitsetの各64bit整数の情報」を持ちます.
... こんな感じで持っていきます. 1つ次のbitsetの大きさはその前のbitsetの64分の1になるので, ワードサイズに収まるくらいの大きさになるところで止めます.あ
```rs
struct Sample64AryTree(Vec<SampleBitSet>);
impl Sample64AryTree {
    fn add(&mut self, mut index: usize) {
        for bitset in self.0.iter_mut() {
            bitset.add(index);
            // bitsetで操作したのは index>>6 個めの整数です
            // 次のbitsetにも「値が追加された」という情報を入れに行きます
            index >>= 6;
        }
    }
    fn delete(&mut self, mut index: usize) {
        for bitset in self.0.iter_mut() {
            bitset.add(index);
            // 操作した整数が0になりきっていない可能性があるので,
            // そういうときはbreakしてやります.
            if bitset.0[index >> 6] != 0 {
                break;
            }
            index >>= 6;
        }
    }
    fn has(&self, index: usize) -> bool {
        // 一番最初のbitsetは普通に情報を持っているので, それを見ます
        self.0[0].has(index)
    }
}
```
最小値を探す処理を実装しましょう.
最後の方(=要素数の少ない方)から順にbitsetを見ていきます. 最後のbitsetはワードサイズに収まるくらい小さく, 64bit整数1つだけで表現されています.
この整数の「1になってる一番下のビットの位置」を調べると, それは「1つ前のbitsetで最小の要素を持つ64bit整数の位置」になります. Rustでは`u64::trailing_zeros`が使えます.
その「1つ前のbitsetの最小の要素を持つ64bit整数」で同じことをしてその前のbitsetの位置を調べて... ということを続ければ, 最小値が分かります
```rs
impl Sample64AryTree {
    fn min(&self) {
        let mut index = 0;
        for bitset in self.0.iter().rev() {
            index = index * 64 + (bitset.0[index].trailing_zeros() as usize);
        }
    }
}
```
# 実装

- [`wodsizetree.rs`](https://github.com/Yukkku/ribitol/blob/main/src/wordsizetree.rs)

このデータ構造の利点は定数倍が軽いことなので, これが生かせるようにunsafeまみれのコードを書きました.

眺めてると区間更新だとかMEXだとか個数の偶奇だとかも処理できそうな気がしてきますが, それはまた今度

# まとめ

いかがでしたか?

明日は数列に何でも聞けるデータ構造を実装します.
Github Copilotを使ってみよう

つい先日、Github Copilotが（制限付きとはいえ）無料で利用できるようになったため、
利用者も増えるだろうと話題に相乗りするやつです。

（一応中の人に話を聞く機会があった人間だし）

## Copilotを使う前に

Copilotを使う上で「提示されたコードが適切か判断できる」ことは大前提です。
「よく分からないけど動くからヨシ！」とか絶対にやめましょう。

また、当たり前ですが学習目的だとほぼ無意味になります。勉強するときは頼らず地道にカタカタしましょう。

## Copilotによるコード補完

普通にプログラムを書いていてもどんどん提案してきてくれますが、
個人的に真骨頂だと思うのはコメントに対してのコードの提案です。

例えば「Redisサーバーに接続」というコメントを書いて提示されてるのがこれ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939482/05caaba1-3f75-211b-5607-3b30787a8b1e.png)

「やりたいこと」をキチンと言語化するのは特に複数人で仕事をする場合は超重要。
「URL指定で」と加えると `from_url()` を使った提案をしてきてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939482/c800e901-6444-bd65-d07f-e4665f44d572.png)


## Copilot Chatへの聞き方のコツ

チャットには「#」から始まるコンテキスト変数が用意されています。
対象箇所を選択して「#selection」や「#terminalselection」などを文言に加えることで、選択範囲を中心とした問い合わせが可能となります。
仕組み上は「ここ」とかでも指定できるそうなのですが、やはりコンテキスト変数を利用した方が精度は上がります。

具体的にはこんな感じ

```
#selection で以下のような例外が出ます。考えられる原因を教えてください。
Traceback (most recent call last):
  File "example.py", line 9, in <module>
    func()
  File "example.py", line 5, in func
    raise SampleException("An error occurred")
__main__.SampleException: An error occurred
Caught an exception: An error occurred
```

一通り書いたあとでこんな感じにレビュー依頼するのもアリ

```
#file 本プログラムをレビュー、リファクタリングの余地などあれば提案してください。
```

また、日々改善してるらしく最近は出なくなりましたが「プログラムに関する質問のみを受け付けます」といった返答が返ってくることがあります。
こちらもコンテキスト変数を使用して範囲を指定することで、求める回答を得られることがあります。

## Copilotを使うメリット

- ロジックの言語化能力UP
    - 一見楽をしているようで「言語化して説明できている」ということでもあります
    - 複数人でシステムを開発するのには重要な能力です
- リファクタリングが楽になる
- 趣味プログラムのレビュー依頼先にできる
    - 特に趣味だと自己レビューになって観点が漏れがち

## Copilotを使うデメリット

- 単純なコーディングがつらくなる
    - 頭の中では完成してるのに、タイプ速度が追いつかずひたすらカタカタ
- リファクタリングやレビュー能力DOWN
    - メリットの裏返し
    - Copilotにレビュー依頼するだけでそれっぽく指摘してくれてしまうので、「Copilotのレビュー結果をレビューする」ような形に
    - ~~ひとまず思いつくままに動くもの書いてCopilotにリファクタリングさせようぜ！~~
- 頼りすぎると麻痺してCopilotの使えない現場で苦労する

## 最後に

私は「エンジニアの仕事は仕事を奪う（＝楽にする）ことである」という持論を持っているのですが、ついにエンジニア自身の仕事がAIに奪われる日が見えてきました。
今のうちに「AI」という武器を振り回せるようになっておき、「AI」と共存していく世界へ順応できるようにしておきましょう。

10米ドル/月ですし、実際使ってみて制限に達するようならサブスク契約も吝かではないかな…
Gitブランチを複数選択して削除するCLIツールを作ってみた

本記事は [SmartHR Advent Calender 21日目](https://qiita.com/advent-calendar/2024/smarthr)の記事です。

## はじめに

普段 Git を利用して開発をしていますが、気づいたら手元に不要なブランチでいっぱい...ということがよくあります。一個ずつ `git branch -D hoge` というの手間。選択したブランチだけを一括で削除する便利コマンドがないものか...と感じていたときがありました。

約4年前の2020年に peco を利用して、複数ブランチの一括削除スクリプトを用意しそれを利用していました。

https://qiita.com/hypermkt/items/ee8d516991ea0c00f347

今回はTypeScriptの勉強がてら同じような機能を再実装してみたいと思います。

## 成果物はこちら

TypeScript を利用してGitブランチの複数選択削除ツール git-branch-deleter-cli というCLIツールを作ってみました。

https://github.com/hypermkt/git-branch-deleter-cli

### 使い方

`git-branch-deleter` コマンドを実行するとローカルのブランチ一覧が表示されます。十字キーの上下で移動して、削除したいブランチをスペースキーで選択します。

```bash
$ git-branch-deleter
? Select branches to delete): (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
❯◯ bar
 ◯ foo
 ◯ fuga
 ◯ hoge
 ```

エンターキーを押すと削除確認が聞かれます。再度エンタキーを押すと...
 ```bash
✔ Select branches to delete): bar, foo, fuga, hoge
? Do you want to delete these branches?
bar
foo
fuga
hoge (Use arrow keys)
❯ Yes
  No
```

一括で削除されるぅ〜、気持ちいぃ〜！
```bash
Deleted branch: bar
Deleted branch: foo
Deleted branch: fuga
Deleted branch: hoge
```

とこれだけのツールなのですが個人的にはとても便利です。

## 利用したライブラリ

### Inquirer.js

https://github.com/SBoudrias/Inquirer.js

Inquirer.js は、Node.js アプリケーションで対話型のコマンドラインインターフェース（CLI）を作成するためのライブラリです。ユーザーからの入力や選択を簡単に扱うことができ、対話的な CLI を簡単に作ることができます。

今回の実装では削除したいブランチを複数選択するために Checkbox, 削除前のはい/いいえ確認用に Select を利用しました。

以下の記事にとても詳しくまとまっているのでご参考ください。

[対話型CLIライブラリのInquirer.jsを試してみた - コドモン Product Team Blog](https://tech.codmon.com/entry/2023/12/08/234628)

### Simple Git

https://github.com/steveukx/git-js

Simple Git は、Node.js アプリケーションで Git コマンドを簡単に実行できるようにするためのライブラリです。

今回の実装では手元のブランチ一覧の取得とブランチの削除時に利用しました。

## npm link で簡易的にCLI化

https://docs.npmjs.com/cli/v10/commands/npm-link

`npm link` は、ローカル開発中の npm パッケージのテストや別プロジェクトで簡単に利用できるようにする便利なコマンドです。リンクを設定するだけで、リアルタイムに変更を反映することができます。

今回はこれを利用してCLIとして利用することにしました。あくまで個人用のツールとして利用する分には十分です。

## まとめ

Inquirer.js を利用することで自分がイメージした通りにGitブランチ一覧の表示と複数選択を実現することができました。

ちょっとしたことですが、ブランチの削除確認を挟むことができたのも良かったです。

TypeScript を利用してCLIを実装するのは初めてでしたが、npm link を利用することで簡単に個人用のCLIツールを作ることができると知れたのは発見でした。

また何か良いアイデアが浮かんだら自分用のツールを作ってみたいと思います。
Tinkercadのコードブロックで誰でも簡単自動モデリング

# はじめに
みなさん、3DCADは何を使っていますか。
初心者の方におすすめなCADとしてTinkerCADというものがあるのですが、今回はTinkerCADでプログラミングによって3Dモデルを自動生成してみようと思います。

# 動機

最近はFusionを使っているのですが、3Dモデリングの始めたての時はTinkerCADでモデリングをしていました。
私は部活動でモデルロケットの3Dプリント部品の製作を担当しており、ノーズコーンをTinkerCADで設計して3Dプリンタで印刷するということを繰り返していました。
しかし直径や長さが変わるたびに3Dモデルを設計しなおすというのは面倒だったので、パラメータを入力したら自動で生成してくれるプログラムを作りたいなと思い、TinkerCADのコードブロック機能で作ることにしました。

# 使い方
TinkerCADのアカウントを作り、ログインしてください。
以下のように、``デザイン -> コードブロック``というところにコードブロックのプロジェクトが表示されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124670/0e0fbe31-23b7-122f-6cbf-3589ae774847.png)

ここで右上の``作成 -> コードブロック``を押して新規作成しましょう。

すると以下のような画面が開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124670/b138fdaf-ca14-198e-d46b-479b14400a8c.png)

Scratchのようなビジュアルプログラミング言語でプログラムを作ることができます。
それぞれのブロックの意味は大体わかると思うのでここでは割愛します。
変数も使用することができるので、以下のようなプログラムを組んで変数にパラメータを入れれば、パラメータに合わせて自動で3Dモデルを出力することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124670/42abc5f5-01ce-edef-4053-daf492c4e224.png)

作成した3DモデルはSTLやOBJなどの形式でエクスポートすることができます。

# 最後に
TinkerCADのコードブロック機能は直感的でわかりやすく3Dモデルを自動生成することができるので、初心者の方にとてもおすすめです。
しかしTinkerCADでできることも限られているので、より高度なことを求めるのであればFusionなどの本格的なCADを使用するといいと思います。
ちなみにFusionもPythonなどでプログラムを書いてモデリングすることができます。そのことに関する記事も書くかもしれません。
【Java】サーブレットについてざっくり

## サーブレットとは
サーブレットはWebコンテナ上で動作するJavaで記述されたプログラムのこと。HTMLタグを埋め込むことで画面を作成できる。

主なサーブレットAPIは以下。
### Servletインタフェース
サーブレットの基本となるインタフェースで、全てのサーブレットが実装すべきメソッドを定義している。

### GenericServletクラス
Servletインタフェースの実装クラスで、HTTP以外のプロトコルにも対応可能な汎用的な機能を提供する。

### HttpServletクラス
GenericServletを継承し、HTTPプロトコルに特化したサーブレットの基盤クラス。GETやPOSTといったHTTPメソッドに対応するメソッドを提供。


## サーブレットのライフサイクル
1. 初期化
インスタンス化時に一度実行

2. サービス処理
サーブレットに対するリクエストを処理

3. 要求待ち
1度実行したサーブレットはWebコンテナに常駐し、次のリクエストを待つ

4. 消滅処理
サーブレット停止前に実行される処理

## サーブレットのメソッド

### service(req,res)
req…リクエスト格納
res…レスポンス格納
サーブレットの処理を記述する。またリクエストによっては後述のdoGetやdoPostを呼び出す。

### init(config)
config…サーブレットに関する各種情報を持つオブジェクト
初期化処理を記述する。

### destroy()
サーブレットの後処理記述。

### getServletConfig()
サーブレットの開発者や著作権などの情報を文字列で返す。

### getServletConfig()
初期化パラメータを読みこんだり、サーブレットの実行環境にアクセスしてServletConfigオブジェクトを取得する。

### doGet(req,res)
GETオペレーションに対するサーブレット処理を記述。

### doPost(res,req)
POSTオペレーションに対するサーブレット処理を記述。

### req.setCharacterEncoding("UTF-8")
エンコーディングの設定。

### req.getParameter("name")
リクエストオブジェクトから名前で指定したパラメータの値を返す。

### req.getParameterValues("name")
リクエストオブジェクトから名前で指定した全てのパラメータの値を返す。

### req.getParameterNames()
リクエスト中の全てのパラメータ名を取得。

### res.setContentType("text/html,charset=UTF-8")

サーブレットのレスポンスに対して コンテンツタイプ（MIMEタイプ） と 文字エンコーディング を設定する。

### res.getWriter()
テキストデータをクライアントに出力するストリームを返す。

### pw.println("<html><head></head></html>")

テキストデータをクライアントに出力。printメソッドだと改行はなし。

## リクエストオブジェクトとは
ServletRequest を拡張し、HTTPプロトコルに特化した機能を提供するインターフェース。
HTTPリクエストに固有のメソッド（HTTPメソッド、ヘッダー情報、セッション情報など）を利用できる。

### setAttribute("fullName",fullName)
リクエストオブジェクトに値を設定。

### (String)req.getAttribute("fullName")
リクエストオブジェクトから値を取得。

### RequestDispatcher rd = req.getRequestDispatcher("/outhello")

引数のパスにフォワードする。


### req.forward(req,res)
リクエストを別のリソースに転送。

## セッション管理
:::note info
**セッション**
サーバ側でセッションIDを生成し、クライアントにセッションIDを渡す（クッキーやURLパラメータを利用）。
クライアントからのリクエスト時にセッションIDを使用して、適切なセッションデータを関連付ける。
:::
### req.getSession()
セッションオブジェクトを返す。引数がtrueだとセッションオブジェクトが無い場合、新規にHttpSessionオブジェクトを生成。falseの場合、セッションオブジェクトが無い場合nullを返す。

### session.setAttribute("name",name)
セッションオブジェクトに値を格納。

### (String)session.getAttribute("name")
セッションオブジェクトから値を取得。

### session.removeAttribute("access")
セッションオブジェクトから値削除。

### session.invalidate()
明示的にセッションオブジェクト破棄。
2024年の生成AIの出来事

OpenAIのDay12待ちですが、今年の生成AIの出来事をざっと年表的にまとめるとこんな感じ

```mermaid
gantt
    title Project Timeline
    dateFormat YYYY-MM-DD
    axisFormat %y%m  

    section OpenAI
        GPT Store: 2024-01-10, 1d  
        GPTメモリ機能: 2024-02-13, 1d
        Sora告知: 2024-02-15, 1d
        GPT-4o: 2024-05-13, 1d
        ChatGPTデータ分析の改善: 2024-05-16, 1d
        GPT-4o mini: 2024-07-18, 1d
        DALL-E3対応: 2024-08-08, 1d
        ChatGPT音声機能: 2024-11-19, 1d
        12Days: 2024-12-06, 16d
        Sora: 2024-12-09, 1d
        ChatGPT Canvas: 2024-12-10, 1d
        ChatGPT VoiceChat: 2024-12-12, 1d
        ChatGPT Project: 2024-12-13, 1d
        o3告知: 2024-12-21, 1d
    section Google/DeepMind
        BardがGeminiに: 2024-02-08, 1d
        Python実行: 2024.02.20, 1d
        Geminiアプリ: 2024.04.30, 1d
        Gemini 1.5 Pro: 2024.05.14, 1d
        Geminiドキュメントアップロード: 2024.05.14, 1d
        Geminiデータ分析: 2024.05.21, 1d
        Gemini Google Workspace対応: 2024.07.15, 1d
        Gemini 1.5 Flash: 2024.07.25, 1d
        GeminiカスタマイズGem: 2024.08.28, 1d
        Imagen3: 2024.10.09, 1d
        Gemini 2.0 Flash Experimental: 2024-12-12, 1d
        Veo2: 2024-12-16, 1d
        Gemini 2.0 Flash Thinking: 2024-12-19, 1d
    section Anthropic
        Claude 3: 2024-03-04, 1d
        Claude 3.5 Sonnet: 2024-06-20, 1d
    section Mystral
        open-mixtral-8x22b-2404: 2024-04-17, 1d
        codestral-2405: 2024-05-29, 1d
        Codestral Mamba, Mathstral: 2024-07-16, 1d
        mistral-large-2407: 2024-07-24, 1d
        mistral-large-2411: 2024-11-18, 1d
    section Microsoft
        Phi3: 2024-04-23, 1d
        Phi4: 2024-12-13, 1d
    section X
        Grok-1 open source release: 2024-03-17, 1d
        Grok-1.5: 2024-03-28, 1d
        Grok-2: 2024-08-13, 1d
        Grok画像生成: 2024-12-09, 1d
```

ソース

## OpenAI

https://help.openai.com/en/articles/6825453-chatgpt-release-notes


# Goole

https://gemini.google.com/updates?hl=ja

# Microsoft

- Copilotの改善はありますが、大きなものとは認識できずPhiのみ取り上げました

# Claude(Anthropic)

https://docs.anthropic.com/en/release-notes/claude-apps

https://ja.wikipedia.org/wiki/Claude

# Mystral

https://docs.mistral.ai/getting-started/changelog/

# Grok

https://x.ai/about

# Perplexity
- 目立つものが分からず
形式的安全性検証ツールProVerifのインストール(Linux)


# はじめに
ProVerifとは、形式モデルでの暗号プロトコルの安全性を自動検証するツールです。
暗号プロトコルを形式モデル化したコードをツールに流すと、秘匿や認証などの安全性要件を自動で検証してくれます。
ProVerifは、Windows、Mac、Linuxなどでサポートしており、公式サイトからソース・バイナリを直接持ってくる方法とOPAM経由でインストールする方法があります。
今回はOPAM経由でインストールしました。
# 前提環境
* WSL上のUbuntu24.04 LTS
* VirtualBox上のAlmaLinux 9
# インストールの流れ
Linux（Debian）でProVerifをOPAM経由でインストールする方法について説明します。
ProVerifのマニュアルには、OPAMインストール済み前提でProVerifのインストール方法が書かれているため、今回はOPAMのインストールも合わせて紹介します。

# Ubuntuのアップデート
まず、パッケージ全体を更新しておきましょう。
```
$ sudo apt update 
$ sudo apt upgrade
```
# OPAMのインストール
次に、OPAMをインストールします。
```
$ sudo apt install opam
```
OPAMの初期化とアップデートをします。
```
# 処理中に出てくる選択肢は全てyes
$ opam init
$ opam update
```
# ProVerifのインストール
ここからは、ProVerifの公式マニュアルに記載のやり方と同じです。(出てくる選択肢は全てyes）
```
$ opam depext conf-graphviz
$ opam depext proverif
$ opam install proverif
```
:::note warn
```opam install proverif``` の実行時に「gcc 11がない」と怒られる場合があります。その場合は下記のコマンドを実行しgcc 11をインストールしてから再度実行してください。
```
# Debian系の場合
$ sudo apt install gcc-11

# RHEL 8系の場合
$ sudo dnf install gcc-toolset-11
$ sudo scl enable gcc-toolset-11 bash
```
:::

:::note warn
 RHEL 9系の場合は、公式サイトからgcc 11のソースを直接インストールしてmakeします。
```
$ wget https://ftp.gnu.org/gnu/gcc/gcc-11.4.0/gcc-11.4.0.tar.gz
$ tar -xvzf gcc-11.4.0.tar.gz
$ cd gcc-11.4.0
$ ./contrib/download_prerequisites
$ mkdir build
$ cd build
$ ../configure --disable-multilib --enable-languages=c,c++
$ make -j$(nproc)
$ sudo make install
```
:::

最後に、シェルにインストールしたパッケージを適用するために、下のコマンドを実行します。
```
eval $(opam env)
```
これで、```proverif```とコマンドを打ったときに、エラーが出なければ、インストール成功です。
# おまけ
### OPAMをHomebrew経由でインストールする方法
RHEL 9といった一部環境では、ディストリビューション標準のパッケージ管理システムにOPAMが入っていない場合があります。そのような場合の対処方法として、Homebrew経由でOPAMをインストールする方法があるので、紹介します。
ちなみに、Mac OSでProVerifのインストールをする際もHomebrewとOPAMを通してインストール可能です。
### Homebrewのインストール
パッケージ管理システムHomebrewをインストールします。まずは、Homebrewのホームページに書かれているインストールコマンドを実行します。
```
#インストール中に何度かyesかnoかを聞かれるが、全てyes
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
インストールが成功すると、次のステップとして、PATH設定とパッケージのインストールをしろという表示が出てくるので、それに従います。下に書かれているコードはその際に出てくる内容と同じです。
```
# PATH設定
$ echo >> /home/[ユーザー名]/.bashrc
$ echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/[ユーザー名]/.bashrc
$ eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
```
### brewの実行に必要なパッケージの追加インストール
```
# Debian系の場合
$ sudo apt-get install build-essential
# RHEL系の場合（8以降）
$ sudo dnf group install "Development Tools"
$ brew install gcc
```

### OPAMのインストール
ProVerifの実行に必要なパッケージをbrewからインストールします。
```
$ brew install ocaml opam graphviz gtk+
```
ここまで成功したら、あとは上述のProVerifのインストール作業と同じです。

:::note warn
私のUbuntuでは、opamの実行に必要なbubblewrapがインストールされておらず、エラーを吐きましたので、下のパッケージを追加でインストールしました。
```
$ sudo apt install bubblewrap
```
:::

# 参考文献

https://bblanche.gitlabpages.inria.fr/proverif/manual.pdf

https://tex2e.github.io/blog/crypto/install-proverif-on-macos
PlatformIOのプロジェクト名を変更したい


PlatformIOでマイコン開発のプロジェクトを立ち上げたが、適当なプロジェクト名で作ってしまった！変更したい！
でもどうすればいいんだ？フォルダ名を変えるだけでよいのか？
という方へ。
# 解決策

フォルダ名以外にプロジェクト名を使用しているものはないので、普通にフォルダ名を変更すればよいです。

あとはそのフォルダをVSCodeで開けば開発を続けられます。
以上。
コードレビューで怒られてるなと感じた時は、困惑と納得をオーバーに表現すると話が丸く収まることがある

# はじめに
コードレビューの場で、レビューアが明らかに怒っていたことはありませんか？
「こんなコード書いたの誰？」と言わんばかりのトーンで指摘されると、正直どう反応すれば良いのか困りますし、人格を否定されたのではと傷ついてしまうこともありますよね。

そんなとき、 __ちょっとオーバー気味に困惑や納得の感情を表現__ することで、話がスムーズに進むことがあります。この記事では、その効果と実践方法などをお話しします。

# レビュアーが怒っている理由
レビュアーが怒っている理由は例えば以下の理由が挙げられます。
- 間違いが繰り返されている
- コードの意図が伝わらない
- ミスの影響範囲が広すぎる
- レビュー対象者の態度が悪い
- 締切りが近い
- 腹の虫のいどころが悪い

重要なのはあくまでもレビュアーから見た現実が怒りの原因なのであって、事実とは異なった解釈をしてしまって感情が昂っている場合もあります。

# 少しオーバーなリアクションの効果

## 受け入れる姿勢を見せられる
「えっ！？こんなミスをしていたなんて、気づきませんでした...！」
レビューアが怒っているときは、自分の指摘が無視されるのではないかという不安も抱えています。このように反応をオーバーにすることで、「あなたの指摘を重要視しています」と示せます。

## 空気を和らげる
「あぁ、なるほど...確かに自分でも『変だな』と思いながら書きました笑」
少し自分を責めつつも、軽い笑いや感情を交えることで場の緊張を和らげるかもしれません。(キャラクターによっては煽りと取られることも多いので気をつけてください。)

## 納得を強調して議論を早く終わらせられる
「確かにその通りですね！修正案をすぐ作ります！」
納得した姿勢をオーバーに示すことで、レビューアが「自分の意見が受け入れられた」と感じ、怒りが収まることも多いです。

# 実践方法：オーバーリアクションのポイント
オーバーリアクションのポイントとしては、以下の4点が挙げられます。
1. 表情や声を使う（特に対面やビデオ会議の場合）
- 例えば少し眉を上げて驚きを表す
- 声のトーンを上げて「えっ、本当ですか？」などと反応

2\. 言葉を工夫する
- 困惑を表現:「あれ、そうなってましたか...?!完全に気づいていませんでした!」
- 納得を表現:「なるほど、それなら確かにこの実装は問題がありますね。」

3\. 自分の非を素直に認める
- 「これは完全に私の見落としです。確認不足でした！」

4\. たまに笑いや軽いユーモアを挟む
- 「今見ると自分でも『なんだこのコード』って思います」

# 注意点：やりすぎに注意！
オーバーリアクションは有効ですが、やりすぎると逆効果になることもあります。(皮肉に聞こえることも)
冷静に、かつ誠実なトーンを保つことが大切です!
相手を馬鹿にしている印象を与えないように、本気で反省していると伝わるよう意識しましょう。

# 怒られないように
そもそもなるべく怒られないように、コードレビューの際には以下の点に注意しましょう。
- 基本的なミスを繰り返していないか、レビューに出す前に確認する
- コードの意図を明確にし、レビューアが理解しやすい状態で提出する
- レビュアーへの感謝と誠意を忘れない

# おわりに
レビュアーもプロジェクトのことを考えて怒ってくれているので、怖いと思ってもなるべく相手の感情を逆撫でしないようにしながらオーバーリアクションでレビュアーを現実に戻してあげ、次に同じミスをしないよう気をつけましょう。
pyenv virtualenv [version] <virtual_env_name> でバージョンがないエラー

# 記事の趣旨

```shell
pyenv virtualenv [version] <virtual_env_name>
```
を実行した際、下記のエラーが出た時の対処法。

> pyenv: no installed versions match the prefix `-f'

※あくまで私が遭遇した時の対処です

# 事象
1. pyenvにvirtualenvを入れてなかったので導入、仮想環境を作ろうとした
1. `pyenv install 3.12.4`を実行
1. pyenvにvirtualenvをインストール
1. `pyenv virtualenv 3.12.4 testenv` を実行

上述のエラーが出ました。

# 対処
1. 一度3.12.4を `pyenv uninstall 3.12.4` で削除
1. 再度、`pyenv install 3.12.4`を実行
1. `pyenv virtualenv 3.12.4 testenv` を実行

成功！！

つまり、3.12.4の再インストールです（※初回のインストールも無事終了してます）。

# 原因
virtualenvを入れる前にインストールしたバージョンは、virtualenvで使えるバージョンとして認識されないようです。

ちなみにvirtualenv-prefixを実行するとこういう結果になります。

> pyenv virtualenv-prefix 
> pyenv-virtualenv: version `3.12.4' is not a virtualenv

何かvirtualenvインストール前にインストールしたバージョンを使えるようにする手段があるのかもしれません。
ご存知の方、いらっしゃればご教示ください。
Cursor + Composerで書いたコードがShadcnを使った場合、都度都度コンポーネントを追加するのが面倒なのでワンライナーで追加する

Cursor + Composerで書いたコードがShadcnを使った場合、都度都度コンポーネントを追加するのが面倒でした。
そのため必要なコンポーネントを追加するワンライナーを書きました。

## 問題

Shadcnでこんなエラーが出ます。

```javascript
Module not found: Can't resolve '@/components/ui/input'
```

![Screenshot 2024-12-20 at 22.55.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/ec63f64b-1f69-8455-32e3-c3f2036320fc.png)


```bash
grep -R -E "@/components/ui/[a-zA-Z]+" . | sed -E "s|.*@/components/ui/([a-zA-Z]+).*|\1|"  | xargs -n 100 -I {} /bin/bash -c 'npx shadcn add --yes {}'
```




## 解説

コマンド | 内容
---|---
`grep -R -E "@/components/ui/[a-zA-Z]+"` | 該当行のimport文を抜き出す
`sed -E "s|.*@/components/ui/([a-zA-Z]+).*|\1|" ` | 該当行からコンポーネント名を抜き出す
 xargs -n 100 -I {} /bin/bash -c 'npx shadcn add --yes {}' | shadcn add を行う

 
 ## 注意点

 - @/components/ui/ で始まっていなかったり改行が入っていたり、コンポーネント名がimport文と違うもの（があるのか不明ですが）場合には動きません
 - `shadcn add` で聞かれるダイアログはすべてエンターキーを押すようになっています


## 参考

### shadcn のオプション

```
Options:
  -y, --yes          skip confirmation prompt. (default: false)
  -o, --overwrite    overwrite existing files. (default: false)
  -c, --cwd <cwd>    the working directory. defaults to the current directory.
  -a, --all          add all available components. (default: false)
  -p, --path <path>  the path to add the component to.
  -h, --help         display help for command
```

https://ui.shadcn.com/docs/cli 


Xargsで引数の渡し方

https://stackoverflow.com/questions/34669239/how-to-pass-command-with-parameters-to-xargs
Sequence Makerで複数のコマンドを改行区切りで書けるという裏技

## はじめに
Sequence MakerはExcelから測定器を制御可能にするアドインです。Sequence Makerは測定器の測定条件を通信コマンドで変えながらデータを取るのに向いています。簡単な自動制御システムならすぐに作れます。今回は複数の条件を設定してトリガーをかけて測定値を取得する際のコマンドの書き方を紹介します。なお、Sequence Makerについての詳細は以下の記事を参照ください。

https://qiita.com/sequencemaker/items/8e6a370ab0a793a06a3b

:::note warn
***お断り***
本記事の内容はSequence Maker開発元の公式見解ではなく、あくまでも個人が趣味の範囲内で執筆しているものです。記事の内容について開発元へ問い合わせることはお控えください。ご質問・要望などは各記事のコメント欄へお願いいたします。
:::

## 簡単な書き方
例えば、周波数とレンジを設定して、トリガーを掛けて測定値を取得するようなケースでは、以下のように書くのが一番簡単です。周波数を1kHz、10kHz、100kHz、1MHz、10MHzと変えながら測定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821782/702ef489-e43b-c9a2-35d9-54b4ed76759d.png)

これでもいいと言えばいいんですが、D列に出力されてる測定値が飛び飛びになってしまい、後でグラフを書いたり、何か統計処理をしたりするときに不便です。

## セミコロン区切りで書く
SCPIに準拠している測定器であれば、セミコロンでコマンドを区切って書くことができます。これならD列の測定値が飛び飛びになることもありません。ただし、コマンドが1行でずらずらと書かれてしまうので、ちょっと見にくい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821782/9d30f36f-12c9-3240-50c5-1a8faa31a24b.png)

## セル内で改行する
実はSequence Makerではセル内でコマンドを改行して書くことが可能です。Excelでセル内で改行するのは「ALT」キーを押しながら「ENTER」です。このように書くと、改行は内部的には「LF」で扱われているので、そのまま測定器に送られ、ちゃんと処理されます。まぁまぁ見やすい（？）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821782/6f8b4c19-ea76-d6ce-8554-dcb8b79e7710.png)

## 別解
ちなみに今回のケースで言うと、実は#OUTPUT関数で出力先を指定しておけば、結果も詰めて出力されるので、実はこの方法が一番良いかもしれません。以下の例では測定器からの応答をF4のセルから下方向に順に出力していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821782/3780287d-82d8-4c71-7ac5-1aa4302e3b16.png)

## まとめ
この記事では複数のコマンドを改行区切りで書けるという内容を紹介しましたが、ここまで書いておいて、実はあまり使いどころが無いのでは。。。と感じていたりしますが、せっかくなので公開しておきます。
【競技プログラミング】AtCoder Beginner Contest 229_D問題

# 問題
https://atcoder.jp/contests/abc229/tasks/abc229_d

## 要約
1. 変数
   - S: 'X' と '.' からなる文字列
   - K: 操作の最大回数

2. 操作
   - '.' を 'X' に置き換える
   - この操作は0回以上K回以下行うことができる

3. 目的
   操作後に、連続する 'X' の最大数を求める

4. 制約
   - 操作回数は0回以上K回以下

# 既存投稿一覧ページへのリンク
[一覧ページ](https://qiita.com/o_chan_t/items/fc759695acfaef8df2cc)

# アプローチ

尺取法を用いて最適な連続する'X'の長さを求める

# 解法手順
1. 入力文字列Sと最大操作回数Kを受け取る。

2. 文字列の長さnを取得する。

3. 二つのポインタiとjを用意し、iは現在のウィンドウの開始位置、jは終了位置を示す。

4. 変数cを用意し、現在のウィンドウ内で'.'から'X'に変換した数を記録する。

5. 変数ansを用意し、最大の連続する'X'の長さを記録する。

6. iを0からn-1まで移動させ、以下の操作を行う：
   a. jがn未満で、かつcに(S[j]が'.'である場合の1)を加えてもK以下である間、jを進める。
   b. jを進めるたびに、S[j]が'.'ならcをインクリメントする。
   c. 現在のウィンドウの長さ(j-i)とansを比較し、大きい方をansに格納する。
   d. iを進める前に、S[i]が'.'ならcをデクリメントする。

7. 最終的なansを出力する。



# ACコード
```python:ac.py
def io_func():
    # 入力文字列Sを受け取る
    S = input()
    # 最大操作回数Kを整数として受け取る
    K = int(input())
    return S, K

def solve(S, K):
    # 文字列の長さを取得
    n = len(S)
    # jは現在のウィンドウの終了位置
    j = 0
    # cは現在のウィンドウ内で'.'から'X'に変換した数
    c = 0
    # ansは最大の連続する'X'の長さ
    ans = 0
    
    # iを0からn-1まで移動させる
    for i in range(n):
        # jがn未満で、かつcに(S[j]が'.'である場合の1)を加えてもK以下である間、jを進める
        while j < n and c + (S[j] == '.') <= K:
            # S[j]が'.'ならcをインクリメント
            c += (S[j] == '.')
            j += 1
        # 現在のウィンドウの長さ(j-i)とansを比較し、大きい方をansに格納
        ans = max(ans, j - i)
        # iを進める前に、S[i]が'.'ならcをデクリメント
        c -= (S[i] == '.')
    
    # 最終的なansを返す
    return ans

def main():
    # 入力を受け取る
    S, K = io_func()
    # 解を求める
    result = solve(S, K)
    # 結果を出力
    print(result)

if __name__ == '__main__':
    main()

###
# S: 入力文字列
# K: 最大操作回数
# n: 文字列Sの長さ
# j: 現在のウィンドウの終了位置
# c: 現在のウィンドウ内で'.'から'X'に変換した数
# ans: 最大の連続する'X'の長さ
# i: 現在のウィンドウの開始位置

# 1. io_func関数:
#    - 標準入力から文字列Sと整数Kを読み取る
#    - SとKを返す

# 2. solve関数:
#    - 引数としてSとKを受け取る
#    - 尺取法を用いて最適な連続する'X'の長さを求める
#    - 最大の連続する'X'の長さを返す

# 3. main関数:
#    - io_func関数を呼び出して入力を受け取る
#    - solve関数を呼び出して解を求める
#    - 結果を出力する

# 4. プログラムのエントリーポイント:
#    - main関数を呼び出す
```

# 思考過程
連続する'X'の最大長を効率的に求める。
尺取法を使用することで、文字列を一度だけ走査しながら最適解を見つけることができる。
ウィンドウを拡大しながら'.'を'X'に変換し、Kを超えたら縮小するという方法で探索する。

# 計算量の概算

O(n)
Djangoでjavascriptを使う

前回の作業

https://qiita.com/ruruchan_nanodesu/items/2ea694c7f37623dd285a

### 1. javascriptファイルを格納するためのフォルダを作成する

操作をする前のフォルダ構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/a8e4905b-2808-e8f6-0647-5d7384e0f01d.png)

下のようにstaticフォルダを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/d48d0b47-2b4e-275a-7721-93e90f9319b9.png)

次に、staticフォルダの中にjsフォルダを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/a00b8c16-d42c-dc31-d17c-cd747448c286.png)

そして、jsフォルダの中に、jsファイルを作成します。
本記事ではyourapp.jsにしますが、任意の名前で作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/48dc60b4-eb5f-1880-3187-a8be8878b6e2.png)

### 2. 作成したjsファイルをプロジェクトと紐づける

プロジェクトフォルダのsettings.pyを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/79a72583-769e-331d-2002-112c0a434f44.png)

BASE_DIRの下に、STATIC_DIRを追加します。

```python:settings.py
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_DIR = Path.joinpath(BASE_DIR, 'static') #追加
```

さらに、下のほうにコーディングされているSTATIC_URLを編集し、STATICFILES_DIRSという設定を追加します。

```python:settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS =[
    STATIC_DIR,
]
```

### 3. 作成したjsファイルをhtmlファイルと紐づける

以前templatesフォルダで作成したindex.htmlを下のように書き換えます。
2行目の{% load static %}が、staticフォルダを読み込むという意味です。
7行目では、static/js/yourapp.js内の関数を使用できるように読み込むという意味です。

```html:index.html
<!doctype html>
{% load static %}
<html lang="ja">

  <head>
    <meta charset="utf-8">
    <script src="{% static 'js/yourapp.js' %}"></script>
  </head>
  
  <body>
    <h1 id="test"> HELLO WORLD</h1>
  </body>
```

最後に、以下のようにyourmap.jsファイルをコーディングしてください。
読みだされたhtmlファイルでid="test"と設定された文字を五秒後に赤く書き換えるという関数です。

```js:yourapp.js
window.onload = function() {
    var changeColor = function() {
        var e = document.getElementById('test');
        e.style.color = 'red';
        console.log("書き換えテスト")
    }
    setTimeout(changeColor, 5000);
}
```

### 4. 設定が成功したか確認する

teminalでコマンドをうち、ローカルサーバーを立ち上げます。

```terminal
python manage.py runserver
```

まずはじめに、下のような画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/33f0290a-52f8-12c3-1b66-0d4e66cc3e6e.png)

yourapp.jsで書いたとおりに、五秒後に文字の色が赤に変われば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1386677/4360edc9-ab08-6501-84e5-43ad382b38b3.png)


### 6. 参考にした記事

@Bashi50(ゆとり PG)　様
https://qiita.com/Bashi50/items/3452bd1692fdd0ac6392

ありがとうございました。
RustでNotionに記事を保存するCLIを作った

# 作ったもの

**clip-to-notion**というCLIで、指定したURLのタイトルやメタデータを取得し、Notionのデータベースに保存することができます。

GitHubリポジトリはこちら

https://github.com/tetzng/clip-to-notion

## 使い方の例

インストール:
`cargo install clip-to-notion`

設定コマンドでNotion APIキーやデータベースIDを登録（初回のみ）:
`ctn init`

URLをNotionに保存:
`ctn run https://example.com --tags foo,bar`

# なぜ作ったか

- 読んだ記事と、その感想をメモしておきたい
- 以前読んだ記事を振り返りやすくしたい
- あとで読む記事を保存しておきたい
- これらをブラウザを開かなくても実行できるようにしたい
	- CLIなら後述のRaycast Script Commandsから手軽に実行できる

# できること

- URLを渡すと、TitleとかDescriptionを良い感じに取ってきてNotion データベースに保存できる
- 保存対象に合わせたフォーマットのNotion データベースを作れる

# どのように使っているか

clip-to-notion (ctn) を Raycast Script Commands 経由で呼び出しています。

## Raycast Script Commands

https://github.com/raycast/script-commands

`ctn` コマンドでは、引数にURLを渡す必要があるが、 URLは事前にクリップボードにコピーされていることを前提として、ここでは `pbpaste` で取得します。

### 筆者のスクリプト

```sh
#!/usr/bin/env bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Clip to Notion
# @raycast.mode silent

# Optional parameters:
# @raycast.icon 🔖
# @raycast.argument1 { "type": "text", "placeholder": "tag1,tag2", "optional": true }
# @raycast.packageName Clip to Notion

# Documentation:
# @raycast.author tetzng
# @raycast.authorURL https://raycast.com/tetzng

export PATH="$HOME/.cargo/bin:$PATH"

url="$(pbpaste)"
# URL以外の文字列のときはctnコマンドが呼ばれないようにしている
if [[ -z "$url" ]]; then
    echo "Error: No URL provided and clipboard is empty."
    exit 1
elif ! [[ "$url" =~ ^https?:// ]]; then
    echo "Error: Invalid URL format."
    exit 1
fi

if [ -n "$1" ]; then
    # タグの表記ゆれを減らす（大文字を小文字に変換、スペースはハイフンに置き換え）
    tags=$(echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')

    ctn run "$url" --tags "$tags" | jq -r '.properties.Name.title[0].plain_text as $name | "\($name)"'
else
    ctn run "$url" | jq -r '.properties.Name.title[0].plain_text as $name | "\($name)"'
fi
```

## 実際の手順

記事を読むのは、主にReeder Classicで、ブラウザ（Arc）からもたまに開くという感じで、だいたい下記のような手順です。おそらく他のアプリを使っていても、近しいことが実現できると思います。

- 気になる記事を見つけたら、記事のURLをコピー
	- Arc Browserでは `Command + Shift + C` で現在のページのURLをコピーできる
	- Reeder Classicでは、Actions and Sharingの設定からCopy Linkにショートカットキーを割り当てられる。筆者は `y` キーに設定して使用している
- Raycast Scriptをホットキーで呼び出して実行する
	- タグ付けオプションを用意したものの、考えるのが手間で、実際にはほとんど使用していない…

# 実際に使ってみて

- クリップしたことで満足して、実際には後で読まないことも多い
- Notionでメモを取る一方で、実際に読むのはブラウザなので、使い勝手が悪いと感じることもある
- iPhoneから読みたい記事を見つけたときにクリップできない

# まとめ

- Rustは書いていて楽しい
- Notionデータベースは沼