OmniAICreator/Qiita-1.07M huggingface dataset:
Twitterのトレンドを取得し、記事を自動生成するアプリケーションを作ってみた。

## 概要
ツイッターを見ていてもその時々のトレンドは自分で見に行かないと分からないですよね。
自分が見ているフィードにツイートが流れてきて、そこに貼ってあるリンク先の記事を読めば今のトレンドの内容が分かるとちょっと楽しいかなと思って、ツイッターの現在のトレンドを取得して、自動的に記事が投稿されるメディアを個人開発で作成してみました。（現在はサーバー閉じています。）
こちらで生成される記事を見れば人気のツイートやトレンドに関連するニュース記事へのリンクが集まっているので、トレンドの内容を正確に素早く知ることができます。

今回は作成したバッチの内容をざっくりと話していこうと思います。

## 処理の流れ
ブログ部分は一から実装するのが面倒だったため、ワードプレスを導入し、そこに今回phpで自作した記事作成バッチを配置して記事の自動生成を行いました。

バッチの処理の流れは以下のようになっています。

```
①twitterAPIを利用して、現在の上位5件とトレンドワードを取得
②同じくTwitterAPIを経由して、上記のトレンドが含まれているツイートで特に人気の高い（いいね数の多い）投稿上位4件を取得
③トレンドワードが含まれているニュース記事のリンク5件を取得
④人気ツイートとニュース記事のリンクを内容に記事をDBに保存
⑤記事投稿をツイッターアカウントに投稿
```

時々TwitterAPIとの接続がうまくいかず人気ツイートを取れないことがあったので、失敗したら次の順位のワードで記事作成という形で作成しました。そのため上位5件のトレンドを取得しています。

## TwitterAPI
https://github.com/abraham/twitteroauth

phpでTwitterAPIを操作するためにTwitterOAuthライブラリを利用しました。

```php
$this->client = new TwitterOAuth($_ENV["TWITTER_CONS_KEY"],$_ENV["TWITTER_CONS_SECRET"],$_ENV["TWITTER_ACCESS_TOKEN"], $_ENV["TWITTER_ACCESS_TOKEN_SECRET"]);
$this->client->get("リクエスト先のPATH","付与したいパラメーター");
```

上記のような感じで、めんどくさい接続周りを行ってくれるので、phpでtwitterのAPIをいじる際は使ってみてください。
今回TwitterAPIの中で利用した機能は以下の2つです。

https://developer.twitter.com/en/docs/twitter-api/v1/trends/trends-for-location/api-reference/get-trends-place

https://developer.twitter.com/en/docs/twitter-api/tweets/search/quick-start/recent-search

ツイート取得は、検索機能にクエリとして、先程取得したトレンドワードを付与して、ツイートを取得しました。

```php
$tweets = $this->client->get('search/tweets', ['q' => $trendWord, 'result_type' => 'popular', 'count' => 4])->statuses;
```

こんな感じでリクエストを作ってあげるとトレンドアワードが含まれる人気ツイート4件を取得することができます。

## ニュース取得
ニュースAPIは色々あったのですが、トレンドワードと関連する記事のヒット率の高かったgoogleNewsのAPIを使用しました。

https://qiita.com/muroya2355/items/62f3bf5e147884d00207

こちらの記事を参考にニュース記事のタイトルをURLを取得して記事に追加しました。

## 作ってみた感想

### トップツイート、ネット記事の内容の統一性がかなり高い。 
トレンドワードでの検索だけでは内容と異なったツイートや記事リンクばかりになってしまうのではというのが開発中の懸念点だったのですが、そんなことはなかったです。
実際にその記事に貼ってあるツイートやリンク先の記事を読めばトレンドに関連する話題を正確に知ることができたので非常に良かったです。

### その時々のツイッター上のトレンドなのでジャンルがバラバラで記事の統一感がない。
ジャンルごとのトレンドみたいのが取得できればよりユーザーの関心が高いメディアにできるのですが、そういった機能はなかったため、アニメから政治まで、何にも特化していないまとめ記事になってしまった。

### 19時~22時までのゴールデンタイムはテレビのトレンドが多い。
これは作ってみて分かったのですが、テレビ番組に関するトレンドが意外と上位を占めていました。
テレビの力はまだまだ健在だなと感じました。

### 記事内容が薄い
トップツイートと、ネット記事へのリンクを貼っているだけなのでちょっと内容が薄かったです。
自動的に記事が増えていけば手間がかからないブログができて良いじゃん！と思ったのですが、内容が濃く、面白い記事を作成するにはもっと工夫しなければいけませんね。

全く手間のかからなくて人の役に立つメディアをいつか作りたいな。。。
TableView関連　まとめUIKit

UIkitとは
--

UIKitフレームワークは、iOSおよびtvOS Appの構築に必要な中核オブジェクトを提供します。これらのオブジェクトを使って、コンテンツをスクリーンに表示し、そのコンテンツを操作して、システムとのインタラクションを管理します。Appはその基本的な動作にUIKitを利用します。UIKitには、特定のニーズに合わせてその動作をカスタマイズするためのさまざまな方法が備わっています(Appleドキュメントより）
TableViewとは
--
tableViewを使用するためにはdelegateとdatasourceを使わなきゃいけない。
UItableViewCellを使用して下記に紹介する関数を用いて様々な処理を実行する。

よく使う処理まとめ

```swift
//viewdidLoad()内、下記のコードでしっかりデリゲートの宣言をしておく
 　　　　　　//tableView.dataSource = self
    //tableView.delegate = self
//上の２つは必須。
//①セルの数を決める関数(必須)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return 数字
    }
 
    
    //②セルを作成する関数(必須)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//指定のセルを取ってくる
      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
       return cell
    }

//③セルがタップされたときに発火する
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        //タップされた時にIndexPathにタップされた行の番号が入って処理が始まる。
    }
 
    
    //④セルの高さ
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        //return view.frame.size.height〇〇掛け算とか割り算とかで調整
    }
    //⑤セクションの数
    func numberOfSections(in tableView: UITableView) -> Int {
        //return 1　　
    }

```
いつか重めの最高の記事を書きたい。。。
discord.pyでファイルを送信しようと思ったらこけた時のこと。

# discord.pyでファイルを送信しようと思ったらこけた時のこと。
タイトルの通りdiscord.pyでbotを作っていた時にこけたので対処法を忘れんうちに書いとく。
まずこけたコードはこれ

```python
from PIL import Image
import io
import discord
async def send_pic(message :discord.Message):
    pic = Image.open("path")
    # -----------
    # 色々画像処理
    # -----------
    data = io.BytesIO() # バイトライクオブジェクト作成
    pic.save(data,format="png") # 保存
    file = discord.File(data) # discord.pyのFileクラスのインスタンスを生成
    await message.channel.send(content="send pic",file=file) # 送信
```
PILで画像を読み込み処理してからBytesIOに保存して送信するコードだが、いざ走らせてみると送信自体はされるがデータが0Bの状態で送られてきていた。

## 原因
BytesIOのシークの位置がpic.save(data,format="png")の際に一番後ろになったままの状態でFileクラスに渡していたのが原因だった。
## 解決策
```python
from PIL import Image
import io
import discord
async def send_pic(message :discord.Message):
    pic = Image.open("path")
    # -----------
    # 色々画像処理
    # -----------
    data = io.BytesIO() # バイトライクオブジェクト作成
    pic.save(data,format="png") # 保存
    data.seek(0) # <- シーク位置を先頭に持ってきている。
    file = discord.File(data) # discord.pyのFileクラスのインスタンスを生成
    await message.channel.send(content="send pic",file=file) # 送信
```
data.seek(0)でシーク位置を先頭に持ってきている。
これで正しく中身のあるデータが送信されるようになった。

よくよく考えたらファイルを書き込んだときは最後のバイトを書き込んだ位置のままなのだから、先頭に戻してやらないと読み込んでもファイルの最後と認識してしまうじゃないか。<-早く気づけ

##参考
[io - Pythonドキュメント]
(https://docs.python.org/ja/3/library/io.html)
[discord.py リファレンス](https://discordpy.readthedocs.io/ja/latest/api.html#discord.File)
既存のファイルをGitHubに上げる方法

## はじめに
今回記事を書くのが初めてで分かりにくい箇所もあるかもしれないが、ご理解ください。

よくGitHubへのあげ方など書いてあったが、既存のフォルダのあげ方が全く書かれてなかったこと、書かれてたとしても私的にはイマイチわからなかったことなどがあったので、ここに記しておく。


下記のことを前提条件とする。
- GitHubの登録ができている。
- GitHubにあげたいフォルダが存在する。



## 目次
1.自身のGitHubに入り、Repositoriesのnewをクリック。
2.リポジトリを作成。
3.２で作成したリポジトリぺージに自動で切り替わるので、表示されているコードをターミナル、またはコマンドをコピペで完成

### 1.自身のGitHubに入り、Repositoriesのnewをクリック。
![A54B9EEF-3C18-4D46-82D6-E403DE02E383_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939251/2d66ceac-7e89-add3-114c-c785bfdba1a2.jpeg)

上記の緑のnewボタンをクリックする。

### 2.リポジトリの作成
![D4018477-5D3A-4E8B-A352-7136B64E5F22_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939251/55131846-290f-eca1-a4f7-a35b1e34c9ec.jpeg)

赤の番号順に項目を埋めていく
①これから使うRepositoryの名前を入力。
②誰でも閲覧可能にする場合はpublic、自分だけまたはこのリポジトリに招待された人のみが閲覧可能にする場合はprivate。
③作成したリポジトリの説明欄であるため、入力しても構わないがなくても問題はない。
④特にGitHubにデータをあげるだけ、管理するだけならチェックする必要がないと思うので、そのままスルー。

### 3.２で作成したリポジトリぺージに自動で切り替わるので、表示されているコードをターミナル、またはコマンドをコピペで完成
![381469C2-8A23-4E13-9F42-F5745FA9FB33_1_201_a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939251/a4f9fef7-6ec5-268a-eb06-8362ae763e28.jpeg)


作成できたら上記のようなページに飛ばされる。
その際にターミナルまたはコマンドプロンプトで、このリポジトリを管理するフォルダまで移動し、赤枠で囲まれている箇所を上から順にコピペすれば完成。


## まとめ
初めて書いてみて、説明することの難しさを知った。
今後なぜこうなるのか、詳しくわかりやすく、尚且つ読みやすくできたらいいなと思う。
LaradockでLaravel開発環境構築

## はじめに
Laradockで Laravel Frameworkをインストール
- Composer、Git、Dockerのインストールについては扱っていません。

## プロジェクト作成
```shell
composer create-project laravel/laravel ${プロジェクト名}
```

上の過程を通じてLaravelをインストールします。

## Laradockインストール
```shell
git clone https://github.com/laradock/laradock.git
```

## envファイルをコピー
```~/laradock.
cp env-example .env
```

.envファイルにはPORTやLaradock設定に関連するほとんどの内容があります。

## ngnixとmysqlを実行
```shell
docker-compose up -d nginx mysql
```

初めて実行した場合、イメージをインストールするため時間がかかります。

## コンテイナー確認

```shell
docker ps
```

立ち上げているコンテイナーの中でNgnixを確認します。

デフォルト値は`0.0.0.0:80->80/tcp、0.0.0.0:443->443/tcp`になっています。

[http://localhost:80](http://localhost/)にアクセスするとLaravelが設置されていることを確認できます。

## 結果
![laravel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1374635/82dd6048-06c3-c8b4-7be4-d7f499cf5253.png)
pythonでバブルソート作ってみた

## 背景
知り合いから、pythonの課題が解けないと言われた。
python全く書いたことない(普段はruby)けど、いけるやろうと思って、タイムアタック的な感じでやってみた。

## 所要時間
1時間くらい。
なんだかんだ文法を調べるのに時間がかかった印象。

## できたソースコード
```python:sample.py
list = [4,6,1,2,8]
is_required_to_sort = True
max_compare_count = len(list) - 1

while is_required_to_sort:
    sort_count = 0
    for i in range(max_compare_count):
        if list[i] < list[i + 1]:
            q = list[i]
            list[i] = list[i + 1]
            list[i + 1] = q
            sort_count += 1

    if sort_count == 0:
        is_required_to_sort = False

print(list)
```

```:実行結果
[8, 6, 4, 2, 1]
```

## 改善版

@StrawBerryMoon さんありがとうございます。

```python:sample.py
is_required_to_sort = True
max_compare_count = len(lst) - 1

while is_required_to_sort:
    is_required_to_sort = False
    for i in range(max_compare_count):
        if lst[i] < lst[i + 1]:
            lst[i], lst[i + 1] = lst[i + 1], lst[i]
            is_required_to_sort = True

```

## あとがき
* アルゴリズム的な問題を解くコードは普段書かないので、勉強になった
* 実行結果は手元で試した限りはあってるが、このコードは本当に正しいのだろうか
* アルゴリズムのコードは、変数がわかりにくすぎるものが多い(まあ、わかりやすくする必要もないだろうが)ので、少しわかりやすくなるよう意識はしてみた
* ~~sort_countはis_sortedにして、真偽値を入れてもいいかもしれない~~
アップロードされた動画の縦横サイズ情報をjavaScriptで取得する方法

## 前提
htmlのinputタグにてアップロードされた動画の縦横サイズ情報を取得して、サーバーサイドにpostする必要があり、その際に作成したコードになります。
fileinputから直接取得はできなかったた、一度videoタグに展開してから、縦横のサイズを取得しています。

## 実装

```sample.js
// ファイル選択時にhandleFileSelectを発火
    fileInput.addEventListener('change', () => {
        // 複数の画像やサイズがmultipleでアップロードされていた場合を想定
        for (let i = 0; i < fileInput.files.length; i++) {
            let file = fileInput.files[i];
            // 動画のみ処理を行う。今回はmp4のみ
            if (file.type === "video/mp4") {
                let element = document.createElement( "video" ) ;
                let name = file.name;
                element.src = URL.createObjectURL(file);
                element.ondurationchange = () => {
                    // videoタグから画像の高さ、横幅を取得する
                    let height = element.videoHeight;
                    let width = element.videoWidth;
                }
            }
        }
    });   

```

限られたシチュエーションだとは思うのですが、誰かの役に立てば幸いです。
tailwindでの中央寄せの方法

# tailwindでの中央寄せの方法
自分用メモとして随時更新していく

## flexを使う方法
`flex justify-center`

```
<div class="flex justify-center ...">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>
```
##### 参考
[tailwindドキュメント](https://tailwindcss.com/docs/justify-content#center)
Ubuntu-Serverの外部接続で難儀した記録(スタブリゾルバとIPv6)

LINE NotifyやGoogle APIで希に接続が失敗する
------------
結論から書くと記事タイトル通りで、スタブリゾルバとIPv6が原因で外部APIの接続に失敗する。やっかいだったのは以下の2点
・ほぼAPIに繋がるが希に失敗するので原因が特定しにくい
・UbuntuのServer版なら勝手にキャッシュしないだろうという認知バイアス
仕事で外部サービスへの接続は過去に何十回も構築しており、一度も同様の不具合が無かったので頭を抱えることとなった。

言語はPHP。LINE Notifの接続はfile_get_contents。Google APIの接続はGoogle_Client。失敗時のエラーは以下

```
LINE Notify
PHP Warning:  file_get_contents(https://notify-api.line.me/api/notify): failed to open stream: HTTP request failed! HTTP/1.1 400
```

```
Google_Client
PHP Fatal error:  Uncaught GuzzleHttp\Exception\ConnectException: Connection refused for URI https://oauth2.googleapis.com/token in /srv/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
```

AWSのロードバランサーはIPが頻繁に変更されるので外部API側も似たような仕様だと推察
------------
最初は相手側の不具合かと思ったが、それにしても頻度がそこそこ多いので自身のシステムを疑うことに。ただバッチ処理を2度叩くと成功したりと訳が判らず。エラーが400と出ているので経験と勘だけで探る。とりあえず名前解決が怪しいのでresolve.confを見ると

```
resolve.conf
nameserver 127.0.0.53
```

127.0.0.53ってなんや・・・。調べるとスタブリゾルバという名のDNSキャッシュらしい。昨今のサービスは外部接続が当たり前で、且つAWSのロードバランサーは頻繁にIPが変わるので追随出来ないのに何故こんな機能をサーバー版でオンにしているのだろうか。LINE側もGoogle側もロードバランサーが同様かどうかは不明だが可能性的には高いのでスタブリゾルバをオフにする

```
/etc/systemd/resolved.conf
[Resolve]
DNSStubListener=no
DNS=8.8.8.8 8.8.4.4 2001:4860:4860::888
```

/etc/resolve.confのシンボリックリンクを書き換え

```
$ cd /etc/
$ ln -sf ../run/systemd/resolve/resolv.conf resolv.conf
```

再起動

```
$ systemctl restart systemd-resolved
```

systemd-resolvedの再起動で/etc/resolve.confも書き換わる。LINE Notifyはこれで解決して接続エラーは出来なくなった。


希に繋がらないGoogle API
------------
Google APIもスタブリゾルバのオフで繋がる頻度は高くなったが、繋がらない事が極めて希の頻度で発生していた。とりあえず不具合時にpingを打つと返ってこない

```
$ ping oauth2.googleapis.com
PING oauth2.googleapis.com(nrt20s09-in-x0a.1e100.net (2404:6800:4004:80b::200a)) 56 データ長(byte)

--- oauth2.googleapis.com ping 統計 ---
送信パケット数 19, 受信パケット数 0, パケット損失 100%, 時間 18439ミリ秒
```

Google側でIPv6が開いているのでIPOE環境だとv6で接続に行ってしまう。とりあえずv4で叩くと正常にIPが引ける

```
ping -4 oauth2.googleapis.com
PING oauth2.googleapis.com (172.217.31.170) 56(84) バイトのデータ
64 バイト応答 送信元 nrt12s22-in-f10.1e100.net (172.217.31.170): icmp_seq=1 ttl=115 時間=9.19ミリ秒
64 バイト応答 送信元 nrt12s22-in-f10.1e100.net (172.217.31.170): icmp_seq=2 ttl=115 時間=10.1ミリ秒
```

原因は不明だがIPv4で解決出来るならIPv4を優先接続にして回避を試みる。

```
/etc/gai.conf
precedence ::ffff:0:0/96  100
(コメントアウトを消して有効にする)
```

一応接続不具合はこれで解消した。IPv6のGoogle DNSがGoogle APIを引けないのは謎のまま。
TableViewをスクロールすると消える影を実装(止めると再表示)

# TableViewに影をつける
<img width="150" alt="D9C0D9D0-CE95-481D-B785-C6BEBEE74F12_1_201_a.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/5de96d4b-3109-4ea7-a826-cba60253aa85.jpeg">

 * tableViewをスクロールすると影が非表示になり、スクロールを止めると影が表示されます 
 * tableViewを押してる様な動きになります

#コード

* `tableView.layer.masksToBounds`を`false`で設定しましょう。
* 影関係の設定はご自由に

```Swift:ViewController
        self.tableView.layer.cornerRadius = 20.0
        self.tableView.layer.shadowOffset = CGSize(width: 10, height: 10) //widthは右側に、heightは下側に影が表示される範囲
        self.tableView.layer.shadowOpacity = 0.5 //影の透明度を設定
        self.tableView.layer.shadowRadius = 7  //影のぼかし具合を設定->設定値が大きい程ぼかしも強くなります
        self.tableView.layer.masksToBounds = false //trueだと影が表示されない
```
 
* 影はこれで表示されるのですが、tableViewをスクロールすると、上のカレンダーにcellが被っています。 

<img width="150" alt="BA7D6FD6-5CEB-4B80-AEF6-873B786ED8A0_1_201_a.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/3d4f0ab5-3c90-bb0d-0af7-0ff3b9cbfd95.jpeg">

* なので、tableViewのスクロールを検知させます。
* tableViewをスクロールする時は`tableView.layer.masksToBounds`の`true`にする。
* tableViewのスクロールが終わった時に、`tableView.layer.masksToBounds`の`false`にする。

```Swift:ViewController
extension ViewController:UITableViewDelegate,UITableViewDataSource{
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { //スクロールが始まった時に呼ばれます
        
        self.tableView.layer.masksToBounds = true
        
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { //スクロールが終わった時に呼ばれます
        
        self.tableView.layer.masksToBounds = false
        
    }
}
```
<img width="150" alt="7970FC33-0AE5-45D3-A75D-99992CCE5EBB_1_201_a.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466343/d81d1226-10b8-1224-b213-fd44e5de3917.jpeg">

* これでcellがカレンダーに被ることも無くなり、`tableView.layer.masksToBounds = true`が呼ばれて影も消えるので、tableViewが押されている様に見えます。
* ところで、`tableView.layer.masksToBounds`とは何かと言いますと、CALayerクラスが持っているプロパティです。
* tableViewは`tableView.layer.masksToBounds`の初期値がtrueになっています。なので普段tableViewを使っていてもcellがtableViewからはみ出る様な表示がされないです。なので、初期値のままだと影も表示されないみたいです。

# 終わり
今日は、食品や医薬品などの期限をチェックをやってくれるアプリを作っているときに起きた事を書きました
ご指摘などがありましたらコメントまで、頂けると嬉しいです
今日も充実してたなぁ
TOYPRO TTC001 解説

#そもそもTOYPROって？
競プロのサイトです。主に、小中学生を対象にしています。なお、そこに数名のAtCoder水、青などの競プロ経験者が来た結果、大変面白いことになっています。僕もその一人です。ごめんなさい。
現在はPythonのみ使用することができます。C++勢の僕にとっては結構いい勉強になると共に、簡単な問題でもかなり解くのに時間がかかります。C++勢でPython勉強したい人は解いてみてください。
#TTC001について
TOYPROの試験的に行われたコンテストを除けば、最初のコンテストになります。
難易度は全ての問題がAtCoderのABC-B以下の難易度のものです。
##TTC001-A
if文の問題です。ifとelseを使えばできます。
##TTC001-B
count文を使うと楽にできます。cuontって書いて永遠にバグらせました。
##TTC001-C
B割引です。B割になるんじゃありません。(自戒)
##TTC001-D
aとbの配列の大きさ書いてない...普通に計算量$len(a)*len(b)$が通るのでfor文使いましょう。
二分探索使ってオーバーキルしてもいいです。
##TTC001-E
出力例1の下の数字の全角と半角がそろってないのが気になって夜も眠れません。
sortしてfor文回します。a.sort()でソートができます。sort(a)を無限にしていた...
##TTC001-F
AとBの配列の大きさ書いてない...これも$len(A)*len(B)$が通るので、for文使いましょう。
これも二分探索で解くことができます。解いてみてください。
#感想
難しくはなかったです。ただ、13分かかりました。Pythonにまだ慣れてないので此れから精進していきたいと思います。
UE4の Pixel2D買ってみたよ (Part7.1) 遠距離攻撃

Creating 2D games with Unreal Engine using Pixel 2D - Part 7.1 - Ranged Attack

https://www.youtube.com/watch?v=1hfALNMtGZY&list=PLSHq8CaNTAqDalhMyicUJPDH4v8RDXz86&index=8

#遠隔攻撃を作るよ！

って書いてあるけどほとんど説明なくね？
いや、これないよね。


#段差で落ちるよ！(02:20)
3Dだったらいいんだけど2Dの場合(このプロジェクトの場合)
値がでかすぎるから変えてね
この高さまで坂として判定するという設定
![2021-07-16_20h42_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/956aa4a7-e950-6799-9933-94bb2ced0467.png)
![2021-07-16_20h41_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/deb8d02b-b750-1f38-3157-3c260e3ce169.png)


#ダブルジャンプ(02:20)
後はジャンプの高さ調整とかもできる。
![7.1.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/78102235-deec-772e-779e-22c6abf66574.png)


#背景設置するよー(04:36)
準備してある素材を使うよ
このT_BackGroundを右クリックでスプライト化して設置する。
奥行はYを調整して調整
![7.1.3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/68726528-6d01-93d1-ad7b-bbc63e3484cb.png)


#変数について(06:46)
変数について補足するよー
情報を入れれるよ
HPとか攻撃力とか
フラグとか。

#キーを押したときの処理(09:30)
上記の変数をいい感じに表示させたりするよ
![2021-07-16_23h03_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/5e795e41-f32d-009c-0f2b-400b8f3b9e75.png)

こんな感じにして実行すればQを押したときにLogが流れるよ。（完）
！![7.1.5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270619/35aff900-d225-8e05-4b8d-63a4422aa279.png)

#終わり
それっぽいこと何もしてないやん
【PHPUnit】@runInSeparateProcess付けるとテストが失敗する？

## はじめに

この記事は自分への戒めのために書きます。
結末は、**普段から出力されているWarningに慣れたらダメだよ。**です。

Warningであっても早期に解消するように心がけましょう。


## 学んだこと

- 個別のプロセスで起動したテストは、標準出力があると失敗することがある。
- Warningだからといって放置してはいけない。

## 環境

- PHP 7.2系
- CakePHP 3.5系
- PHPUnit 6.5系

## 何が起きた？

- `@runInSeparateProcess`をつけるとテストが失敗してしまう。。。  
他の`@runTestsInSeparateProcesses`や`@runClassInSeparateProcess`でも同じ結果。。。
- コンソールに出力されるのは親プロセスの起動時にも出力されるものと全く同じWarning。。。

↓こんな感じの

```
PHP Warning: PHP Startup:　...
```

今思えばこれが原因ではないか？っと気づきそうではあるのですが、

- よく出力されるメッセージ
- `@runInSeparateProcess`を付ける前は、Warningが出ていても動いていた
- アノテーションつけていないものは正常のテスト結果を返している

ので、これが原因であるとは全く考えませんでした😇

DockerやPHPUnitの設定が悪さをしているのか、それともWebサーバー...などだいぶ時間を使ってしまいました。。。

## どうやって気づいたのか

ググってみても良い感じの記事を見つけ出すことができず、PHPUnitをデバッグしてみることにしました。

するとPHPUnitの`processChildResult（）`という関数で例外が発生していることがわかりました。

以下、`processChildResult（）`の一部です。

```php:PHPUnit\Util\PHPAbstractPhpProcess.php
    /**
     * Processes the TestResult object from an isolated process.
     *
     * @param Test       $test
     * @param TestResult $result
     * @param string     $stdout
     * @param string     $stderr
     */
    private function processChildResult(Test $test, TestResult $result, $stdout, $stderr)
    {
        $time = 0;
        // $stderr(標準出力)があるとエラーとして扱われる
        if (!empty($stderr)) {
            $result->addError(
                $test,
                new Exception(\trim($stderr)),  // ここで例外が発生している。コンソールに表示されるのはいつものWarning。
                $time
            );
        } else {
        // ... 省略 ...
        }

        $result->endTest($test, $time);

        if (!empty($output)) {
            print $output;
        }
    }

```

はい、ご覧いただいたように**標準出力($stderr)**があるとテスト結果は失敗し、私の場合は、**普段から出力されていたWarningが原因でテストが失敗**となっていたことがわかりました。
（テストの失敗後、コンソールに出力されるのは$stderrだけなのでちょっとわかりづらいなぁっと思いました。もちろん悪いのは私です！）

## まとめ

普段から出力されているWarningは、やはり何らかの悪影響を引き起こすことを身をもって体験できました。そして反省しました。

Warningだからと放置せず、すぐに対応する。慣れないことが大切ですね。

この記事が同じような事象に出くわした方の助けになれば幸いです🙏
Stylishの設定が反映されない（Linkを訪問済みの場合は色を変えたいのに変わらない問題）

通常はCSSをサイト外から直接触る拡張機能
[stylish](https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe)（CSSを拡張機能から触るものなら何でもok。）
これらを使えば問題なく動きます。
でもサイトによっては[stylish](https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe)の設定が反映されない場合があります。

# 悩み
[stylish](https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe)を設定しても反映されない。

例
[stylish](https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe)でこのように設定してみます。

```
a:visited {
    color: #00FF00;
}
```

しかしリロードしても反映されませんでした。
この問題を解決します。

# 理由
例えば、

```
<a style="color: #0000FF;" href="https://www.google.com/">google</a>
```

このように直接タグに色を指定していると、CSSの優先順位によって[stylish](https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe)の設定が反映されません。

# 解決策

```
a:visited {
    color: #00FF00 !important;
}
```

と`!important`を設定して強制的に反映させます。

CSSはより細かく指定するほうが優先されるからです。通常サイト制作時にこの`!important`を使うのは禁じ手ですが、今回のような場合には有用です。
CSSの優先順位のルールは各自調べてください。
ゲストログイン機能 [Rails/LexicallyScopedActionFilter:]

ゲストログインを実装時にRubocopでチェックすると、
`Rails/LexicallyScopedActionFilter: update, destroy`
このように指摘されました。

その際のコードは下記の通りです。

```ruby:registrations_controller.rb

module Users
  class RegistrationsController < Devise::RegistrationsController
    before_action :ensure_normal_user, only: %i[update destroy]

    def ensure_normal_user
      redirect_to root_path, alert: "ゲストユーザーの更新・削除はできません。" if resource.email == "guest@example.com"
    end

                #〜〜〜〜略〜〜〜〜〜
end
```

https://www.rubydoc.info/gems/rubocop/0.58.0/RuboCop/Cop/Rails/LexicallyScopedActionFilter
こちらを参考に下記のように編集。

```ruby:registrations_controller.rb
module Users
  class RegistrationsController < Devise::RegistrationsController
    before_action :ensure_normal_user, only: %i[update destroy]

    def update; end

    def destroy; end

    def ensure_normal_user
      redirect_to root_path, alert: "ゲストユーザーの更新・削除はできません。" if resource.email == "guest@example.com"
    end

                 #〜〜〜〜略〜〜〜〜
end
```

`rubocop -a`を実行すると指摘されることは無くなりました。

```rails:ターミナル
❯ rubocop -a
Inspecting 52 files
....................................................
52 files inspected, no offenses detected
```

しかし、ゲストログイン機能としては問題ありませんが通常のログインでアカウント編集ができなくなりました。

```rails:ターミナル
No template found for Users::RegistrationsController#update, rendering head :no_content
Completed 204 No Content in 32ms (ActiveRecord: 1.6ms | Allocations: 7284)
```

元々の親メソッドが機能していないということですね。
そこで `super` を使用し下記のように変更

```ruby:registrations_controller.rb
module Users
  class RegistrationsController < Devise::RegistrationsController
    before_action :ensure_normal_user, only: %i[update destroy]

    def update
      super
      update_internal
    end

    def destroy
      super
      destroy_internal
    end

    def update_internal; end

    def destroy_internal; end

                 #〜〜〜〜略〜〜〜〜
end
```

これで `rubocop -a` で指摘されることもなく通常ログインでのアカウント編集も問題なく動きました。
【Laravel】脱同じ条件式~クエリスコープを用いてSQLの条件式をメソッドのように分ける

# はじめに


PHP及びLaravelにて、DBから値を取得する際に複数のWHERE句を1つのメソッドのようにまとめる方法を以下に記載する。


# 結論

以下ルールに則ることでメソッドと同じように利用が可能。
* 先頭にscopeを付与する
* 呼び出す際にはscopeを除外する

```PHP
/ **
* 20歳以上の男性の名前を取得する
*/
public function getAdultMaleName()
{
    return $this->
        select('userName',)
        ->isAdultMale()
        ->get();
}

private function scopeIsAdultMale($query)
{
    return $query
        ->where('age', '>=', 20)
        ->where('gender', '=', 1);
}

```
残プロ　第-45回　~複数のpdfファイルを自動でタイトル付け~

# tkinterとargpurse

前回のプログラムにargpurseによるオプションの追加，GUIによるファイル・フォルダの選択を拡張しました．


~~~ python:PDFRename.py
import os
from os.path import expanduser
import argparse
import tkinter.filedialog
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LAParams, LTContainer, LTTextLine

def get_objs(layout, results):
    if not isinstance(layout, LTContainer):
        return
    for obj in layout:
        if isinstance(obj, LTTextLine):
            results.append({'text': obj.get_text(), 'height': obj.height})
        get_objs(obj, results)


def get_title(path):
    with open(path, "rb") as f:
        parser = PDFParser(f)
        document = PDFDocument(parser)
        laparams = LAParams(all_texts=True)
        rsrcmgr = PDFResourceManager()
        device = PDFPageAggregator(rsrcmgr, laparams=laparams)
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        page = next(PDFPage.create_pages(document)) #１ページ目
        interpreter.process_page(page)
        layout = device.get_result()
        results = []
        get_objs(layout, results)
    f.close()
    if results:
        title = max(results, key=lambda x:x.get('height'))
        return title.get('text').strip()
    raise OSError


def main(path):
    try:
        os.rename(path, "{}/{}.pdf".format(os.path.dirname(path), get_title(path)))
        print("PDF Rename Success:{}".format(path))
    except OSError:
        print("PDF Load ERROR:{}".format(path))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='')
    parser.add_argument('-D', '--dir', action='store_true', help='ディレクトリで選択')
    args = parser.parse_args()
    home = expanduser("~")
    iDir = os.path.abspath(os.path.dirname(home))
    if args.dir:
        dir_path = tkinter.filedialog.askdirectory(initialdir = iDir)
        print(dir_path)
        files = os.listdir(dir_path)
        file_paths = [f for f in files if os.path.isfile(os.path.join(dir_path, f))]
        pdf_paths = [f for f in file_paths if f.endswith('.pdf')]
        for pdf_path in pdf_paths:
            main("{}/{}".format(dir_path, pdf_path))
    else:
        fTyp = [("", "*")]
        path = tkinter.filedialog.askopenfilename(filetypes=fTyp, initialdir=iDir)
        main(path)
~~~


実行の際に--dirを指定することで，フォルダを選択できます．フォルダ内のpdfファイル全てに対してリネームを行います．
初学者による復習[カラムを追加してみよう!]

ものすごく初歩的なアウトプットなので現役の方には有益な情報ではないと思うが、
自身の復習も兼ねてアウトプットをしていこうと思う。
今回はカラムを追加するために必要なことを記述していく。

**そもそもカラムとはなんぞや**
カラムとはものすごく噛み砕いて説明するとデータベースに入っているデータの項目のこと。
例えば名前、電話番号、アドレスなどなど。

それでは実際に記述してみると下記のようになる。

```Ruby
create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :name,               null: false
      t.string :password,           null: false
      t.string :postal_code,        null: false
      t.string :address,            null: false
      t.string :phone_number,       null: false
```

t.stringとは型と呼ばれるものでいくつか種類があるがここでは割愛。
ちなみにt.stringの意味は文字（255文字まで）という意味。
その次に追加したいカラム名を記述する。
最後に制約というものを設ける。
ここでいうnull: falseというのは空の状態で入力できないよ！という制約。

最後に忘れてはいけないのがターミナルで

```
rails db:migrate
```
上記のコマンドを実行すること。
【今日の学びメモ】VSCode でPrettier を使って自動フォーマッティングする

# Prettier のインストール
VSCode の Extensions で Prettier をインストールする
![スクリーンショット 2021-07-16 22.02.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748993/6ca106f7-e5dc-a28a-6880-0cb817c89b5a.png)

# デフォルトのフォーマッターを指定

- VSCode の Settings を開く。
画面左下の歯車マークから開いてもいいが、`command + ,`のショートカットキーが便利。
- **Search settings** と書かれた検索ボックスに`format`と打つ
- `Editor Default Formatter`項目が出てくるので **Prettier** を選択する
![スクリーンショット 2021-07-16 22.12.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748993/35047af5-3f22-5302-41f0-0c43e7b6dfaa.png)

# 自動整形の設定

- `Format On Type`にチェックを入れる
- `Format On Save` にチェックを入れる

![スクリーンショット 2021-07-16 22.18.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748993/96d18066-de45-6cd9-386a-a3e46d82764c.png)

`Format On Type`にチェックを入れると、タイピングすると同時に Prettier のフォーマットが走るようになる。

`Format On Save`にチェックを入れると、ファイルを保存すると同時に Prettier のフォーマットが走るようになる。
<br>
新しいプロジェクトにアサインするたびに忘れて困るのでメモ。
Power Automate Object配列から文字列配列の作り方

# 概要
SharePoint Rest API などで取得した結果から必要な部分のみ抽出するのに非常に便利な Select (Action) ですが、実は文字列配列にも簡単に変えることができますよ、というお話。

# 結果イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/69eb46d8-fec4-2bf2-fab5-5e149c822738.png)

# 何がいいのか？
- オブジェクト配列の場合
    - 他のプロパティを再利用する場合には、こちらじゃないとダメなので、普通はこっちが便利
- 文字列配列にする場合
    - 対象のプロパティなどを Condition (Action) ですぐに見つけられる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/e705cbb9-5a27-19f8-843e-65662707f783.png)

# やり方
- 単純に、Text Mode にして、値選択するだけ :laughing: 
![SelectAsStringArray.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/399a6280-d5b2-4094-e76a-d14528408486.gif)


# 参考：C# のイメージ
```c#:Object
var data = lists.Where(w=> new {
    w.Name,
    w.Value
})
```

```C#:string
var data = lists.Where(w=> w.Name)
```

# description
How to Convert JSON Objects to Primitive Array (String Array Example)
(定積分)「2019年度 東京大・理系数学 問題1」をwolframalphaとsympyでやってみたい。

オリジナルリンク

https://www.densu.jp/tokyo/19tokyospass.pdf#page=1

# 問題

https://mathexamtest.web.fc2.com/2019/201910261/2019102610100mj.html#top-0105


## WolframAlphaで

https://www.wolframalpha.com/input/?i=integrate%28%28x%5E2%2B%28x%2Fsqrt%281%2Bx%5E2%29%29%29*%281%2B%28x%2F%28%281%2Bx%5E2%29*sqrt%281%2Bx%5E2%29%29%29%29%2C%28x%2C0%2C1%29%29&lang=ja

定積分:
integral_0^1 (x^2 + x/sqrt(1 + x^2)) (1 + x/((1 + x^2) sqrt(1 + x^2))) dx = 1/24 (-70 + 60 sqrt(2) + 3 π)
≈1.0116

https://www.wolframalpha.com/input/?i=pi%2F8%2B5%2F2*sqrt%282%29-35%2F12&lang=ja

近似値:
1.01156.....

# Pycharmで
私のwin10で計算時間が4分かかります。

```python
from sympy import *
import time
var("x")
start = time.time()
myKekka = integrate((x**2+(x/sqrt(1+x**2)))*(1+x/(1+x**2)/sqrt(1+x** 2)), (x,0,1))
print("#積分結果",myKekka)
print("#積分結果 {:.5f}".format(float(myKekka)))
print("#積分時間 {:.0f}".format(time.time() - start) + "秒")
#積分結果 -35/12 + pi/8 + 5*sqrt(2)/2
#積分結果 1.01157
#積分時間 219秒

```

# SymPy Liveで
上記のソースコードを貼り付けて、Evaluateです。「Error: Operation timed out.」がでます。

https://live.sympy.org/



# 参考

https://wethink.wonderlabedu.com/2019/03/15/tokyouniv-exam/

https://qiita.com/fantm21/items/3dc7fbf4e935311488bc
useStateのhooksを使用して、連想配列のstateの一部だけを変更したい

useState()のhooksを使用して、連想配列の一部だけ変更したい場合について個人的にメモ
（下記例はTypeScriptを使っています。）

```
type User = {
  id: number;
  name: string;
};

const Test = () => {
  const [userState, setUserState] = useState<User>({ id: 1, name: '太郎' });
  useEffect(() => {
    setUserState((state) => ({ ...state, name: '進次郎' }));
  });
};
```

`userState`の初期値は`{ id: 1, name: '太郎' }`だが、

`setUserState((state) => ({ ...state, name: '進次郎' }));`を行うことで、

`userState`の値が`{ id: 1, name: '進次郎 }`のように変更される。
MAMP エラーのブラウザ表示設定

Every Qiita #14
のんびり独学初学者の毎日投稿チャレンジ 14日目
今回は・・・
MAMPのエラー表示設定の備忘録です。

###php.iniの編集
**MAMP/bin/php/php(version)/conf/php.ini** ※versionはMAMPで設定しているversionのファイルを選択してください。

```ini:php.ini(変更前)
display_errors = Off 
```

```ini:php.ini(変更後)
display_errors = On 
```

470行辺りの`display_errors`をOnにしてMAMPを再起動すればエラー表示されているかと思います。
next.jsで、コード上で環境変数を設定する。

環境変数なんだけど秘匿するほどではなくソース管理する方が都合の良い場合がある。
公開されているAPIのエンドポイントとか。

configs/env.js ファイルに環境変数をまとめ、importして使う。
next.config.js の設定が必要。

```js
// configs/env.js
const development = {
  API_ENDPOINT: 'http://test.api.com',
}
const staging = {
  API_ENDPOINT: 'http://test.api.com',
}
const production = {
  API_ENDPOINT: 'http://production.api.com',
}

if (process.env.ENV === 'development') {
  module.exports = development
} else if (process.env.ENV === 'staging') {
  module.exports = staging
} else if (process.env.ENV === 'production') {
  module.exports = production
}
```

```js
// next.config.js
const webpack = require('webpack')
module.exports = {
  ...
  webpack: (config) => {
    config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.ENV': JSON.stringify(process.env.NODE_ENV)
      })
    )
    return config
  }
}
```

使い方

```js
// 使うファイル
import env from '.../configs/env'

console.log(`${env.API_ENDPOINT}/hoge`)
// => http://test.api.com/hoge
```
[Rails]テストコードを学ぼう

# はじめに
ご意見やご指摘など頂けますと大変助かります。
今後のITの世界へ入る身分として、是非ともご教授頂ければと思います。
また、自分自身の成長と共に
初学者の方々の力になればと思い少しずつ共有できればと思います！

# テストとは。
アプリケーションの挙動を確認するためのコードのことです。
手動でぽちぽちとブラウザで挙動を確認するよりも`断然効率が良い`です。

__手動によるデメリット__
・人為的なミスの可能性
・仕様変更の手間（もう１回全部やり直し）
・確認した記録が残らない

# テストコードを学ぶポイント
・細かい記述は、調べれば良い。覚える必要はない。
・どの部分を確認したいのかを明確にする。
・不安ならば、「binding.pry」

# テストコードで登場するもの列挙
＊私が学習した範囲で列挙します

`Rspec`
Ruby on Railsのテストコードを書くために用いられるGem


`rails_helper`
Rspecを用いてRailsの機能をテストするときに、共通の設定を書いておくファイル


`describeメソッド`
テストコードのグループ分けを行うメソッド
do~endの間に、さらにdescribeメソッドを記入することで、入れ子構造にもできる。


`itメソッド`
describeメソッドよりも詳細にグループ分けができ、
どのようなテストを行うのか明記する。


`example`
itメソッドで分けたグループを指す。
これから行うテストコードの内容についてタイトルで示している。
タイトルの部分を""で記載している。
＊個人的に重要なんではないかと考えています。


`valid?メソッド`
バリデーションを実行させて、エラーの有無を判断する。


`expectation`
検証した結果の挙動が想定通りなのかを確認する構文のこと。
__expect().to matcher()__が雛形。
テストの内容に応じて引数やmatcherを変えて記述。


`matcher`
「expectの引数」と「想定した挙動」が一致しているかどうかを判断する。
複数種類がある。


`eq`
「expectの引数」と「eqの引数」が等しいことを確認するマッチャ。

```ruby
expect(120 + 180).to eq(300)
```


`include`
「expectの引数」に「includeの引数」が含まれていることを確認するマッチャ。

```ruby
expect(['赤', '青', '黄', '黒']).to include('青')
```


`errorsメソッド`
インスタンスにエラーがある際に、そのエラー内容を返すメソッド。

＊今までエラーの画面を見て「ダメか・・・」でブラウザを閉じていましたが、
エラーに記載の内容は__かなり重要__だったことを理解しました。


`full_messagesメソッド`
エラーの内容から、エラーメッセージを配列として取り出すメソッド。
以下のようなエラー。

```
"○○○ can't be blank"
```

# 終わり
現在学習している範囲ではおおよそ以上の通りですが、
初めに学習するものこそ基礎の基礎で学ぶことだと思います。
備忘録という形ではありますが、
１つ１つ積みあげ、テストの理解に努めます！
Googleカレンダーの予定を一括で削除するGAS

CRMシステムと遊んでいたら「間違えてカレンダー数千件のイベントを立ててしまった」ということがよくあるでしょう。
1件1件削除していたら1週間かかってしまいます。1週間この仕事に集中するのもおすすめですが、もっとおすすめなのがGASを使った一括削除です。

# 事前準備
まずこのようなスプシを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6144/36b12e0a-76aa-7d17-9a1c-07870224ad35.png)

あとはGASに次のスクリプトをセットします。

```
function getCalendarEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  const startTime = new Date('2021/07/14 00:00:00'); // 間違えてデータを入れてしまった日付の開始日
  const endTime = new Date('2021/07/15 00:00:00'); // 間違えてデータを入れてしまった日付の終了日
  const events = calendar.getEvents(startTime, endTime);
  const values = [];
  for(const event of events){
    const record = [
      event.getTitle(),
      event.getStartTime(),
      event.getEndTime(),
      event.getId()
    ];
    values.push(record);
  }
  SpreadsheetApp.getActiveSheet().getRange(2, 1, values.length, values[0].length).setValues(values);
}
function deleteCalendarEvents(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var calendar = CalendarApp.getDefaultCalendar();
  var ids = sheet.getRange(2, 4, sheet.getLastRow() - 1).getValues();
  for(var i=0; i<ids.length; i++){
      var event = calendar.getEventById(ids[i]);
      event.deleteEvent();
  }
}
```

# やってみよう
初回実行時はGoogleカレンダーへアクセス許可が求められるので、覚悟を決めたらOKしてください。
1. `getCalendarEvents()` を実行する
2. うまくいくと以下のキャプチャのように予定が取れます
3. 消したくない行を削除します
4. `deleteCalendarEvents()` を実行して、待ちます。
5. みるみるうちに（意外と遅いペースで）予定の件数が減っていきます。
6. 実行が終わったらおしまいです。

↓ステップ２がうまくいったよ
![無題のスプレッドシート_-_Google_スプレッドシート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6144/3e5bafb0-f126-4c6a-a7dc-5cfc3498cafb.png)

# 最後に
どうでしょう。この記事を書き始めるときに残り２０００件だったカレンダーのイベントが、もう５００件になろうかとしています！

Don't be afraid of mistakes!
（久しぶりにコード書いて楽しかったです。弊社の愛すべき開発チームがQiita LGTMを集めているので貢献します）

# 追記
Rate limited! しきい値は15分以内に2500件てところかな。
大量すぎる場合は何回かに分けて実施しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6144/63442fbf-46e9-e695-3c96-a48b8fc7c965.png)

今回はDelete結果をロギングしてないので、リジュームする場合は、一回スプシを空っぽにしてGetからやり直すのが良いと思います。
Azure Application Insights 料金節約術 - Web ページ解析編

# Intro はじめに

Azure Application Insights は、監視サービスである Azure Monitor の機能のひとつで、インフラ層よりも上のアプリケーション層のパフォーマンスや状態を監視します。
サーバーサイドのアプリケーションログだけでなく、ウェブページのページビューやパフォーマンス解析も可能です。Google Analytics や Adobe Analytics の役割、といったほうが伝わりやすいかもしれません。
![alt](https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/media/app-insights-overview/diagram.png)
出典： https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/app-insights-overview

この記事では、この機能を使うときの料金を節約する方法を紹介します。

# Application Insights の料金

Application Insights の料金は、おおまかに以下の2つのパラメータで決まります。

- ログをボリュームに書きこむ量：データインジェスト（GB あたり〇〇円）
- ログを保持する期間：データ保持期間 Data Retention（ひと月、GBあたり〇〇円）

詳細は、公式ドキュメント [Azure Monitor の価格](https://azure.microsoft.com/ja-jp/pricing/details/monitor/) を参考に。さらに、 [料金計算ツール](https://azure.microsoft.com/ja-jp/pricing/calculator/?service=monitor)で見積もってみると想像つきやすいかもしれません。

上記2つのうち、後者のデータ保持期間は後から [ワークスペースの設定を変更することで調整できます](https://docs.microsoft.com/ja-jp/azure/azure-monitor/logs/manage-cost-storage#change-the-data-retention-period) が、**前者のデータインジェストはあらかじめログ収集時に調整する必要があります。**また、そもそも収集する量を小さくすることで、長期間保持したときの料金も抑えることができます。

# How to configure 設定方法
Web ページ解析は、ページに以下のような `<script>` タグのスニペットを埋め込むことでログデータが Azure へ送られます。

```
<script type="text/javascript">
!function(T,l,y){
...
cfg: { // Application Insights Configuration
    instrumentationKey: "YOUR_INSTRUMENTATION_KEY_GOES_HERE"
    /* ...Other Configuration Options... */
}});
</script>
```
※ スクリプトの多くを省略しています。正しいスニペットはドキュメントを見てください。
https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/javascript

このスクリプト上の `cfg` に最低限必要な `instrumentationKey` が設定されていますが、この箇所に以降で説明する構成を行っていくことで、このスクリプトが送信するログの量を小さくすることができます。

構成フィールドの意味やすべての一覧は[ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/javascript#configuration)を参照ください。以下にデータインジェスト量に影響がありそうなフィールドを抜粋していきます。デフォルトで送信しないようになっているものもありますが、関連しそうなものは明示的に書きます。 **太字**になっている箇所が、デフォルト値と異なるものです。

| 名前        | Default     | 最小となる設定値 |
|:-----------|-------------|--------------|
| disableExceptionTracking | false        | **true**       |
| disableTelemetry | false        | **true**       |
| enableDebug | false        | false       |
| loggingLevelConsole | 0        | 0       |
| loggingLevelTelemetry | 1        | **0**       |
| samplingPercentage | 100        | 100 <br>※これを変更するとサンプリングされるためデフォルト値のまま |
| disableAjaxTracking | false     | **true**      |
| disableFetchTracking | true     | true      |
| disableCorrelationHeaders | false     | true      |
| correlationHeaderExcludedDomains | undefined     | **string[]** <br>※対象外ドメインを指定     |
| disableCookiesUsage | false     | **true** <br>※ただしcookieデータを扱わないだけでデータインジェスト量はそれほど変わらないはず |

## 設定サンプル
とりあえずこれを構成すれば限りなく最小に近づきます。ただし、本来必要としていたログがオフになってしまうと本末転倒ですので注意してください。

```
cfg: { // Application Insights Configuration
    instrumentationKey: "YOUR_INSTRUMENTATION_KEY_GOES_HERE",
    disableExceptionTracking: true,
    disableTelemetry: true,
    loggingLevelTelemetry: 0,
    disableAjaxTracking: true
}});
```
tfstateがS3からダウンロードできない。

# はじめに
tfstateファイルを保存するAWSアカウント、Terraformでリソースを作成するAWSアカウントの2つのAWSアカウントでTerraform構築した際に、tfstateファイルをS3からダウンロードできないってことがありましたので、原因を記載します。
まずはじめに、Terraformでは以下のコードのようにtfstateの保存先をbackendで指定することができます。


```hcl
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "3.50.0"
    }
  }
  # この部分
  backend "s3" {
    bucket = "xxxxxxxxxxxxxx"
    key = "terraform.tfstate"
    region = "xxxxxxxxxxxxxx"
    profile = "xxxxxxxxxxxxx"
  }
}

provider "aws" {
  profile = var.aws_profile
  region = var.aws_region
}
```

# 私が発生した事象
backend s3、provider awsのprofileプロパティの切り分けを行わなかったことで、tfstateを保存したアカウントと、Terraformを実行したアカウントが一緒になりました。

tfstateファイルの保存先アカウントのバケットに、Terraformでリソースを作成したアカウントでtfstateファイルを格納していました。

つまりこんな感じです。
tstateファイルを格納したバケットの権限：tfstateファイルを保存する**予定だった**AWSアカウント
tstateファイル（オブジェクト）の権限：Terraformでリソースを作成したアカウント


# 予防策
前述の通り、backendブロック、providerブロックで正しくprofile指定してあげることで、tfstate保存先アカウントでtfstateファイルをダウンロードすることができます。

つまり、以下のように記載します。

```hcl
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "3.50.0"
    }
  }
  # この部分
  backend "s3" {
    bucket = "xxxxxxxxxxxxxx"
    key = "terraform.tfstate"
    region = "xxxxxxxxxxxxxx"
    # tfstate保存先アカウントの権限でtfstateファイルを格納する。
    profile = "xxxxxxxxxxxxx"
  }
}

provider "aws" {
  # AWSリソースを作成する環境と権限は、Terraformでリソースを作成するアカウントを指定する。
  profile = var.aws_profile
  region = var.aws_region
}
```

# もし、時すでに遅しな場合
Terraformでリソースを作成したアカウントでaws cliなどでtfstateファイルをダウンロード、オブジェクトの権限変更などを検討してみてください。
Apps Up 2021の申し込み期間が延長されました

Apps Up 2021の申し込み期間が2021年8月20日から2021年9月5日に延長されました。賞金総額はUS$1,000,000もあるので、まだ参加していない方はぜひお見逃しなく！ 

詳しい情報はこちらをご参照ください！

https://qiita.com/Rei_2020/items/55bac1aa83ea0cfd7132
unicorn起動時に発生した(Errno::ENOENT)の解消法

## 前提
Ruby on Railsで開発したWebアプリをEC2にデプロイしようとしています。

・環境：Mac OS（M1） Big Sur 11.4
・Ruby -v：3.0.2

## 発生した事象
unicornを起動しようとした以下のコマンドをEC2内で叩きました。

```EC2
$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
```

するとすると…
以下のように怒られてしまいました。

```EC2
/home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/lib/unicorn/configurator.rb:85:in `read': No such file or directory @ rb_sysopen - config/unicorn.rb (Errno::ENOENT)
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/lib/unicorn/configurator.rb:85:in `reload'
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/lib/unicorn/configurator.rb:78:in `initialize'
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/lib/unicorn/http_server.rb:78:in `new'
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/lib/unicorn/http_server.rb:78:in `initialize'
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/bin/unicorn_rails:209:in `new'
	from /home/ユーザ名/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unicorn-6.0.0/bin/unicorn_rails:209:in `<top (required)>'
	from /home/ユーザ名/.rbenv/versions/3.0.2/bin/unicorn_rails:23:in `load'
	from /home/ユーザ名/.rbenv/versions/3.0.2/bin/unicorn_rails:23:in `<main>'
master failed to start, check stderr log for details
```

一番最後に記述されている```master failed to start, check stderr log for details```と書いてあるのでstderr.logを確認してみることにしましょう。

```EC2
$ cd /var/www/アプリ名/log
$ ls
```

```EC2
capistrano.log  production.log
```

あれ？　```stderr.log```なんて無いじゃん。ついでに言うと```stdout.log```も無いじゃん。

## 解決方法

#### 結論 → 必要なファイルがなかった

そう言えばこの辺の記載は```/config/unicorn.rb```に記載していた気がするので、確認してみることに。

```EC2
app_path = File.expand_path('../../', __FILE__)
worker_processes 2
working_directory app_path
stderr_path File.expand_path('../../log/unicorn/stderr.log', __FILE__)
stdout_path File.expand_path('../../log/unicorn/stdout.log', __FILE__)
timeout 30
listen File.expand_path('../../tmp/sockets/unicorn.sock', __FILE__)
pid File.expand_path('../../log/unicorn/unicorn.pid', __FILE__)

preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = '#{server.config[:pid]}.oldbin'
  if old_pid != server.pid
    begin
      Process.kill 'QUIT', File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end
```

上から4行目と5行目に書かれてましたね…
と言うことで該当の2つのファイルと2つのファイルを格納している```unicorn```ディレクトリを作ります。

```EC2
$ mkdir unicorn
$ cd /unicorn
$ touch stderr.log
$ touch stdout.log
$ ls
```
上記のように```/var/www/アプリ名/log```上で```unicorn```ディレクトリを作成し、```/unicorn```に移動した後に2つのファイルを作成すると以下のようにあるべき2つのファイルが```/var/www/アプリ名/log/unicorn```内に作成されます。

```EC2
stderr.log  stdout.log
```

これで再び、最初のコマンドを叩くことで無事に起動することができました。

```EC2
$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
```

### 反省点

・まずはエラー内容を見る
・次にログを見る

よく言われることだと思いますが、本当にこれに尽きると言うのが現実。
IV と nonce の比較

# 概要

「暗号化における IV と nonce の違いってなんだろう？よくわからない 🤔」とふと思い、調べてみた。

# IV と nonce の比較

## 定義

### IV

IV は初期化ベクトル (initialization vector) のこと。[初期化ベクトル - Wikipedia](https://ja.wikipedia.org/wiki/%E5%88%9D%E6%9C%9F%E5%8C%96%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB) によると

> 初期化ベクトルはビット列であり、ストリーム暗号またはブロック暗号を任意の暗号利用モードで実行するとき、同じ暗号鍵でストリームを生成しても毎回異なるストリームを生成するのに必要とされる。これにより、毎回暗号鍵を替えるといった時間のかかる作業を省くことができる。

加えて IV は予測不可能な乱数である必要がある。

余談だが、暗号化のたびに初期化ベクトルは変更することが望ましい。さもないと、例えば CBC モードで同じ初期化ベクトルを使った場合、同じ平文から同じ暗号文が生成されてしまう。つまり同じ平文であることを暗号文から推測されてしまう。

### nonce

nonce は number used once の略であり、一度だけ使用される数字のこと。[ノンス - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%8E%E3%83%B3%E3%82%B9) によると

> ノンスは、暗号通信で用いられる、使い捨てのランダムな値のことである。ノンスはたいてい、認証の過程で使われ、リプレイ攻撃を行えないようにする働きを担っている。

そしてこの Wikipedia には IV との比較に関しても言及されている。

> ストリーム暗号でも、同じ鍵で暗号化の結果をメッセージごとに異なったものとするためにノンスが使われることがある。この場合、メッセージの番号を使うことも多い。同様の目的で使われる初期化ベクトルも、見方を変えればノンスの一種とも考えられる。

### 比較

[cbc - Difference between a nonce and IV - Cryptography Stack Exchange](https://crypto.stackexchange.com/questions/16000/difference-between-a-nonce-and-iv) という記事の回答を参考にした。

- 前提として両者は異なる概念である。
- IV を暗号化のたびに変更する場合、つまり IV が「一度だけ使用されるランダムなビット列」であればそれは nonce とも言える。
- nonce の生成方法として、単に増加する数を使う場合もある。
  - ブロックチェーンの文脈での nonce がこれに該当するようだ。
    - FYI: [Ethereum Series — Understanding Nonce](https://medium.com/swlh/ethereum-series-understanding-nonce-3858194b39bf)

要は「IV と nonce は違う概念だけど、IV の使い方によっては nonce とも呼べる」ってことかなと結論づけた。
【論文要約】変分オートエンコーダ（VAE）を用いた文生成

## はじめに
オートエンコーダ（AE）とは、左下の画像のように潜在変数 z にエンコードした後、もとの入力と一致するようにデコードするネットワークのことです。潜在変数 z は入力 x を圧縮したものだと考えることができます。一方で、z の存在する潜在空間がどのように使われているかは分からないため、適当な z をデコードしても意味のあるデータは生成されません。

変分オートエンコーダ（VAE）は、右下の画像のように、潜在変数 z がある確率分布に従うことを仮定しています。学習時には x' が元の x と一致するように、かつ z が仮定した分布に近づくようにKL損失を設定してパラメータを更新します。これによって、仮定した分布に従う z をデコードすると、学習したデータを生成することができます。学習時には **posterior collapse** と呼ばれる、KL損失を小さくしようとするあまりに仮定した分布と完全に一致してしまい、**潜在変数 z を無視したデコードが行われてしまう現象**が起こらないように気をつける必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614092/e568e3a1-dac0-f996-c30c-67fbf6470a91.png)
[画像引用元](https://www.renom.jp/ja/notebooks/tutorial/generative-model/VAE/notebook.html)

なお、VAEについては[こちらのqiita記事](https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24)が非常に詳しいです。

## VAEによる文生成

[[Bowman+, 2015]](https://arxiv.org/abs/1511.06349)はVAEの枠組みを文生成に適用しようと試みた研究です。
VAEの学習モデルは以下となります。潜在変数 z はガウス分布に従うことを仮定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614092/a3d966ac-4982-6454-0e5f-a3290682cd5c.png)

ところで、ガウス分布に従う z1, z2 を 0 ~ 1 の t で内分するとそれもガウス分布に従います。
<img width=250 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614092/02803aa3-cedb-ceb6-7f6e-0f70b48fb84e.png">
潜在空間を連続的に横断する複数の z から生成された文は次のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614092/551830e1-99be-9bc2-b965-c72466ea1ffa.png)

VAEの学習がうまくいったとすると、これらの文は学習データの特徴を捉えた何らかの文だということになります。似た単語を含む文は潜在空間で近くからデコードされているようですが、意味的に似ているとは限りません。（似た意味の文が潜在空間で近くにくるような学習方法をとっていないからです）

著者はテキストの学習にVAEを用いると、posterior collapse を起こしやすいと言っています。デコーダの入力に対してランダムな単語をUNKに変換することで軽減しているそうですが、そもそも学習時にデコーダにも文の情報を渡してしまうと潜在変数の情報を見なくなる posterior collapse が起こりやすくなるのは納得です。

## おわりに
GANによる文生成もそうですが、離散的な単語列を学習する際にどうしてもデコーダ側で1単語ずつ生成しなければいけないことが本質的な問題だと感じました。CNNのdeconvolutionのように一発で単語列を生成できたら文生成も可能になりそうですが、そんなことしたら文法崩壊必至なので難しいです。ポジションエンベッドを応用してなんとかなりそうな気はしています。
研究DXに必要な、課金してでも使うクラウドサービス 

変化が早く、研究成果数のインフレ状態に対応しながら、海外と戦っていくための、研究業務のDXが必要です。
以下、使って便利だと思ったクラウドサービスとその感想です。すべて課金しての利用です。今後も使い続けます。
高くてもせいぜい年間1~2万円程度ですので、お試しあれ。

# Gmail (+ Google Drive)
- メールで多くのOfficeファイルをやりとりしているため、５年、１０年経つと、無料利用では容量が足りません。昔は、少しずつデータを消して容量を明けたりしていましたが、一度課金し始めたら、その作業が完全になくなり時間を有効活用できるようになりました。
- 若手教員は同じ研究機関などにずっと所属することはほぼなくなりました。海外に出ても、一生使えるメールアドレスとしてGmailをメインで使って下さい。所属機関が発行するE-mailに届くものはGmailに飛ばし、Gmailから所属機関メールとして送ります。過去のEmailのコピーやバックアップ作業から解放されます。
- ノートPCを買い換えるごとにわざわざ過去のメールをコピーしたりする必要はありません。いったい何度この作業をやりましたか？
- 昔はフリーメールはセキュリティの問題が言われていましたが、今では一転して、研究室や学部・学科で自前でメールサーバーを持っている方がセキュリティ上のリスクが高くなっています。セキュリティのゆるいWebサーバーからランサムアタックを受けて、ネットワークにつながっているPCのデータがすべて吹っ飛ぶリスクがあります（経験済み）。

# Dropbox 
- 課金すると1TBまで自由に使え、その快適さはやめられません。外付けハードディスクにバックアップをしてもいつか壊れます。USBの規格も形状も変わっていきます。これからは、情報保護の観点からハードディスクを捨てるのにもお金がかかり、苦労します。一体、何台使ってもいない古いハードディスクを持っているのか、わかりません。
- 大学などの研究機関ではMicrosoftのoneDriveやGoogle Driveを使えるようにしてくれています。でも、所属機関を移動したら使えなくなります。外付けHDを買ってコピーし、新天地で再度アップロードをし直す。コピーの手間が増えます。しかも、引っ越しで忙しいときに・・・。自腹を切っておいた方が良いです。
- 機密情報にはパスワードつけることが求められているのでご注意を。リスクと利便性を天秤にかけて覚悟をもってアップロードします。むしろ、危ない書類は持たないように工夫します。大事なものは事務方が必ず紙媒体で保管しています。

# Overleaf（オンラインLaTeX) 
- 課金するとコンパイルにかかる時間制限（無料版は１分まで）がなくなります。学会要旨程度であれば、無料利用でも大丈夫なはずですが、なぜか学生がつくるLaTeX原稿はコンパイルに時間がかかるものです。（画像ファイルサイズが大きいなどが原因。）
- 課金するとDropboxと連携されますので、オンラインでなくてもローカル環境で編集できます。
- 過去のバージョンのスナップショットは定期的に残しておくと良いです。Dropboxではどのバージョンにも戻れますが、どれがよいか分からなくなります。
- 昔ながらの方法で、レポートや論文を紙に印刷して先生の部屋をノックして渡し、数日後に修正がされたものを受け取って、赤いミミズ文字を解読するのに１日を費やすという作業が、圧倒的に効率化されます。まさにDXです。
- 先生は、はじめは学生と原稿を白紙からシェアして、どんどん書き進める作業をオンラインでやってみせることができます。次の段階で、言って聞かせながら少しずつ書いてもらい、最終的に、任せるという３段構えです。オンライン時代ではなおさら便利です。こまめな修正フィードバックが可能なので、学生自身も大きな手戻りが発生しません。対面で話すときには論文の構想に時間をかけられます。
- 海外共同研究の場合は、さらに強力ですが、誰がどこをこれからどのように書き直すかについては、結局コミュニケーションが肝心です。チャット機能があり、数式もLaTeX形式で打てます。
- 自分が課金して、共同研究者と共有すれば相手は無料で使えます。そして、成果物はこちらで握ります。
- LaTeXを使ってくれない共同研究者には、少なくともWordファイルをDropboxなどでシェアさせてもらいましょう。それもできなければ、最終原稿のpdfを送ってコメントをもらいます。もし、Wordにコピーし直してメールで送ってくれと言われたら、そうしてください。相手がそれだけの価値があると判断してのことですから。
- 科研費の申請書フォームも整備されており、Overleafでもきれいにコンパイルできます。
    - テンプレート検索で"科研費LaTeX"と入力すると2020年度版が見つかります。 https://www.overleaf.com/latex/templates?addsearch=%E7%A7%91%E7%A0%94%E8%B2%BBLaTeX
- 学会の要旨は、例えば: https://www.overleaf.com/latex/templates/liu-ti-li-xue-hui-nian-hui-yong-yuan-gao-tenpureto2019/hmnsnpqvyjxx

# Grammarly
- スペルチェッカーです。非常に優秀です。正直言って、かなり英語の勉強になりました。
- 自分の文のクオリティがスコア化されます。80点以上のクオリティを目指しましょう。
- 赤ペン先生のチェックとほぼ同じ効果があります。三単元のsが抜けていたり、主語動詞が抜けていたり、文になっていなかったらすぐわかります。
- 誰かからメール添付されてきた原稿のスコアが30点台だったら、すぐに突き返しましょう。（使ってみればわかりますが、どうやったらそんなスコアが出せるのか不思議です。）
- 学生は先生に見せる前に、必ずスペルチェックを通し、先生には論文のストーリーに集中してもらうのが良いでしょう。
- どうしてもうまく表現できないところは英語ネイティブの友達に相談しましょう。
- Chromeブラウザ上でも稼働するので、GmailやこのQiitaの記事にもスペルチェックが通ります。
- Overleafとの連携もプラグインで可能らしいです。 https://www.youtube.com/watch?app=desktop&v=WxMH7Bk9tC0

>Qiitaでは日本語の文章と１行空けないとスペルチェックが通らないようです。

:::note
以上、個人が使ってるものです。もっと良いものがあると思います。他にもオススメあればご紹介ください。
:::