OmniAICreator/Qiita-1.07M huggingface dataset:
未知の冒険への第一歩　LINEBotでイメージキーパー＆メモライザー

みなさんはじめまして某スーパーで管理職として働いているsh00taです。
デジタル未経験者の私がデジタル革命に挑戦し、その内容を発信していきます！！


https://twitter.com/u_sh00ta

# LINEBotでイメージキーパー＆メモライザーの開発経緯
私は仕事の立場上、クレームで野菜が傷んでいたとか、
店舗の設備異常などが出たときには、写真を関係各所に報告書として提出する業務も行っています。

**スマホから会社のドライブに保存する手間を一発で解決したい！**
そして...
クレームや連絡事項の情報を一箇所で管理して各部門や店全体で共有できればなぁーと考えてました。

**「ドライブに連絡事項の情報と写真（画像）が1箇所にで保存できればめっちゃ楽やん！」**
そんなときに、LINEBotでドライブ保存できるやんってことで今回LINEBotを使って作成してみました！


# 完成動画はこちら


イメージキーパー＆メモライザー　
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">試作品<br><br> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a><br> <a href="https://twitter.com/hashtag/%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8%E3%83%A3%E3%83%BC%E3%82%BA?src=hash&amp;ref_src=twsrc%5Etfw">#チャレンジャーズ</a> <a href="https://t.co/dYWlr4jMre">pic.twitter.com/dYWlr4jMre</a></p>&mdash; sh00ta (@u_sh00ta) <a href="https://twitter.com/u_sh00ta/status/1705136727688634804?ref_src=twsrc%5Etfw">September 22, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
動画の通り、
LINEBotに文字を入力するとメモライザーへ画像を送ればイメージキーパーへ記録されました！
　　　　　　　　　　　　


## Qiitaの記事にX(Twitter）を埋め込む方法

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Qiitaの記事にX(Twitter)を埋め込む方法<br>CopyCodeを記事に貼り付け<br><br> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a><br> <a href="https://twitter.com/hashtag/%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8%E3%83%A3%E3%83%BC%E3%82%BA?src=hash&amp;ref_src=twsrc%5Etfw">#チャレンジャーズ</a> <br> <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a> <a href="https://t.co/3noSKB6ziF">pic.twitter.com/3noSKB6ziF</a></p>&mdash; sh00ta (@u_sh00ta) <a href="https://twitter.com/u_sh00ta/status/1706354344143229269?ref_src=twsrc%5Etfw">September 25, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>





## 参考記事
**アイディアが浮かんで作成を始めたらすでに私が目指していたものを完成せている先輩を発見...
作成するか悩みましたが、やっぱりやりたいことだったので参考にさせて頂きました！**



https://qiita.com/yuichi-e/items/7246e2aeb13798e7a713


## 用意するツール
[LINE Developersアカウント](https://developers.line.biz/ja/?status=success)
[make](make.com)
GoogleDrive
GoogleSpreadsheet


## シナリオ
シナリオの全体図はこんな感じです！
![3564.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535733/46ce58bd-ef0e-1fad-d0dc-254c437b7f1d.jpeg)







# 私が苦戦したところ
**Drive**の接続設定でかなり苦戦しました...
**Drive**を接続する為にGoogleアカウントを接続したのですが、
写真の通りエラーがでて接続できず、手詰まりになりました。

![3624.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535733/c6716080-313b-1708-576b-2c13ef658819.jpeg)


自分で問題解決をしようとしましたが、うまくいかず、周りの方に相談した結果、
以下の参考記事を見つけることができました。
[エラー対処方法](https://www.integromat.com/en/help/connecting-integromat-to-google-services-using-a-custom-oauth-client)


このエラーの意味は <font color="red">Gmailの個人アカウントではできない</font>ということでした。

なので私は
<font color="blue">yahooのメールアドレスでGoogleアカウントを作成することでこの問題が解決しました。
異なるメールドメインを使うことで成功。</font>

## 終わりに
次回はよりわかりやすくお伝えできるように心がけます！
これからも学びとアップデートを継続して、組織をより良くしていくことを楽しんでいきたいと思います！
ここまで読んで下さり、ありがとうございました！
【プラクティス紹介】1分でさらっと分かる「ソースコードの共同所有」

## プラクティス名（別名）
ソースコードの共同所有　（コードの共有、Shared Code、Collective Ownership）

## プラクティスの目的・狙い
* チームの誰でも、いつでも、どのコードでも、修正できるようにする
* チーム全員でコードの品質を担保する
* 属人化の防止

## どんな時に使うか
* 他のコードにも影響する修正を行う際、調整や待ち時間が発生し、スムーズにこなせない時
* メンバーのコーディングスタイルの違いやコード品質の差が大きい時

## 実施手順
1. ベースのマインドセットとしてチームの共同責任についてDEV全員が合意する
1. コードの取り扱いに関する運用ルールを定める（半日以内にコミットする、など）
1. コード毎に担当を割り当てるようなことはしない
1. 限られた人だけが許可/承認するような手順も極力無くす

コードの共有はCI（継続的インテグレーション）、トランクベース開発といったプラクティスを実践するための前提概念でもあり、その結果、最終的に到達する理想の状態ともいえる。ペアプロやモブプロを行うことで「私のコード」という意識を低減させることができる。

## アレンジ例
* コードレビューをしていて、そもそも読みづらい箇所はリファクタリングする

## アンチパターン
* テストもしてない修正コードを無責任にコミットする、またそれに対して誰も何も言わない
* 暗に「私のコードに触れるな」というオーラを出す
* 職人的な個人技に依存するコード（カウボーイコーディング）

## 参考情報

https://www.ohmsha.co.jp/book/9784274217623/

https://www.agile-studio.jp/post/apm-collective-ownership

## こぼれ話（私的コメント）
コーディングスタイルにこだわりがあるベテランほど「私の芸術的なコードが、無知な新人に汚された」という心理に陥りやすいもの。でもこれは逆に考えると教育のチャンスであり、何故そういった書き方をしない方がいいのかを伝えるきっかけになります。また、結局システム全体の品質はチーム全員のコード品質の総和でしかない、という当たり前の事実を思い出させてくれたりもします。このプラクティスによって真に共同所有されるのはソースコードではなく、「チームの責任」かもしれません。

　[アジャイルプラクティス一覧へ戻る](https://qiita.com/kakashi_h5/items/20584998c5ca4594b9ea)
ログ分析・監視がより効率よく出来るように！

## はじめに
New Relicではシステムのあらゆるログを集約して、ログの分析や監視を行うことができます。今回、ログ分析のUIがより使いやすいデザインに改善されましたので、本記事でご紹介します。

:::note info
この機能に関する詳細はこちら
[New Relic アップデート(2023年6月)](https://newrelic.com/jp/blog/nerdlog/new-relic-update-202306 "New Relic アップデート(2023年6月)")
[New Relic アップデート一覧](https://newrelic.com/jp/blog/nerdlog/new-relic-update)
:::

## 主要な機能の可視性が向上
今回のアップデートで主要な機能がページ上部の検索バーの左右に配置されるようになっており、主要な機能をより効率よく使えるようになっています。

例えば、ログをグループ化または整理して、クエリをより高速かつ効率的に行える、パーテーションのメニューをすぐ開けるようになっていたり、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485441/a2a292f1-c8de-0d86-5934-b662d56d2b21.png)
検索したログ結果から簡単にアラート条件を作ることも出来るようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485441/9c377758-621f-b447-fa80-1067dae2f0aa.png)
また、よく使うログの検索条件をビューに保存しておくことが出来ますが、保存した検索条件をすぐに取り出して、検索できるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485441/877938ba-1a30-bcc0-05b6-cc85bf845aec.png)

ログのUIからパーティション、検索条件ビューを使って、すぐにログの検索が出来るため、大量にあるログから必要な情報を迅速に見つけることが出来るようになっています！また、検索したログからアラートをすぐに作ることができるため、ログ監視をすぐに開始することができます！

## パーティションメニューの改善
パーティションのメニューのUIにも改善があり、「最近見たパーティション」、「全てのパーティション」をより簡単にメニューから選択できるようになりました。「最近見たパーティション」からよく使うパーティションを呼び出すことができるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3485441/8628287f-4d83-7306-90c7-6e60d61f1eb1.png)

## まとめ
今回のアップデートでログの検索機能のUIデザインが改善されたことで、より直感的にユーザーがログの検索機能を利用できるようになり、ログの検索・監視をより効率よく出来るようになりました！また、UIデザインを最適化したことで、全体的なパフォーマンスが向上しています。是非、ご活用ください！

:::note info
この機能に関する詳細はこちら
[New Relic アップデート(2023年6月)](https://newrelic.com/jp/blog/nerdlog/new-relic-update-202306 "New Relic アップデート(2023年6月)")
[New Relic アップデート一覧](https://newrelic.com/jp/blog/nerdlog/new-relic-update)
:::
:::note info
無料のアカウントで試してみよう！
[New Relic フリープランで始めるオブザーバビリティ！](https://qiita.com/ktst79/items/5ee74b34e6adb8a7a6fa)
:::
Snowpipe REST API のメモ (Architect)

## これは何？

Snowpipe REST API についてまとめてみたメモ。

公式情報

- [概要](https://docs.snowflake.com/ja/user-guide/data-load-snowpipe-rest-overview)
- [REST-API](https://docs.snowflake.com/ja/user-guide/data-load-snowpipe-rest-apis)

## エンドポイント

これを書いている時点では3つある。

|エンドポイント名|説明|メソッド|URL|
|-|-|-|-|
|insertFiles|データファイルの取り込み|POST|https://<アカウント識別子>.snowflakecomputing.com/v1/data/pipes/<パイプ名>/insertFiles?requestId=<リクエストID>|
|insertReport|ロード履歴レポート(直近の履歴を取得)|GET|https://<アカウント識別子>.snowflakecomputing.com/v1/data/pipes/<パイプ名>/insertReport?requestId=<リクエストID>&beginMark=<開始マーク>|
|loadHistoryScan|ロード履歴レポート(指定した2点間の履歴を取得)|GET|https://<アカウント識別子>.snowflakecomputing.com/v1/data/pipes/<パイプ名>/loadHistoryScan?startTimeInclusive=<開始時刻>&endTimeExclusive=<終了時刻>&requestId=<リクエストID>|

パラメータ名については、公式ドキュメント内で和英混じりだったりしていて表記揺れが大きい...


### 特徴など

制約系は理解しておいた方が良さそう。

### insertFiles

- 実行するとインジェストするファイルの`キューにファイルが追加`される
- 必ずしもファイルがインジェストされたことを意味するわけではない
- ほとんどの場合、数分以内に新しいデータをターゲットテーブルに挿入される

#### 制約

- 投稿には最大`5000個`のファイルを含めることができる
- 指定された各ファイルパスは`UTF-8`としてシリアル化される場合`1024バイト以内`の長さである必要がある

### insertReport

- イベントはinsertFilesを介して提出されたファイルのデータがテーブルにコミットされ、クエリで利用可能になったときに発生する

#### 制約

- 最新の`10,000件`のイベントのみが保持される
- イベントは最大`10分間`保持される

### loadHistoryScan

- 過去の2点間(開始、終了)を指定して履歴情報を得る
- できれば`insertReport`を使うことが推奨されている
- 429エラー(リクエストのレート制限)が発生する可能性が高いらしい
- 制約を気にせずSnowpipe(あるいはロード)の履歴を見たいなら、INFORMATION_SCHEMAのテーブル関数[COPY_HISTORY](https://docs.snowflake.com/ja/sql-reference/functions/copy_history)を見ればいいんじゃね説

#### 制約

- 最大`10,000個`の履歴を返却する

### レスポンスコード

特にエラーのものは理解しておいた方が良さそう。

#### 429 Request rate limit exceeded.(リクエストのレート制限を超えました。)

- 短時間に多数のリクエストがAPIに送信された場合にこのエラーが発生する可能性がある
- `loadHistoryScan`よりも`insertReport`を使うことを推奨する理由にも関連する
    - `loadHistoryScan`は気をつけないと制約に引っかかる
    - 具体例としては、過去24時間の履歴を毎分読み取るようなリクエストを行うと発生する可能性があるらしい
    - 制約を避けるには、例えば履歴の最後の10分を8分毎に読むようにリクエストするといい、といったことが[公式](https://docs.snowflake.com/ja/user-guide/data-load-snowpipe-rest-apis#:~:text=%E5%B1%A5%E6%AD%B4%E3%81%AE%E6%9C%80%E5%BE%8C%E3%81%AE10%E5%88%86%E3%82%928%E5%88%86%E3%81%94%E3%81%A8%E3%81%AB%E8%AA%AD%E3%82%80%E3%81%A8%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8D%E3%81%BE%E3%81%99)に書かれてある(実際はログの量次第とは思うが)

#### 404 <pipeName> not recognized.(<パイプ名> は認識されません。)

- <パイプ名>が間違っている
- <パイプ名>を実行する[権限](https://docs.snowflake.com/ja/user-guide/data-load-snowpipe-rest-gs#label-snowpipe-rest-endpoints-access-control)がない
    - `insertFiles` : OPERATE
    - `insertReport`,`loadHistoryScan` : MONITOR
DatabricksとTabular Editorの連携

こちらのツールの存在を知りました。

https://docs.tabulareditor.com/index.html?tabs=TE3

オープンソースソフトウェアであり、表形式データのデータモデリングを行うためのエディタだそうです。PowerBIのサイトでも紹介されてました。

https://learn.microsoft.com/ja-jp/power-bi/transform-model/desktop-external-tools

Databricksとの連携はサポートされているのかと調べたところ、今年4月のリリースでサポートされていました。わーい。

https://blog.tabulareditor.com/2023/04/19/tabular-editor-3-april-2023-release/

なので、早速試してみます。

30日間のフリートライアルでTabular Editor 3をダウンロードしてWindowsマシンにインストール。

事前準備としては、DatabricksでアクセスするSQLウェアハウスのホスト名とHTTPパス、パーソナルアクセストークンをメモしておきます。

メニューから**File > New > Model**を選択します。今回はテストなので、この際に**Use workspace database**のチェックは外しておきます。
![7B6DF21CED24450FB9740EC05C98D73B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/52259d66-924e-ada7-25fb-05863049addf.png)

メニューから**Model > Import tables**を選択します。Data source connectionでDatabricksを選択します。
![BDD4FA99FD0648A794D18E54F0BFD6F3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/b9bd077d-77a5-f269-b0a2-88dbde7a7fcc.png)
![378C0E2D861A4412A14ABE0603390B83.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/5fb85f86-a4c8-0edc-b06d-e62f809c7fb2.png)

上でメモしたホスト名を**Server Hostname**、HTTPパスを**HTTP Path**、パーソナルアクセストークンを**Authentication**にコピーします。
![4400236723CD41C5837C68FB19D4B712.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f2d6cf9d-433d-80d7-b951-6d36257fc3dd.png)

リストからテーブルを選択します。
![AF7F88265ED4433985BB408DBDA16EDC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/58eb6c3f-a2eb-ff95-b2f5-8f4074502527.png)
![5892DBC651764EB295CE0BA46321BC50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/8b13aee6-91a7-36bc-82a3-2aa118bfcf13.png)

テーブルが取り込まれました。
![B607D0B1DD56419DBC977A8D05147215.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/b4106037-0f8e-9eea-8765-b33186361ba9.png)
![44CAB01FDCC94EF5BABA5F908960173E.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/8880f561-520a-4fbb-f5d3-d8056d92be14.png)

リレーションも設定できます。
![B37811EA1D894228B433211649A9C7F4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/698d7d80-6144-547b-2dd2-144190f4f471.png)

私自身、Tabular Editorに慣れていないので今日はここまでですが、スタースキーマの設定など楽にできそうです。


### Databricksクイックスタートガイド

[Databricksクイックスタートガイド](https://www.amazon.co.jp/dp/B09V1YXFVQ/)


### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)
「Amazon QuickSight で Amazon SageMaker 機械学習予測を視覚化する」のブログ記事の実施時の注意点

## 概要
SageMakerとQuickSigthでの連携ということで、以下のサイトの通り実施してみたが、2023年9月22日時点ではエラーが発生してしまうため、その注意点のメモ。

### ブログ記事
https://aws.amazon.com/jp/blogs/news/making-machine-learning-predictions-in-amazon-quicksight-and-amazon-sagemaker/

### 実施で使うソースがあるGitHub
https://github.com/aws-samples/quicksight-sagemaker-integration-blog.git

### 修正点
ブログ記事のほうの「ノートブックを実行する」という項で、以下のJupyterファイルを使って実行するが、ところどころエラーになる。

quicksight-sagemaker-integration/customer_churn.ipynb

#### In 2番目
```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import io
import os
import sys
import time
import json
from IPython.display import display
from time import strftime, gmtime
import sagemaker
from sagemaker.predictor import csv_serializer
```
最後の`csv_serializer`は最新のバージョンでは無くなっていてエラーになる。
パッケージ含め変わっているようなので、以下に変更。
```python
from sagemaker.serializers import CSVSerializer
```

### In 3番目
```shell
!wget http://dataminingconsultant.com/DKD2e_data_sets.zip
!unzip -o DKD2e_data_sets.zip
```
シェル実行になるが、wgetで取得しようとしているファイルは、すでに無くなっていて404になる。
イシューでも上がっているが、取得先をその通りに変更する。

https://github.com/aws-samples/quicksight-sagemaker-integration-blog/issues/6

```shell
!wget https://raw.githubusercontent.com/srepho/srepho.github.io/master/Churn/churn.txt
```

### In 4番目
```python
churn = pd.read_csv('./Data sets/churn.txt')
pd.set_option('display.max_columns', 500)
churn.head()
```
１つ前の修正で、対象のファイルの配置先のパスが変わっているので変更する。
```python
churn = pd.read_csv('./churn.txt')
```

### In 8番目
```python
from sagemaker.sklearn.estimator import SKLearn
sagemaker_session = sagemaker.Session()
git_config = {'repo': 'https://github.com/aws-samples/quicksight-sagemaker-integration-blog.git','branch': 'master'}

script_path = 'quicksight-sagemaker-integration/preprocessing.py'

sklearn_preprocessor = SKLearn(
    entry_point=script_path,
    git_config=git_config,
    role=role,
    instance_type="local",
    framework_version="0.20.0")
sklearn_preprocessor.fit({'train': s3_input_train})
```

実行すると docker-compose がインストールされてないとエラーになる。
このコードセルの上に新たなコードセルを追加し、以下を入力・実行して docker-compose をインストールする。
Docker Composeのバージョンはv2.18.1なら実行可能だった、それ以上上げるとエラーになる。
多分すでに入っているDockerのバージョンが、20.10.23だったので、それとの兼ね合いだと思われる。

```shell
!sudo curl -SL https://github.com/docker/compose/releases/download/v2.18.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
!sudo chmod +x /usr/local/bin/docker-compose
!docker compose version
```

あとは、そのまま実行していけば、このブログ記事通りに、Sage Makerにあるモデルを使って、
QuickSightでデータセットとモデルを連携させて、可視化を行うことができる。
Homebrewでnodeとyarnをインストールする

## 問題
dockerコンテナを立ち上げようとしたところ
`env: node: No such file or directory`というエラーが出た。
nodeがインストールされていなかったためインストールする。
nodebrewとの競合問題もあるので、ついでにyarnもインストールする。

## 環境
macOS Ventura ver13.6 （ Apple Silicon ）
※homebrewインストール済み：[【公式】インストール方法](https://brew.sh/)


## 確認すること

以下のコマンドをそれぞれ実行し、インストールされているか確認する。
```sh
yarn -v
node -v
```

## homebrewでのnodebrew install

1.まずは、nodebrewをインストールする。
```sh
brew install nodebrew
nodebrew setup
```
2.pathを通す
```sh
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
```
3.nodeの使いたいバージョンをインストール
```sh
#安定版
nodebrew install stable
#version指定（例）
nodebrew install v8.9.4
#最新版
nodebrew install latest
```
4.現在使用している & インストールされているnodeのバージョンを確認
```sh
nodebrew ls
```
以下のように、`current:`欄にversionが表示されていたらOK
指定されていない場合は、`current:none`と表示される。
```sh
% nodebrew ls
v20.7.0

current: v20.7.0
```
5.nodeのバージョンを指定する場合（例）
```sh
nodebrew use v20.7.0
```
6.インストールの確認
```sh
node -v
```

## homebrewでのyarn install

1.yarnのインストール

```sh
brew install yarn --ignore-dependencies
```
`--ignore-dependencies`はnodeの2重インストールを防ぐためにつけるオプション。
nodeはすでにnodebrewで管理しているため、yarnのみのインストールを行う。

2.brewで管理しているパッケージを確認（一覧にyarnがあるはず）
```sh
brew list
```

3.インストールの確認
```sh
brew -v
```

## アンインストールしたい場合
```sh
brew uninstall nodebrew
brew uninstall yarn
#または
brew uninstall --force nodebrew
brew uninstall --force yarn
```
Pythonを使用してPDFドキュメントからテキストと画像を抽出する方法

PDF文書のさまざまな特殊なスタイルを保持しながら、編集、コピー、検索などが難しいものです。PDF文書のコンテンツを処理する必要がある場合、テキストと画像をPDF文書から抽出して、これらのコンテンツをさらに操作したり、他の目的に使用したりするために、ツールを使用することが選択肢となります。この記事では、Pythonを使用して**PDFファイルからテキストと画像を抽出して保存する方法**について説明します。

この方法には、[Spire.PDF for Python](https://www.e-iceblue.com/Download/Spire-PDF-Python.html)が必要です。公式ウェブサイトからダウンロードするか、pipを使用して直接インストールすることができます。
```
pip install Spire.PDF
```

## PDFファイルからテキストを抽出する方法
**ExtractText** メソッドは、個々のページのすべてのテキスト（空白を含む）を抽出し、文字列として返します。したがって、ドキュメントの各ページを反復処理し、このメソッドを使用して各ページのテキストを抽出し、テキストファイルに書き込むことで、PDFファイルからテキストを抽出することができます。詳細な手順は以下の通りです：

- **PdfDocument** クラスのオブジェクトを作成します。
- **PdfDocument.LoadFromFile()** メソッドを使用してPDFドキュメントをロードします。
- 抽出されたテキストを保存するためのテキストファイルを作成します。
- ドキュメントのページを反復処理し、**PdfPageBase.ExtractText()** メソッドを使用してページのテキストを取得し、テキストファイルに書き込みます。

**Python**
```Python
from spire.pdf import *
from spire.pdf.common import *

# PdfDocumentクラスのオブジェクトを作成する
pdf = PdfDocument()

# PDFドキュメントをロードする
pdf.LoadFromFile("サンプル.pdf")

# 抽出されたテキストを保存するためのTXTファイルを作成する
extractedText = open("output/抽出したテキスト.txt", "w", encoding="utf-8")

# ドキュメントのページを反復処理する
for i in range(pdf.Pages.Count):
    # ページを取得する
    page = pdf.Pages.get_Item(i)
    # ページからテキストを抽出する
    text = page.ExtractText()
    # テキストをTXTファイルに書き込む
    extractedText.write(text + "\n")

extractedText.close()
pdf.Close()
```

**サンプル**
![2023-09-22_174843.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/5d4e06a5-170b-42b7-0a67-046223283032.png)

**抽出したテキスト**
![2023-09-22_174953.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/6754269c-35c2-a4a1-038f-3981aa704b2e.png)

## PDFファイルから画像を抽出する方法
画像を抽出する方法は、テキストを抽出する方法と似ています。各ページを反復処理し、**ExtractImages** メソッドを使用して個々のページからすべての画像を抽出し、リストとして返します。その後、リスト内のすべての画像をファイルに保存します。詳細な手順は以下の通りです：
- **PdfDocument** クラスのオブジェクトを作成します。
- **PdfDocument.LoadFromFile()** メソッドを使用してPDFドキュメントをロードします。
リストを作成します。
- ドキュメントのページを反復処理し、**PdfPageBase.ExtractImages()** メソッドを使用してページ内の画像を取得し、作成したリストに追加します。
- リスト内の画像を保存します。

**Python**
```Python
from spire.pdf import *
from spire.pdf.common import *

# PdfDocumentクラスのオブジェクトを作成します
pdf = PdfDocument()

# PDFドキュメントをロードします
pdf.LoadFromFile("サンプル.pdf")

# 画像を保存するためのリストを作成します
images = []

# ドキュメントのページを反復処理します
for i in range(pdf.Pages.Count):
    page = pdf.Pages.get_Item(i)
    for img in page.ExtractImages():
        images.append(img)

# 抽出した画像を保存します
i = 0
for image in images:
    i += 1
    image.Save("output/Images/Image-{0:d}.png".format(i), ImageFormat.get_Png())

pdf.Close()
```

**抽出した画像**
![2023-09-22_175302.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/8bcce157-23c2-c340-e3e8-c17a0bafc389.png)

> **上記は、PDFファイルからテキストや画像を抽出するためのSpire.PDF for Pythonの使用方法についてです。PDFファイルの操作について詳しく知りたい場合は、[Spire.PDF for Pythonのチュートリアル](https://www.e-iceblue.com/Tutorials/Python/Spire.PDF-for-Python/Program-Guide/Spire.PDF-for-Python-Program-Guide-Content.html)をご覧ください。 また、[Spire.PDFフォーラム](https://www.e-iceblue.com/forum/spire-pdf-f7.html)で議論に参加することもできます。**
デモとかにつかえるRestAPIのマイクロサービスもどき

Springboot３ ＋ H2 + OAS3 で簡単に動かせるマイクロサービスをつくってみました。
通常のRestライクなAPIとHttpStatusCodeを任意で応答するテスト用APIを内包させています。

詳細はREADMEにて

https://github.com/toomorirh/easyms
Jupyter Labでセルの編集禁止・削除禁止を設定したい

## はじめに

Jupyter Labを利用していて、作成したセルを勝手に編集されたり削除されたりしないように設定したいと思って調べました。


## 作業環境

- WSL2
- Ubuntu 20.04
- Jupyter Lab 3.6.3

## 設定手順

1. 編集禁止・削除禁止を設定したセルを選択した状態で、右側の**歯車マーク**を押します。

    ![process1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911578/f6d6ab39-4786-43cf-cf7c-3082e2fee3be.png)

2. 『Advanced Tools』の右側の▼を押します。

    ![process2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911578/e45e1de7-2dd8-dc6c-c65b-a3e99df14925.png)

3. 『Cell Metadata』に設定を追加します。

    ![process3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911578/c0ddb1a9-4519-0a62-07ff-c2033a6537ce.png)

- 必要に応じて設定値を追加して ✓ を押します。
    - 編集禁止にしたい場合：`"editable": false`
    - 削除禁止にしたい場合：`"deletable": false`

**セルの見た目に変化はありませが、編集、削除ができなくなっています。**

:::note warn
 注意
 **ここで✓を押し忘れたり、項目の間にカンマを入れなかったりすると変更が反映されません**ので注意してください。
:::

ちなみに、『Cell Metadata』にはデフォルトで`"trusted": true`と` "tags": []`がありますが、削除しても今回の操作には影響はありませんでした。ただし、` "tags": []`は削除しても保存時に勝手に追加されます。

:::note warn
注意
編集禁止を設定したセルであっても**置換はできてしまいます**。[^1]
:::

## あとがき

本当は**ノートブック全体を編集禁止、削除禁止状態にしたかった**のですが、そのような設定は見当たらなかったため、セルを一つ一つ設定しました。
もし方法があれば教えていただけますと幸いです。

## 参考

- [read-only cells in ipython/jupyter notebook](https://stackoverflow.com/questions/18162970/read-only-cells-in-ipython-jupyter-notebook)


[^1]:置換は Ctrl + f で実行できます
messages.propertiesから日本語メッセージが取得されない

以下のエラーが発生しました。
No message found under code for locale 'ja'

問題点としては、日本語用の`messages.prpoerties`ファイルに変更する必要があるため、`messages_ja.properties`としました。

それから、application.propertiesファイルに変更の有無を追記しました。

```application.properties
spring.messages.basename=messages_ja
```

ちゃんと日本語メッセージが表示されるようになりました。
中国の華為Labが新しい物体検出モデル:GOLD-YOLOを公開した

昨夜華為Labが新しい物体検出モデルを公開しました。
公開したばかりなので、まだ参考できる記事とかないですが、一応内容だけ共有したいと思います。
論文は非常に長いので、重要な部分だけをまとめて説明します。

①サマリー
物体検出のタスクに対して、精度自体はTransformer系が一番高いですが（VIT,SwinTなど）、処理スピードが遅いために、
one-stageのYolo系（Yolo8,YoloXとか）はより業務上で使われています。そこで、今回のGold-Yoloは、従来Yoloの一つ大きな弱みを改善できました。
下の精度グラフをみると、一番上の赤い線がGold-Yoloです。最新のYolov8よりは2.4%精度上がったと示しています。![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556459/a8e8f4de-4f32-2923-0a7d-5763d6c0bcb3.png)


②YOLOの弱み:
YOLOの構造（FPN）によって、multi-level間の特徴量取得は、一部の情報をなくすようになります。
下の例の画像見るとわかりますが、level1がlevel２のデータを利用したいときには、
そのままつながっていますので、問題なく必要な情報を取得し、あまり損失がなく統合できます。
ですが、level1がlevel3の情報を利用したいなら、直接繋がっていないために、まずlevel2からlevel3の情報を取得して、それで、level1がlevel2+level3の統合情報をもらうことで、間接にlevel3のデータを手に入れます。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556459/cd323ab6-5709-43af-24f6-5bb01caf62cf.png)

このような伝統的な再帰方だと、一部のデータは必ず転送中でなくします。
なぜかというと、level2はleve3にもらう情報はすべてではなく、使われる情報だけ取りますので、使われてない部分はの残さず捨てられます。結局、多層間の情報転送はフルー転送できないです。
下の画像の通り、左上が層ごとの転送で情報がなくなりました。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556459/d28cab44-3a8c-da14-0658-66fda6faa5ea.png)



③GOLD-YOLOの対策:
この弱みを解決するために、開発者がGather-and-Distribute（GD）と呼ばれるー「収集+配布」機能を追加しました。
GDは今までの再帰手法ではなく、まず統一なモジュールからすべての情報を集まって統合します。
そのあと、情報転送際に、必要な分だけモジュールがlevelに直接渡します。（Transformerっぽいですね）
GDはまたFAM,IFM,INjectという3つのモジュールで構成しています。
（これら三つに関する詳しい説明はかなり長くて難しいので、興味ある方は直接論文を読んでください。）

*YOLOとの比較
YOLOV8より2.6%,YOLOV6-3.0より2.4%,他のYOLOバージョンよりは大体3-5%高いです。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556459/4e67ff3d-ac5e-e6eb-06bb-5769367c97d9.png)


論文:https://arxiv.org/abs/2309.11331
github:https://github.com/huawei-noah/Efficient-Computing/tree/master/Detection/Gold-YOLO
【電子工作】Arduino IDEでESP32入門

ESP32を買ったのでとりあえず動かしてみます。

[ESP32-DevKitC-32E ESP32-WROOM-32E開発ボード 4MB | 秋月電子通商](https://akizukidenshi.com/catalog/g/gM-15673/)

# ■ IDEのインストール

公式サイトでIDEをダウンロード & インストール
https://www.arduino.cc/en/software

# ■ IDEの設定

## ESP32用のボードマネージャーをインストール

`esp32 by Espressif Systems` をインストール

<img width="700" alt="スクリーンショット 2023-09-22 15.46.20.png (234.2 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/ffb80979-2ffc-4966-9658-fece097fcd2e.png">


# ■ 実装

サンプルコードをそのまま使ってみます。

「ファイル」 -> 「スケッチ例」 -> 「WebServer」 -> 「HTTPServer」を選択

<img width="700" alt="スクリーンショット 2023-09-22 15.52.16.png (1.0 MB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/4e316037-5b3e-46b8-a105-4663e3974fde.png">


ソースコードのssidとpasswordに自宅のwifiルーターの情報を指定します。

```ino
const char* ssid = "xxxxxxxxxxxxxxxxx";
const char* password = "xxxxxxxx";
```


# ■ ボードへの書込み

## ESP32とパソコンを接続

ESP32とパソコンをUSBで接続します。

## ボードとポートの選択

書込み先のボードとポートを選択します。

<img width="700" alt="スクリーンショット 2023-09-22 15.57.11.png (195.4 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/e00531db-5cd4-4b1e-a722-76b18b75a227.png">


<img width="700" alt="スクリーンショット 2023-09-22 15.56.54.png (227.3 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/1c423a84-0732-4c1d-a27b-3a6db35d16a8.png">

## シリアルモニタの表示

動作確認用にシリアルモニタを表示しておきます。
「ツール」 -> 「シリアルモニタ」を選択し、ESP32にシリアル接続

<img width="700" alt="スクリーンショット 2023-09-22 17.33.26.png (523.8 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/6709c31e-4394-41fe-b546-13fcd5a53f91.png">

## 書込み

書込みボタンをクリックして書込みを開始します。

<img width="700" alt="スクリーンショット 2023-09-22 17.30.04.png (244.9 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/1fbb1ef0-13ff-4ce1-9fe3-b0564167af3d.png">


# ■ 動作確認

シリアルモニタに `HTTP server started` が表示されていれば成功

<img width="700" alt="スクリーンショット 2023-09-22 17.48.31.png (361.6 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/893c6c3c-b8e5-4d2c-b1da-7d4c50cac222.png">


表示されているIPにブラウザでアクセスしてみましょう。

<img width="451" alt="スクリーンショット 2023-09-22 17.51.09.png (32.6 kB)" src="https://img.esa.io/uploads/production/attachments/5809/2023/09/22/19598/7dd2f41d-6449-4e96-98a0-315f7d63d8e9.png">
ローカルでのDeno KV動作時のデータ構造

- Denoには組み込みのKey Value Storeがあり、外部ストレージを用意せずとも簡単にデータの出し入れが可能です。
- 今回はローカルで使用してみて、動作やデータファイル群の場所などの確認を行います。

## 環境
- deno 1.36.4

## 内容

### 基本的な使用
- まず、Deno KVで「**接続・書き込み・読み取り**」を行う基本的なコードを示します。
    - **※各オプションについては今回は割愛します。**

```typescript
// 接続
const kv = await Deno.openKv();

// 書き込みデータ
const book1 = {
  title: "hoge",
  desc: "very hoge."
}

// 書き込み
await kv.set(["books", book1.title], book1);

// 読み取り
const result = await kv.get(["books", book1.title]);

// 結果出力
console.log(result.value)
```

- 上記のコードを例として`sample.ts`というファイルに書き込んで実行した結果が以下です。

```shell
$ deno run -A --unstable sample.ts

{ title: "hoge", desc: "very hoge." }
```

### データファイル
- まずDenoからデータベースへ接続するためには`openKv`メソッドを使用します。
- デフォルトでは構成なしで以下の環境に[自動的に接続します](https://deno.com/blog/kv#run-locally-or-managed)。
    - ローカル: SQLite
    - Deno Deploy: FoundationDB
- プログラムが実行されると、ローカルではデフォルトでは以下の場所にSQLiteのデータファイルが作成されます。

```shell
# 格納先
$ ls $HOME/Library/Caches/deno/location_data

xxxxx111111111xxxxxxxxxxx11111111111

# データファイル
$ ls $HOME/Library/Caches/deno/location_data/xxxxx111111111xxxxxxxxxxx11111111111

kv.sqlite3
```

- またデータファイルの場所を指定したい場合は以下のように記述します。
    - **※例としてスクリプトファイルと同じディレクトリに「tmp.db」という名前で保存します。**

```typescript
const kv = await openKv("tmp.db");

// 省略
```

- 修正して再実行すると、指定した場所に3つのSQLite関連のファイルが作成されていることを確認します。

```shell
# ファイル確認
$ ls

sample.ts	tmp.db		tmp.db-shm	tmp.db-wal

# ファイルタイプ確認
$ file tmp.db*

tmp.db:     SQLite 3.x database, last written using SQLite version 3043001, writer version 2, read version 2, file counter 1, database pages 1, cookie 0, schema 0, unknown 0 encoding, version-valid-for 1
tmp.db-shm: data
tmp.db-wal: SQLite Write-Ahead Log, version 3007000

# 書き込みログ確認
$ strings tmp.db-wal

:SQLite format 3
+tablekvkv
CREATE TABLE kv (
  k blob primary key,
  v blob not null,
  v_encoding integer not null,
  version integer not null
＊＊＊＊＊＊
＊＊＊＊＊＊
＊＊＊＊＊＊
```

- このことからDeno KVではSQLiteの[walモード](https://www.sqlite.org/wal.html)を利用していることがわかります。
    - **shmファイル**: Shared Memory File。SQLiteデータベースの同時アクセスをサポートするために使用。
    - **walファイル**: Write-Ahead Logging File。データベースへの書き込み操作に対するログを保存し、トランザクションの整合性を確保するために使用。

### スキーマ
- 次に作成されたデータファイルの中身を任意のクライアント(例: TablePlus)で確認します。
- 作成された`tmp.db`をTablePlusで開きます。
- 確認すると、以下のようなスキーマとなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/dc161145-ef3a-48da-a764-3742a0e4a497.png)

- 試しに上記の基本的なプログラムを以下のように変えて実行した後のデータの中身を見てみます。

```typescript
// データ変更
const book1 = {
  title: "fuga",
  desc: "very fuga."
}

// 省略
```

- 実行後、以下のように`data_version`や`kv`テーブルなどに更新及び追加がされていることを確認します。

![data_version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/ba8ff8c9-0d7e-7128-91ab-c19840c5eedd.png)

![kv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/124e3410-7f1c-2367-137a-379277101503.png)

## まとめ
- Deno KVでは構成なしでプログラムから簡単に高機能なKey Value Storeを利用することができます。
- その容易さを構成しているものを深掘ることで、恩恵をより感じることができると感じました。

## 参考
- [ドキュメント](https://docs.deno.com/kv/manual)
- [sqliteの一時ファイルについて](https://www.sqlite.org/tempfiles.html)
- https://til.simonwillison.net/deno/deno-kv
Goのホットリロードツール (Air) で変更反映時のtcpポート競合を回避する方法

# 環境
Docker: 4.20.1
Nginx: 1.23
Air: 1.45.0

## 記事の要約
Goのホットリロードツール(Air)による再実行時にtcpポート競合が起きてしまっていた問題を**Airの設定ファイル**を修正することで解決したという記事。

## 前提
- Dockerについてある程度知っている
- Docker環境でのAirの導入は既に出来ている



# 目次
- [背景](#背景)
- [問題](#問題)
- [解決](#解決)
- [終わりに](#終わりに)



# 背景
Goは（RubyやPHPとは異なり）コンパイル言語であるため、実行中のアプリケーションのソースコードへの変更を反映させるためには**実行停止＋コンパイル＋実行**を毎回行う必要がある。
これは明らかに面倒だし効率が悪すぎるということで、この辺りの不満を解決してくれるホットリロードという機能が提供されている（GoではAirというモジュールが主流らしい）。



# 問題
Go言語での開発においてソースコードを書き換えを勝手に検知してbuild+再実行をしてくれるAirであるが、
```
# listen tcp :8080: bind: address already in use
```
というエラーが発生しており、　変更が反映されないこともしばしばあり、とても困っていた。



# 解決
下記に示す様に、airの設定ファイルである".air.toml"ファイルのdelayを0から50に変更する。
```.air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ."
  delay = 50
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true

```


# 終わりに
```
# listen tcp :8080: bind: address already in use
```
このエラーが発生する際にはbuildが2回走っており、今までのプロセスがkillされる前に新しいプロセスが走っていた、もしくは変更検出後の再実行が2回走っていたのではないかと思われる。ここでdelayを設定することによって、buildの際に待ち時間が発生することでkillの時間稼ぎ、もしくは変更検出したプロセスがすでに走っていることの判定ができる様になることでもmん大が解決したのかな？と思っている（なぜ治ったのかよくわかっていない）。
もし詳しい理由をご存知の方がいたらぜひコメントよろしくお願いします！
CognitoをOIDCの外部プロバイダーとして設定する

# 背景
社内でAzureAD(今はEntraID?)と連携したアプリケーションが複数あり、新しくアプリケーションを作るたびにAzureAD Appを作らなければならなかった。それぞれのアプリケーションで許可されるユーザーが同じものが多かったため統合したかった。

# 前提
- 筆者は認証周りに疎く調べながらやったため、BestPracticeではない(と思う)
- Cognitoの利用経験あり
- 導入先のWebアプリケーションもCognitoによる認証を導入することとする。
※OIDCとして提供できるのでCognito以外のFrameworkやServiceでも利用可能(だと思う)

# 図解
```mermaid
%% ここに全体のフロー図

sequenceDiagram
		autonumber
		%% Entities
		
		actor user as User
		participant app as Application
		participant appc as App Cognito
		participant lpc as Cognito as OIDC Provider
		participant azure as AzureAD
		
		user ->> app: access
		app ->> appc: auth request
        appc -->> lpc: authentication with OIDC
        lpc ->> azure: redirect to authenticate w/SAML
        azure -->> lpc: callback
        lpc -->> appc: callback

		appc -->> app: response
		app -->> user: response
		
```

# 統合プロバイダーのCognito設定

> AWS Consoleが英語表示なのは、公式Docを基本enで見るため


まずはAzureADとのSAML連携をする必要がある。
そこはインターネッツにたくさん資料が転がっているので自力で頑張ろう。

では、OIDC提供準備に入る。

## Cognito App clientを作成

「App integration」 → 「App client list」 → 「Create app client」


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365080/1bc7ac7a-2a70-15a0-08b1-a93dc6632cc0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365080/c9c742a0-10e2-bb1e-9c14-752993050732.png)

**App client**
- Client secret
    - Generate a client secretを選択

**Hosted UI settings**
- Allowed callback URLs
    - 提供先のcallbackURLを入れる
    - 今回は提供先もCognitoを使う前提のため `https://{AppClientName}.auth.{region}.amazoncognito.com/oauth2/idpresponse`を入れる
    - 適当に入れておいて後から編集でも問題なし
- Identity providers
    - 連携しておいたAzureADを選択

他はお好みでok

今回は
`OAuth 2.0 grant types`を`Authorization code grant`のみ
`OpenID Connect scopes`を`OpenID`のみ
にしてみた。
Attribute permissionsも利用属性などに合わせてお好みで。

さて、完成したところでOIDCで必要な情報をメモ

- Client ID
- Client secret
- User pool ID

# 提供先の認証
今回はCognitoで簡単に準備してみる。

ユーザープールを作るところまでは、何でもOKなので公式ドキュメント通りに。

大切なのは、OIDC連携の紐づけから。

「Sign-in experience」 → 「Federated identity provider sign-in」 → 「Add identity provider」

OpenID Connect(OIDC)を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365080/1c6de500-8b99-1966-ce2f-1fbacaf3acc2.png)

- Client ID
    - さっきメモしたClient ID
- Client secret
    - さっきメモしたClient secret
- Retrieve OIDC endpoints > Setup method
    - Auto fill through issuer URL
    - issuer URL
        - さっきメモしたUser pool IDを使って
          `https://cognito-idp.{region}.amazonaws.com/{UserPoolID}`

必要に応じて、このCognito Appの`App client name`を使ったcallbackURLを統合プロバイダー側のCognitoに設定する。

`https://{AppClientName}.auth.{region}.amazoncognito.com/oauth2/idpresponse`

こっちのCognitoのHosted UI > Allowed callback URLsにはテスト用に
`https://www.google.com`
とか入れておけば、「View Hosted UI」で検証できる。

ログインに成功すると、アプリケーション利用想定のCognitoのUsersにAzureAD認証を通ってきたUserが追加されている。


# まとめ

「cognito as oidc provider」と検索をかけてもCognitoにOIDC連携を登録するドキュメントや海外の古い記事が出てきたため実際にやってみました。
まだ検証レベルなので細かい設定ができているか不安ではありますが実際にアプリケーションとの連携をしてみてさらなる検証を進めてみます。
【SwiftUI】モーダル表示の却下

# この記事は何？

SwiftUIアプリの[シートやポップオーバー表示を取り消す方法](https://developer.apple.com/documentation/swiftui/dismissaction)について、Appleの開発者向けドキュメントを独自に解説する。

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

# dismissプロパティ

`Dismiss`環境値を使用して、特定の`環境`に対してこの構造体のインスタンスを取得する。
次に、インスタンスを呼び出すと、表示の取り消しを実行する。
インスタンスを呼び出すときにSwiftが呼び出す`callAsFunction()`メソッドを定義するため、インスタンスを直接呼び出す。

このアクションを使用して、次のことができる。

- シートやポップオーバーなどのモーダル表示を取り消す
- `NavigationStack`から現在のビューをポップする
- `WindowGroup`または`Window`で作成したウィンドウを閉じる

アクションの具体的な動作は、どこから呼び出すかによって異なる。
たとえば、シートとして機能するビュー内で`DismissAction`を呼び出すボタンを作成できる。

```swift:
// モーダル表示されるシート
private struct SheetContents: View {
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        Button("Done") {
            dismiss()
        }
    }
}
```

`SheetContents`ビューを表示すると、シートのボタンをタップしてシートを閉じることができる。

```swift:
// 通常の画面
private struct DetailView: View {
    // 「シートが表示されているかどうか」を追跡する状態プロパティ
    @State private var isSheetPresented = false

    var body: some View {
        // ボタンをタップすると、シートがモーダル表示される
        Button("Show Sheet") {
            isSheetPresented = true
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetContents()
        }
    }
}
```

## 注意すべき点

アクションが適切な環境で定義されているかを確認する。
たとえば、上例の`DetailView`ビューで`dismiss`プロパティを作成することは適切ではない。
そのようにして、`dismiss()`アクションを`.sheet(item:onDismiss:content:)`修飾子のコンテンツクロージャから呼び出しても、シートを正しく却下できない。

```swift:不正な方法でのモーダル表示
private struct DetailView: View {
    @State private var isSheetPresented = false
    @Environment(\.dismiss) private var dismiss // Applies to DetailView.

    var body: some View {
        Button("Show Sheet") {
            isSheetPresented = true
        }
        .sheet(isPresented: $isSheetPresented) {
            // .sheetモディファイア内でdismissアクションを呼び出さないこと
            Button("Done") {
                dismiss() // Fails to dismiss the sheet.
            }
        }
    }
}
```

アクションは「それを宣言した環境」に適用される。
つまり、先の方法では、アクションが「`sheetContents`の環境」ではなく「`DetailView`ビューの環境」に適用されてしまったので、シート表示が取り消されない。
実際に、この`DetailView`ビューがルートビューだった場合、macOSとiPadOSの`dismiss()`アクションはウィンドウを閉じる。

`dismiss()`アクションは「表示中でないビュー」に影響しない。
SwiftUIが「ビューを表示しているかどうか」を照会したい場合は、`isPresented`環境値を利用する。

# isPresentedプロパティ

「環境に関連付けられたビュー」が表示中かどうか、を示す真偽値。

```swift:宣言
var isPresented: Bool { get }
```

この環境値を読み取るには、`@Environment`プロパティラッパーを使用してプロパティを作成する。

```swift:プロパティを作成して、コードで呼び出す
@Environment(\.isPresented) private var isPresented
```

SwiftUIが「そのビューをいつ表示するか」を知りたい場合は、他の[環境値](https://developer.apple.com/documentation/swiftui/environmentvalues)を利用する。
たとえば、`.onChange(of:perform:)`修飾子を使用して、SwiftUIがビューを表示したときにアクションを実行する。

```swift:
.onChange(of: isPresented) { isPresented in
    if isPresented {
        // Do something when first presented.
    }
}
```

この`.onChange(of:perform:)`は、すでにナビゲーション階層にあるビューに戻るときなど、SwiftUIが特定のプレゼンテーションに対して複数回呼び出せる`.onAppear(perform:)`とは動作が異なる。

表示中のビューを取り消すには、`dismiss`アクションを使用すること。
AWS CloudShellでの対話的操作で手軽にboto3を試すには

# はじめに

この記事では、AWS CloudShellでPythonのSDK、boto3を対話的に使用する方法を解説します。

CloudShellには、Pythonとboto3がプリインストールされています。さらに、CloudShellはマネージメントコンソールから認証情報を引き継ぐため、boto3に認証情報を明示的に渡す必要がありません。これにより、セットアップが不要です。

また、この手法は、AWSリソースの操作だけでなく、boto3のAPIを試す際にも非常に便利です。

# AWS CloudShellとは

AWS CloudShellは、マネージメントコンソールから直接アクセスできる、ブラウザベースのシェル環境です。AWS CLIや各種SDK、その他のシェルユーティリティをプリインストールされた状態で使用することができます。

https://docs.aws.amazon.com/ja_jp/cloudshell/latest/userguide/welcome.html

# boto3とは

boto3は、AWS SDKのPython版であり、AWSのリソースにプログラムからアクセスするためのPythonライブラリです。

https://aws.amazon.com/jp/sdk-for-python/

# boto3を対話的に実行する

AWS CloudShellを使用してboto3を対話的に実行する方法は、以下の通りです。

1. CloudShellの起動

    マネージメントコンソールにログイン後、ヘッダにあるCloudShellのアイコン（`>.`の形をしています）をクリックします。

2. Python インタープリタ起動
    シェルが起動したら、Python インタープリタを起動します。
    ```bash
    python3
    ```

    インタープリタを起動すると、`>>>` というプロンプトが表示されます。出力例を示します。

    ```bash:Output
    [cloudshell-user@ip-xx-x-xx-xxx ~]$ python3
    Python 3.7.16 (default, Aug 30 2023, 20:37:53) 
    [GCC 7.3.1 20180712 (Red Hat 7.3.1-15)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 
    ```

    インタープリタを終了するまでは、Pythonの各種機能とboto3のAPIを対話的に試すことができます。以降のコマンドは、このインタープリタに対して実行します。

    尚、インタープリタは、タブで入力補完が効きます。

3. boto3インポート
    以下のコマンドを実行してboto3をインポートします。
    ```python
    import boto3
    ```

4. セッション初期化
    boto3のセッションを初期化します。認証情報とリージョンはマネジメントコンソールから引き継がれますが、明示的に指定することも可能です。

    東京リージョンを指定する例です。

    ```python
    sess = boto3.Session(region_name='ap-northeast-1')
    ```

    セッション初期化時に認証情報を渡すことも可能ですが、本記事では割愛します。

5. AWSリソース操作
    セッションが確立されたら、AWSの各種リソースを操作できます。

    セキュリティグループを一覧表示する例です。

    ```python
    ec2 = sess.client('ec2')
    res = ec2.describe_security_groups()
    print(res)
    ```

    ```python:Output
    {'SecurityGroups': [{'Description': 'for test', 'GroupName': 'test-sg', 'IpPermissions': [], ... 
    ```

    このようにJSONの出力が見づらい場合は、Pythonのjsonモジュールを使って整形することができます。具体的には、`json.dumps()`関数の`indent`引数を利用すると、きれいに整形された形で出力できます。

    ```python
    import json
    print(json.dumps(res, indent=2))
    ```

    ```python:Output
    {
      "SecurityGroups": [
        {
          "Description": "for test",
          "GroupName": "test-sg",
          "IpPermissions": [],
          "OwnerId": "3328xxxxxxxx",
          "GroupId": "sg-xxxxxxxxxxxxxxxxx",
          "IpPermissionsEgress": [
            {
              "IpProtocol": "-1",
              "IpRanges": [
                {
                  "CidrIp": "172.24.1.1/32",
                  "Description": "allow 172.24.1.1/32"
                }
              ],
              "Ipv6Ranges": [],
              "PrefixListIds": [],
              "UserIdGroupPairs": []
            }
          ],
          "VpcId": "vpc-xxxxxxxxxxxxxxxxx"
        },
        ...
    ```

6. インタープリタ終了
    最後にインタープリタを終了します。
    ```python
    quit()
    ```

# まとめ

この記事では、AWS CloudShellとboto3を用いて、AWSリソースを対話的に操作する方法について解説しました。

CloudShellであれば、セットアップなしでboto3を利用することが可能です。また、Pythonのインタープリタを使えば、対話的にboto3のAPIを試すことができます。
【PHP】PHPの配列比較。array_intersect(アレイインターセクト)を使う

# コード
```php
<?php
$data = [
    ['1','2','3','4','5']
   ,['88','288','83','84','85']
   ,['1','2','3','4','5']
];


$search = ['1','2','266'] ;

foreach ($data as $value){
   if(array_intersect($search,$value)){
        echo true;
    }else{
        echo 'false';
    }
}

```

# 結果1
```
array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(1) "2"
}
array(0) {
}
```
# 結果2
```
array(0) {
}
```
LXD で Debian 11 を使う

PHP7.4 を使うために、仮想環境に Debian 11 をインストールする方法です。
Chromebook の Linux 環境と同じになります。

インストールが完了してログインすると次のようになります。

```text
$ ssh debian11.local
Linux debian11 6.2.0-33-generic #33-Ubuntu SMP PREEMPT_DYNAMIC Tue Sep  5 14:49:19 UTC 2023 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Sep 22 17:22:50 2023 from 10.112.67.1

uchida@debian11:~$ cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

## OS のインストール

```bash
lxc launch images:debian/11/amd64 debian11
```

root のパスワードを設定

```bash
lxc exec debian11  -- bash
```

login

```text
$ lxc console debian11
To detach from the console, press: <ctrl>+a q

debian11 login:
```

## Login 後の設定

ユーザーの作成

```bash
adduser --uid 1200 uchida
gpasswd -a uchida sudo
```

sshd のインストール

```bash
sudo apt install ssh
```

avahi のインストール

```bash
sudo apt install avahi-daemon
```

avahi が動けば、次のようにしてログインができます。

```bash
ssh debian11.local
```

タイムゾーンの設定

```bash
sudo timedatectl set-timezone Asia/Tokyo
```

パスワードなしで、sudo が実行できるようにする

```bash
sudo visudo
```

```text
（省略）
%sudo ALL=(ALL:ALL) NOPASSWD:ALL
（省略）
```

## PHP のインストール

```bash
sudo apt install php
```

必要なライブラリーのインストール

```bash
sudo apt install php-mbstring php-mysql php-curl php-xml
sudo apt install php-fpm php-gd php-bz2 php-zip
```

確認

```text
$ php -v
PHP 7.4.33 (cli) (built: Jun  9 2023 16:51:37) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.33, Copyright (c), by Zend Technologies
```

## MariaDB のインストール

```bash
sudo apt install mariadb-server
```

確認

```text
$ sudo mariadb -uroot
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 31
Server version: 10.5.19-MariaDB-0+deb11u2 Debian 11

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 
```
キーワードをもとに表を転記する

# VBAで表の内容を転記する
<!-- 発端や概要を記載 -->
共通するワードをキーとしてシートから別シートに転記するVBAを作成しました．

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [事前準備](#Chapter1)
1. [部分説明](#Chapter2)
1. [完成](#Chapter3)

<a id="#Chapter1"></a>
# 1.事前準備
下記のようにExcel上で２つのシートを用意します。
シート1にシート2の内容を転記するようにVBAを書いていきます。

・シート1　（転記を受け取るシート）
![IMG-7341.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2870401/7994cb22-81af-d370-0868-702179e66a48.png)

・シート2（送る側のシート）
![IMG-7342.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2870401/d4157400-029e-6fae-4ec1-ce0fde16d71d.png)

<a id="#Chapter2"></a>
# 2.部分説明
ここでは特筆する必要のある個所を説明していきます。


・シートの名前セット
宣言することでこれ以降シート１をSh1として扱います。
```
Set Sh1 = ThisWorkbook.Worksheets("Sheet1")
```

・最終行取得
下記のコードでシート内の最終行を取得します。
これをもとにループの終了判定を取ります。
```
Worksheets("xxxx").Range("xx").End(xlDown).Row 
```

・転記
シート１の１行目から２行目にシート２の３行目から４行目を転記します。
数値を変えることで範囲を変えることも可能です。

 ```
Range(Sh1.Cells(line_count, 1), Sh1.Cells(line_count, 2)).Value =                
           Range(Sh2.Cells(keyword_count, 3), Sh2.Cells(keyword_count, 4)).Value 
```

<a id="#Chapter3"></a>
# 3.完成
最終的にはこのようなコードになりました。
変数宣言無駄に多くなってしまったのでもう少しスマートに書けるようになりたいですね。
どなたかの参考になれば幸いです。

 ```
 Sub ボタン1_Click() 
    '変数の定義 
    Dim i As Integer 
    Dim Sh1 As Worksheet 
    Dim Sh2 As Worksheet 
    Dim count_loop As Long 'While文のカウント 
    Dim keyword_count As Long 'シート2のキーワード行カウント 
    Dim keyword_column As Long 'シート1のキーワード列参照 
    Dim keyword_lastcell As Long 'シート2の最終行参照 
    Dim start As Long 'シート1の開始行 
    Dim end_count As Long 'シート1の最終行 
    Dim line_count As Long 'シート1の参照行 
    count_loop = 1 
    keyword_column = 3 ' シート１の参照列指定 
    keyword_lastcell = Worksheets("Sheet2").Range("B5").End(xlDown).Row + 1 ' シート2の最終行取得 
    Application.ScreenUpdating = False ' 描画を停止する 'ワークシートを指定する 
    Set Sh1 = ThisWorkbook.Worksheets("Sheet1") 
    Set Sh2 = ThisWorkbook.Worksheets("Sheet2") 
    start = (Sh1.Cells(1, 1).End(xlDown).Row) 
    end_count = (Sh1.Cells(1, 3).End(xlDown).Row) + 1 ' シート1の入力側最終行取得 
    line_count = start + 1 
    Do While line_count < end_count 'シート2の開始の行 
      keyword_count = 5 
      Do While keyword_count < keyword_lastcell 'キーワード一致 
        If Sh1.Cells(line_count, keyword_column) Like "*" + Sh2.Cells(keyword_count, 2) + "*" Then 
           Range(Sh1.Cells(line_count, 1), Sh1.Cells(line_count, 2)).Value =                
           Range(Sh2.Cells(keyword_count, 3), Sh2.Cells(keyword_count, 4)).Value 
        End If keyword_count = keyword_count + 1 
      Loop 
      line_count = line_count + 1 
    Loop 
End Sub
```
YouTubeで再生地点指定するのってどうしてんの？？

# はじめに
YouTube大好き！！
週に17時間26分見るくらい好き！！
この時間を法律の勉強に充てれば大体4年で司法試験に合格できる。

<img width="200"src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3335033/6a24c595-dbde-c8c1-44aa-6fe8c93f53e5.jpeg">

最近気になってるのが
履歴から動画再生したら前停止したとこから再生できるんだけど？！？！？！？！
めっちゃ便利なんで？！これなんで？！

# 目的
YTSwiftyPlayerの基本的な使用法を学ぶ。
evaluateJavaScriptメソッドの使用法を理解する。
YouTube動画のシーク操作を理解する。

# YoutubeKit
### 結論から言うとよくわからんかった
でも[YoutubeKit](https://github.com/rinov/YoutubeKit)というライブラリを見つけた。
この中にシークバーに関するメソッドも存在したのでそこから想像してみたいと思う。

YoutubeKitについてChatGPTに雑に投げてみた。
```markdown
YoutubeKitは、Youtube IFrame APIおよびYoutubeDataAPIを完全にサポートするビデオプレーヤーです。
これにより、Youtubeアプリケーションを簡単に作成できます。
このライブラリは、rinovによってGitHubで公開されており、Swiftで書かれています。

主な特徴:
YTSwiftyPlayer: Youtube IFrame APIをサポートするビデオプレーヤーです。これにより、高性能でメモリ効率の良いビデオプレーヤーを実装できます。
YoutubeDataAPI: YoutubeDataAPI(v3)をサポートしており、様々なAPIリクエストを行うことができます。

使用方法:
YoutubeKitを使用すると、Youtubeビデオの再生や、YoutubeDataAPIを使用したビデオ情報の取得などが可能になります。また、認証リクエストもサポートしており、GoogleSignInを使用してアクセストークンを取得できます。
```

以下が再生地点に関するメソッド群
```swift
    public func seek(to seconds: Int, allowSeekAhead: Bool) {
        evaluatePlayerCommand("seekTo(\(seconds),\(allowSeekAhead ? 1 : 0))")
    }
```
```swift
    // Evaluate javascript command and convert to simple error(nil) if an error is occurred.
    private func evaluatePlayerCommand(_ commandName: String, callbackHandler: ((Any?) -> ())? = nil) {
        let command = "player.\(commandName);"
        evaluateJavaScript(command) { (result, error) in
            callbackHandler?(error != nil ? nil : result)
        }
    }
```

### 何してるの？
1. public func seek(to seconds: Int, allowSeekAhead: Bool)
動画を特定の時間位置にシークするためのメソッド。evaluatePlayerCommandメソッドを呼び出してJavaScriptコマンドを評価します。

2. private func evaluatePlayerCommand(_ commandName: String, callbackHandler: ((Any?) -> ())? = nil)
JavaScriptスクリプトを評価し、その結果またはエラーをオプショナルなコールバックハンドラーに渡すメソッド。

動作の流れ:
シークメソッドの呼び出し:

```swift
seek(to: 30, allowSeekAhead: true)
# この呼び出しにより、evaluatePlayerCommandメソッドが以下のコマンドで呼び出されます。

"seekTo(30,1)"
# evaluatePlayerCommandメソッドは、構築したコマンドをevaluateJavaScriptメソッドで評価します。

"player.seekTo(30,1);"
# このJavaScriptコマンドが評価され、動画は30秒の位置にシークします。

結果の処理:
コマンドの評価が完了すると、結果またはエラーがコールバックハンドラーに渡されます。`必要に応じて結果の処理やエラーハンドリングを追加できます。
```

### 直接player.seekTo()呼ぶじゃダメなの？
- player.seekTo()はJavaScriptのメソッドで、Swiftから直接呼び出すことはできません。
SwiftからJavaScriptのメソッドを呼び出すには、evaluateJavaScriptのようなメソッドを使用してJavaScriptコードを定義する必要があります。
- evaluatePlayerCommand(_:)メソッドは、このJavaScriptコードの定義をカプセル化し、SwiftのコードからJavaScriptのメソッドを簡単に呼び出すためのインターフェースを提供する。

# まとめ
seekToの内部処理までおいたかったが断念。
ただ、上記ライブラリのような書き方、特に単一責任の原則がきっちり守られてて美しいコードを書けるようになりたいと思いました。
YouTubeおもれぇからみんなも見ようぜ。
JavaScriptの変数宣言とイベント発火でよく見るエラー集

# JavaScriptの変数宣言とイベント発火でよく見るエラー集

・JavaScriptで変数を宣言する時にvarではなくletが良いと聞いたけど、それって何が理由でそう言われてるんだっけ……？
・関数の宣言をしたいのになぜかしょっちゅう見たことがあるエラーが表示される……これ何だっけ…

よく見かけるけれど、つい忘れがちな部分を備忘録としてまとめました。

## 変数の型について
|変数の型|const|let|var|
|:--:|:--:|:--:|:--:|
|変数の代入|×|○|○|
|変数の再宣言|×|×|○|
|ブロックスコープ|○|○|×|
|関数スコープ|○|○|○|

constは定数（固定された値）を宣言する際に使用する。
変数の宣言は基本的にletを使用する。
varの使用は非推奨。

### 変数の再宣言とは
letで変数を再宣言した場合
```JavaScript
let a = 1;
let a = 10;
console.log(a)  // Uncaught SyntaxError: Identifier 'a' has already been declared
```
「既にその変数は宣言されています」とエラー文が出力される。

varで変数を再宣言した場合
```JavaScript
var b = 1;
var b = 10;
console.log(b); // 10
```
後から宣言された値「10」が出力される。

**ブロックスコープとは**
{ }で囲まれた範囲。forやifなども含む。

**関数スコープとは**
function などで宣言された範囲。

同じ名前で変数が再宣言されてしまうこと、関数スコープのみでしか使用できないので影響範囲が広いことからvarでの変数宣言は非推奨となっている。

## イベントの発火について
クリックした際にイベントが発火する処理を書きたい場合、HTMLの属性でイベントの発火を指定しない。

ダメなコード例
```HTML
<div id=”btn” onclick = “関数名() ”>
```
イベント発火の処理をしたい場合はaddeventlistener (‘ トリガーになる動作 ’ , 関数名() );　を使用してJavaScript側に記載する。

**理由**
HTMLは文章構造を定義するためのものなので、JavaScriptの動作を記載してしまうと役割が混合してしまい、メンテナンスや変更があった際にコードの可読性やメンテナンス性が下がるため。

## エラー文が出力される
**Cannot read properties of null**
「プロパティが不足しています」と言う時に表示されるエラー文。
指定したDOM要素（idやクラスなど）がない場合に発生する事が多い。
**解決策**
要素がちゃんと宣言されているか、有効なスコープ内に宣言がされているかを確認する。

**hoge.addEventListener is not a function**
表示されるケースは以下のようなケース。
```HTML
<input type="checkbox" name="hoge" id="dog">犬
<input type="checkbox" name="hoge" id="cat" checked>猫
```
```JavaScript
const hoge = document.getElementsByClassName('hoge');
hoge.addEventListener('change',check);
function check(){
  let cats = document.getElementById('cat');
  cats.checked = false;
}
```
 document.getElementsByClassNameでDOM要素を取得すると、idと違ってname属性は複数あるためHTMLcollectionという配列のような形で要素が取得されてしまうため、addEventListenerでイベント発火ができない。
**解決策**
document.getElementsByIdなど、複数の要素が取得できないものを使用する。

## DOM要素の追加について
inner HTMLは非推奨である。
**理由**
- フォームで入力された内容をinnerHTMLで追加されるとバグを起こす可能性がある。
- SQLインジェクションなどの危険性もある。
なぜなら……タグの中にタグが追加される危険性あるから。
**解決策**
createElementメソッドはJavaScriptのオブジェクト形式でHTML要素を作成するようにする。

配列や関数についてもまた別途まとめて行きたい所存です。