OmniAICreator/Qiita-1.07M huggingface dataset:
Symantec Endpoint Protectionに画像全消しされた件

# なにがあった

クチコミサイトを運営してた時、画像投稿もできたので、ウイルス対策でアップロードされた画像をSymantec Endpoint Protectionでウィルスチェックしていました。

このSymantec Endpoint Protectionがなかなか曲者で、3ヶ月に１回くらい謎の暴走をしてサーバのCPUを食い尽くす事案がありました。

それはまあ稀にだったので放置していたのですが、ある日・・・**数万件の口コミ画像が全て消えました**。

# どうしてそうなった

ファイルアップロード時のウィルスチェックに加えて、1日1回深夜にサーバ内のファイルをウィルスチェックするようにしていたのですが、ログを見てみると**全ての画像がウィルス扱いされて削除されていました**。

原因はわからないのですが、急に全ファイルウィルス感染したとは思えないので、それもSymantec Endpoint Protectionの暴走だったんじゃないかと思っています。

結局デイリーでとっているバックアップから復元するしかなく、バックアップ以降に投稿された画像はあきらめました。

# どうすればよかった

Symantec Endpoint Protectionの暴走なのでなんともできないのですが、とりあえず1日1回の全ファイルウィルスチェックは止めて、Symantecには問い合わせを投げておきました。

Symantecに限らず、セキュリティ対策ソフトは通常の通信までブロックしたり暴走しがちなので、利用時は要注意です。
就活では相手 (採用する側) の手の内を知れ

初めて各記事が技術的な内容じゃないのが申し訳ありません。
とあるアドベントカレンダーの記事を依頼されたので、それに合わせて就活 (しかも新卒) の豆テクニックを書いてみます。

技術的なことは学校で学んでいるでしょうから授業にまじめに取り組み、理解していれば就活的には問題ないでしょう。ということで__授業にはまじめに取り組み、理解していることを前提に__書きます。

アドベントカレンダーですからネタはシンプルにただひとつ、「相手 = 採用する側の手の内を知れ」です。

皆さんは○○ナビなどの就活サイトには登録してるし、検索で「面接のマナー」とか「一般常識練習問題」などのキーワードで「受ける側」の情報はたくさん収集してると思います。でも採用する側=企業が「こんなトンデモ面接があった」とか「採用したい○○な人材」とか「面接官はここを見る」のような情報は見てますか?　

このような情報はビジネスマン向けの雑誌、有名どころでは「週刊ダイヤモンド」「週刊東洋経済」「プレジデント」「日経ビジネス」あたりにあるのですが、オンラインでもある程度の情報を無料で得られます。
でもなぜか学生さんはこういう情報にアクセスしない人が多いんですよね。小難しく見えるからでしょうか。逆に言えばこれらのメディアに触れていれば他の人と差をつけられるかもしれません。また、小手先の知識やテクニックを得るだけでなく、普段の生活でも就職に有用な心構えができてくるのではないでしょうか。

ぜひのぞいてみてください。リンク置いてきますね。

+ [東洋経済オンライン][https://toyokeizai.net/]
+ [日経ビジネス電子版][https://business.nikkei.com/]
+ [ダイヤモンドオンライン][https://diamond.jp/] 
+ [プレジデントオンライン][https://president.jp/]

比較的わかりやすいなと思っているのが東洋経済オンラインです。ここは
[「キャリア・教育 > 就職四季報プラスワン」][https://toyokeizai.net/category/shushoku]
ていう就職関連のカテゴリがたてられているので情報を探しやすいと思います。

プレジデントは特に年齢層高めの管理者・経営者向けのイメージ (=より採用側の視点) がありましたが今はそうでもないか。

本気出すなら雑誌を定期購読するとか、ネットの有料版を購読するのもいいと思います。

では、メリークリスマス (今日が本当のクリスマスです)
Webの勉強はじめてみた その16～HTTP通信と通信bot作成編～

N予備校「プログラミング入門 Webアプリ」を受講しています。
今回は、第2章10節と11節になります。

<h2>HTTP通信</h2>
もっと具体的な内容だったりはたくさん情報があるのでそこで補完したい。

:::note
HTTPは、HyperText Transfer Protocolの略。
HTMLなどのHyperTextを受け渡すアプリケーション層のプロトコル。
:::

<h3>サーバーに接続して、GETメソッドを実行する</h3>
`nc nnn.ed.jp 80`
nnn.ed.jp サーバーの 80 番ポートに接続する

`GET / HTTP/1.1`
HTTPのGETメソッド。
`/`(ドキュメントルート)の情報をHTTPプロトコル バージョン1.1で取得するという要求(リクエスト)。

`400 Bad Request` がレスポンスとして返ってくる。

<h3>Ubuntu上でHTTPサーバーを立ち上げる</h3>
`python3 -m http.server 8000`
Pythonを利用してサーバーを立ち上げる。

`curl localhost:8000/index.html`
コンソールからアクセスする

<h3>ポートフォワード</h3>
```
config.vm.network "forwarded_port", guest: 8000, host: 8000
```
仮想化された Ubuntu のポートへの通信を、実際に使っている Windows や Mac のポートに転送する設定。
外から(guest)8000番でubuntuにアクセスされたら自分の(host)8000番に転送する。

`vagrant reload --provision`
vagrant を再起動するとブラウザから確認できるようになる。


ポート番号が同じだから余計にややこしいような気がする。
ただ言いたいことはなんとなくわかる。

<h2>bot作成</h2>
:::note
bot : 自動的に何かしらの処理を行ってくれるプログラム
RSS : Web サイトの見出しや要約を XML (Extensible Markup Language) というマークアップ言語で記述した形式
:::

```bash
#!/bin/bash
dirname="/home/vagrant/workspace/itunes-topsong-rss"
#ディレクトリ作成
mkdir -p $dirname
#現在時刻のファイル名
filename="${dirname}/hourly-topsong-`date +'%Y%m%d%H%M'`.xml"
#RSS XMLを取得
curl -s -o $filename -H "User-Agent: CrawlBot; xxxx@xxxx" https://itunes.apple.com/jp/rss/topsongs/limit=10/xml
echo Save to $filename
```

`mkdir -p` 
ディレクトリが既にある場合でもエラーを表示しない。

`${dirname}`
文字列の中で変数を使いたい場合、`${}`と記述する。

`date +日付のフォーマット`
バッククォートで囲うことでコマンドを実行できる。

```bash
nowdt=`date +'%Y%m%d%H%M'`
```

`curl -s`
転送情報などを出力しない

`curl -o`
実行結果を指定ファイルへ保存する

https://qiita.com/yasuhiroki/items/a569d3371a66e365316f

<h3>cronを使って自動化</h3>
:::note
プログラムを決められたスケジュールにあわせて自動実行してくれるプロセス
:::

`crontab -e`
cronの設定ファイルを編集する

```
* * * * * /home/vagrant/workspace/bot/itunes-topsong.sh
```
冒頭の5つの`*`がそれぞれ、
Minute(分) Hour(時間) DayOfMonth(日) Month(月) DayOfWeek(曜日) を指す。

:::note warn
crontab をオプションなしで実行した場合は、Ctrl + C で抜けること
:::

オプションなしで実行すると、

https://yassblog.blog.ss-blog.jp/2015-01-13-1

忘れたころにやってしまいそう。


<h2>まとめ</h2>
ネットワークについてはもっと掘り下げて勉強したい。

https://qiita.com/ryosuketter/items/ba54d595a31f4ad0a58f

12節以降はGitHubの使い方なので、記事にはしにくいかも。
年内には第2章終わらせたい。
WordPressのAWS環境からさくらレンタルサーバーのデータ移行

会社からテスト環境であるAWSのEC2に置かれたWordPress環境を本番環境であるさくらレンタルサーバーにデータを移行する際に鬼詰まりしたので共有します。
## 前提条件
さくらレンタルサーバーに契約して、さくらのURLやコントロールパネルやFTPの情報をもらっている。
## DBの移行
旧環境のDBをphpMyAdminなどからエクスポートします。
さくらレンタルサーバーのコントロールパネルからデータベースを新規作成します。
新規作成後はデータベース一覧から対象のDBのphpMyAdminに入って、そこから先ほどエクスポートしたSQLファイルをインポートします。
インポート後はデータを一部変更する必要があります。
wp_optionsテーブルのsiteurlとhomeが旧URLになっていると思うので、こちらを新URLに変更する。
![スクリーンショット 2021-12-23 15.16.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671576/19575d86-2873-d687-3fa3-764225236d85.png)

##　ファイルの移行
FTPソフトなどを使って、旧環境からローカルPC環境に保存する。
新環境に適応される為に2つのファイルを変更する。
#### .htaccess
AWSでテスト環境を構築した場合、下記の`BEGIN/END SAKURA Internet Inc.`の記述がない為、こちらを追加する必要がある。
これを追加しなかったため、本ッッ当に詰まりました！！
この記述はコントロールパネルからWordPressをインストールした際に.htaccessの中に生成されたものを流用しました。

```
# BEGIN SAKURA Internet Inc.
<IfModule mod_deflate.c>
    SetOutputFilter DEFLATE
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript
</IfModule>
<IfModule mod_expires.c>
    ExpiresActive On
    <FilesMatch "\.(css|js)$">
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
    <FilesMatch "\.(gif|jpe?g|png)$">
        ExpiresDefault "access plus 1 month"
    </FilesMatch>
</IfModule>
# END SAKURA Internet Inc.

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase / 
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L] 
</IfModule>
# END WordPress
```

## wp-config.php
ここはさくらのコンパネで作ったDBの情報を入れるだけです。
他のサイトにもよく解説されているので、難しくないです。
1点だけMySQLのホスト名の部分はデータベースサーバー名を入れます。
この部分はさくらのコンパネのデータベース詳細から確認する事ができます。

```
// ** MySQL 設定 - この情報はホスティング先から入手してください。 ** //
define( 'DB_NAME', '' );

/** MySQL データベースのユーザー名 */
define( 'DB_USER', '' );

/** MySQL データベースのパスワード */
define( 'DB_PASSWORD', '' );

/** MySQL のホスト名 */
define( 'DB_HOST', '' );

/** データベースのテーブルを作成する際のデータベースの文字セット */
define( 'DB_CHARSET', '' );

/** データベースの照合順序 (ほとんどの場合変更する必要はありません) */
define( 'DB_COLLATE', '' );
```

構築を進めた後に、新しいURLでサイトが正しく表示されないケースがあります。
その場合はこのファイルの最下部に下記のコードを入れるとうまくいきます。

```
update_option( 'siteurl', 'http://[サイトURL]' );
update_option( 'home', 'http://[サイトURL]' );
```

ファイルを変更した後は、新しい環境にFTP接続をして`/home/[サイト名]/www/`直下に先ほど保存したWPのファイルを置きます。

## エラー対応
以上で基本はWordPressの画面が表示されると思いますが、それでもサイトが表示されない場合があります。
その場合は以下の原因も調査してみるといいと思います。
・パーミッションの変更
・プラグインの一時無効化
BlenderのパーティクルをUnityに持ってくる方法

STYLYアドベントカレンダー24日目の記事です。

2021年のSTYLY NEWVIEW AWARDSに参加して、宮沢賢治の「やまなし」という童話を題材にした作品を制作しました。

https://twitter.com/kazuyaplus/status/1454776679692308487?s=20

この作品で優雅に泳いでいるモブの魚たちはあまり目立たない存在ですが、実はBlenderのパーティクルで魚の群れのシミュレーションをして、Unityに持ってきています。この記事では魚の群れ表現を実現するために調べたことをまとめたいと思います。

## 魚の群れの動きをBlenderでシミュレーション

「やまなし」ではカワセミの登場で魚が逃げ惑う表現を検討していたので、リアルな魚の動きをシミュレーションしたいと考えていました。Unityで魚の群れをつくりたいときは通常C#のプログラムで「Boid」の仕組みを作ったり、群れ制御のアセットを使ったりすると思うのですが、STYLYで実現するためにはPlayMakerというアセットが必要になるので他の方法があるかどうか調べたところ、Blenderのパーティクルでも実現することがわかりました。

Boid Particle
https://www.youtube.com/watch?v=J3TUPYVsxow

## Blenderのパーティクルをアニメーションに変換

BlenderのパーティクルはそのままUnityに持ってくることができません。そこで、パーティクルの動きをアニメーションに変換するベイク処理をします。Blenderは標準ではベイクをサポートしていないのですが、pythonを利用する方法がBlenderのStackExchangeに書いてありました。

https://blender.stackexchange.com/questions/4956/convert-particle-system-to-animated-meshes?answertab=active#tab-top

1. まず、Scriptingのタブを開いて上記のコードを貼り付けます。
2. 次に、パーティクルとして動かしたいオブジェクト（この場合は魚一匹）とパーティクルを選択します。
3. 2つのオブジェクトを選択した状態でスクリプトを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192685/ed2e1b2a-7668-b28f-b3df-fba357e1046a.png)

しばらく待つとパーティクルの動きがアニメーションとしてベイクされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192685/0a943537-318d-ac91-6eaf-b2861530b5f1.png)

出力はこのように設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192685/da9c1cdb-6214-6d2d-d804-b3c1ff9b800a.png)

## おわりに

演出の関係で魚が激しく動き回るようなモーションを使うことはありませんでしたが、BlenderのパーティクルをUnityに持ってくることができるとわかり、いろいろと試してみたいと思いました。
Raspberry Piのセットアップ(これで完結)（初心者）



#はじめに
Raspberry Piってご存知ですか？
ラズパイともいうのですが、
~~簡単に言うと安価で手に入って、IoTするのにはもってこいの機械なんですけど~~

「プログラミングを行い、電子部品を本体に接続することによってさまざまな機能を実装できるラズベリーパイは、IoT開発を手軽に体験できるツール」です。

ここでは、**組み立て・セットアップ・そのあと使えるようにするまでの、ラズパイの導入を3サイト(この記事と途中のURL)で完結**できます。
ここに書いてるURLだけでしたので、難民になってる人ぜひ見てください。

簡単に作れそうだけど、種類が多くて使う？使わない？って部品もあって
組み立て方法や、設定の時にすると良いことを書きます。


#組み立てる
セットに入っているものを組み立てる

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502903/f33de717-751c-3d82-8d5c-43be8b51c6c4.png">

セット内容はこんな感じです。


組み立ててると、これ何って部品が2つが出てきます。しかし、今は置いておきましょう。使いません。

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502903/5b78abae-c6ad-3411-e79a-895a1324526c.png">

ネジを回してこんな感じになるまで組み立てます。
<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502903/4e430e01-8a55-eaea-e2b7-e64b7d30ab72.png">



#初期設定
このサイトでOSの初期設定をしてきてください。


「自国の設定
パスワードの再設定
無線LANの設定」
を設定します。
[Raspberry PiのセットアップとOSの初期設定](https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-setup/)




<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502903/a432c4b7-1cca-525d-e124-866addf37ccf.png">



#しておいたほうがいいこと

**・VNC**
ずっとラズパイの前で座らなくても自分のパソコンからラズパイを触れます。
[VNCでRaspberry Piにリモートデスクトップ接続 (Windows/Mac/Linux対応)](https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-vnc/)




**・バックアップ**
これをしてないで、データが飛んでまた何日かかけて設定しないといけなくなるので、早いうちにしておきましょう！！(DDコマンド)

#SSH接続
これをするとマウス、キーボード、ディスプレイが必要なくなくなります。
Raspberry PiのIPアドレスを
固定する

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502903/48c006ca-298a-a16e-ea67-aff56ebcde68.png">








#最後に
今年の夏(約半年前)にしたことなので、思い出して書いてみました。
私は組み立てを迷ったので参考にしてくれたら嬉しいです。
今研究室でラズパイを使って研究もしているのですが、これからも頑張っていきましょう！

良いクリスマスイブを！！

#参考文献

[Raspberry Pi（ラズベリーパイ）とは？IoT開発ができるラズパイの使い方](https://www.internetacademy.jp/it/programming/programming-basic/how-to-use-raspberry-pi.html)
エンジニアの悪夢、緊急コールを実装する

# はじめに

こんにちは。
エンジニアの卵のなりかけです。

緊急コールとかやめて欲しいですよね（夜勤するから夜勤手当てください）。
「実装しようとしたんですが、工数めちゃくちゃかかるので無理です！」って言いたかったのに、簡単に作れちゃった....

注）
ここでの緊急コールは「エラー通知飛んでるから携帯見ろや」的なものを指します。
要するに、この電話がかかってきたら夜間だろうが休暇中だろうがSlackのエラー関連スレッド開いてねってことです。

# 作り方

設定ファイルを作る

```json:setting.json
{
    "on":[0, 1, 2, 3, 4],
    "from":22,
    "end":7,
    "to":"XXXXXXXXXX"
}

```

- `on`：緊急コールを行う曜日（0から月曜日で、6が日曜日）
- `from`：緊急コール機能開始時刻（午後10時からであれば22）
- `end`：緊急コール機能終了時刻（午前7時までであれば7）
- `to`：電話かける先（e.164フォーマット）

定数を設定する

```py:const.py
"""定数記述ファイル"""

ACCOUNT_SID = "XXXXXXXXXX"
AUTH_TOKEN = "YYYYYYYYYY"

FROM_NUMBER = "+00000000000"

```

関数を作る

```py:main.py
"""緊急コールを仕込む"""
# 定数ファイル読み込み
import const

from datetime import datetime, timedelta, timezone
import json
from twilio.rest import Client

setting_file_path = "./setting.json"

def isOnToday(day_number, on_days_list):
    """
    動作する日か確認
    """
    if day_number in on_days_list:
        return True
    return False

def isWorkignHour(current_hour, start_hour, end_hour):
    """稼働時間か確認"""
    if current_hour >= start_hour or current_hour <= end_hour:
        return True
    return False

def emergecyCall(setting_file_path):
    """緊急時にコールする"""

    with open(setting_file_path) as f:
        settings = json.load(f)

    JST = timezone(timedelta(hours=+9), 'JST')
    dt_now = datetime.now(JST)

    if not isOnToday(dt_now.weekday(), settings['on']):
        return
    if not isWorkignHour(dt_now.hour, settings['from'], settings['end']):
        return

    account_sid = const.ACCOUNT_SID # Your Account SID from www.twilio.com/console
    auth_token  = const.AUTH_TOKEN# Your Auth Token from www.twilio.com/console

    client = Client(account_sid, auth_token)

    client.calls.create(
        to=settings['to'],
        from_=const.FROM_NUMBER,
        url="http://demo.twilio.com/docs/voice.xml"
    )

    return


if __name__ == "__main__":
    emergecyCall(setting_file_path)

```

この`emergecyCall`関数をトライキャッチ文に仕込むとか、監視Botから呼び出すとかすれば緊急コールの完成です

# 最後に

なぜ悪夢を現実化してしまったのか...
MovableTypeのmoduleのincludeについて

MovableTypeのmoduleのincludeについて
===

# include方法

```
<$MTInclude module="var"$>
```

# 引数を渡すことができる

```
<$MTInclude module="var"$ title="一覧ページ | ">
```

引数は、呼び出されたmodule側で、以下のように使う

```
page_title=<MTVar name="title">メインページ
```

# その他の記事

https://qiita.com/TomoakiNagahara/items/2f91d505c9f555719643
Macでランダムな文字列をサクッとつくる

# 背景・目的
テストデータなど作成する際に、ランダムな文字列が欲しくなるのでメモしておく。

## openssl
- 環境のセットアップなしでMacでサクッと作れる。

```
$ openssl rand -base64 12
CNnu8tZYagKCdV7I

$ openssl rand -base64 15
wsPE5ZItiWKnuQfX3GI6
```
AWSで機械学習モデルを動かしてみる

機械学習について学びたいと思い、手始めにAWSの10分チュートリアルを参照しながら機械学習モデルをコーディングしてみました。

<<参考>>Amazon SageMaker を使用して機械学習モデルを構築、トレーニング、デプロイ

https://aws.amazon.com/jp/getting-started/hands-on/build-train-deploy-machine-learning-model-sagemaker/

このチュートリアルに沿ってPythonコードを書くと銀行の顧客データからその顧客が新規商品の契約をしてくれるかどうかを予測する機械学習モデルを簡単に作成できます。

ただ、チュートリアルに沿うだけだと一体何をしているのか分かりづらいので、自分なりにこのコードがしていることをまとめてみましたので参考にしていただければと思います。

#具体的にやっていること

![AWS-SageMaker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1926542/79716881-0859-f664-e123-78a959a2089d.png)

まず最初にAWS環境にSageMaker環境を構築、その上でjupyter notebookをインストールするという準備が必要になります。

:::note
SageMaker：AWSで利用できる機械学習環境
Jupyter notebook：pythonをブラウザ上でコーディングしながらインタラクティブに実行できる環境を提供
:::

##①顧客サンプルデータをpandasデータに変換する
無償で提供されている顧客サンプルデータ(CSV)をS3にダウンロードした上で、pandasライブラリを使って加工しやすいデータに変換します。

:::note
pandas：機械学習用にデータを扱いやすくするライブラリ
:::

##②③データを学習用データとテスト用データに分離
機械学習のためには学習用データが必要になります。
また、学習完了後、実際に学習できているかを確認するためのテストデータも必要になります。
そのため、①で取り込んだデータをnumpyライブラリを利用して、学習用データとテスト用データに分離します。

:::note
numpy：Pythonで利用できる機械学習用のデータ加工ライブラリ
:::

##④機械学習モジュールを作成し、学習用データで学習させる
XGBoostベースの機械学習モジュールを作成し、学習用データで学習させます。

:::note
XGBoost：様々な機械学習アルゴリズムを簡単に利用できるようにしたフレームワーク
:::

##⑤学習がうまくいったかどうかテストする
学習が完了したら、学習がうまくいったかどうかをテスト用データによって検証します。

##⑥テスト結果を検証する
実際にどれくらいの精度で学習ができていたかを成功率という形で表示させます。


#まとめ
こうしたチュートリアルは、手順に沿って作業をしてみるというだけでなく、自分なりに実際を何が行われたのかをまとめてみることで初めてみについたと言えるような気がします。これからもいくつかサンプルコーディングしながら、理解を深めつつ仕事に活かしていきたいと思います。
[HTTP] HTTPリクエスト

# HTTPリクエスト
HTTPリクエストは、クライアント側で作成してサーバに送る情報。
HTTPリクエストは以下の3つの要素から構成される。
①**HTTPリクエスト**
②**HTTPヘッダー**
③**ボディ**
**HTTPヘッダー**と**ボディ**の間には1行空行が入る。

## HTTPリクエスト
リクエストは、サーバに送るリクエストの種類を指定する。
リクエストの構成は、**メソッド**・**対象のURI**・**HTTPのバージョン**の3つの要素からなる。

```:例
GET /index.html HTTP/1.1
```
メソッドには、```GET```、```POST```、```HEAD```などがある。

## HTTPヘッダー
HTTPヘッダーは、リクエストに関する補足情報（ブラウザやデータタイプなど）を指定する。
HTTPヘッダーの書式は、以下となる。

```
<フィールド名> : <内容>
```
ヘッダーのフィールドについては、次のページを参照。[MDNのHTTPヘッダーのページ](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers)

# ボディ
主に、POSTメソッド使用時のパラメータなどを必要に応じて記載する。

```:POSTのパラメータ記載例
keyword1=value1&keyword2=value2
```
例のように```<キー>=<値>```の形で書き、パラメータが複数の場合は```&```で繋ぐ。
【Flutter/Dart】mocktailを使用したテストで　type 'Null' is not a subtype of type 'Future<void>'　のエラーが出た場合の対処法


Flutterのテストにおいて、タイトルのエラーが出た際に手間取ったので、対処方法を備忘録として残します！

### 環境

```dart
-> % flutter --version
Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Tools • Dart 2.14.4
```

今回はテストライブラリである`mocktail` を使用します。

```dart
 mocktail: 0.2.0
```

### エラー内容

以下に例をあげたような、Future メソッドのテストしようとしたところ

```dart

// メソッド
 Future<void> testMethod(String id) async {
    await _testRepository.test(id);
  }

// テストコード（メソッドをモック化する）
 group('testMethod()', () {
    test(
      'should test.',
      () async {
        when(() =>_testRepository.test('test_0001'))
           .thenAnswer((_) async {});

       // expect() の処理は省略します。     
      },
    );


```

エラーが発生、テストが落ちるようになりました。

```dart

type 'Null' is not a subtype of type 'Future<void>'

```

### 解決方法

mocktailのドキュメントやstack overflow に記載されていた方法をいくつか試す。

最終的に、以下のようにモック化すればテストがパスするようになりました！

```dart
 group('testMethod()', () {
    test(
      'should test.',
      () async
// 引数に任意のIDを指定していたところをany()に修正
        when(() =>_testRepository.test(any()))
            .thenAnswer((_) async {});
          ),
        });
      },
    );

// ドキュメントには空文字指定の方法が紹介されていましたが、空文字だとテストが通りませんでした…

    group('testMethod()', () {
    test(
      'should test.',
      () async {
        when(() =>_testRepository.test(''))
            .thenAnswer((_) async {});
          ),
        });
      },
    );

```


### 参考

https://pub.dev/packages/mocktail

https://github.com/felangel/mocktail/issues/90


https://stackoverflow.com/questions/67899067/type-null-is-not-a-subtype-of-type-futureresponse-when-testing-mocked-http
Youtube liveのコメントで安価出来るソフト作った話

# 簡易自己紹介
一般メカトロ高専生の**Kazuryu**です 
ロボコンで制御を少しかじってました [Twitter](https://twitter.com/hello_im_kzry)

# TL;DR
- Youtube liveのコメントで安価出来るソフト(AnkaChan☆)を作ったよ！  
- Python Onlyで書いたよ！

# AnkaChan☆紹介
![cont.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379533/53c212ae-659e-c942-0077-c29f34062cc9.png)
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379533/393620a5-ed1d-3ac3-54a7-3240ccec1c52.png)
配布ページは[こちら](https://github.com/Kazuryu0907/ANKAChan/releases/tag/v1.0.0)(GitHub)

# 機能紹介
- Youtube liveのURLから簡単に安価が出来る！
- 安価の範囲選択や、再安価の機能もある！
- 絵師さんとかどうでしょう ~~\(ういママとか使ってくれないかな\(ﾊﾅﾎｼﾞ~~

:::note warn
追記（2025-06-06）
[ChumuNote](https://x.com/tmgnrei)さん[配信](https://www.youtube.com/live/e-MgupCEW4Q?si=RHzAbXJQCYpymf4-)で使用していただきありがとうございます・・・
こんなクソキモイ記事ですいません・・・
:::

# 安価について
安価スレとは2ch用語で、簡単に言うとスレ主のこれからの行動・展開を**ほかのユーザーの書き込みに決めてもらう**というものである 
参考記事 [なんj用語「安価スレとは？」](https://gimon-sukkiri.jp/anka-sure/)

# 技術的要素
## 使用したライブラリ
- Threading(コメント取得の並列処理)
- Tkinter(GUI作成) --Qtのほうがよかったかな？
- Pytchat(コメント取得用ライブラリ) [GitHub](https://github.com/taizan-hokuto/pytchat)
- Pyinstaller(exe化)
- PyGithub(アップデート機能)

## ソースコード
GitHubにあります [こちら](https://github.com/Kazuryu0907/ANKAChan)

## 苦労点
- Threadingの定期実行や並列処理の理解が甘く、実装に時間がかかった。まだ理解が甘い点がある。
- 再安価や削除用にデータとWindowオブジェクトの紐づけがややこしくなり、実装に時間がかかった(今回はdatetimeのsha256で紐づけ)

# おわりに
Outputが大事って偉い人が言ってた
Flask + SQLAlchemy でよさそうなトランザクション管理

## よくあるやつ

[Transactions using Flask-SQLAlchemy](https://saswatac.medium.com/transactions-using-flask-sqlalchemy-f5b26d23e2f2) にあるように

```python
from contextlib import contextmanager
@contextmanager
def transaction():
    try:
        yield
        db.session.commit()
    except Exception:
        db.session.rollback()
        raise
```

を作成して、それを

```python
def hogehoge():
  with transaction():
    ...
```

と処理を書く。

記述ところが少しなら良いけど、色んなところにあると、少しめんどくさい。

## よさそうなやつ

```python
from functools import wraps


def transactional(func):
    @wraps(func)
    def decorator(*args, **kwargs):
        if db.session.info["in_transaction"]:
            return func(*args, **kwargs)
        else:
            db.session.info["in_transaction"] = True
            try:
                result = func(*args, **kwargs)
                db.session.commit()
                return result
            except Exception as e:
                db.session.rollback()
                raise e
            finally:
                db.session.info["in_transaction"] = False

    return decorator
```

`@transactional`アノテーションを用意して、それをトランザクション管理したいところに付与する。

```python
@transactional
def hogehoge():
  ...
```

基本的に、トランザクション管理する時ってfunction単位でやると思うので、
個人的にはこっちの方が良いなって思う。
gcp private service connect

# terraformでの構成例


まずは動くものを
https://gist.github.com/mhiroe/e4c10cd5731cdfb9f57ae7d9e9d41e21

Aレコードのみの endpointもありますが、構成上区別つけないほうがシンプルになるので、すべてzoneを用意するようにしています
併せて、リゾルバを利用してendpointのipを引いてこれるようにしています

# private service connect の使い所

private.googleapis.com や restricted.rooleapis.com など、内部ネットワーク経由の googleapiアクセス手段が用意されているが、これらが利用できない場合がある

- オンプレ(インターネットとの接続なし)とGCPをVPNなどでつなぎこみ、オンプレ環境からGoogleAPIを利用したい場合、間の経路にFireWallが挟まっていると、FireWallで 199.36.153.x のパケットがドロップされる、というような事象が見られた

- 同様に、オンプレ環境から GoogleIdentityによる認証を行いたい場合 accounts.google.com へのアクセスが発生するが、
private.googleapis.com や restricted.rooleapis.com では エンドポイントが用意されておらず利用できない、ということがあった。

- 上記のようなgoogleapi以外にも、gcpプロジェクトの内向きに APIエンドポイントを置く(インターネットを経由させない) ことが可能になる



# psc endpoint が生まれた背景について

1. 情報漏洩を防ぎたいという要望があった (外部からのアクセス、内部からの漏洩)
2 外部からのアクセスは VPC SC で制限できる
3. 内部から外部への漏洩も VPC SC である程度守れるが、たとえば Box へのアップロードは防げない
4. そのため、そもそもインターネットへのアクセスを全部遮断したい
5. しかしそうすると(パブリックアドレスを持つ) Google APIs にもアクセスできなくなってしまう
6. そこで、Google APIs のうち VPC SC に対応してるサービスにだけアクセスできる restricted.google.apis.com 199.36.153.4/30 というエンドポイントを新設 (VPC SC に対応してるサービスに限定した理由は、そうしないとそのサービス経由で漏洩される危険性があるため。たとえば、X というサービスが VPC SC に対応しておらず、かつデータをアップロード/ダウンロードできるサービスの場合、そこを経由してデータの漏洩の可能性が生まれてしまう。)
7.VPC SC は使わないが、Google API に専用線やVPN経由でアクセスしたい(インターネットを経由したくない)、かつ VPC SC に対応してない API も使いたい、という要望が多かったため、private.googleapis.com 199.36.153.8/30 というエンドポイントも追加で作られた
8. 199.36.153.x というグローバル IP アドレスを使いたくない(グローバルな経路をオンプレミスに流したくない)という要望(と、そもそもプライベートなAPIエンドポイントを公開したいという要望)を満たすために、Private Service Connect というサービスが作られた
Vue.js2で作ったサイトがIE11で表示されなくなった調査の軌跡

# 前提

`pakcage.json`に`browserslist`は指定してあって、ある時点まではIE11でも動いていた。

# エラーの原因を特定

![{5DB2ECAB-E045-4E79-BEA6-90E21F38D16E}.tmp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96507/2013462c-b7d7-3262-2403-da20391f601b.png)

こちらの記事と似たような事象だと思ったが。

https://qiita.com/maroKanatani/items/fbb7c96b5c49edd81339

キャプチャのようにエラーがバンドルされて１つになったJS内で起きていたためどこに原因があるのか全く分からなかった。

そこでまずはこちらの記事を参考に読み込んでいるモジュール単位にビルド後のファイルを分割するようにした。

https://jacklyons.me/blog/how-to-fix-vuejs-not-working-in-ie11/

```javascript:vue.config.js
    configureWebpack: {
      optimization: {
        runtimeChunk: 'single',
          splitChunks: {
            chunks: 'all',
            maxInitialRequests: Infinity,
            minSize: 0,
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name(module) {
                // get the name. E.g. node_modules/packageName/not/this/part.js
                // or node_modules/packageName
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

                // npm package names are URL-safe, but some servers don't like @ symbols
                return `npm.${packageName.replace('@', '')}`;
              }
            }
          }
        }
      }
    }
```

# 自作のVue.jsコンポーネントの構文エラー

上記の対応を行うと、Minifyされていないソースコード位置にジャンプできるようになったので読んでみると、自作のチェックボックスのコンポーネントを読み込むところでエラーになっていた。

こちらの記事を参考にstrictモードを切ってみようとするもうまくいかず。

https://qiita.com/tsunogai/items/e39a28e058f4aed69bb9

こちらの投稿にチェックボックスで`v-model`と`checked`を一緒に使ってるとエラーになると書かれていたので試しに消してみるとエラー箇所が変わった。

https://stackoverflow.com/questions/50512933/vue-js-multiple-definitions-of-a-property-not-allowed-in-strict-mode

# IE11非対応のライブラリのトランスパイル

コンポーネントのエラーが解消したところで別のエラーが出るようになった。
`node_modules`配下にインストールされているライブラリはデフォルトではトランスパイルの対象とならないため、IE11非対応のライブラリの場合エラーになるとのこと。

https://minerva.mamansoft.net/Vuetify/IE11%E3%81%A7%E5%8B%95%E3%81%8F%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B

バンドルを抑制したことでライブラリごとのjsファイルが生成されるようになり、どのライブラリでエラーが出ているのか確認できたので、これを指定。
今回は`package.json`に書かれているライブラリと`package-lock.json`に書かれている依存ライブラリにエラーがあった。

```javascript:vue.config.js
  },
  transpileDependencies: ['library1', 'library2'],
  configureWebpack: {
```

---

この２つの対処をしたところ無事表示されるようになった（早くIEなくなってくれ…）。

# 追記

## スライダープラグインでエラー
一部のページでIEの時だけ下記エラーが発生するようになった。

```
TypeError: Invalid attempt to destructure non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.
```

こちらは先ほどの手順ではエラーのソースコード位置が特定できなかったため、原因の特定に難儀したが

https://tech.arms-soft.co.jp/entry/2021/03/24/090000

こちらの記事と同じような状況だと思ったので、エラーの出るページ内で使っていた `vue-slick` および `vue-slick-carousel` のプラグインを `transpileDependencies` に追加したところ動作するようになった。
Salesfroceのキャンペーンメンバーオブジェクトで使用できる数式項目を作成してみた

Salesfroceにはキャンペーンメンバーオブジェクトがあります。

キャンペーンメンバーオブジェクトにあるデフォルト項目は
・姓
と
・名
で、姓名両方くっついた項目はございません。

姓名両方くっついた項目の数式は下記となります

（数式項目の項目型はテキストです）
LastName + FirstName

しかしながら↑ではあくまで姓名くっついただけなので、リンクがないです。
そのため、例えば上記数式項目をレポートやダッシュボードで表示してもリンクがないのでクリックしてレポートからレコードへ遷移することはできません。

そこで、以下の数式を作成すれば、数式項目からクリックしてレコードに遷移が可能です

HYPERLINK("https://〇〇〇.lightning.force.com/lightning/r/Account/"+Contact.AccountId+"/view", LastName + FirstName)

※〇〇〇の部分は「ドメイン」のためお使いのSalesforceの環境により異なります。
※今回は個人取引先へのレコードのみの遷移を想定しています。

上記数式であれば、リンクをクリックすれば個人取引先レコードに遷移可能です。

今回に限らずHYPERLINK関数を使った数式項目では、レポートやレコードから別のページに遷移可能な便利関数です。

ぜひ使ってみてください。
JGAURORA A3S 3Dプリンタ スライサ設定(Cura)をし印刷するまで

初めに、JGAURORA A3Sを購入し付属しているSDカードに入っているデータは古いので最新のものをダウンロードします。
SDカードの購入時の中身はこんな感じです。

![スクリーンショット 2021-12-24 143541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/8e790e14-398a-89a9-bcd4-88a8f4318fdb.png)


[こちら](https://ultimaker.com/software/ultimaker-cura) から Ultimaker Cura の最新版をダウンロードしインストール
![スクリーンショット 2021-12-24 14.28.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/1bd455be-de7d-4266-db99-94209d538e11.png)

設定を進めていくとNetworkedプリンターの設定が出てきます。今回のプリンターはネットワークに対応していないのでnonNetworkedプリンターを選択し下までスクロールすると >JGAurora が出てくるので JGAurora A3S を選択すればOKです。

![スクリーンショット 2021-12-24 14.49.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/119059c4-9eef-f03b-7b3e-9476cb5b5e7b.png)

![スクリーンショット 2021-12-24 14.49.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/9de9481e-764b-5afe-2cd4-51f32f2f91e5.png)


印刷データを作成、今回Fusion360で作成し stl で出力
![スクリーンショット 2021-12-24 15.08.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/8428f8b1-ebd0-7d1c-e17f-0023a9ecd5da.png)

Ultimaker Cura を使って　STLで出力した3Dデータを 3Dプリンターで印刷に必要な.gcodeに変換します。Ultimaker Cura でサポートしているのは以下の形式のようです。
![スクリーンショット 2021-12-24 15.10.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/7d56a5b7-a671-ee10-4505-8bb1d1a9ab28.png)


デフォルト設定でスライスしてみましたが厚み1cmで2時間ほどかかります。この時間は印刷精度(0.1~0.4)を変更すれば印刷解像度と引き換えに大幅に改善しますので試してみてください。
![スクリーンショット 2021-12-24 15.11.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/ae1d8478-3873-f7b8-4dc2-2123953e9d04.png)
![スクリーンショット 2021-12-24 15.11.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363815/7d5fa30f-cc61-cc56-4a96-f113a3fb05f0.png)
AWS Certified Solutions Architect - Professional (SAP)取得時勉強法 x Notion

# はじめに
Qiitaには多くのAWS資格試験の勉強方法やサービスの紹介はあるので僕は、Notionを使って実際にAWS Solutions Architect - Professional (SAP)の勉強をして資格を取得したのでその際にどのようにNotionを利用したかを共有します。

https://www.credly.com/badges/ae367acb-5669-4c38-a1cd-4a81ed5d6916/public_url

# Notionとは？？
いろいろ出来る情報集約ツールです。

https://www.itmedia.co.jp/news/articles/2110/13/news100.html

# 勉強に使ったサービス
今回はこのAWS WEB問題集を中心に利用して勉強しました。
この問題集は7問で1セットになっていて隙間時間に勉強出来るので今回はこれを利用しました。

https://aws.koiwaclub.com/exam/sap/

問題集とか勉強法とかこの記事確認あたりこれらの記事を参考にしてます。

https://qiita.com/saito_ry/items/2fccda49e970c30927c2

https://qiita.com/kabik/items/7367639900bae46a9cae

# AWS勉強方法 x Notion
僕のSAP勉強用のNotionページです。
勉強に利用している問題集のページを上部に表示して問題の総数をその下に表示して全体の残量を把握できるようにしてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1985649/b727f850-4684-d40a-21b2-1ae618aacdbb.png)

## 利用方法
問題集を解いていき終わったら、Doneにチェック！このページにはDoneにチェックされたものは表示されない様にしています。
（どこまでやったっけ？がなくなります。）
DBの最下部にはUncheckの数量を表示して残数を表示。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1985649/aeae9731-2802-cb3b-af3a-6ac49cdc85b6.png)

## 間違えたところをメモ
Notionでは1項目ごとにPageになっていてその問題集を解いたときにわからなかった部分やつまずいたものをメモしておきます。
Pointにはその問題での正答率を入れています。
一覧で表示した際に得点を確認して低いところが苦手なところなので、一通り実施すると苦手が把握できるノートが出来上がります！

自分で苦手だと思ったところはプロパティにメモを残しておくと一覧で表示した際に確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1985649/07f3d7f4-25eb-1c62-5e53-2c4b1d3ad770.png)

## 復習
これをもとに2回ほど繰り返し。

## 試験直前
苦手な部分だけを集中的に見直し。

# 最後に
Notionを使うと自由に設定が可能なので、自分に合ったフォーマットを使って勉強を進めることが出来ます。
それだけでも勉強へのモチベーションが上がります。
簡単に列を追加したり、プロパティを追加できるので作っていて楽しいです。
ですが、、若干痒い所に手が届かないことがあるので凝るといろいろ設定を積み重ねていくことになるので自分に合った丁度いい設定を見つけてみてください！
七つの大罪：怠惰（詰めの甘いローカルテスト）

# 懺悔
詰めの甘いローカルテストをしたせいで、ちゃんと確認しておけば余裕で未然に防げた（テスト回す前に修正できた）のに、テスターに無駄な時間を使わせてしまった
反省してまーす
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690270/a60a5c73-1099-b415-9312-2379e6917fbc.png)

# 言い訳
弊部署のローカルテストはC0（命令網羅）まで行うこととされている
でも画面表示部分だったらPCとSP（**chrome**）で表示ちゃんとされてるかの確認だけでいいよね！

# 何があった
safariでデザイン崩れがあった

# どうしてそうなった
使用していたcssプロパティがsafari対応してなかった

- [writing-mode](https://developer.mozilla.org/ja/docs/Web/CSS/writing-mode)

なぜか`writing-mode`のとある値が効かない。。。
しかもsafari対応させるのにちょうどいいプロパティ見つからず、結構苦戦した
writing-modeは古いsafariでも`-webkit-`拡張で効くはずだったけど効かず、結局htmlタグをネストしてそのネストしたタグに`writing-mode`プロパティを使用した（解決した）

# どうすればよかった
~~google大好きマンだったのが仇になった~~
ちゃんとsafariもセットで確認しておけばよかった

**たかが画面表示テスト、されど画面表示テスト**
シェアの高めなブラウザ[^1]はセットで確認した方が後々の修正指摘が少なくて済むかも
[^1]: ただしIE、テメーはダメだ

# 最後に
キングかわいいよキング
https://7-taizai.net/character/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690270/359349be-de71-4884-308a-ab0526c0be82.png)
【python】dict変数に苦労した（１）

2021年8月からAWSのLAMBDAとdjangoでpythonの仕事をやり始めたんですが、これまでPHPばかりに偏ってたものでpythonに慣れるのに結構苦労した事の中で、dict変数があったのでその辺の事を書いてみます。  

###１.PHPだと連想配列は簡単に作れたが、dictは宣言が無いとエラーになって勝手に作ってくれない
PHPでは、自分の必要な時に

```php
$sample1["hoge"] = "aaabbbccc";
```
のように書けば、配列変数\$sample1が作成され、$sample1["hoge"]という連想配列が作成され、aaabbbcccを代入してくれる。

pythonのdict変数でこれをやると、NameErrorとなってしまう。

```
C:\project\python_test>python3 sample1.py
Traceback (most recent call last):
  File "sample1.py", line 2, in <module>
    sample1["hoge"]  = "aaabbbccc"
NameError: name 'sample1' is not defined
```
通常の変数は宣言が必要ないのに、dictやlist変数になると、途端に話が変わってくる事に最初はとまどった。
以下のように書くと正常に実行できる。

```py
sample1 = {"hoge":""}
sample1["hoge"]  = "aaabbbccc"
```
ここで、覚えたての頃は、sample1 = {"hoge":""}と書くのかsample1 = dict(hoge="")と書くのがいいのか？といった表記で悩んだが、どっちでもいいというのがわかった。

##２.連想配列のキーを変数で渡したい時
次に、連想配列のキーを変数で渡したい時

```php
$keystr = "hoge";
$samples1[$keystr] = "aaabbbccc";
```
PHPでは定数 or 変数で連想配列のキーを渡したい時にさほど違いはないが、dictも同様に

```py
keystr = "hoge"
sample1 = {keystr:""}
sample1[keystr]  = "aaabbbccc"
```
で、同じ操作を実現可能です。1点気を付けることがあるとすれば、keystrを他のobjectから取り出した値のような場合に、必ず文字列になってるとは限らないので、str()関数で文字列化しておくと安全です。

##３.連想配列に複数のキーを変数で渡して代入したい時
最初に一番悩んだ部分がここだった。
というのは、jsonに変換した際に、
{"hoge1":"a","hoge2":"a"}
となってほしかった。
PHPでこういう事をしたかった。

```php
$sample1 = array();
$keylist = array("hoge1","hoge2");
foreach($keylist as $keystr)
{
    $sample1[$keystr] = "a";
}
```
これと同じことをpythonのdictでやろうとするとPHPと同様に

```py
keylist = ["hoge1","hoge2"]
value = "test"
sample1 = {}
for keystr in keylist:
    sample1[keystr] = value
```
先にsample1 = {}とsample1がdict変数であることを定義しておけば問題ない。

４.既に中身の入ったdict変数に要素を追加する時

```php
$sample1 = array("hoge1"=>"test");
$keystr = "hoge2";
$value = "test2";
$sample1[$keystr] = $value;
```
この時もほぼPHPと同様な方法で代入できる。

```py
keystr = "hoge2"
value = "test2"
sample1 = {"hoge1":"test"}
sample1[keystr] = value
または
sample1.update({keystr:value})
```
1回目はここまで。
磁気浮上キースイッチに改造する

#注意事項
この記事を読んで、同じような作業をして、ケガやその他、損害等が生じる危険性があります。
作業は**自己責任**でしてください。
磁石の危険性を理解した上で、下記の記事を読んでください。
購入した磁石の取り扱いの説明と、注意事項をよく読んで、十分に理解してから作業をしてください。
反発した磁石が飛ぶ恐れがあるので、とても小さくて、一度、無くすと確実に見つけられなくなります。
飛んだ磁石が目に入り、ケガの恐れがあります。安全のために保護メガネの使用を検討してください。
#キーボードスイッチの分解と測定
キーボードスイッチを分解して、ステムの寸法を測定していた。
Gateron Pre-lubed 3 Pins Linear　Sliver
ピン径　φ1.9
ボス径　約φ4.1　　ノギスで測れず、Gateron Caps switcheのスプリングが入った　
ふと、リング型の磁石があれば、スプリングを磁気反発に置き換えができるかも。
#磁石の選定
特注になるかもと、思いながら既成品がないか検索。
ありました。
株式会社マグファイン
NR0103
ネオジム Φ4mm X Φ2mm X 1.5mm
NR0097
ネオジム Φ4mm X Φ2mm X 4mm

磁石の補足事項
内径公差がφ2±0.1のためマイナス公差の磁石は
ステムのピンに入らないか擦れるため
スムーズに入る磁石だけを選んで使用してください。

#磁石の組み込み
キースイッチを分解して、スプリングと磁石を入れ替える。
ステムにNR0103をはめる。１０個買ったうちの1個だけ、ステムから抜けなくなった。公差の小さい個体は抜けなく場合もある。
ボトムケースには、そのままでは入らないので、センターのボスピンを削る。
φ4のドリルで平らになるまで削る。先端の刃先分はすり鉢状に凹むが、今回は影響なしと判断しそのまま進める。
ステムにNR0103をつけ、反発するようにNR0097をはめたまま、ボトムケースに収める。
ペンなどで、ステムを押さえながら、トップカバーをペンに通して、嵌め合わせる。
接着材は使用をしなくても、ステムのピンがガイドになり磁石が外れることは無い。
#スイッチの動作確認
磁石の反発を利用してるため、押し下げるごとに押し込み強さが強くなる。
簡易的に強さを測る。
押下圧が異なるスイッチを用意して、向か合わせでスイッチを押しあう。
下がりはじめ　２５ｇ以下　２５ｇのスイッチより先に下がる。
動作点　　　　３５ｇ　　　３５ｇのスイッチが押し下がるときに、動作する。
下がり終り　　７０ｇ以上　７０ｇのスイッチが先に下がりきる。
例えて言えばトランポリンなように感触。押せば押した分が返ってくる感じ。
軽い押し込みなら、底付きをせずにスイッチを戻せる。
ステム形状が同じスイッチなら、スプリングをマグネットに変えることが出来そうです。
AVG AntiVirus で URL:Mal が出てサイトにアクセスできなくなった時の対応

# URL: Mal 出てアクセスできない！

ある日、クライアント様ご利用の某超大手のクラウドサービスのコンソールにアクセスできなくなった。

![Screen Shot 2021-12-24 at 11.43.19 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/1544e7ec-1bd4-05b9-d5a2-7f3110001f0c.png)

なんか感染してる？セキュリティ的に、その某超大手は信頼できそう？クライアント様は…どうだろう。なので、きちんと対応することにした。

## 再現環境
- macOS Catalina Version 10.15.7
- AVG AntiVirus Version 20.0
- Chrome, Safari latest

## URL:Mal ってなに？
このサイト、マルウェアに感染してるっぽいよと、Avast! が言ってる。AntiVirus をオフにして不用意にアクセスすると、PC がマルウェアに感染するかも知れない。

Avast! が該当URLをブラックリストに追加したので、Alertが出てブロックされるようです。
でも、アクセスできないことには！！

## 対応策
- サイトが安全か調べる
- 感染してるようであれば、管理者に対応を依頼
- 感染していなさそうであれば、AntiVirus の Exception の設定をする

### 安全かどうか調べる

[Google セーフブラウジング](https://transparencyreport.google.com/safe-browsing/search?hl=ja)

権限があれば他にも調べようがあるとは思いますが、今回はこいつを信じることに。URL を入力して検索。
大丈夫であれば、こんな結果が返ってきます。
![Screen Shot 2021-12-24 at 12.33.33 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/bce18f5e-c09f-51ef-feb0-816ba4f6fd7c.png)

大丈夫でなければ、管理者に報告し、対応完了するまでアクセスしないようにしましょう。それがクライアントであっても、仕事であっても。

### AVG AntiVirus の Exception を設定する

大丈夫そうであれば、メニュー開いて追加する。
![Screen Shot 2021-12-24 at 11.45.52 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/7f45e696-c265-6c3d-5411-5ecc44ed03c6.png)
![Screen Shot 2021-12-24 at 11.46.04 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/5240d5d0-700c-f7c4-c980-af52a3ccbb70.png)



おわり。
cakephp3からcakephp4に移行する際のMiddlewareの後方互換性の保ち方

## 概要
会社のプロジェクトをcakephpの3.8から4.0に上げる際にMiddlewareの互換性について調べたのでメモ。


## 4.0でのMiddlewareの変更点
cakephp4からpsr-15の仕様をサポートするようになったためcakephpが提供しているMiddlewareはMiddlewareInterfaceを実装しています。

cakephp3で独自のMiddlewareを作成している場合は互換性が気になるところですが、ドキュメントに書いてある通り後方互換性が保たれているのでバージョンを4にするあたり何か既存のMidllewareを書き換えなくてはいけないということは基本はないはずです。(もちろん書き換えないといけない場合もあると思います。)

https://book.cakephp.org/4/ja/appendices/4-0-migration-guide.html#id14




## どの様に互換性を保っているのか

結論から言うとMiddlewareInterfaceを実装していないクラス(__invoke( )を実装してる)をDoublePassDecoratorMiddlewareクラスでラップしているだけです。

1 . DoublePassDecoratorMiddlewareクラスの__constructにMiddlewareを引数で渡して$callableプロパティに代入する( __invoke( )を実装しているインスタンスはcallable型です)

```php
class DoublePassDecoratorMiddleware implements MiddlewareInterface
{
    
    protected $callable;

    
    public function __construct(callable $callable)
    {
        $this->callable = $callable;
    }
}
```

2 . 最終的にDoublePassDecoratorMiddlewareクラスのprocessメソッドが呼ばれる。実態は$callableプロパティのMiddleware->__invoke( )が実行される。

```php
class DoublePassDecoratorMiddleware implements MiddlewareInterface
{
    protected $callable;

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        return ($this->callable)(
            $request,
            new Response(),
            function ($request, $res) use ($handler) {
                return $handler->handle($request);
            }
        );
    }
}

```

(もっと詳しい流れが知りたい方は是非コードを読んでみてください)


### 補足

4.3.0からDoublePassDecoratorMiddlewareクラスは非推奨となっています。
(代わりにクロージャーかMiddlewareInterfaceを実装しよう！と書いてあります。)

https://github.com/cakephp/cakephp/blob/a420c2a6a263933edf4c1e1767ade9e3544afff5/src/Http/Middleware/DoublePassDecoratorMiddleware.php#L42-L44
C#でカノジョをつくる（25日目）

お疲れ様です。たなしょです。
まずはここまで25日間、駄文に付き合っていただきありがとうございました。
読書の方々がいたからこそ、思い付きで始めた企画ですがプログラミングをしてブログを書き続けることができました。
今日は最終日なので今までの振り返りができればいいなと思います。

## Cシャープを勉強してみる
デスクトップアプリを作りたいということでC#を一から勉強してみました。
Javaや他の言語の経験があるので基本的な文法はすぐにキャッチアップすることができました。
今までの記事でも散々書いてきましたが記法は綺麗でとても読みやすいのとVisualStudioというIDEの力もあってとてもスムーズに開発することができました。

このような感じに`{}`を書くのが新鮮でとても見やすかったです。

```c#
class CollectionInitializable : IEnumerable
{
    public void Add(string item)
    {
        Console.WriteLine($"{item} Added");
    }

    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }
}
```
あとLINQなどの特殊な文法も学びました。
LINQに関してはSQLを書いてる感覚でデータセットを処理できるのはわかりやすいなと思う反面、SQLを書いた経験がない初学者には理解するのが大変な文法なのでないのかと思いました。
実際私も1冊目の参考書では理解できず、2冊目の参考書である程度理解することができました。

## VisualC#でアプリを作る
VisualC#でアプリを作るのはとても楽しかったです。
フォームで簡単なアプリを作り、電卓をつくたりしながらVisualC#でのアプリ開発を学びました。
各パーツへの値の渡し方がわかればデスクトップアプリを作ることができるのは感動しました。
電卓はラベルを用意して各ボタンがクリックされたタイミングで計算式をラベルに入れるだけで作れました。

## カノジョ（チャットボット）を作る
VisualC#になれたところでカノジョを作りました。
返信用のスーパークラスを作成してサブクラスとして各種返信用のクラスを作成して、返信ごとの機能を追加していきました。
始めはランダムな辞書によるあいさつとオウム返ししかできなかったアプリが、ランダム辞書を作成し、単語とそれに連なる返信を切り分けるParseクラスを作成することで、ユーザーの発言がパターンにマッチした場合はパターンに沿った返信をしてくれるようになりました。
またパターン辞書に数値を入れることで機嫌値という概念を設けました。
特定のキーワードをユーザーが発言すると機嫌値が上下して喜怒哀楽まで表現できるようになり、これには感動しました！

## さらにコミュニケーションをできるようにした
MeCabというライブラリはPythonを触っていた時に耳にしたことはあるのですが今回使うのは初めてだったので使ってみて機能を知ることができました。
形態素解析をしてユーザーの発言を切り分けて名詞と判別したものをパターンとして登録し、パターンを連なる発言をユーザーの発言から学習するようにさせたことでたくさん会話すれば色々なキーワードにあった会話を楽しむことができるようなりました。
さらにテンプレート辞書を作成することで%noun%という文字列をユーザー発言内にある名詞に置き換えることでさらに会話の幅が広げることができました。
完成したカノジョです！
![girlfiriend_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268048/b37043bf-bae4-f334-988c-1a16bda0cddf.gif)

今回書いたコードもGitHubにアップしましたのでぜひ参考にしてみてください！

https://github.com/jacoloves/GirlFriend

## 今後の課題
まだ発展途上のアプリなので少ないですが今後は時間を見つけて以下の課題に取り組んでいきたいと思っています。

```
・ チャットボットからデスクトップマスコットへ変更
・ Grep機能などのその他機能の追加
```

## 最後に
25日間お付き合いいただきありがとうございました！
優良な記事が多いQiitaの中でこのような駄文を垂れ流してしまってごめんなさい。
今回は当初の目標であるカノジョ（アプリ）を作ることを達成することができました。
ものを作ることが楽しいというモチベーション一本で何とかここまで続けることができました。
作っていて初めからWindowsフォームからがちがち作っていったほうがもっと良いアプリが作れたのではないかという反省点もありますが、それは今後の開発で生かしていこうと思います。

年末は低レイヤーの世界にでも引きこもってOSっぽいものでも作ろうかと思います。
あとは息抜きでGoでCLIツールを作ったりPHPでWebアプリでも作りながら家に引きこもってると思います。
ではではみなさん良い年末をお過ごしください！
ありがとうございました！
C#でカノジョをつくる（24日目）

お疲れ様です。たなしょです。
今日は昨日に引き続きテンプレート用の返信する処理を作成していきます。

## テンプレート返信用クラスを作成する
1.ParseItemオブジェクトを1つずつ取り出して、ユーザーの発言にキーワードが含まれていた場合リストに追加します。

```c#
public override string Response(string input, int mood, List<string[]> parts)
  {
      List<string> keywords = new();
  
      foreach (string[] morpheme in parts)
      {
          if (Analyzer.KeyWordCheck(morpheme[1]).Success)
          { 
              keywords.Add(morpheme[0]);
          }
      }
```
2.キーワードの単語が1個以上あり、テンプレート辞書にcountと同じキー値が存在する場合、処理に入ります。

```c#
if ((count > 0) && (Cdictionary.Template.ContainsKey(Convert.ToString(count))))
```
3-1.count変数と同じ数の%noun%をもつ、テンプレート文字列のリストを取得します。

```c#
var templates = Cdictionary.Template[Convert.ToString(count)];
```
3-2.取得したリストからランダムに1個のテンプレートを抽出します。

```c#
string temp = templates[rdm.Next(0, templates.Count)];
```
3-3.正規表現として%noun%を登録します。

```c#
Regex re = new("%noun%");
```
4.キーワードのリストからキーワードを取り出して、テンプレートの%noun%をキーワードに置き換えます。

```c#
foreach (string word in keywords)
{
    temp = re.Replace(temp, word, 1);
}

return temp;
```
5.キーワードの単語が0、またはキーワード認定の単語の数が合わない場合はランダム辞書からフレーズを抽出します。

```c#
return Cdictionary.Random[rdm.Next(0, Cdictionary.Random.Count)];
```

## 各返信用モジュールに形態素解析の結果を取得するパラメーターを追加する
今まで作成してきた返信クラスの引数に形態素解析用のパラメータを追加します。下記はスーパークラスに追加したときのものです。

```c#
public virtual string Response(string input, int mood, List<string[]> parts)
```

## 本体クラスにテンプレート返信用のオブジェクトを実装する

```c#
private TemplateResponder _res_template;
```
応答フレーズにもテンプレート返答を追加します。

```c#
else if (num < 7)
   　_responder = _res_template;
```

実行してみるとカノジョの文言のバリエーションがさらに増えて、会話らしい会話ができるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268048/77564e38-5a35-aa1b-d38e-30937a4c9b6c.png)
ダイアログを見てみるとそこそこ会話できるようになっています。

## 最後に
テンプレート辞書を用いた会話ができました。
これで少しは楽しく会話できそうです！
明日は最終日なので今までの総括をします。
Webページの配色をユーザーが選択するJavaScript

過去にWebシステムの管理画面を作った際に、管理画面としては無難な配色でリリースしたのですが、
一部の利用者からページの色合いがイマイチ気に入らない、といった意見をいただきました。

一部の意見で配色を変更したとしても、別の一部から同様の意見が出ないとも限らないため、
だったら各自で好みの配色を選択できるようにしてしまおう！

ということで、JavaScriptでそんな機能を作ってみました。

## HTML

まずはHTMLです。
青、緑、赤の各ボタン押下をすることで、
&lt;ul&gt;のメニュー部分と&lt;h1&gt;の見出しの配色を変更することとします。
&lt;button&gt;のvalue値に配色コードを持たせています。

```html:sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>sample</title>
<link rel="stylesheet" type="text/css" href="default.css">
<script src="sample.js"></script>
</head>
<body>
<ul>
	<li>メニューA</li>
	<li>メニューB</li>
	<li>メニューC</li>
</ul>
<h1>見出しサンプル</h1>
<p>下のボタンをクリックで配色を変更します</p>
<button type="button" class="colorChangeButton" value="1">青</button>
<button type="button" class="colorChangeButton" value="2">緑</button>
<button type="button" class="colorChangeButton" value="3">赤</button>
</body>
</html>
```

## CSS

以下が基本となるCSSです。

```css:default.css
body {
	background: #eee;
	color: #111;
}
li {
	display: inline-block;
	padding: 2px 5px;
	list-style: none;
}
```

以下は３パターンの配色用CSSファイルです。

```css:color1.css
ul { background: #006; }
li { background: #669; }
h1 { color:#006; }
```

```css:color2.css
ul { background: #060; }
li { background: #696; }
h1 { color:#060; }
```

```css:color3.css
ul { background: #600; }
li { background: #966; }
h1 { color:#600; }
```

## JavaScript

localStorageにユーザーが選択した配色コードを保存することで、
次回以降にページを開いても選択した配色で表示されます。

append()で配色用cssファイルを読み込ませるlinkタグを追加します。

各ボタンのonclickイベントにchange()を登録し、
ボタンクリックすることでlocalStorageに配色コード保存と配色CSSファイルの差し替えを行います。

```javascript:sample.js
document.addEventListener('DOMContentLoaded', () => {
	const cssId = 'colorCss';
	const selector = '.colorChangeButton';
	const keyName = 'colorType';

	append(localStorage.getItem(keyName) || 1);
	document.querySelectorAll(selector).forEach(function (button) {
		button.addEventListener('click', {value: `${button.value}`, handleEvent: change});
	});

	function append(colorType) {
		const link = document.createElement('link');
		link.id = cssId;
		link.setAttribute('rel', 'stylesheet');
		link.setAttribute('type', 'text/css');
		link.setAttribute('href', 'color' + colorType + '.css');
		document.getElementsByTagName('head')[0].appendChild(link);
	}
	function change() {
		if (this.value === localStorage.getItem(keyName)) {
			return;
		}
		localStorage.setItem(keyName, this.value);
		document.getElementById(cssId).remove();
		append(this.value);
	}
});
```

## さいごに
簡単なHTMLとCSSの例でしたが、これを応用することで複雑な配色やレイアウトの切り替えなど、使える場面はいろいろありそうな気がします。
何かの参考になれば幸いです。
GASでRedashのAPIを叩いてスプレッドシートに記入するサンプルコード

## やりたいこと
「ユーザー登録画面で、どこまで進んだ人(どこで離脱した人)が何人いるか」をスプシに書き込みたい。
![CleanShot 2021-12-24 at 14.12.41@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244893/144c89bc-7f54-2924-5775-85b59fd4a597.png)


## 実装
### メインの処理
```js:スプシに書き込む関数.gs
function writeWeeklySampleDataToSpreadSheet() {
  const SHEET_ID = "{sheet_id}";
  const spreadSheet = SpreadsheetApp.openById(SHEET_ID);
  const sheet = spreadSheet.getSheets()[0];
  const yesterday = calcYesterday();
  const sevenDaysAgo = calc7daysAgo();
  const weeklySampleData = fetchWeeklySampleDataFromRedash();
  sheet.appendRow([
    sevenDaysAgo + ' ~ ' + yesterday,
    weeklySampleData[0]['count(*)'],
    calcTransitionRate(weeklySampleData[0]['count(*)'], weeklySampleData[1]['count(*)']),
    weeklySampleData[1]['count(*)'],
  ]);
}
```

### 呼び出されるメソッド
```js:呼び出されるメソッドたち.gs
function fetchWeeklySampleDataFromRedash() {
  const endpoint = 'http://{redash-host}/api/queries/{query_id}/results.json?api_key={api_key}'
  return fetchDataFromRedash(endpoint)
}

function fetchDataFromRedash(endpoint) {
  const response = UrlFetchApp.fetch(endpoint);
  return JSON.parse(response.getContentText()).query_result.data.rows;
}

function calcYesterday() {
  var date = new Date();
  const day = date.getDate();
  date.setDate(day-1);
  return Utilities.formatDate(date, 'JST', 'yyyy-MM-dd');
}

function calc7daysAgo() {
  var date = new Date();
  const day = date.getDate();
  date.setDate(day-7);
  return Utilities.formatDate(date, 'JST', 'yyyy-MM-dd');
}

function calcTransitionRate(before, after) {
  a = Math.round((after/before)* 1000)
  return (a/10 + '%')
}
```

### Redash上のクエリ
```sql:
SELECT '年齢まで入力済' AS 'registration_status', count(*)
FROM users
WHERE created BETWEEN DATE_ADD(CURRENT_DATE, INTERVAL - 7 DAY) AND CURRENT_DATE
  AND age IS NOT NULL

union

SELECT '性別まで入力済' AS 'registration_status', count(*)
FROM users
WHERE created BETWEEN DATE_ADD(CURRENT_DATE, INTERVAL - 7 DAY) AND CURRENT_DATE
  AND age IS NOT NULL
  AND gender IS NOT NULL
```
Google Cloud Searchで検索する

# はじめに

Google Workspace を利用している場合、Google Cloud Search を利用すると、

- Gmail
- Google カレンダー
- Google ドライブ
- Google サイト
- グループ

などをまとめて検索することができます。

![GCS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/f65d605f-f888-e8b3-b7d2-134c22f2f45e.png "Google Cloud Search画面")

::: note warn
Google Cloud Search は仕事用のアカウントのみで利用できます。 
:::


# 利用方法

1. Chrome を開きます。
1. <https://cloudsearch.google.com/> にアクセスします。
1. 検索バーに検索したいキーワードを入力します。

:::note info
Cloud Search のロゴをクリックすると、Google Cloud Search のトップページに移動します。
:::

# アドレスバーから利用する

Chrome のアドレスバーから Google Cloud Search で検索することもできます。

## キーワードショートカットの作成

1. Chrome を開きます。
1. [設定] ⇨ [検索エンジン] ⇨ [検索エンジンの管理] に移動します。
1. [その他の検索エンジン] の右側にある [追加] をクリックします。
1. 次の情報を入力します。
    - 検索エンジン: Cloud Search
    - キーワード: cs
    - URL: <https://cloudsearch.google.com/cloudsearch?q=%s> 

![検索エンジンの追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/ce1b2983-6a5e-0951-6399-83e1b1d78cee.png "検索エンジンの追加")

## 検索方法

1. アドレスバーに「cs」と入力して Tab キーを押します。
1. [Cloud Search を検索] と表示されますので、検索クエリを入力しEnter キーを押します。 

![アドレスバーからCloud Searchで検索する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/80821154-d4a8-7e9d-b5d2-49b25c1bd4f6.png "アドレスバーからCloud Searchで検索する")


# 参考リンク

<https://support.google.com/cloudsearch/answer/7002495>
【ARC007】 B - 迷子のCDケースをC++で解説 (Atcoder)

#初めに
　皆さんこんにちは！ 競プロの問題の理解を深めるために解説記事を書いていこうと思います！今回は **ARC129 A** です！
 気に入っていただけたら**LGTM**して頂けるとうれしいです！
何かあれば**コメント**や[Twitter](https://twitter.com/python05238919)からお願いいたします!
#[C - ABC conjecture](https://atcoder.jp/contests/abc227/tasks/abc227_c)
####問題概要
 初めに持っているCDのケースがないので違う曲を聴くときその入ってケースに入れ替えることをしました．これを$M$回繰り返した後のケースにはいっているCDの番号を求めなさい．
####制約
・$1 \leq N \leq 100$
・$0 \leq M \leq 100$
##考察
####ポイント
 **全探索しましょう**
####解説
 今持っているCDと次聞くCDを入れ替えるのを$M$回繰り返します．この時に次聞くCDがどこにあるのかを探します．
制約からCDケースを1から$N$まで順番に毎回探しても計算量は$O(NM)$となり，十分間に合います．
これを実装していきましょう．
#実装
 ディスクのケースは`vector`で用意し，最初に初期化します．前のCDと次にきくCDの番号が同じ場合は次にいき，それ以外の時は配列を`find`関数で探して`index`をイテレータから求めます．
　そしたら前聞いたCDの番号と`disk[index]`を入れ替え，前に聞いたCDの番号を次に聞くCDの番号に更新します．
これを繰り返します．

##正解コード

```C++:
#include <bits/stdc++.h>
using namespace std;

int main()
{
    //入力
    int N, M;
    cin >> N >> M;
    vector<int> disk(N);
    //diskの初期化
    for(int i = 0; i < N; i++) disk[i] = i+1;
    int cd = 0;
    for(int m = 0; m < M; m++){
        int popcd; //次に聞くCDの番号
        cin >> popcd;
        if(popcd == cd) continue; 
        else{
            //popcdと同じ値を探す
            auto itr = find(disk.begin(), disk.end(), popcd); 
            int index = itr - disk.begin(); 
            swap(cd, disk[index]); //CDを入れ替える
            cd = popcd; //番号の更新
        }
    }
   //出力
    for(int i = 0; i < N; i++){
        cout << disk[i] << "\n";
    }
}
```