OmniAICreator/Qiita-1.07M huggingface dataset:
Bloggerの投稿画像のlazy-loadの自動化のJavaScriptプログラムと説明 

Bloggerの記事／追加ページの投稿画像や動画に遅延読み込みのHTML属性のloading-lazyを自動的に付けるJavaScriptプログラムの内容を説明する。

```JavaScript:JavaScript
// 記事の本文の要素（psbd）と記事の本文の中身に付けたnoscript要素（blct）と記事の本文の要素の複製（npsbd）と新しいdiv要素（nl）の取得
const psbd = Blog1.querySelector("div.post-body"), blct = psbd.querySelector("noscript.blog-content"), npsbd = psbd.cloneNode(false), nl = document.createElement("div");

// 新しいdiv要素の高さを画面の縦幅と同じにする
nl.style.height = "100vh";

// 新しいdiv要素をnoscript要素の前に挿入する（無地の画面が最初に素早く出るようにしてプログラムの実行で記事の表示が遅れた際の違和感を減らすため）
psbd.insertBefore(nl, blct);

// 記事の本文の複製にnoscript要素のソースコードを入れる（noscript要素のソースコードはテキストだからそのままではHTMLとして扱えないため）
npsbd.insertAdjacentHTML("afterbegin", blct.innerText);

// 記事の本文の複製から画像要素を取得
const blimgs = npsbd.querySelectorAll("img");

// 画像の場合（画像か動画ありで振り分け済みの記事の本文から動画を除外するため／遅延読み込みに動画を含めるならば不要）
if (blimgs[0]) {

// 画像の配列をPromise.allとmapで並列処理
Promise.all([...blimgs].map(blimg => {

// 画像の縦横比（aroi）の取得
const aroi = blimg.dataset.originalWidth / blimg.dataset.originalHeight;

// 画像に縦幅がある場合、画像に横幅がなくて縦横比があれば算出して付ける
if (blimg.height) {
if (!blimg.width && aroi) blimg.width = blimg.height * aroi; }

// 縦幅がなくて横幅がある場合、縦横比があれば算出して縦幅を付ける
else if (blimg.width) {
if (aroi) blimg.height = blimg.width / aroi; }

// 縦幅も横幅もなくて縦横比があってCSSの縦横比の指定のaspect-ratioがない場合、画面の横幅に対する画像の縦幅を算出して画像の位置（ブログの最上段からの距離）を推測するためにCSSのaspect-ratioを付ける 
else if (aroi && !blimg.style.aspectRatio) {
blimg.style.aspectRatio = aroi; }

// 画像のalt属性が空だったら「イメージ」（日本語の場合）を記載する（Google検索のサイト評価のモバイルユーザビリティの警告を避けるため）
if (!blimg.alt) blimg.alt = "<data:messages.image/>";

// 画像要素のURLをカスタムデータへ移動してsrc属性から取り除いて画像要素を次のthen関数へ渡す
blimg.dataset.src = blimg.src; blimg.src = "": return blimg; })).then(results => {

// 記事の本文の複製を元の記事の本文（中身がnoscriptタグで止められたもの）と入れ換える（画像の位置を把握するために記事の本文を表示させるが、この時点ではsrc属性が空だから画像は読み込まれない）
psbd.parentNode.replaceChild(npsbd, psbd);

// 画像の配列をforEachで繰り返し処理
results.forEach(result => {

// 画像の上側が画面の高さと同じかそれよりも大きい（画面に表示されない位置にあるかあると推測される）場合、loading属性のlazyを付ける
if (result.getBoundingClientRect().top >= window.innerHeight)
 result.loading = "lazy";

// 画像要素のsrc属性にカスタムデータのURLを移動して画像の読み込みを可能にして（loading-lazyを付けたものは画面に入るまで読み込まれない）カスタムデータを空にする。
result.src = result.dataset.src; delete result.dataset.src; }); }); }

// 動画の場合（遅延読み込みに動画を含めるならば不要）、記事の本文の複製を元の記事の本文（中身がnoscriptタグで止められたもの）と入れ換える（記事の本文が全て表示される）
else {
psbd.parentNode.replaceChild(npsbd, psbd); }
```

出典：[Bloggerの記事／追加ページの投稿画像にloading-lazyを自動的に付けるプログラム](https://www.nagahitoyuki.com/2022/01/automatic-lazy-load-program-for-post-and-page-images-on-blogger.html)
参考：[画像のloading-lazy：遅延読み込みによる高速化について](https://www.nagahitoyuki.com/2021/03/about-speeding-up-by-loading-lazy-of-images.html)
React で指定した数コンポーネントを繰り返したいとき

# Reactで指定した数繰り返したいとき
とりあえずお手軽に書きたいときに
```typescript
// 100回繰り返される
{[...Array(100)].map(() => <HogeComponent />)}
```
配列オブジェクトをスプレッド構文`...`で展開しmapでJSXで埋めている感じです
「達人に学ぶSQL徹底指南書」の演習問題 4-2を解いてブラウザのMySQLの実行環境で実行してみた

## この記事の概要
- この記事は「達人に学ぶSQL徹底指南書/ミック著」の演習問題をブラウザのSQL実行環境([db-fiddle.com](https://www.db-fiddle.com/))で動かしてみたというものになります。
- 解説は書籍の方を参照してください。
- カラム名がアルファベット表記になっていることに注意してください

## [db-fiddle.com](https://www.db-fiddle.com/)の使い方
- テーブルの作成: Scheme SQLの欄にCREATE TABLEを書いてテーブルを作成
- データの挿入: Query SQLの欄にINSERT文を書いてデモデータを挿入する
- データの検索: Query SQLの欄にSELCT文を書く

## 問題文

本文中のStudentsテーブルを使って、特性関数の練習を少しやっておきましょう。
それでは、「全員が9月中に提出済みの学部」を選択するSQLを考えてください。
答えは、「経済学部」ただ一つになります。
理学部は100版の学生が10月になってから提出しているので却下、
文学部と工学部は、そもそも未提出の学生がいる時点でこれも駄目です。

## データセットの準備
- Schema SQL
```sql
CREATE TABLE Students(
  student_id int NOT NUll,
  dpt varchar(255) NOT NULL,
  sbmt_date date
);
```

- Query SQL
```sql
INSERT INTO Students VALUES 
('100', 'science', '2005-10-10'),
('101', 'science', '2005-09-22'),
('102', 'literature', NULL),
('103', 'literature', '2005-09-10'),
('200', 'literature', '2005-09-22'),
('201', 'engineering', NULL),
('202', 'economics', '2005-09-25');
```

## 解答

詳しい解説は書籍を参照してください。

```sql
# 解答パターン1: 学部ごとの行の合計と指定した期間に提出した学生の行の合計が等しい学部だけ抽出
SELECT dpt
FROM Students
GROUP BY dpt
HAVING 
	COUNT(*) = SUM(CASE WHEN sbmt_date BETWEEN '2005-09-1' AND '2005-09-31'
                  						THEN  1 ELSE 0 END);

# 解答パターン2: 学部ごとの行の合計とsbmt_dateに2005と09という文字列が含まれている行の数の合計が一致する学部だけ抽出
SELECT dpt
FROM Students
GROUP BY dpt
HAVING
  COUNT(*) = SUM(CASE WHEN EXTRACT (YEAR FROM sbmt_date) = 2005
                      AND  EXTRACT (MONTH FROM sbmt_date) = 09
                      THEN 1 ELSE 0 END);
```
laravel ErrorCase setAttribute(), 1 passed ...HasAttributes.php ... exactly 2 expected

### Laravel Illuminate\Database\Eloquent\Model::setAttribute(), 1 passed in HasAttributes.php on line 525 and exactly 2 expected

引数が足りないということはないはずで、以下の記事にも該当しない。
https://qiita.com/qiita-kurara/items/d14a8d26954c843cdca3
https://toriyaru.com/2019/11/08/laravel%E3%81%AE%E3%83%97%E3%83%A9%E3%82%A4%E3%83%9E%E3%83%AA%E3%82%AD%E3%83%BC%E3%81%AE%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%B8%E3%81%AE%E3%82%A4/

$primaryKey = null;
の件も該当しない。

やっていることは updateOrcreate の引数に、ディレクトリ配下のファイル群を指定していた。
調べてたら .DS_Store がディレクトリ配下に作成されておりファイル群に含まれてしまっていたためだった。
複数のディレクトリをforeachで回しているところだったので気づき辛かったが、処理がDB更新ということもあり、エラーが出力する前までの処理はDBに正しく反映されていたため、特定ができた。

.gitignore に .DS_Store を追加して、ことなきを得た。

我ながらアホらしい。
GASでSlack検索＆結果を投稿

※初めての投稿なので、温かい気持ちで読んでいただけると幸いです。

# 背景
Slackを全社導入し、拡大する中で増えてくる参加チャンネル。
当然、届くメッセージも増えてくるわけでいつしかできたマイルール。
特定のリアクションをつけて、後から「has:reaction hasmy::{リアクション名}:」で検索。
ところが、それすら忘れる始末。いかん。

# やったこと
* GAS（GoogleAppScript）でSlackの投稿を検索
* 検索結果をSlackの特定チャンネルに投稿
※Botユーザの作成は済んでいる前提の記事となります

# GAS（GoogleAppScript）でSlackの投稿を検索
```javascript:slackManage.gs
const token = "{Slackのトークン}";
const channelId = "{送信先チャンネルのID}";

function getAtode() {
  var date = new Date();

  if (date.getDay() == 0 || date.getDay() == 6) {
    return;
  }

  let options = {
    "method": "get",
    "contentType": "application/x-www-form-urlencoded",
    "payload": {
      "token": token,
      "query": "has:reaction hasmy::atode:"
    }
  }
  
  let url = 'https://slack.com/api/search.messages';
  let response = UrlFetchApp.fetch(url, options);
  
  let obj = JSON.parse(response);
  let count = obj.messages.total;
  let matches = obj.messages.matches;
  
  let resMessage = "【後でやる一覧】\n";
  
  for (var i = 0; i < count; i++) {
    resMessage = resMessage + matches[i].permalink + '\n';
  }
  sendSlack(resMessage, channelId);
}
```

# 検索結果をSlackの特定チャンネルに投稿
```javascript:slackPost.gs
function sendSlack(message, channelId) {
  let token = "{Slackのトークン}";
  let slackApp = SlackApp.create(token);

  slackApp.postMessage(channelId, message);
}
```

# 最後に
GASでめんどくさいを自動化してハッピーライフ

# 参考
[GASとは](https://developers.google.com/apps-script)
[Slackのメッセージ検索](https://api.slack.com/methods/search.messages)
AttributeError: 'WebDriver' object has no attribute 'find_element_by_id'　の解決策

## 課題
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1446396/1bd0fa63-0fc3-72f5-8ea3-0dc2ecb6de98.png)
JupyterLabでスクレイピングを行おうとしたら
AttributeError: 'WebDriver' object has no attribute 'find_element_by_id'
とのこと。

## 仮説
・タイプミス
・取得しようとしてる場所がおかしい

## 原因
結局仮説は外れており、ぐぐったらすぐ出てきた。
seleniumのバージョン4以上から、find_element_by_xxxのメソッドが使えないみたい。
以下コマンドでバージョンを確認してみると...

```
pip list
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1446396/5a7b6657-11db-fedb-18f3-afb7e132a16a.png)
4.3.0でした！

## 解決策
・seleniumバージョンのバージョン下げる
・4.3.0の書き方で記述
バージョン変えるの面倒だから4.3.0用の書き方でやってみる。

```
finf_element_by_name　　　　　　　　　　　　　　　　　　　　旧バージョン
find_element(By.ID, "xxx")　　　　　　　　新バージョン
```
ただ、これでもNameErrorが出てしまい、どうしたものかと思っていたら、
Byをimportしなければいけないようだったので、こちらも忘れずに！！
```
from selenium.webdriver.common.by import By
```
これで無事動きました。

## 学び
・バージョンによって書き方変わることがある
・仮説を立ててから課題解決に向かう(ぐぐる)と早い
・今回は仮説全然違ったけど、違うってことがわかるだけでまぁよかったかな
・仮説の精度はこういう経験の蓄積だと思われる
・当たり前なんだけど、そもそも吐き出されたエラー内容を正しく理解する
思いつきによる初心者のiOS開発 part.1

思いつきでSwiftでiOSのアプリを開発することにしました。
触ったこともないので単純な画面と単純な機能を作成してみます。

1.画面イメージの作成
今回は、単純な画面イメージを作成します。
なぞなぞを出題し、回答するアプリケーションとしており、上部に問題文を表示し、下部に回答を入力するテキストボックスとボタンを作成する予定です。
テキストが解答とあっていると、正解と表示されます。下部のボタンをタップすると、正解の表示が消え、問題文が表示される状態になります。
以下は画面のイメージです。

![スクリーンショット 2022-07-26 13.31.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2126675/1479bb01-c462-a379-0ed8-8f860083ef2b.png)

2.XCodeで実装
単純なものですが、作成するのに５時間ほどかかりました。
画面イメージとは少し違う感じですが、まあよいとしましょう。
なにか作ることが目的だったので本人としては満足です。
以下はソースコードと作成した画面になります。

```ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var answer = ""
    @State var buttonText = "Button"
    @State var flag = false
    
    // 画面サイズ
    @State var screenSizeHeight = UIScreen.main.bounds.height
    @State var scSizeWidth = UIScreen.main.bounds.width
    
    var body: some View {
        VStack {
            Spacer().frame(height:screenSizeHeight/16)
            if (self.flag){
                Text("正解！！")
                    .frame(width: scSizeWidth, height: screenSizeHeight/8, alignment: .center)
                    .font(.largeTitle)
                    .background(.red)
                    .foregroundColor(.white)
            }else{
                Text("")
                    .frame(width: scSizeWidth, height: screenSizeHeight/8, alignment: .center)
            }
            Spacer().frame(height:screenSizeHeight/128)
            Text("みないでのむのみものは？")
                .font(.title)
                .foregroundColor(Color(red: 122/255, green: 122/255, blue: 122/255, opacity: 1.0))
            Spacer().frame(height:screenSizeHeight/8)
            TextField("Answer", text: $answer)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
                .offset(x: 0, y: screenSizeHeight/8)
                .frame(width: scSizeWidth*0.8, height: screenSizeHeight/8, alignment: .center)
                .background(.white)
            Spacer().frame(height:screenSizeHeight/8)
            Button(action: {
                if (self.flag ){
                    buttonText = "Return"
                    flag = false
                }else if(answer == "みず"){
                    buttonText = "Button Tapped"
                    flag = true
                }
            }){
                Text(buttonText)
                   .font(.largeTitle)
                   .foregroundColor(Color(red: 122/255, green: 122/255, blue: 122/255, opacity: 1.0))
                   .padding()
            }
            Spacer().frame(height:screenSizeHeight/16)
        }
        .frame(maxWidth: .infinity,
               maxHeight: .infinity)
        .background(.white)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
            ContentView()
    }
}

```

![IMG_3191.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2126675/29702306-6baf-8fe4-3232-2add76dd923e.png)
![IMG_3192.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2126675/73c5cf1c-6c5b-4ca7-a345-912b0ca0f70c.png)

３.今後の予定
気まぐれでiOS開発をしようなかと思っています。
答えが間違っている時に動作がなにもないので、変更しようかな。
part.1としましたが、今回が最終回の予定も気がしなくはないです。。。
【jQuery】超初心者 初日の学び

アニメーション

・hide
　隠す

・fadeOut
・sideUp
　これらは大文字！注意。

・$('#signup-modal')などmodal系ゴリゴリ
・addClass
・removeClass
　マウスを乗せたときの表示反応たち

・hasClass
 持ってるクラスによって動作変える

こうやってただただ新しく覚えたこと列挙していくだけでも記憶の整理されそう。
Symfony Stringでアローラロコンをアーロンにする

PHPの標準機能でもアローラロコンをアーロンにできるのだが、Symfonyの[String Component]というやつがちょっとおしゃれな文字列演算を提供しているので、せっかくなのでこれを使ってみよう。

https://qiita.com/tadsan/items/828cf43ec902440255cb

## Symfony String Componentとは

https://symfony.com/doc/current/components/string.html

だいたいドキュメントを見ればわかる。

Symfony Stringは`ByteString` `CodePointString` `UnicodeString`という3種類のクラスを用意しているが、基本は`UnicodeString`が扱う[書記素(grapheme)](https://ja.wikipedia.org/wiki/%E6%9B%B8%E8%A8%98%E7%B4%A0)という単位で扱うのがいいだろう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121/a3f3a754-9126-bfde-75c3-9bf1d14dd397.png)

## Stringオブジェクトを作る

普通の文字列値をStringオブジェクトに変えるには`new`で新しいインスタンスを作る方法とヘルパー関数を使う方法がある。

まあ、こだわりがなければヘルパー関数を使っておくと見ためがすっきりするだろう。

```php
<?php
use function Symfony\Component\String\u;

$str = u('アローラロコン');
//=> Symfony\Component\String\UnicodeString {#2673}
```

`u`という短かすぎる関数名が趣味に合わなければ、 `use function Symfony\Component\String\u as unicode;`のようにすると、そのファイル内では `unicode('文字列')` のように扱える。

さて、Symfony Stringオブジェクトは`(string)`キャストすると普通の文字列値に戻せる。

```php
>>> (string)$str
//=> "アローラロコン"
```

## chunkとarray_map()とsliceを使う方法

```php
<?php
use function Symfony\Component\String\u;

echo implode(array_map(fn($s) => $s->slice(0, 1), u('アローラロコン')->chunk(2)));
```

* 元の文字列をSymfonyの`UnicodeString`にする
    * `u('アローラロコン')`
* `UnicodeString::chunk()`で文字列を分解する
    * `[u('アロ'), u('ーラ'), u('ロコ'), u('ン')]`
* `array_map()`でそれぞれの要素から `UnicodeString::slice()` で1文字だけ抜き出す
    * `[u('ア'), u('ー'), u('ロ'), u('ン')]`
* `implode()`を1個の文字列に結合する
    * `'アーロン'`
    * 配列の要素は`string`ではなく`UnicodeString`オブジェクトなのだが、`implode()`関数は配列要素が`string`ではなかったときに`(string)`キャストしてくれる
        * 内部的には `UnicodeString::__toString()` [マジックメソッド](https://www.php.net/manual/language.oop5.magic.php#object.tostring)が呼ばれている
* **完成！**

`UnicodeStrging::join()`というメソッドもあり、これを使うと`string`にキャストせず`UnicodeString`のまま結合できるのだが、今回はそのまま出力するので標準関数の`implode()`をそのまま呼んでしまってよい。

```php
echo u('')->join(array_map(fn($s) => $s->slice(0, 1), u('アローラロコン')->chunk(2)));
```

## chunkとarray_chunk()とarray_column()を使う方法

```php
<?php
use function Symfony\Component\String\u;

echo implode(array_column(array_chunk(u('アローラロコン')->chunk(), 2), 0));
```

PHPの解法とほとんど変わらないので手順は割愛する。手数は一見多そうだがコードとしては`array_map()`を噛ませるよりちょっぴり短い。

## 動作確認

https://phpsandbox.io/n/lively-forest-ddgd-3shiu にまとめてある。

余談だが、普段オンラインサンドボックス環境で使っている[3v4l](https://3v4l.org/)は軽量かつPHPの複数バージョンを同時実行できて便利だが、ライブラリに依存したコードが書けない。[phpsandbox.io](https://phpsandbox.io/)は任意のComposerパッケージを追加した状態で実行できるので、場合によって使い分けるのがいいだろう。

## まとめ

文字列処理にはSymfony Stringを使え。

なんかもうちょっと短く書ける解法がありそうな気もするのでSymfonyマスターのひとは教えてください。

[String Component]: https://symfony.com/doc/current/components/string.html
sass-migratorでnode-sassからdart-sassに移行する時メモ

前回はdart-sassに移行した、もしくは新しくsassを使う場合の
dart-sassの変更点などをメモしたました。

今回は既存のプロジェクトで既にnode-sassが使われており、
それをdart-sassに移行する時に便利な方法だと思います。
※コマンドはyarnで行っております。

## 既存のnode-sassを削除する
```zsh
yarn remove node-sass
```
こちらで既存のnode-sassを削除できます。

## dart-sassをインストールする
```zsh
yarn add -D sass
```
ただのsassになっていますが、dart-sassのパッケージ名はsassのようです。

## そしてsass-migratorをインストール
```zsh
$ yarn add -D sass-migrator
```
sass-migratorが自動で移行してくれます。

今回は下記のような構成を準備しました。
```
root
style.scss
_base.scss
-- _mixin.scss
-- _valuse.scss
```

各ファイルの中身
```_valuse.scss
$pc: 769px; // PC
$sp: 768px;  // スマホ
```

```_mixin.scss
@import "../values/valuse";

@mixin pc {
  @media (min-width: ($pc)) {
    @content;
  }
}
  
@mixin sp {
  @media (max-width: ($sp)) {
    @content;
  }
}
```

```_base.scss
@import "./values/valuse";
@import "./mixin/mixin";

body{
 font-family: '小塚ゴシック Pro L','Kozuka Gothic Pro Light',sans-serif;
}

.pc {
  display: block;
  @include sp{
    display: none;
  }
}

.sp {
  display: none;
  @include sp{
    display: block;
  }
}
```

```style.scss
@import "./base"
```

## sass-migratorを実行
今回はディレクトリのルートにまとめている`style.scss`があるので、
下記のように実行したいscssを指定します。
```zsh
$ yarn sass-migrator --migrate-deps module style.scss
```
実行すると下記のように変更されました。

```_mixin.scss
@use "../values/valuse";

@mixin pc {
  @media (min-width: (valuse.$pc)) {
    @content;
  }
}
  
@mixin sp {
  @media (max-width: (valuse.$sp)) {
    @content;
  }
}
```

```_base.scss
@use "values/valuse";
@use "mixin/mixin";

body{
 font-family: '小塚ゴシック Pro L','Kozuka Gothic Pro Light',sans-serif;
}

.pc {
  display: block;
  @include mixin.sp{
    display: none;
  }
}

.sp {
  display: none;
  @include mixin.sp{
    display: block;
  }
}
```

```style.scss
@use "base";
```
`@import`が`@use`に変更されました。
また、読み込んだ変数やmixinにも名前空間がついて変更されています。

#### オプションについて
`--migrate-deps`をつけて実行していますが、
これをつけると指定したファイル、今回だと`style.scss`だけでなく、
`@use`、`@forward`、`@import`を使って依存しているファイルも変換くれるそうです。

他にもオプションが多数ありますが、自分は特に使わなかったので、
詳細は下記参考にいただければと思います。

https://sass-lang.com/documentation/cli/migrator#global-options

以上、node-sassからdart-sassに移行する方法でした。
既存のプロジェクトなどではは、`@import`を使っているものがたくさんあると思うので、
なにかと重宝しそうです。
これらの新しいwebpack5機能はご存知でしょうか。

2018年にwebpack4がリリースされて以来、数多くのマイナーバージョンがリリースされています。2年後の10月10日、20日にバージョン5.0がついにリリースされました。 公式サイトの言葉を借りるには、画期的な大きな変化は嫌いなので、多くの困難を重ね、画期的な変化を起こさなければなりません。 もちろん、そのエコロジーもそれに応じて更新されます。これまでのところ、4.xバージョンは依然として企業の主流ですが、時間の経過とともに、それらのほとんどは5.xバージョンに移行されます。
投稿の前に

投稿の前に，単純な準備を

コードブロックについて

単に　　\`\`\`　 とする人が多いですが，言語により，
たとえば Python ならば 　\`\`\`Python　 としましょう

注釈記号　　# の後には 半角空白を置きましょう

その他，プログラムの書式については，多くありますが，たとえば

https://python.keicode.com/lang/control-basic-rule.php

その他を見ましょう。

人様に見せるプログラムは，動けばいいというものではないのです。
【メモ】非同期でhttpリクエストを実行する。

こんな感じでhttpリクエストができる。（メモ程度）

```typescript:members.component.ts    
  ngOnInit(): void {
    const myAsync = async (url) => {
      const response = await fetch(url); //await で fetch() が完了するまで待つ
      const data = await response.json(); //await で response.json() が完了するまで待つ
      console.log('1');
      return data;
    }
    myAsync('http://localhost:8080/api/messages').then(value => this.members = value).catch((err) => this.disp('データの取得に失敗しました。'));
    console.log('2');
  }
```
```typescript:members.component.ts
ngOnInit(): void {


    const myAsync = async (url) => {
      let d:any;
      const response = await fetch(url,{method: "POST",body: JSON.stringify(d)}); //await で fetch() が完了するまで待つ
      const data = await response.json(); //await で response.json() が完了するまで待つ
      console.log('1');
      return data;
    }
    myAsync('http://localhost:8080/api/messages').then(value => this.members = value).catch((err) => this.disp('データの取得に失敗しました。'));
    console.log('2');

  }
```
Fig-dig-deepパネリストによるFig-dig-deepの内容まとめ

## この記事の概要

[2022年7月27日開催のFigma Japan Community Event](https://figmajapancommunityevent.splashthat.com/)にて、Fig-dig-deepというコーナーがありました。

FigmaのCEOのDylanさん、プロダクト責任者のYuhkiさん、Chief Customer OfficerのAmandaさんに、普段では聞けないような質問をたくさんしてみよう！というコーナーです。

[Shunさん](https://twitter.com/shunwitter)と[坪田 朋さん](https://twitter.com/tsubotax)と私とで色々と聞かせてもらいました。

せっかくなのでその内容を記事にしてみます。

:::note warn
- パネリストがゆえに、正確に覚えていない箇所もあります
    - 質問の順番は結構怪しいです
    - 前日にFigmateの方と話していたのもあり、若干記憶が混同している箇所があるかもしれません
    - メモをとれていないので、漏れもあると思います
    - 記憶の中分解、合成してしまっている気がして、質問と回答の対応づけが若干違うかもしれません
- 英語が苦手ゆえに、不正確な訳もあると思います
    - 同時通訳を入れてもらっていましたが、どうしてもラグが生まれて英語を聞いていたシーンもあります
:::

## 内容

### Figmaの無い時代、Figmaをどうやって作っていた？

作り始めた頃（2012年くらい）はFireworks。

そのうち、FigmaでFigmaを作れるようになった。

これは大きな転換点だった。

### Figmaのアップデートは、ユーザーが本当に欲しいものばかりを提供してくれる。何故それが実現できるのか？

ユーザーの声を聞く。聞くけど、すべてを鵜呑みにはしない。

「なぜその機能が欲しいのか？その背景は？」を大切にする。

漸進的な変化と革新的な変化をバランスよく。

ポートフォリオを上手に構成するように。

同時編集機能も、欲しいという声自体は全然無くて、でも絶対役立つと思って作った。

### 優先度決定はどうしている？揉めることはない？

ユーザーストーリーをしっかり捉えて、それが達成できるものを重視する。

意見に相違はほとんどないけど、生まれたときは徹底的に話し合う。

### 日本のコミュニティに期待することは？

意見をたくさん欲しい。何を求めているかなど、どんどんフィードバックをもらいたい。謙虚な気持ちで受け入れたいと思っている。

コミュニティに参加してほしい。プラグインやウィジェットを作って、公開してほしい。

こういう会もそうだし、どんどん盛り上がって欲しい。

### コミュニティでのコラボレーションはGitHub的な進化をする？

大規模なオープンソースのデザインを作りたい。

1つのものを100人とかで作ってるような。

コミュニティファイルにコントリビュートできるようにしたい。

### ブランチ機能はProfessionalプランで使えるようになる？/現状あまり上手く使えていないけどどうしたらいい？

大規模なデザインシステムがあるような会社だと良いと思う。

だからOrganizationプラン以上で提供している

例えばMicrosoftさんのデザインシステムだと、どの変更がエクセルに影響して、パワーポイントに影響して……と分からない。

だから、全体を把握している詳しい人が上手く管理するのにブランチを使うと良いと思う。

### Auto layoutなど、コードと親和性の高い機能が揃っている。今後どうなる？

Figmaには良い機能がたくさんあるけど、複雑にはしたくない。

簡単で楽しい！って気持ちとちゃんと両立させたい。

コード的な表現（flexの挙動とか）は段階的に増やしていってる。

いきなり全部を出しても良くないので、少しずつユーザーに提供している。

### すごく便利だけど多機能で、初心者に教えるときに少し迷う。どうしたら良い？

いきなり白紙のファイルから作らず、コミュニティのファイルを色々参考にしてほしい。

Inspectで見て、真似てみることからはじめるとか。

全部を自分で作らなくて良いのも（コミュニティファイルを上手く活かして作れるのも）Figmaの魅力。

それに、FigmaはAuto layoutが使いこなせなくても楽しい！

### Figmaをデザイナー以外が使うのって、どう？

素晴らしい！

アイディエーションからプロダクションまで、どんどんFigmaの幅は広げていきたいと思ってる。

### 知らないうちに課金が増えちゃう

簡単に共有できるからこそ、というのもある。

これもまた簡単さと堅牢さのバランスが難しい。

エンタープライズプランでは管理できる、とかもあるんだけど。

メールでお知らせするとかで悪い体験にはならないようには心がけてる。

## 実際に質問した上での感想

非常に緊張しましたが、他では聞けない話を直接聞けて良かったです。
特に、ユーザーの意見は大切にするけど、鵜呑みにせず背景の「Why」を重視するバランス感を真似られるようになりたいと思いました。

また、Qiitaを運営している者として、コードとの連携もこれからたくさん増えていきそうなのも楽しみです。
今も[AWS Amplify StudioとFigmaの連携](https://aws.amazon.com/jp/blogs/news/aws-amplify-studio-figma-to-fullstack-react-app-with-minimal-programming/)などはありますが、Figmaからコードへどうアプローチしていくのかが楽しみです。。

デザイナーだけでなくエンジニアにとっても良いツールだと思っているので、シンプルでパワフル、そして楽しく使えるFigmaがもっと広がっていくと良いなと思います。

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox
インフラエンジニアがぷよぷよプログラミングをやった。

2022年4月から新しい教育要領となり、高校生の情報が必修となりました。
当方がインフラ寄りのエンジニアなのでプログラミングと接点は少ないですが、何年後かに業界入りする人に「ぷよぷよが作れないのが許されるのは高校生までだよね」と言われないようにやってみました。
※ぷよぷよプログラミングが必修科目というわけではないです。


# 実施内容

以下の内容をやりました。
教育者・小中高生向けなどあり、迷子になりやすいです。
https://edu.monaca.io/puyo


## Monacaアカウント登録
MonacaはブラウザベースのIDEのようです。
MSアカウントでログインしました。


## デバッガーアプリのインストール（スマートフォンで動かしたい場合のみ）

Monacaでデバッグできたので割愛しました。


## ぷよぷよプログラミングに挑戦

基礎・初級・中級・上級のコースがあります。
コースごとのソースコード全体の差はなく、抜けてる行を写経する量が増えていきます。

基礎からステップアップし、徐々にぷよぷよが完成していくというものを想像していたので、基礎コースで４行書いて「えっ、もう終わり！？」となってしまいました。

### コードの写経

Monocaにソースをインポートします。コースごとにインポートされる行数が違います。
抜けている行をPDF資料に記載されているコードを写経していきます。
PDFはちゃんとコピペ出来ないようになっていました。

普通にタイプミスで動かない時ありました。


### デバッグ

IDE右ペインがデバッガー（エミュレーター？）になっていました。
基礎コースでは１行ごとに落下、左右移動、回転、消去と動作が増えて行きますので、１行追記したら動作確認をしていきます。
普段この手のリッチ（？）な動作確認をしないので環境の問題かもしれませんが、やたらとゲームスピードが早いような気がしました。
なんなら描画速度が追いついておらず、瞬間的にぷよが重なって見えます。
４行なので写経ミスはないと信じたいです。
割愛したスマホ用のデバッガーアプリならちゃんと動くのかもしれませんが、ブラウザでもスマホのOSを指定はしてるんですよね…


# 活用の仕方
この教材を一通り行ったからといっても、でぷよぷよのようなものが作れる！ということはないと思います。
プログラミングを覚えるというよりもパソコンの操作を覚える、成果物を作った経験（≒快楽）を得る、という目的で入門編として使うのがよいかなと思いました。

未経験新人採用を行っている会社ではPCの使い方、タイピングなどから研修を行うので、当座の研修資料として使うこともできるかなと思います。
特に、手順書の通りに正確に作業を行う能力を養う、という観点ではいい感じに罠がちりばめられているので、有用な気もします。
あと、ゲームという見栄えがしてわかりやすい媒体というのもポイントが高いと思います。
~~ネットワークとかPingの応答があったから達成感を感じることができる新人は稀有かなと思うので。~~

[学校向け](https://edu.monaca.io/puyoschool)配下の資料をあされば、教育的な知見も得られるかもしれません。

~~今の若い人はぷよぷよは知らないとかは言わないでほしいと思います。~~



# 参考
https://www.mext.go.jp/a_menu/shotou/zyouhou/detail/1416746.htm
基礎情報技術者理論(3)－確率

確率とは，ある事象（結果）が発生する可能性を表す数値である。事象 A の確率P(A)は、事象Aの起こりうる場合の数（ｒ）と全ての事象の数（ｎ）から、次のように求める。

世紀文保はガウス文保とも呼ばれ、総計学では最もよく用いられる連続型の確率文保である。自然界でも多く見られ、理論的にもとても場合の良い性質を持っている。実用面でも多くの場面で誤差関数（仮定した異論的な値と観測された値との差）として正規文保が仮定されている。

確率変数（実数）をx、平均を表すパラメータ（実数）をμ、標準偏差を表すパラメータ（正の実数）をσとすれば、
正規文保の確立密度関数は次式で表される。

１…正規分布
平均値を中心とする左右対称で釣鐘状の連続確率分布のこと

２…一様分布
すべての事象が起こる可能性が等しい現象を表す確率分布のこと

３…ポアソン分布
離散的に発生し，発生確率は一定である離散確率分布のこと
【Next】動的なページへの遷移後に、指定の位置(hash)までスクロールさせたい

個人的な議事録です。

# 環境
- react: 17.0.2
- next: 11.1.3

# やりたいこと
- 動的に高さの変わるページへhash値を渡して、ページを遷移後に指定位置にスクロールさせたい

# 試してダメだったこと
- 遷移前の component に next/link で hash を渡す

```tsx:一覧のcomponent
import NextLink from 'next/link';

// 省略

const UserListComponent = () => {

	return (
		<NextLink
		  href={{
		    pathname: `/user/${id}`,
		    hash: 'scroll',
		    query: {
		      hoge: hoge,
		    },
		  }}
		  passHref
			/**
			 * ページの先頭にならないように、false にしてみた
			 * @see https://nextjs.org/docs/api-reference/next/link
			*/
			scroll={false}
		>
		  <p>
		    Moves to user details page
		  </p>
		</NextLink>
	)
}
```

```tsx:スクロールさせたいcomponent
// 省略

const FormComponent = () => {
return (
	<div id={'scroll'}>ここまでスクロールしてほしい</div>
	)
}
```

- これだと スクロールさせたい DOM が生成される前なので、場所を把握できずスクロールしなかった

# 指定位置までスクロールできたコード
- スクロールさせたい component のレンダリング後に、自身で hash を追加するとキレイにスクロールした
- 流れとしては以下の通り
	- fetch 後に動的な component を描画
	- スクロールさせたい component のレンダリング後、初回だけ router.replace で hash を追加

```tsx:fetch
export const useUser = (id: string) => {
  // 省略

  const { data, error } = useSWR(`/api/user/${id}`, fetcher)

  return {
    user: data,
    error,
    isLoading: !error && !data,
  }
};
```

```tsx:一覧のcomponent
import NextLink from 'next/link';

// 省略

const UserListComponent = () => {
	return (
		<NextLink
			// ここで hash を追加しても意味ないので、今回はなし
		  href={{
		    pathname: `/detail/${id}`,
		    query: {
		      hoge: hoge,
		    },
		  }}
		  passHref
		>
		  <p>
		    {text}
		  </p>
		</NextLink>
	)
}
```

```tsx:親component
// 省略
const { user, error, isLoading } = useUser(id)

// fetch前に component が表示されてしまうと、位置がズレるため、親component で fetchData があれば、完了するまで表示させない
if(isLoading) {
	return null;
}

const ProfileComponent = () => {
	return (
		// 動的に生成される component
		<InformationComponent />
		// スクロールさせたい component
		<formComponent />
	)
}
```

```tsx:スクロールさせたいcomponent
// 省略

/**
 * useEffect 内で(レンダリング後に) hash を追加する
*/
useEffect(() => {
	// history に追加しないように router.replace
  router.replace({ hash: 'scroll' }, undefined, { shallow: true });
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const FormComponent = () => {
	return (
		<div id={'scroll'}>ここまでスクロールできた</div>
	)
}
```
PHPでアローラロコンをアーロンにする

PHPでもアローラロコンをアーロンにしたい。

つまり、

https://twitter.com/hirokiky/status/1552193259820032001

Pythonの[str型はテキストシーケンス型と呼ばれていて](https://docs.python.org/ja/3/library/stdtypes.html#text-sequence-type-str)、これに[スライスという操作](https://docs.python.org/ja/3/tutorial/introduction.html#strings)をしてやるといい感じになるのですが、**PHPにそんなおしゃれな機能はない**。

ではどうするのか。

## 配列演算を使う方法

```php
<?php
echo implode(array_column(array_chunk(mb_str_split('アローラロコン'), 2), 0));
```

* 元の文字列
    *  `'アローラロコン'`
* `mb_str_split($str)` で1文字ごとの要素に分解
    * `['ア', 'ロ', 'ー', 'ラ', 'ロ', 'コ', 'ン']`
* `array_chunk()` で2要素ごとの塊に分解
    * `[['ア', 'ロ'], ['ー', 'ラ'], ['ロ', 'コ'], ['ン']]`
* `array_column($array, 0)` でそれぞれの配列の`[0]`を抜き出した配列を作る
    *  `['ア', 'ー', 'ロ', 'ン']`
* `implode()` で配列を1個の文字列に結合
    * `'アーロン'`
* **完成！**

動作確認: https://3v4l.org/G8OKf

## 正規表現を使う方法

```php
<?php
preg_match_all('/(.).?/u', 'アローラロコン', $m);
echo implode($m[1]);
```

* 元の文字列
    *  `'アローラロコン'`
* `preg_match_all()` で、2文字単位でマッチさせ、先頭文字をキャプチャする
    * パターンに `/u` 修飾子を使ってUnicodeとして評価することでカナも1文字としてマッチする
        * `'ア', 'ロ'` `'ー', 'ラ'` `'ロ', 'コ'` `'ン'` それぞれにマッチする
    * `(.).` と書くことで2文字のペアのうち、先頭だけをキャプチャする
        * `$m[0]`には ``['アロ', 'ーラ', 'ロコ', 'ン']`` が入っている
        * `$m[1]`には ``['ア', 'ー', 'ロ', 'ン']`` が入っている
* `implode()` で `$m[1]`を1個の文字列に結合する
    * `'アーロン'`
* **完成！**


動作確認: https://3v4l.org/7aS79

やりましたね！

## まとめ

PHPでもユニコードとテキストシーケンスを言語サポートしているPythonほどではないが、PHPでもアローラロコンをアーロンにできる。

キミの好きな言語でもアローラロコンをアーロンにしてみよう！

## 追記

PHPでもSymfony Stringを使った解法を別記事に書いた。

https://qiita.com/tadsan/items/7ea5c9bc4df9f908628e
Power BI「ワイルドカード文字のフィルター方法」

## 概要

ワイルドカード文字のフィルター方法です。何を今更いってるんだと言われそうですが、即答できなかったので自習して備忘録に。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/bfa73a14-9561-c6c6-6c50-c31ea77a3921.png)

## 相談

「*」を含む文字列をフィルターで除外または選択したい

## とりあえず

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/3696e848-1ae1-c330-e884-2a601062d433.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/a65b3e68-ddda-0f35-d0b6-ffd3a1d61e4e.png)

うまくいかない

## CONTAINSSTRING

https://docs.microsoft.com/ja-jp/dax/containsstring-function-dax

>? と * のワイルドカード文字を使用できます。 ワイルドカード文字をエスケープするには ~ を使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/ebc28287-98de-218f-5acb-4734f25216df.png)

## 解決

フィルターにおいてもチルダ「~」でエスケープするだけでした。

「*」を含む文字列をフィルターで除外または選択したいときは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/77a19252-4582-1ce2-7d6c-44c42a1bb2a5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/5ffa166c-825e-02ab-e508-bc80d4325656.png)

「***」アスタリスク3個だけを指定したいとき、それ以上アスタリスクの個数が存在しないとわかっているなら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/880769bd-8148-8f93-7ff9-2790f80a954b.png)

では「**」アスタリスク2個だけを指定したいときは？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680039/6a5023b9-c20e-9420-8832-c722039463d3.png)

「*」アスタリスク1個のときは同じ要領なので略
「?」についても略

## まとめ

2022年7月の備忘録でした。
そもそも文字列に「*」アスタリスク混ぜないでほしいなｗ
【#41 エンジニア転職学習】HTTP POSTメソッド作成時の動作確認エラー

# はじめに
富山県に住んでいるChikaといいます。
毎日投稿を目標に、バックエンドエンジニア転職に向けた学習内容をアウトプットします。

7月いっぱいまでGoのフレームワークであるGinについて、
学習をしていきます。

バックエンドエンジニアになるまでの学習内容は以前投稿した以下の記事を基にしています。


https://qiita.com/Chika110/items/ef54dddd565a0193ef44

# 本日の学習内容
GinのTutorialとして簡単なRESTfulAPI作成の学習をしました。

* **POSTメソッド作成　動作確認時のエラー　←Topics!!**
* 特定のアイテムを返すGETメソッド作成

# POSTメソッド作成時の動作確認エラー
本日もGinのAPI作成チュートリアルを進めていたのですが、
その中でターミナルでの`curl`コマンドを使用した
POSTリクエストの動作確認時にエラーが発生して、解決に手間取りましたのでまとめます。

#### 解決策
header情報内の`"`の前に`\`を入れてエスケープさせる
#### 原因
Windows環境でcurlコマンドを使用したとき限定の不具合のようです
Linux等では問題なく実行できるようなのでWindowsのみ気を付ける必要あり
#### エラーが発生したコマンド

```
curl -X POST -H "Content-Type:aplication/json"  
   -d "{"id": "4","title": "The Modern Sound of Betty Carter", 
   "artist": "Betty Carter","price": 49.99}" "http://localhost:8080/albums"
```
#### エラーメッセージ
```
curl: (3) URL using bad/illegal format or missing URL
curl: (6) Could not resolve host: Modern
curl: (6) Could not resolve host: Sound
curl: (6) Could not resolve host: of
curl: (6) Could not resolve host: Betty
curl: (3) URL using bad/illegal format or missing URL
curl: (3) unmatched close brace/bracket in URL position 20:
Carter,price: 49.99}
                   ^
```
#### 修正後のコマンド
```
curl -X POST -H "Content-Type:aplication/json" 
   -d '{\"id\": \"4\",\"title\": \"The Modern Sound of Betty Carter\",
   \"artist\": \"Betty Carter\",\"price\": 49.99}' http://localhost:8080/albums
```


##### 使用している教材はこちら↓


https://go.dev/doc/tutorial/web-service-gin


# おわりに
最後までお読みいただきありがとうございました。
アドバイス・応援コメント等いただけますと幸いです。
クラウド上のLinuxサーバを踏み台にしてSSHを経由してリモート環境へアクセスする

SSHポート転送を利用すれば、リモート環境のSSHやリモートデスクトップへアクセスできます。  
本記事ではリモート環境へSSH接続する手順を解説します。  
この手法を使えば下記のようなことが実現できます。  

- 職場から自宅のLinuxサーバへのsshアクセス
- 職場から自宅のRaspberry Piへのsshアクセス
- ネットワークが繋がっていない別開発拠点のLinuxサーバへのsshアクセス

また、この手法を応用すれば下記のようなことも実現できます。  

- 職場から自宅のPCへのリモートデスクトップ接続
- ネットワークが繋がっていない別開発拠点のPCへのリモートデスクトップ接続

## 概要

1. リモートサーバのsshポート[22]をssh リモートポートフォーワーディングで踏み台Linuxサーバのポート[2222]として転送
1. 踏み台Linuxサーバのポート[2222]をssh ローカルポートフォーワーディングでローカルのポート[2223]として転送
1. ローカルＰＣのポート[2223]へアクセスすることにより、リモートサーバのsshへ接続可能

<img src="https://user-images.githubusercontent.com/38905609/181226202-6f63ced6-0dd0-4688-8cae-195f7e0db9b3.png" alt="image" width="70%"/>

※ どこが`リモートポートフォーワーディング`で、どこが`ローカルポートフォーワーディング`か混乱しやすいので気を付けて下さい。

## リモートサーバから踏み台Linuxサーバへの接続

基本的な接続については[SSH を使用した Linux インスタンスへの接続](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html)を参照下さい。

- プライベートキーファイルをリモートサーバへ転送します。  
   この手順ではプライベートキーファイルを`~/.ssh/key-pair-sample.pem`とします。  
- リモートサーバのsshポート[22]をssh リモートポートフォーワーディングでEC2のポート[2222]として転送します。  

EC2のパブリックIPを`xxx.xxx.xxx.xxx`とします。  
EC2はubuntuのAMIとします。(amazon linux の場合はユーザ名は`ubuntu`ではなく`ec2-user`です。)  

```sh
ssh -R 2222:localhost:22 -i ~/.ssh/key-pair-sample.pem ubuntu@xxx.xxx.xxx.xxx
```

接続時の警告を回避するオプションを追加しています。

```sh
ssh -t -t -R 2222:localhost:22 -i ~/.ssh/key-pair-sample.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ec2-user@xxx.xxx.xxx
```

## ローカルPCから踏み台Linuxサーバへの接続

- この解説ではTeratermを使ってまずローカルPCから踏み台Linuxサーバへssh接続されていることを前提とします。  
接続については[SSH を使用した Linux インスタンスへの接続](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html)を参照下さい。
- EC2のポート[2222]をssh ローカルポートフォーワーディングでローカルのポート[2223]として転送します。  
<img src="https://user-images.githubusercontent.com/38905609/181381247-aa16d5bf-0a43-4243-875c-50bb50cefce8.png" alt="image" width="50%"/>  

<br/>
<img src="https://user-images.githubusercontent.com/38905609/181220258-4bf15c19-0088-42aa-99d0-8e1532797d25.png" alt="image" width="30%"/>

## ポートフォーワーディングを経由してローカルPCからリモートサーバへ接続

- ローカルポート[2223]へ接続します。  
![image](https://user-images.githubusercontent.com/38905609/181221820-3977301b-aca9-4993-8dae-7fe76c7bf106.png)

## 応用

- リモートデスクトップはTCPの3389番ポートを使用します。  
- 同様の手順で3389番ポートをポートフォーワーディングすればリモート環境のPCへリモートデスクトップ接続することが可能です。  

## 👇参考URL

- [クラウド上のLinuxサーバを踏み台にしてSSHを経由してリモート環境へアクセスする](https://docs.saurus12.com/tips/ssh_port_forward)

## 本記事へのリンク

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708851/b686895d-01b7-7e38-9473-3c708e36a3b8.png)

### [https://docs.saurus12.com/tips/ssh_port_forward](https://docs.saurus12.com/tips/ssh_port_forward)

[keywords]  
ssh ポートフォーワーディング Linux リモートデスクトップ

更新日:2024年02月22日
「モノリスからの脱却って実際どうなの？マイクロサービス化について赤裸々に語るの」メモ書き残しておく

※大丈夫なのかな？

リアタイで聞きながらメモった内容を書きだしてみた。
聞き漏らしていたり表現を自分なりにわかりやすくとかもやっているのでまんまではないことをあしからず。


https://increments.connpass.com/event/252389/



# 以下メモ


## 参加パネラー企業

ディップ、カカクコム、カオナビ（敬称略）

## モノリス→マイクロサービス

### テクニック
- レガシーな状態でやるのはむしろコスト
- モノリスな状態で複雑性をベンチマークしながら下げることを先にコツコツすすめた方がいい
- インフラのリアーキテクトによって捨てられるコードもあるのでそういう手段もある
    - 食べログはkafkaとかを使って16日かかっていた検索用インデックスの同期を3.5時間にした？
- DBはあと、先にコード
- フロントは変えずバックエンドのリファクタリング
- kubernetes（スペルあってるか？）
- マイクロサービスのDB分割は後回し？
- マイクロサービスの前にAPI化
- やりやすいところ、新機能から事例を作る？
- [マイクロサービスアーキテクチャ](https://amzn.to/3zAfDGh)にとにかく忠実に
- revert多発前提
- 武器屋（チーム）を作る

### 組織、教育
- CTOが旗を振っている
- CTO配下に専任チームを作った？
- 設計が悪くて開発スピードが出ないという背景をCTOがビジネス側の人たちに説明し、工数なりを捻出
- メンバーへはドキュメント化、丁寧な説明などによって裾野を広げる理解を深める
- モノリスのデメリット、マイクロサービスのメリットを提示しROIのメリットを明確にする
- 6人で6ヶ月？（規模わからね）
- スキル（クリーンアーキテクチャ、DDDらへんの）高いメンバーを寄せ集めてチームを作った
- ワクワクを感じる人達
- 事業継続の面からあるべき姿を定義して経営層への説明などを進めた（冗長化しやすいとか）
- 横展開（隣のチームが「それうちも使いたい」といって先行しているチームがハンズオンとか外枠の設計とか提供してもらったり）

### 課題はあるかあったか？聞きたいことはあるか？
- [質とスピード](https://speakerdeck.com/twada/quality-and-speed-2022-spring-edition)のあるべきすがたのようなものをかなり忠実にやっていけばいいだろうと考えている
- プロパーだけでやってる？
    - BPも巻き込んでいる、分断などはあまりない。自律性はプロパーのほうがある。
    - 人数をと工数を見積もり、欲しいスキルを持った人材をジョブ型的に採用（プロパー、フリーランス、BP問わず）
    - 予算の問題はあまりないが、人員構成とかをきっちり作ることで弾かれない。

- 向いているプロダクト、組織
    - あまり解散しない組織は向いている、心理的安全性
    - スクラムチーム（各自が当事者意識を持っている）
    - 実行力（やる）
    - マイクロサービスにするだけ大きい組織、プロダクト
    - フルサイクルだけでなく、スペシャリストを賄えるだけの組織（技術、ビジネスとも）

- マイクロサービスの目的
    - リアーキテクチャリング、正しい顧客へのoutcome
    - ビジネスモデルに沿って、売上につなげる、そのためにマイクロサービスが最適だろうという仮説
    - 少人数で自立的開発できる（フルサイクルエンジニア）


# 最後に

どこかにアーカイブ残ってないかな、前半結構業務片手間に聞いてて結構逃している。
MX KEYS MINIをlinux(debian bullseye)で使ったときのメモ

# まとめ

+ MX KEYS MINIのパッケージには"Linux"対応の文字列があるものの、実はデフォルトだと動作しない
+ https://github.com/pwr-Solaar/Solaar を使うと解決した
+ 「BIOS(UEFI)に入る前にDELキーを押す」という用途でも使えた

# Solaarのインストール方法

+ ここを見ればOK: https://pwr-solaar.github.io/Solaar/installation

+ 手順(2022/7/27時点)
  + 1.1.4のzipをダウンロード：https://github.com/pwr-Solaar/Solaar/releases/tag/1.1.4
  + zipファイルを解凍
  + `pip install --user .` でインストール
  + 管理者権限でファイルコピー: `sudo cp ~/.local/share/solaar/udev-rules.d/42-logitech-unify-permissions.rules /etc/udev/rules.d/`
  + SolaarをGUI経由で起動して、Logi boltのUSBレシーバーを抜き差しする、認識したらそのまま指す
yarn startでサーバーが立ち上がらない

# 概要
UdemyでReact Hooksの講座を学習中、環境構築で詰まったのでまとめる

https://www.udemy.com/course/react-hooks-101/

# エラー文
Error: error:0308010C:digital envelope routines::unsupported

# 解決策
https://qiita.com/yuzu_283879391/items/8e9acaea257d64231037

こちらの記事と同様の問題と判明。
使用しているNodeのバージョンが新しすぎたことが原因らしい。
講座で使用しているバージョンに変更したところ、無事にサーバーが立ち上がった。
変更方法は以下の記事を参考。

https://qiita.com/k3ntar0/items/322e668468716641aa5c
すでに存在しているCloudWatchアラームからCloudFormationテンプレートを作成する

## 大事なことなので要確認
CloudFormationテンプレートからCloudWatchアラームを作るのではなく、すでに存在しているCloudWatchアラームからCloudFormationテンプレートを作ります。
繰り返します。アラーム設定からCloudFormationテンプレートを作ります。

## アラーム数が少ない場合
CloudWatchアラームが少ない場合は手作業でソースを取得しても良い。
やり方としては、AWSコンソールより、対象となるCloudWatchアラームを選択 > アクション > ソースを表示より、JSONやYAML形式でダウンロードできます。

また、aws-cliを使うことで、取得とある程度の整形もできます。
JSONとYAMLの整形ライブラリであるjqとyqをインストールした状態で、
```sh
brew install jq python-yq
```

下記のようにすると、resouces以下に記述すべき内容が取得できます。このままではフォーマットがあってないため、手動で修正してください。

```sh
aws --profile <YOUR_PROFILE> cloudwatch describe-alarms | \
jq '.MetricAlarms | map({"AlarmName": .AlarmName, "AlarmDescription": .AlarmDescription, "ActionsEnabled": .ActionsEnabled, "OKActions": .OKActions, "AlarmActions": .AlarmActions, "InsufficientDataActions": .InsufficientDataActions, "MetricName": .MetricName, "Namespace": .Namespace, "Statistic": .Statistic, "Dimensions": .Dimensions, "Period": .Period, "EvaluationPeriods": .EvaluationPeriods, "DatapointsToAlarm": .DatapointsToAlarm, "Threshold": .Threshold, "ComparisonOperator": .ComparisonOperator, "TreatMissingData": .TreatMissingData})' | \
yq -y
```

## アラーム数が多い場合
下記例では、Pythonにてアラーム設定を全件取得し、YAML形式で出力します。boto3をインストールした状態でお試しください。CloudFormationテンプレートのResources以下に全行インデントを合わせた状態で挿入してください。また、繰り返し使用するARNや環境はParameterとして定義することをオススメします。

```py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import yaml
from boto3.session import Session

session = Session(profile_name=YOUR_PROFILE)
cloudwatch = session.client('cloudwatch')

alarms = []
# 取得件数最大値を指定しない場合は50件が最大値となる
# 全件取得するためにループを回す
response = cloudwatch.describe_alarms()
alarms = response['MetricAlarms']
while True:
    if 'NextToken' in response:
        response = cloudwatch.describe_alarms(NextToken=response['NextToken'])
        alarms.extend(response['MetricAlarms'])
    else:
        break

# CFnに必要なキー
keys = [
    "AlarmName", "AlarmDescription", "ActionsEnabled", "OKActions",
    "AlarmActions", "InsufficientDataActions", "MetricName", "Namespace",
    "Statistic", "Dimensions", "Period", "EvaluationPeriods",
    "DatapointsToAlarm", "Threshold", "ComparisonOperator", "TreatMissingData"
    ]

for alarm in alarms:
    properties = {k: v for k, v in alarm.items() if k in keys}
    # resource名として既存のAlarmNameを使用しているが適宜変更してください。
    # ハイフンやアンダースコアは使えません
    dic = {
        alarm["AlarmName"]: {
            "Type": "AWS::CloudWatch::Alarm",
            "Properties": properties
        }
    }
    resource_str = yaml.dump(dic, allow_unicode=True)
    print(resource_str)

```

## 補足
作成したYAMLファイルがCloudFormationテンプレートとして文法的に正しいかどうかは下記コマンドでチェックできます。

```sh
aws cloudformation validate-template --template-body ./cloud-watch-alarm.yml
# ファイル内文字列が長い場合はS3上にアップロードする必要があります
aws cloudformation validate-template --template-url https://s3-ap-northeast-1.amazonaws.com/xxxx/cloud-watch-alarm.yml
```
Androidの画面遷移にNavigationを利用する

# Navigation

Android用の画面遷移ライブラリ  
XMLで画面と画面間の接続を定義し、コードから呼び出して利用する  
`Activity`で`startActivity`,`startActivityForResult`や`Fragment`で`FragmentManager`を利用する方法だと、  
各`Activity`と`Fragment`を確認しないと画面同士の繋がりを把握できないが、  
`Navigation`を利用する場合XMLファイルを確認するだけで把握することができる


## 準備

https://developer.android.com/guide/navigation/navigation-getting-started
に従う

`SafeArgs`と併用すると便利


## 使い所

基本的に画面遷移はこれで実装すると良い  
`Activity`や`FragmentManager`は扱いがめんどくさいので`Activity`を1つだけ用意し、  
`Fragment`の遷移で画面遷移を実装するのが楽  
SingleActivityとか呼ばれるパターンらしい。Jake Warthonさんもこれがいいって言ってたらしい  
`ActivityContext`のリークとかにあまり気を使わなくてよくなるのも良い  
20以上は画面があるようなアプリで利用してみたが、特に大きな問題なく利用できた

ChromeCastを利用する場合は注意が必要  
[Googleのガイドライン](https://developers.google.com/cast/docs/design_checklist) での必須要素として、通知領域の実装と通知領域からのExpandedControllerの実装が必要とされている  
これを実装するためのAPIは`setExpandedControllerActivityClassName`となっていて、起動されるActivity名を指定することになる  
SingleActivityの場合、特定の画面を指定できないので、特別な調整が必要となる


## 複数のNavigation

`Navigation`のXMLは1つである必要はないので、アプリケーション内に複数あっても問題ない  
例えば、2022時点のPlayStoreのようなアプリで考えると、大きく2つの`Navigation`になる  
`BottomNavigation`をタップしたときの遷移のNavigationと  
コンテンツをタップしたときに遷移する詳細画面や設定画面への遷移のような画面全体の`Navigation`のような感じ  
ただし、`BottomNavigation`とバックキーの挙動には注意が必要  
私的には。バックキーでアプリが終了する前に`BottonNavigation`の一番左にフォーカスが移動するタイプが好き  
場合によっては、`Navigation`を使用せずに実装することも視野に入れる　


## SafeArgsの利用

`SafeArgs`を利用して`Navigation`に`Action`を記述すると、`XXXDirections`というクラスが生成される  
`arguments`も合わせて、idを利用した遷移より安全なので、利用すると良い


## NavigationとDialogFragmentの注意点

`DialogFragment`も記述できるが少々工夫が必要だったので、無理に利用しないほうが良いかもしれない  
`targetFragment`を利用して`DialogFragment`の結果で遷移するとクラッシュしたりした記憶がある  
`currentDestination`との整合性のとり方が意外と難しい  
その他、`DialogFragment`が多重に表示されたりする  
私の設計では、`DialogFragment`は通常通りに`FragmentManager`を利用して表示するほうが調整しやすかった  
`Navigation`と混在しても問題はあまり発生しない模様


## 画面遷移と関数

画面遷移を一種の関数として考えると、わかりやすくなる場合がある  
コンテンツの購入画面やログイン画面、ダイアログ的に利用する画面がそれに当たることが多い  
コンテンツの購入画面は購入対象のコンテンツのIDを必要とし、コンテンツの購入結果を遷移元画面に返却すると考えるとわかりやすい  
擬似的にコードっぽくすると以下のようになる  

```Kotlin
fun navigateToPurchase(contentId: String): Boolean
```

`contentId: String`の部分は、`Navigation`と`SafeArgs`を利用すると作りやすい  
`Boolean`を返却する部分は、先に書いた方法では作成できないので、  
`NavBackStackEntry#savedStateHandle`を利用すると、遷移先のFragmentから遷移元に値を返せるようなので、  
これを利用すると良さそう

NavBackStackEntryを使った実装は本番で利用したことがないので、使うことがあればまた書きたい


## その他

`DeepLink`とかの機能もあるけど利用したことはない
【PHP初級㉝】for文応用5

[問題]　　(参照：http://www.cc.kyoto-su.ac.jp/~mmina/bp1/hundredKnocksPrimary.html)
整数値を入力させ、1から9まで、入力値以外を表示するプログラムを作成せよ。


# コード
```php
$m = intval(fgets(STDIN));
for($a = 1; $a <= 9; $a++){
    if($a <> $m){
        echo $a,PHP_EOL;
    }    
}    
```

↓「3」と入力

# 結果
```
1
2
4
5
6
7
8
9
```
【React×TypeScript】Property 'value' does not exist on type 'EventTarget'. への対応


## はじめに

型って難しい・・・。

## 本論

画面上にボタンを表示し、クリックするとコンソールにvalueの値（test）を表示するだけのコードです。
VS codeで書きます。
（VS codeで型推論を利用するため。）

```App.tsx
function App() {

  const checkEventType = (e) => {
    console.log(e.target.value);
  }

  return (
    <div className="App">
      <button type="button" value="test" onClick={checkEventType}>Click me</button>
    </div>
  );
}
```

この状態でボタンをクリックすると、下記エラーが発生します。

```
Parameter 'e' implicitly has an 'any' type.
     5 | function App() {
     6 |
  >  7 |   const checkEventType = (e) => {
       |                           ^
     8 |     console.log(e.target.value);
     9 |   }
    10 |

```

イベント（e）に型が指定されていないことが原因です。
明示的にany型を設定すると、問題なく動くようになります。

```App.tsx
function App() {

  const checkEventType = (e:any) => {
    console.log(e.target.value);
  }

  return (
    <div className="App">
      <button type="button" value="test" onClick={checkEventType}>Click me</button>
    </div>
  );
}
```

ただし、イベントにもEvent型というものが存在します。
コードを書き換えます。

```App.tsx
function App() {

  const checkEventType = (e) => {
    console.log(e.target.value);
  }

  return (
    <div className="App">
      {/* 一旦関数を外します。 */}
      <button type="button" value="test" onClick={(event) => { }}>Click me</button>
    </div>
  );
}
```

eventにマウスオーバーすると、下記の通り型が推測されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2608940/c5b97405-af60-4a65-190a-f052b4e5cb2b.png" alt="image.png" width="600">

これをイベントの型に指定します。

```App.tsx
function App() {

  const checkEventType = (e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    console.log(e.target.value);
  }

  return (
    <div className="App">
      {/* 再度関数を指定します。 */}
      <button type="button" value="test" onClick={checkEventType}>Click me</button>
    </div>
  );
}
```

この状態で再びボタンをクリックすると、今度は下記エラーが発生します。

```
TS2339: Property 'value' does not exist on type 'EventTarget'.
    2 |
    3 |   const checkEventType = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
  > 4 |     console.log(e.target.value);
      |                          ^^^^^
    5 |   }
    6 |
    7 |   return (
```

これはEventTarget型にvalueプロパティがないことが原因なので、型アサーションで対応します。

```App.tsx
function App() {

  const checkEventType = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    // 型アサーションでHTMLButtonElementに変換
    console.log((e.target as HTMLButtonElement).value);
  }

  return (
    <div className="App">
      <button type="button" value="test" onClick={checkEventType}>Click me</button>
    </div>
  );
}
```

この状態でボタンをクリックすると、問題なく動作します。

https://codesandbox.io/s/priceless-sanne-wtj5fs?file=/src/App.tsx


## 参考

[【TypeScript】Reactでのイベントに設定する型の理解が少しだけ深まる](https://reffect.co.jp/react/react-typescript-event#i-2)

[EventTarget](https://developer.mozilla.org/ja/docs/Web/API/EventTarget)

[Property 'value' does not exist on type 'EventTarget' in TS](https://bobbyhadz.com/blog/typescript-property-value-not-exist-type-eventtarget)
MacでRocket ChipのVerilogを出力するまで

# 概要

オープンなRISC-Vの実装であるRocket ChipのVerilogコードをmacOS上で出力するまでのメモ。シミュレーションやFPGAへの実装等は含まない。

エディタとしてVisual Studio Codeを使う際の設定にも触れる。

## 必要なソフトウェアのインストール

HomebrewでOpenJDKをインストールする。

```
$ brew install openjdk@11
```

## ソースの取得とMakefragの編集

Rocket Chipのソースコードをクローンする。

真面目に開発するならforkしてからブランチを切る。

```
$ git clone https://github.com/chipsalliance/rocket-chip.git
```

次に、先ほどインストールしたバージョンのOpenJDKが使われるようにする。

`PATH`を書き換えても良いが、以下のdiffのようにMakefragファイルを編集した方が楽だと思われる。

（おそらくM1 Macの場合はJavaのパスが違う。/usr/local/optではなく/opt/homebrew以下？）

```diff
--- a/Makefrag
+++ b/Makefrag
@@ -25,7 +25,12 @@ EMPTY :=
 SPACE := $(EMPTY) $(EMPTY)
 COMMA := ,
 
-SBT ?= java -Xmx$(JVM_MEMORY) -Xss8M -jar $(base_dir)/sbt-launch.jar
+ifeq ($(shell uname -s),Darwin)
+JAVABIN ?= /usr/local/opt/openjdk@11/bin/
+else
+JAVABIN ?=
+endif
+SBT ?= $(JAVABIN)java -Xmx$(JVM_MEMORY) -Xss8M -jar $(base_dir)/sbt-launch.jar
 SHELL := /bin/bash
 
 FIRRTL_TRANSFORMS := \
@@ -33,7 +38,7 @@ FIRRTL_TRANSFORMS := \
 
 FIRRTL_TEST_JAR ?= $(base_dir)/firrtl/utils/bin/firrtl-test.jar
 
-JAVA ?= java -Xmx$(JVM_MEMORY) -Xss8M
+JAVA ?= $(JAVABIN)java -Xmx$(JVM_MEMORY) -Xss8M
 FIRRTL ?= $(JAVA) -cp $(ROCKET_CHIP_JAR) firrtl.stage.FirrtlMain
 GENERATOR ?= $(JAVA) -cp $(ROCKET_CHIP_JAR) $(PROJECT).Generator
 

```

## Verilogの出力

以下でデフォルト構成のVerilogが出る。初回は結構時間がかかる。

環境変数`RISCV`はツールチェインの場所を指定するものだが、Verilogを吐くだけなら定義さえされていれば良い。

実際の開発時は`CONFIG`や`MODEL`をいろいろ変えることになると思う。もしくはMakefragファイルのデフォルト値を編集する。

```
$ cd vsim
$ export RISCV=RISCV
$ make verilog CONFIG=freechips.rocketchip.system.DefaultConfig MODEL=TestHarness
```

ちなみに、Verilogソースは以下のファイルから構成される。

- vsim/generated-src/freechips.rocketchip.system.DefaultConfig.v
  メインの出力ファイル。`TestHarness`がトップモジュール。
- vsim/generated-src/freechips.rocketchip.system.DefaultConfig.behav_srams.v
  キャッシュメモリのVerilogモデルが含まれる。
- src/main/resources/vsrc/plusarg_reader.v
  シミュレーション用のファイル。あまりわかっていない。
- src/main/resources/vsrc/EICG_wrapper.v
  ラッチを使用したクロックゲーティングセルのモデル。

## Visual Studio Codeの設定

1. 以下の拡張機能がコード補完や定義へのジャンプ等ができて便利。

   https://marketplace.visualstudio.com/items?itemName=scalameta.metals

2. クローンしたRocket ChipのルートディレクトリをVSCodeにD&Dして開く。

3. Do you trust...のような警告が出たらYes, I trustを選ぶ。

   以下、ポップアップが消えた場合はVSCodeを再起動するとまた出てくる。

4. 初回はUnable to find a Java 11...のようなポップアップが右下に出るので、Open SettingsしてExtensions->Metals->Java Homeに`/usr/local/opt/openjdk@11`を設定してReload windowする。

5. Multiple build definitions found...のようなポップアップが出たら、sbtを選ぶ。

6. New sbt workspace detected...のようなポップアップが出たら、import buildを選ぶ。

7. しばらくするとインデックスができ上がり、警告が大量に出る。

8. コンソールから`make verilog`すると便利。

9. .gitignoreには以下を追加すると良い。
   ```diff
   --- a/.gitignore
   +++ b/.gitignore
   @@ -13,3 +13,8 @@ docs-target/
    /test_lib/
    /testbuild/
    rocketchip.jar
   +.bloop/
   +.metals/
   +.vscode/
   +project/project/
   +metals.sbt
   
   ```
【簡単】Webサイトの差分チェック＋LINE通知をシェルスクリプトでやる

# はじめに（自己紹介）
筆者は、HottyDBという全文検索エンジン＋機械学習ランキング＋レコメンデーションの機能を搭載したRDBMSを個人開発しています！
本記事とは関係ないですが、HottyDBを解説した記事もあるので、是非そちらも見てみてください！

https://qiita.com/toru1055/items/0fe400989373823e9c78


# この記事の概要
## 概要
簡単なシェルスクリプトでWebサイトの更新をチェックし、差分があった場合にLINE通知する仕組みを紹介します。
EC2(AWS)のようなVPS環境で、crontabを使って実現する方法です。
かなり単純な方法ですが、いざWebサイトの差分をチェックしなきゃ！となったときのためにまとめておきます。

## 背景（なぜこれをやるか？）
* 子供の保活がきっかけ
* 保育園見学の予約受付開始がホームページでのみ連絡され、早い者勝ちで予約が取れる状況だった
* メール通知では気づかない。LINE通知なら筆者も妻も気付くことができそう。
* 他にも小児科のインフルエンザ予約開始がホームページでのみ連絡されるケースもあったので、保活以外にも有効活用できそう

# 手順
## 1. LINEグループ作成
まず通知先となるLINEグループを作成します。
LINEアプリを開いてグループを作成しましょう。

### ポイント
* グループ名は任意で良い
* グループメンバーに 「**LINE Notify**」 のアカウントを追加するのを忘れないようにしましょう（後からでも追加できます）

他にも通知したい相手がいれば、その人をグループメンバーに追加しておきましょう。

## 2. LINE Notify設定
続いて、LINE Notifyのアクセストークンを取得します。

1. [LINE Notifyのサイト](https://notify-bot.line.me/ja/) にアクセスします。
2. 右上のログインボタンでログインします（QRコードログインが便利です）
3. ログインすると右上にアカウント名が表示されるので、そこをクリックし「**マイページ**」にアクセスしましょう
4. 画面を少しスクロールし「**トークンを発行する**」ボタンをクリックしましょう
5. トークン発行画面で「1の手順」で作成したLINEグループを選択し、発行しましょう
6. トークンが表示されたら忘れずにコピーして、どこかに保存しておきましょう

## 3. シェルスクリプト作成
### 環境
* bashのあるLinux環境
* curlとxmllintのコマンドが使えれば動くはずです
* 筆者の場合、EC2でAmazon Linuxのインスタンスを立てたらそのままで動きました

### スクリプト
`diffcheck.sh` という名前で作成したとします。
下記がそのスクリプトの中身です。
```
#!/bin/bash

### 設定 ###
LINE_ACCESS_TOKEN=xxx # 発行したLINE Notifyのアクセストークン
SITE_URL="https://www.hottydb.com/news/blog/" # 差分チェックしたいサイトのURL
MESSAGE="差分がありました" # 通知メッセージ
XPATH="//body" # HTMLから抽出したいXPATHを指定

### スクリプト本体 ###
# 指定サイトの、XPATHで指定した箇所の現在値をnowに保存
curl -s $SITE_URL | xmllint --html --xpath $XPATH - 2>/dev/null > now

touch pre # 初回実行時のエラー回避用
diff now pre  > diff
if [ $? -ne 0 ]; then
  # 差分があればLINE通知実行
  DIFF=`cat diff`
  curl -X POST -H "Authorization: Bearer ${LINE_ACCESS_TOKEN}" -F "message=$MESSAGE $SITE_URL $DIFF" https://notify-api.line.me/api/notify
fi
mv now pre
rm diff
```
スクリプトは以上です！
前半部分はただの変数設定なので、結構簡単じゃないでしょうか？

### 設定のポイント
使う際は下記の設定部分をご自身の状況に合わせて変更してください。
```
### 設定 ###
LINE_ACCESS_TOKEN=xxx # 発行したLINE Notifyのアクセストークン
SITE_URL="https://www.hottydb.com/news/blog/" # 差分チェックしたいサイトのURL
MESSAGE="差分がありました" # 通知メッセージ
XPATH="//body" # HTMLから抽出したいXPATHを指定
```

#### SITE_URL
SITE_URLには差分チェックしたいサイトのURLを指定します。
```
SITE_URL="https://www.hottydb.com/news/blog/" # 差分チェックしたいサイトのURL
```
この設定は、筆者が開発した[HottyDBの公式サイト（ブログ一覧ページ）](https://www.hottydb.com/news/blog/)の差分チェックをする設定となっています。

#### XPATH
XPATHを指定してHTML内のどの部分の差分をチェックしたいかを指定します。
```
XPATH="//body" # HTMLから抽出したいXPATHを指定
```
この設定の場合は、`body`タグ全体の差分チェックをすることになります。



## 4. crontab設定
crontab実行ユーザーのホームディレクトリに先ほど作成したシェルスクリプト 「**diffcheck.sh**」 を配置しましょう。
`chmod 755 diffcheck.sh` などのコマンドで実行可能ファイルに変更しておきましょう。

続いて `crontab -e` コマンドで下記ジョブを追加しましょう。
```
*/30 * * * * ./diffcheck.sh > latest_log.txt 2>&1
```
※サイト側に迷惑にならない程度の頻度で実行するようにしましょう。この設定では30分に1回の設定となります。

# さいごに
手順は以上となります。

これでWebサイトの更新を定期的に確認し、差分があった場合にLINE通知してくれるBOTができました！
VPSの環境さえあれば結構簡単な方法ではないでしょうか？
皆さんも是非試してみてください。

最後に繰り返しになりますが、筆者が個人開発したHottyDBに関する記事もありますので、そちらも是非×2見てみてください！！

https://qiita.com/toru1055/items/0fe400989373823e9c78

# 参考にしたサイト
* https://chiritsumo-life.com/20200821/line-notify-bash/