OmniAICreator/Qiita-1.07M huggingface dataset:
LINE BotのNode.js SDKでプロフィール取得のメモ #linedc


userIdからプロフィールを取得したいってのをちょくちょく使うけど忘れるのでメモ

> [SDKのサンプルコード](https://github.com/line/line-bot-sdk-nodejs/blob/next/examples/kitchensink/index.js#L118-L130)

`client.getProfile()`を利用します。

```js
const profile = await client.getProfile(event.source.userId);
console.log(profile);
```

```js
{
  userId: 'Ubxxxxxxxxxxxxxxxxxxxxxx',
  displayName: 'n0bisuke',
  pictureUrl: 'https://profile.line-scdn.net/ch/v2/p/xxxxxxxxxxxxxxxxxxxxxxxx/exist',
  statusMessage: 'こんにちは',
  language: 'ja'
}
```

## コピペ用: リプライ

[1時間でLINE BOTを作るハンズオン](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)の記事から引用したコードです。

```js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '作成したBOTのチャンネルシークレット',
    channelAccessToken: '作成したBOTのチャンネルアクセストークン'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  
  let msg = ``;

  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  
  //☆ココがプロフ取得箇所
  const profile = await client.getProfile(event.source.userId);
  console.log(profile);
  msg = `${profile.displayName}さんこんにちは。 あなたのユーザーIDは${profile.userId}です。`

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: msg //実際に返信の言葉を入れる箇所
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
```

## コピペ用: プッシュメッセージ

[友達全員にメッセージを送るシンプルな送信専用LINE BOTを作る【Node.js】 #linedc](https://qiita.com/n0bisuke/items/7c2616068cb918ba31a5)の記事から引用したコードです。

ただブロードキャストだと他の人にUserIdなど送るのはあまりよくないのでPushメッセージで自分だけに送信するようにしてます。

```js
'use strict';

const line = require('@line/bot-sdk');

const config = {
    channelSecret: 'チャンネルシークレット',
    channelAccessToken: 'チャンネルアクセストークン'
};

const client = new line.Client(config);

let msg = ``;
  
//☆ココがプロフ取得箇所
const userId = `Ubxxxxxxxxxxxxxxxxxxxxxxxx`; //管理画面や他APIなどから取得しておく
const profile = await client.getProfile(userId);
console.log(profile);

msg = `${profile.displayName}さんこんにちは。あなたのユーザーIDは${profile.userId}です。`

const main = async () => {

    const messages = [{
        type: 'text',
        text: msg
    }];

    try {
        const res = await client.pushMessage(userId, messages);
        console.log(res);        
    } catch (error) {
        console.log(`エラー: ${error.statusMessage}`);
        console.log(error.originalError.response.data);
    }
}

main();
```

## こんな感じに

> ![スクリーンショット 2021-08-27 9.13.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/3efdcb21-727c-c2f2-0f06-669fc10a3c6b.png "スクリーンショット 2021-08-27 9.13.26.png")

なります。
テキストフィールドにピッカービューを設定してリスト選択する方法

今回作成するものはこちら
　
![スクリーンショット 2021-08-26 21.12.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494487/631ebbec-3e92-54e3-a1c2-5ffcd16e3948.png)

そして今回の全てのソースはこちら。


    import UIKit

    class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

    @IBOutlet weak var ageTextField: UITextField!

    //ピッカービュー
    var pickerView: UIPickerView = UIPickerView()

    //120までの数字を格納した配列
    var list = Array(0...120)

    override func viewDidLoad() {
        super.viewDidLoad()

        pickerView.delegate = self
        pickerView.dataSource = self
        
        textFieldをタップしたときにピッカービューが表示されるように変更
        textField.inputView = pickerView
    }

    //表示する列の数
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    //表示する数
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        //今回は120個
        return list.count
    }
    
    //最初に表示される数字
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String {
        0〜120までの数字
        return list[row]
    }
    
    //選択された時
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        self.textField.text = list[row]
    }
　　

それぞれ説明します。

    class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

pickerViewを使用する際に、UIPickerViewDelegateとUIPickerViewDataSourceを使います。

なので作成したいクラスに上記を付け加えてください。
　

多分何も手を加えていなければ、初期状態はこんな感じだと思います。

    class ViewController: UIViewController

そこにUIPickerViewDelegateとUIPickerViewDataSourceを加えてください。
　

上記をつけた後にピッカービューの設定を加えていき

        var pickerView: UIPickerView = UIPickerView()

        pickerView.delegate = self
        pickerView.dataSource = self

次にtextFieldをタップしたときにpickerViewが表示されるように変更します

        textField.inputView = pickerView

　
もしtextFieldをOutlet接続してないと言う方はtextFieldをOutlet接続してください。
やり方がわからないと言う方はこちらを参考に学習しましょう。

https://capibara1969.com/1253/

　
上記が完了したらpickerViewで必要な関数を加えていきます。

    //表示列
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    //表示する数
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return list.count
    }
    
    //表示内容
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String {
        return list[row]
    }
    
    //選択された時
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        self.textField.text = list[row]
    }

上記の関数がないとエラーでビルドできません。
　

何か問題があればコメントをお願いします。
【sedコマンド】特定ファイル内すべてのダブルクォーテーションを削除

- 特定のファイル内のダブルクォーテーションを削除するコマンド
  - `-i` ... ファイルを上書きするオプション
  - `-e` ... 正規表現のオプション（他のオプションを利用しない場合や、別ファイル保存の場合は**不要**）

```bash
# ダブルクォーテーションを削除して、上書き
sed -i -e 's/"//g' result.csv

# 結果を確認
head result.csv
```

削除した結果を **別ファイルで保存** したい場合
（元のファイルは保持しておきたい場合）

```bash
# ダブルクォーテーションを削除して、別ファイル(result_edited.csv)として保存
sed 's/"//g' result.csv > result_edited.csv

# 結果を確認
head result.csv
head result_edited.csv
```
シンプルアナログ時計

本当にシンプルな時計が欲しくて作ってみました。
画像群は Google図形描画 で作成。
jsなのでローカルPC時刻に依存。
<h1>サンプル</h1>
<a href='https://gtlgqvxdkbh2dm3axzu5fq-on.drv.tw/clock/analogue.htm' target='_blank'>アナログ時計</a>
<h1>ソース</h1>
analogue.htm
<pre>
&lt;html>
  &lt;head>
    &lt;title></title>
    &lt;style>
      img{width: 860px;height: 860px;}
      #base {width: 860px;height: 860px;position: relative;}
      .clock {
        position: absolute;
        top: 0px;
        left: 0px;
      }
      .clock_hour_shd {
        position: absolute;
        top: 15px;
        left: 15px;
      }
      .clock_minute_shd {
        position: absolute;
        top: 30px;
        left: 30px;
      }
      .clock_second_shd {
        position: absolute;
        top: 45px;
        left: 45px;
      }
    &lt;/style>
  &lt;/head>
  &lt;body style="background-color:#ffffff;">
    &lt;div id="base">
      &lt;img src="img/dial.png" class="clock">
      &lt;img src="img/hour_shd.png" id="hour_shd" class="clock_hour_shd">
      &lt;img src="img/minute_shd.png" id="minute_shd" class="clock_minute_shd">
      &lt;img src="img/second_shd.png" id="second_shd" class="clock_second_shd">
      &lt;img src="img/dial_1.png" class="clock">
      &lt;img src="img/hour.png" id="hour" class="clock">
      &lt;img src="img/minute.png" id="minute" class="clock">
      &lt;img src="img/second.png" id="second" class="clock">
    &lt;/div>

    <script>
      var time;
      var hour = document.getElementById("hour");
      var minute = document.getElementById("minute");
      var second = document.getElementById("second");
      var second_shd = document.getElementById("second_shd");

      function main() {
        time = new Date();
        hour.style.transform = "rotate("+(time.getHours()*30+time.getMinutes()*0.5)+"deg)";
        minute.style.transform = "rotate("+(time.getMinutes()*6)+"deg)";
        second.style.transform = "rotate("+(time.getSeconds()*6)+"deg)";
        hour_shd.style.transform = "rotate("+(time.getHours()*30+time.getMinutes()*0.5)+"deg)";
        minute_shd.style.transform = "rotate("+(time.getMinutes()*6)+"deg)";
        second_shd.style.transform = "rotate("+(time.getSeconds()*6)+"deg)";
        document.title = time.getHours()+":"+("00"+time.getMinutes()).substr(-2,2) + ":" + ("00" + time.getSeconds()).substr(-2,2)
        setTimeout(main, 1000-time.getMilliseconds());
      }

      main();

    </script>
  &lt;/body>
&lt;/html>
</pre>
単純すぎるドメイン駆動設計Javaサンプルコード


## 背景
チーム内でドメイン駆動設計（以降、DDDと記述）についてちょっとした勉強会をやることになった際、いわゆる手続き型設計との違いについて、具体的なコード例で説明したいと思い、サンプルコードを用意してみました。チーム内でよく使用されているJavaで記述しています。

https://github.com/ynstkt/learn-ddd

勉強会参加者にはプログラミングに疎いメンバもいるので、なるべく理解するのに負担が少ないよう、できる限り単純な例にすることを意識しました。

一方で、実際にはDDDを取り入れるまでもない（むしろ取り入れない方がよい）ほど例が単純すぎることによって、DDDの必要性・効果がうまく伝わらず、むしろ面倒なことをしている印象になってしまう懸念はありますが、概要説明用と割り切っている点はご了承ください。

なお、前提として、そもそもDDDとは何かという部分については、個人的に以下のスライドが内容や流れがわかりやすかったので、勉強会でもそのまま紹介させていただくことにしました。

https://speakerdeck.com/player/7d40c1a566414aefa4bc947f6052ec0c

注意点として、DDDでより本質的なのは、ドメインエキスパートと開発者のコミュニケーション、知識のかみ砕き、モデルへの落とし込み、モデルとコードの一致といった部分だと思っていますが、本記事でこれから紹介するのは、あくまでモデルを実装に落とし込んだコードの例であり、実装手段の一部にすぎません。このような実装手段の部分のみにフォーカスした状態は「軽量DDD」と呼ばれることがあり、賛否両論ある点はご注意ください。


## 説明の流れ

Javaで書いたごく単純な足し算アプリを例にして説明します。

まずは、DDDとの対比で手続き型設計と呼ばれるような書き方で書いてみます。

```java
public class TransactionAddition01 {
    public static void main(String[] args) {
        // 引数チェック
        if (args.length != 2) {
            System.err.println("引数を２つ指定してください");
            System.exit(1);
        }
        int param1 = 0;
        int param2 = 0;
        try {
            param1 = Integer.parseInt(args[0]);
            param2 = Integer.parseInt(args[1]);
        } catch (NumberFormatException e) {
            System.err.println("引数には整数を指定してください");
            System.exit(1);
        }

        // 足し算を実行
        int result = param1 + param2;

        // 結果を表示
        System.out.printf("%s + %s = %s%n", param1, param2, result);
    }
}
```

これを起点として、以下の流れで説明していきます。

- [単純すぎるドメイン駆動設計Javaサンプルコード (1) DDDらしいコード](https://qiita.com/ynstkt/items/ddbb905adf0583121530)
  - まずは、上記コードをDDDらしく修正していきます。
  - **ポイント**
      - ドメインの隔離
          - レイヤ化アーキテクチャ
      - 値オブジェクト

- [単純すぎるドメイン駆動設計Javaサンプルコード (2) 仕様追加その１](https://qiita.com/ynstkt/items/6ed4fac3ba200f73acd3)
  - 次に、ちょっとした仕様追加が発生した際に、手続き型設計とDDDのそれぞれの場合の対応例を考えます。
    - **ポイント**
      - ドメイン知識の凝集

- [単純すぎるドメイン駆動設計Javaサンプルコード (3) 仕様追加その２](https://qiita.com/ynstkt/items/518c8d9bea997da7b270)
  - さらに仕様追加が発生し、データの永続化が必要となるケースの対応例を説明します。
  - **ポイント**
      - エンティティ
      - リポジトリ

- [単純すぎるドメイン駆動設計Javaサンプルコード (4) テスト](https://qiita.com/ynstkt/items/6d390a6e0a4248905af5)
  - 最後に、手続き型設計とDDDのそれぞれの場合で、テストがどうなるかを説明します。
効率的にテストコードを書く

#FactoryBotについて
インスタンスをまとめることができるGem。
別のファイルに記述しておくことで各ファイルへ引用できる。
###インストール方法


```ruby:gemfile
group :development, :test do
  
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 4.0.0'
  gem 'factory_bot_rails'
end
```
```
% bundle install
```

インストールが完了後はファイルを手動で作成。
/spec/factories/ファイル名

###buildメソッド
newメソッド同様の意味を持つ。

######【FactoryBot.buildを使用、不使用の例】

```ruby
#FactoryBotを利用しない場合
user = User.new(nickname: 'test', email: 'test@example', password: '000000', password_confirmation: '00000000')
#FactoryBotを利用する場合
user = FactoryBot.build(:user)
```
これにより、userの情報はひとまとまりになる。
userの記述はbeforeを使うことで、より簡素化できる。

###before

処理を実行する前に、共通したコードを予め記述し、各処理へ受け渡すことができる。

```ruby
require 'rails_helper'
RSpec.describe User, type: :model do
  before do
    @user = FactoryBot.build(:user)
  end

  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      @user.nickname = ''
      @user.valid?
      expect(@user.errors.full_messages).to include "Nickname can't be blank"
    end
    it 'emailが空では登録できない' do
      @user.email = ''
      @user.valid?
      expect(@user.errors.full_messages).to include "Email can't be blank"
    end
  end
end
Nimのプロセス起動 レシピ的ななにか

Nimのプロセス起動は便利だが、意図したとおりに使う場合(主にプロセスオプションやコンパイルフラグといった仕組みのために)注意が必要である。
ここではいくつかのよくあるパターンについて、どのような書き方で実現するかをレシピ集っぽくまとめてみた。

- 公式ドキュメント: https://nim-lang.org/docs/osproc.html

## プロセスの終了を待ちたくない

実行に長時間かかるプロセスなどはそのあいだに他のタスクを行いたいといった要求がある。

この場合 `srartProcess` を使うことができる。 `startProcess` はジェネリックなプリミティブであり、コマンドの終了を待つために `waitForExit` を呼んでいる。特にプロセスオプションを指定しないデフォルトの状態では、この関数はシェルを経由してプロセスの実行を行っている。

```nim
import osproc

proc main() =
  let p = startProcess("true")
  defer: discard p.waitForExit

when isMainModule:
  main()
```

## シェルを経由したくない

シェルを経由したい場合にはプロセスオプションの指定から `poEvalCommand` 除いた状態で `startProcess` もしくは `execProcess` に渡せばよい。

```nim
import osproc

proc main() =
  let p = startProcess("true", options={poUsePath})
  defer: discard p.waitForExit

when isMainModule:
  main()
```

## 標準エラー出力を標準出力にマージしたい

リダイレクトの振る舞いで標準エラー出力を標準出力にマージする方法だけはプロセスオプションでサポートされている。なおこのオプションはoptionsが未指定の場合デフォルトで有効になっている。

```nim
import osproc

discard execProcess("nim", args=["c", "main.nim"], options={poStdErrToStdOut, poUsePath})
```

## 環境変数を指定したい

環境変数を指定できる。未指定の場合は `nil` が渡され、この場合は親プロセスの環境変数が引き継がれる。環境変数をクリアしたい場合は空のStringTableを渡してやる。

```nim
import osproc, strtabs
echo execProcess("ls -l", env={"LC_ALL": "C"}.newStringTable)
```

## カレントディレクトリを指定したい

プロセスが実行される際のディレクトリを指定できる。このオプションには一点注意が必要で、`posix_spawn` を使う場合(Linux以外の`posix_spawn`を持っているOSではこれがデフォルトの挙動になる)はプロセス起動の前に親がカレントディレクトリを変更し、サブプロセスの実行が終わった後で元に戻す挙動を行う。この振る舞いが好ましくない場合は `-d:useFork`をコンパイルオプションに渡すことで `fork(2)` を使うように変更できる。

```nim
import osproc
echo execProcess("ls", workingDir="/usr/bin")
```

## clone(2)を使いたい

Linuxの場合、`CLONE_VM or CLONE_VFORK` オプションをつけて `clone(2)` を呼ぶと `vfork(2)` 相当の挙動となりメモリの複製が行われなくなるためメモリ使用量の削減が可能となる(正確にはスタック領域に関しては親子で別の領域となるため完全なvfork(2)の模倣にはならない、より安全な実装になっている)。

Nimで `clone(2)` を使いたい場合は `-d:useClone` をコマンドラインから渡してやる。

```nim
# Run: nim c -d:useClone subprocess.nim
import osproc
discard execProcess("/bin/true", options={})
```

## よいところ

ここがいいなと思った所について箇条書きにした。

- `clone(2)` を使うオプションが提供されている
  - メモリ効率・実行速度が必要な場合に便利となる
- `useFork` `useClone` などコンパイルオプションで振る舞いを変えることができる
- キーワード引数があるため関心のないオプションに触る必要がない
  - これはどちらかというとNimの言語機能の話だが


## 不満

以下は現状の実装において多少不満に思っていることを箇条書きにした。

- fork-exec間でやれる処理が少ない
  - setuidとかやりたい
  - プロセスオプションを追加することで個別に対応はできそう、`preexec_fn`(Python)的なものもあり
- リダイレクトの指定に自由度がない
  - poStdErrToStdOutしかできない
  - 自由にパイプを渡してファイルに出力などができない
【Ruby on Rails】デプロイ後、本番環境で画像が表示されない不具合を解消する方法


#対象者

* ローカル環境で表示される画像が本番環境で表示されない方


#目的

* 本番環境でも問題なく画像を表示させる

#実際の手順と実例
###1.前提

ruby 2.5.1
rails 5.2.5
AWS

* app/assets/imagesの中に画像が保存されている
* 自動デプロイできている状態

###2. 結論

変更前

````
<img src="assets/search.png" alt="#">
````


↓↓↓


変更後

```
<%= image_tag asset_path("search.png", alt: "") %>
```

これで実装完了です！


###3.原因

本番環境では、画像もコンパイルされることが原因のようです。

※コンパイルとは、ソースコードを機械語に翻訳する作業のこと

本番環境ではパス名と画像の名前が変わっちゃうので
両方に対応できるasset_path()を使い、解決できました！

#参照

>[【ちゃんと理解してる？】コンパイル、ビルド、デプロイの違い](https://cheat.co.jp/blog/archives/2049)


####投稿者コメント

開発環境で表示されていたのに本番環境で反映されていないと血の気が引きますよね。。。笑
上記変えるだけで簡単に解決できます！


####My Profile
プログラミング学習歴３ヶ月目のアカウントです！
プログラミングスクールで学んだ内容や自分が躓いた箇所等のアウトプットの為に発信しています。
また、プログラミング初学者の方にわかりやすく、簡潔にまとめて情報共有できればと考えています。
もし、投稿した記事の中に誤り等ございましたら、コメント欄でご教授いただけると幸いです。
【Ruby on Rails】gem, bundlerって？

# この記事について

この記事は、筆者がRuby on Railsを学習・使用する中で疑問に思った`bundle` コマンドについて、どういった意味があるのかという内容をまとめていきます。

まだまだ初学者ではあるので、なにか間違った理解等ございましたらぜひご指摘いただけますと幸いです。

# bundlerとは

プロジェクト内で使用するgemのパッケージ管理ツール。

gemのバージョンやgemの依存関係を管理してくれる「gem」

## bundlerの特徴

1. bundlerを使ってgemをインストールすると、gem同士の互換性を保ってくれる。
2. Gemfileという1つのファイルにgemを書くので、gemの管理がしやすい
3. Gemfileを使ってアプリごとにgemを管理できる
4. 環境ごとにインストールするgemを管理できる
5. gemの互換性を保ってくれる。Gemfileさえ共有すれば複数人での開発でもエラーを起こさない

参考

[https://pikawaka.com/rails/bundler](https://pikawaka.com/rails/bundler)

つまりgemを複数使っている場合に発生するgem同士の依存関係を、ひとまとめに管理してくれる便利なgemという理解で問題はないっぽい。

## gemとは

gemとは**rubyのライブラリ**のこと。

gemはRubyGemsと呼ばれるRuby用のパッケージ管理システムで管理されており、RubyGemsが提供するgemコマンドを通じてインストール等ができる。

パッケージ管理システムは各言語ごとに用意されている。

Python → pip

Java      → maven

node.js → npm

### Gemfileとは

railsアプリで利用するgemの一覧を整理（記述）するファイル。

`bundle install` コマンドが実行されると、このGemfileに記載されているgemの一覧を参照し、まだインストールされていないgemがあればインストールを実行してくれる。

### Gemfile.lockとは

Gemfileをもとに実際にインストールされたgemの一覧とバージョンが記載されたファイル。

Gemfile.lockには依存関係にあるgemも含め、bundlerによってインストールされたすべてのgemとそのgemのバージョンが記載されている。

Gemfileをもとに、bundlerによってインストールされたgemの結果が出力されるファイル。

そのため、Gemfile.lockを手動で更新することはない。

`bundle install` や`bundle update` を実行することで自動更新される。

# まとめ

`gem` **rubyのライブラリ**（便利機能ツール）

`Gemfile`  **Ruby on Railsアプリで使用するgem**を記述してまとめたもの

`Gemfile.lock` bundle installなどによって、**実際にインストールされたgemの結果が出力される**ファイル

# 参考

[https://qiita.com/nishina555/items/1b343d368c5ecec6aecf](https://qiita.com/nishina555/items/1b343d368c5ecec6aecf)
[https://nishinatoshiharu.com/difference-gemfile-gemfilelock/](https://nishinatoshiharu.com/difference-gemfile-gemfilelock/)
[https://qiita.com/kamohicokamo/items/ded4cad5be1778547640](https://qiita.com/kamohicokamo/items/ded4cad5be1778547640)
gasでLinebot 作ってみた

2021 8月現在

gasは代入分割対応してるみたいだぞん

```js
function doPost (e) {
  // LINE developersのメッセージ送受信設定に記載のアクセストークン
  const ACCESS_TOKEN =
    'hoge'

  // Webhookで受信したデータをjsで読めるように変換して格納
  const data = JSON.parse(e.postData.contents)

  // Webhookで受信した応答用Tokenとメッセージ取得
  const { replyToken, message } = data.events[0]

  // 応答メッセージ用のオブジェクト作成
  const messages = [
    {
      type: 'text',
      text: `${message.text}する勇気`,
    },
  ]

  // 応答メッセージ用のAPI URL
  const url = 'https://api.line.me/v2/bot/message/reply'

  // リクエストオプションのオブジェクトを作成
  const options = {
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
      Authorization: `Bearer ${ACCESS_TOKEN}`,
    },
    method: 'post',
    payload: JSON.stringify({ replyToken, messages }),
  }

  UrlFetchApp.fetch(url, options)

  return ContentService.createTextOutput(
    JSON.stringify({ content: 'post ok' }),
  ).setMimeType(ContentService.MimeType.JSON)
}

```



## 参考
https://www.dcom-web.co.jp/lab/bot/make_a_line_bot_with_gas

https://qiita.com/hakshu/items/55c2584cf82718f47464
Freescaleのマイコン

HardOffでSONYのUSB赤外線リモコン受光機を110円で購入してきました。

SONYのUSB赤外線リモコン受光機は二種類あって、グレーなどの長方形のものと丸く薄い黒いものがありました。両方とも入手して分解した事があって、おそらく長方形の方が古くて丸いほうが新しいのではないかと思われます。長方形のほうはNECの78KのOTPなマイコンが使われていて、丸いほうはFreescaleのマイコンが使われています。

今回入手して物は丸い黒いものです。

ネジはゴムの足の下の二箇所を中央のシールの下一箇所です。

![IMG_20210827_070250.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/fbf385f2-3b37-007f-d9b5-17d50306022a.jpeg)

東京にあるSMKという会社が製作したもののようです。

チップにはロゴと98JB8 4636の刻印に見えます。小さくて不鮮明なのでひょっとしたら間違ってるかもしれません。

プラスチックのケースの下には06 12の位置に印が上には07 1の位置に印があります。おそらく2007年1月の製造と思われます。

マイコンチップの形状は24-Pin QFNで5mm角くらいの大きさです。

形状からするとMC9S08JS16やMC9S08QB8が考えられます。

しかしMC9S08QB8はUSBがありませんし、MC9S08JS16はデーターシートでUSBが14,15ピンとありますが、基板のものは8,9ピンのようです。

またMC9S08JS16は2009年くらいの発売で時期もあいません。

MC9S08JS16にはUSBからの書き込みができるようですが、プログラム端子と思われるパターンがあるので、USB書き込みがサポートされる以前の仕様なのではないかとも想像されます。

このチップはおそらくFLASHでプログラム端子も出ているような気がするので、情報がわかれば再プログラミングできるのではないかと思います。

いろいろ調べたところ、MC9S08などより前の世代で、MOTOROLAの時代から作られていたMC68HC908JB8のような気がしてきました。HC908シリーズは6805の流れをくむワンチップマイコンでおそらく90年代からあり、USBのサポートがあるJB8は最後の頃の製品だと思われます。

http://www.hc08web.de/

http://www.sparetimelabs.com/funwith08/funwith08.php

https://physics.mcmaster.ca/tech/HC908/HC908Intro.htm

2000年のまだMOTOROLAだった頃のデータシートを見ると44-pin QFP, 28-pin SOCI,20-pin DIPのパッケージがあったようですが、Freescaleになってから24-pin QFNを作った可能性があるのかと思われます。2006年の製品一覧にはないので、2006年後半の製品かもしれません。検索で探してみるとMC68HC908JB8EPというパーツだったと思われますが、データシートは見つかりません。MC9S08への移行期だったのであまり出回らなかったのかもしれません。データーシートは時間をおいて検索すると引っかかったりするので、またいつか探してみたいと思います。

44-pinのパッケージから28-pin,20-pinの削減パターンを元に推測してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/c1152bf4-77bc-d43c-78fb-72276e7bd84b.png)

もう一本なのですが、大体こんなところではないかと思われます。USBとクリスタルが対称の位置にありクリスタルの隣りがVSSになっているので、ほぼほぼこんなところだと思われます。

モニターはRST,IRQ,PTA0とVDD,GND接続できるようです。

基板のパターンを見るとクリスタルの隣のVSSが2本につながっているので、もう2本すくなくてPTE0,PTE2が無いような気がします。

MC68HC908JB8にはモニターが入っていて、そのモニターでFlashの書き込みができるようです。モニターの操作はシリアル通信で行えるようです。

モニターは二種類あって8Vなどの高電圧が必要なチップ内蔵のモニターと、Flashに書き込まれた、高電圧が必要ないモニターがあるようです。

こんなのも見つかりました。

https://github.com/thomas374b/HC908

HC908にはSecurity機能があって書き込まれてるfirmwareを読み出しができなくできるようなのだが、消してしまえば書き込みは可能の模様。

一列に並んだ7個のパッドがPTA0,1,2,3,4,6,7とするとIRQが出てない事になり、書き込みができない。書き込みできないのにパッドに出すのは意味がないので、そんなことないと思うんだけど。

富士通やNECのリモコン受信USBモジュールもSMK製のものがありMC68HC908JB8を使っていたので、おそらく908JB8だと思われます。

7つのパッドは右から、RST,PTA0,PTA1,PTA2,PTA3,?,IRQかもしれません。(2025/04)
モデル単体のテスト方法

#describe
『どの機能にテストをするか』を明記するメソッド。
describeはテストのグループ分けができるメソッド。各テストコードを入力する。
describeに続く、do~endの中に更にdescribeを入力することで入れ子構造で分けることも可能。

####it
itメソッドはdescribe同様にテストコードをグループ分けできるメソッド。
より詳細なテスト内容を入力する。
itメソッドで分けたグループを『exsample』とも呼ぶ。

####Valid?
validメソッドは任意のタイミングでテストを行える。
本来、validatesが機能するタイミングはデータベースに保存される前なので、テストを実行したい場合はValid?メソッドを使って、正誤判定を行える。

##expection,macher
エクスペクテーションとは検証した内容が想定通りの挙動だったのかを確認する構文。
expect().to macher()を雛形に検証内容に合わせて引数やmacherを変えて記述する。

###include
includeは、「expectの引数」に「includeの引数」が含まれていることを確認するマッチャ。
【例】includeマッチャのテストコード

```ruby:qiita.rb
describe 'フルーツ盛り合わせ' do
  it 'フルーツ盛り合わせにメロンが含まれている' do
     expect(['りんご', 'バナナ', 'ぶどう', 'メロン']).to include('メロン')
  end
end
```
このように記述することで、りんご、バナナ、ぶどう、メロンが入った配列に、メロンが含まれることを想定されている。
このテストコードを実行した場合、想定通り配列のなかにメロンは含まれているため、テストは成功。

###eq
eqは、「expectの引数」と「eqの引数」が等しいことを確認するマッチャ。
【例】eqマッチャのテストコード

```ruby:qiita.rb
describe '加算' do
  it '1 + 1の計算結果は2と等しい' do
     expect(1 + 1).to eq(2)
  end
end
```
このように記述することで、1 + 1という計算の結果が、2と等しいことを想定している。
このテストコードを実行した場合、想定通り1 + 1は2と等しいため、テストは成功。

##errors
errorsは、インスタンスにエラーを示す情報がある場合、その内容を返すメソッド。
確認にはrails cを用いる。
複雑なエラーの情報は、expectの中には記載できない。ここからエラー内容を取り出すとき、full_messagesメソッドを用います。

###full_messages
full_messagesは、エラーの内容から、エラーメッセージを配列として取り出すメソッド。
expectの引数には検証で得られた挙動を指定したいため、valid?メソッドを使用した後のインスタンスのエラーメッセージを指定する。expect(user.errors.full_messages)となる。
さらに、full_messagesの返り値は配列であるため、includeマッチャを用いて、配列にどのようなエラーが含まれていればよいか指定する。

```ruby:qiita.rb
expect(エラー対象のインスタンス.errors.full_messages).to include("エラー文")
```
guard let について

今回は、よく出てくるguard let について調べてみたので、自分なりにわかりやすくまとめたいと思います

参考記事
https://programfromscratch.com/swift%E5%85%A5%E9%96%80-guard-let%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/



＃どんな時になぜ使うのか

guard letは、オプショナル型から値を安全に取り出したい時に使います。
Swiftでは、オプショナル型から値としてnilをそのまま取り出すと、エラーになってしまうので、
nilならこの処理、nilじゃなければこの処理と処理を明確にしなければいけないのです。

guard letは日本語訳すると『nilなら処理する』みたいなイメージだとわかりやすいです。
つまり、guard letは値がまだ決まっていない部分の変数宣言の時に使います。
Powershellでクラスの継承スクリプトを開発しました

Powershellでクラスの継承スクリプトを作成しました


```Powershell
##### Classの継承テスト
##### 2021/8/26  乃木坂好きのITエンジニア

# スーパークラス定義
class Nogizaka{
    [string] msg(){
        return "乃木坂46"
    }
}

# サブクラス(継承するクラス)定義
class Ikkisei : Nogizaka {
     [string] msg2(){
        $Data = ([Nogizaka]$this).msg()
        $Data += "の1期生は10年活動している"
        return $Data
    }
}
class Nikisei : Nogizaka {
     [string] msg3(){
        $Data = ([Nogizaka]$this).msg()
        $Data += "の2期生は8年活動している"
        return $Data
    }
}

class Sankisei : Nogizaka {
     [string] msg4(){
        $Data = ([Nogizaka]$this).msg()
        $Data += "の3期生は5年活動している"
        return $Data
    }
}


# インスタンス化
$TestObject = New-Object Ikkisei
$TestObject.msg2()
$TestObject = New-Object Nikisei
$TestObject.msg3()
$TestObject = New-Object Sankisei
$TestObject.msg4()

```
初めてSwiftで電卓アプリを作った感想とわからなかった知識のまとめ

初めまして。今回は、初めてSwiftで電卓アプリを作った感想と知識のまとめをしていこうと思います。

まず今回で理解した知識は、オプショナル型についてです。


参考記事

オプショナル型
https://www.sejuku.net/blog/35070
https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r17



・オプショナル型
通常では持てない、値のない状態を保持する変数の型のことです。
つまり、オプショナル型は自動で最初からnilが変数として代入されるということです。
その時は、？をつけてコードを書きます。

ただし、nilではない形を持った変数を表すときは、アンラップする必要があるため、『！』をつける必要があります。
出ないと自動的にnilが代入されてしまっているので、エラーが起きてしまいます。

僕のイメージでは、nilをラップで包んでいて、そのラップを剥がしてnilと実体を保つ変数と入れ替えるイメージがアンラップです。

主に、オプショナル型はif型と用いて、『Binding』を行います。
Bindingとは、オプショナル型の、値がnilならfalse、それ以外ならtrueと返す特性を使って、比較することを言います。

感想
何もかもが初めてで、とにかく写経みたいな形になってしまって理解がとても浅い状態になってしまっているので、自分のローカル環境で改めて実践してみようと感じました。
【PHPの基本】関数とは

#はじめに#
　今回はPHPの関数について見ていきたいと思います。
我々初学者が最初につまづくポイントの一つでもあると思うので、自分なりにまとめて、しっかり定着させていきたいと思います。


#関数とは#
　PHPでは下記のように書きます。

```php:sample.php
 function addition($x,$y)
{
  $sum = $x + $y;
  return $sum;
}

echo addition(2, 5);
```

　functionとすることで**関数**が定義されます。()内には**引数**、{}内に処理を書き、returnは**戻り値（または返り値）**を示しています。そしてその関数を使いたい場面で実行します。実行する際は上記のように関数名（引数）とすることで実行できます。ちなみに引数は必要ないこともありますので必要に応じて使っていきましょう。

　用語について、一つずつ見ていきましょう。

　関数：処理をひとまとめにして好きな場所で使いまわせるようにしたもののこと。
　引数：関数内で使われる値のこと。
　戻り値(返り値)：関数の結果のこと。

　分かりにくいと思うので、イメージで見ていきます。例えば、関数をミキサー、引数を果物、戻り値をジュースとして考えてみましょう。関数であるミキサーに材料の果物や牛乳などを入れて（渡して）混ぜる（処理する）と、その結果ジュースが出来上がりますよね。これと同じように上記の関数に当てはめると、additional関数に引数として"2","5"を渡します。すると関数内で処理が行われます。そしてその結果、戻り値として"7"が返されるわけです。

PHPの関数には大きく分けて２種類あります。「**組み込み関数**」と「**ユーザー定義関数**」です。

##組み込み関数##
　PHPに元々用意されている関数のことです。様々な種類がありますのでいくつか紹介していきます。

①var_dump
　引数には何かしらのデータが渡されます。var_dump関数を用いることで渡された引数のデータ型や値を調べて、戻り値として調べた結果の値を返します。想定したデータ型で処理できているか、値は間違っていないかなどデバッグによく使いますのでぜひ覚えておきましょう。

②strlen
　引数に与えられた値のバイト数を返します。

③empty
　与えられた値の内容が空であるかどうかを返します。空文字や０、null、falseなどの際にtrueが返されます。


その他にも多数の関数が用意されています。公式ドキュメントなどで調べてみると良いでしょう。


##ユーザー定義関数##
　ユーザー定義関数とは、自分で定義した関数のことです。アプリケーション内で使いたい処理の内容を関数にまとめ、好きな時に使うことが出来ます。先程のadditional関数もユーザー定義関数です。
　
　ユーザー定義関数には様々な利点があります。

・同じ処理を何度も書かなくて済む。
・プログラムが読みやすくなる。
・変更があった際に関数の中身だけ修正すれば良い。

　一度関数を作ってしまえば、同じ処理を行う際関数を呼び出すだけで良くなります。そうするとコードの量が少なくなり可読性が上がり、変更があった際は関数を修正するだけで、使用している全ての処理に修正が反映されます。何か処理を行う際は全部関数化してしまっても良いですね。


#まとめ#
　このような関数はコードの品質を上げる上で非常に重要な要素になってきます。ぜひ使いこなせるようにしっかり学習していきましょう！
Qiita Daily Survey memo 2021.08.27

# Qiita Daily Survey memo 2021.08.27
Qiita の記事を可能な限り毎日読むために、読んだmemoを公開することでモチベーションを維持しようという試み。

# [「挫折しないドメイン駆動設計」参加ノート](https://qiita.com/e99h2121/items/63e902a0d1c74bd0a0fd)
Nobuko YAMADA (e99h2121) さんの記事。
ドメイン駆動設計（DDD）に関する発表を聴講された際の、ノートを共有してくださっている記事です。

ドメイン駆動設計という言葉を初めて耳にしました。
顧客の業務を理解して設計をしましょう、そのために共通言語を作って行きましょう…と読み取りました。
論じられている通り、システムを構築したはいいけど使われなかった…というのを回避するためにも、有効な手法のように感じました。

興味があるのは、一般的にシステムを構築する際はお客様の業務要件を聞いて、AsIs と ToBe を提示するのは普通のことのように感じます。
ドメイン駆動開発だと、一般的な業務要件のヒアリングや AsIs と ToBe の可視化に加えて、どんなことを注力するのか…。
また、一次受け、二次受け、三次受け…と業務を委託してく場合に、上流で整理したことがうまく下流に伝えられるか。

〇〇駆動というと、テスト駆動開発がぱっと思い浮かびます。
テスト駆動開発では開発のフローがイメージしやすいですが、ドメイン駆動設計では設計フェーズの詳細がイメージできず、実際の案件ベースでプロセスや成果物の話を伺いたいなと思いました。

# [Webサービスの認証についてまとめる](https://qiita.com/it_tsumugi/items/263aa1f91cc8b05c93b0)
つむぎ (it_tsumugi) さんの記事。
Webサービスにおける認証の方法、セキュリティ、各認証方法のメリットデメリットを共有してくださっている記事です。
多くの参考文献をもとに記事を書いてくださっているため、すごくかっちりした記事で勉強になりました。
ITパスポートや基本情報で登場自体はしますが、この記事ほど深堀して理解しているメンバーはなかなかいないかなと感じました。
現在は業務で Web系 の開発を行っていているため、チームにも共有しようと思います。
Firebase 9.0.0 を使うと export 'firestore'  was not found とか言われる

# トラブル

ある日突然、`import firebase from "firebase/app";` とかすると、`"export 'firestore' (imported as 'firebase') was not found in 'firebase/app'` とか言われて、何をどうしても直らなくなりました。

# いきなり結論

試した環境は以下の通り (Ubuntu 20.04LTS)。

```.sh
＄vue --version
4.5.13
$ firebase --version
9.16.5
$ npm -v
7.21.0
$ yarn -v
1.22.11
$ node -v
v14.17.4
```

これで、

```
$ vue create myproject
```

とすると、firebase 9.0.0 が使われるようです。それを 8.10.0 に治したらエラーが出なくなりました。下記のような感じです。

```package.json
  "dependencies": {
    "core-js": "^3.6.5",
    "firebase": "^8.10.0",
    "firebaseui": "^5.0.0",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuetify": "^2.4.0"
  }
```

後は普通に、

```.js
import firebase from 'firebase/app';
import "firebase/auth";
```
とかで、エラーはでなくなりました。

これ、まじで分らんかったんだけど、こんなこと起こるのうちだけ？

## 参考

- [VueでFirebaseを使うと "export 'firestore' (imported as 'firebase') was not found in 'firebase/app' 解決法](https://qiita.com/kokogento/items/84b97b8b0046ba9a5bc5)
Custom Blockerでブログのコメント荒らしを非表示にする．

某ブログは，記事の内容は面白いのにもかかわらず，荒らしコメントを放置しているサイトがあります．

そのようなコメントに対して，Custom Blockerを用いてNGワードを設定することでコメント欄をすっきりさせます．

# 手順 「Livedoor blog向け」

- Custom Blockerを入れて有効にする
- 入れた拡張機能を選択して「このサイト用の新規ルール作成」を選択
- URLの欄に例えば「ｈttp://blog.livedoor.jp/nanjstu/archives/*」と入力する．
    - アスタリスクはワイルドカードとなる．
- 隠す要素→候補を表示
- 欄がUL.comment-infoになるように選択するか，打ち込む
- 条件→キーワードを使ってフィルタ→キーワードを入力
- 保存

# 参考画像
![2021-08-27 05.37.28 blog.livedoor.jp 500ca8d1ca02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162025/4ce2ef86-15d5-186f-6b83-06e7e0adf84c.jpeg)

![2021-08-27 05.42.56 blog.livedoor.jp b9c30bb88fe5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162025/eebff513-a9d3-e278-e18e-5643d209350e.jpeg)
非技術テクニック　覚え書き(開発の契約、見積もり編)

開発に戻った際に忘れないように自分用メモ。
開発見積もり系のテクニック。

#見積もりや契約の前提基礎知識おさらい

見積もりの系統としてざっくり以下がある。
これは基本情報とか応用？のレベル。

1.既存の経験から今回の案件を見積もる(パラメトリック法)
2.実装する機能、という観点で見積もる
3.WBSを出し、それらの合計を元に見積もる

1はCOCOMO法とか、CoBRAとか色々あるが、最終的にはKKD法になりがち（KKD法:経験・勘・度胸でエイヤする時の揶揄）
2はお出しする機能に対する費用と考え、他技術や言語で同様に実装した際の金額と剥離しない金額に収まる。ファンクションポイント(FP)法。
3は（同じ作業をしたことがないため、）具体的な作業をイメージし細かく刻んで見積もる。積み上げ法。

個人的な経験では、ウォーターフォールの更改は1が多く、完全新規だと2、規模が小さめでちょっと変わった依頼だと3を取ることが多い。

#見積もりに必要なインプット
当たり前であるが大事な前提として、契約に対する「インプットとアウトプット」をお客様と合意した上で見積もる。
インプットとして、要求仕様と、その外側にある要素（工期、納期、責任範囲や立場（SESなのか、請負なのか）、開発環境、お客さんのITリテラシーとか）。
アウトプットとして、ナニがどこまで必要なのかをヒアリングする必要がある。

具体的には、例えば普通の開発ならソースと設計ドキュメント、テストエビデンス一式だとは思うけど、、運用手順書や運用設計書が必要なのか、ユーザマニュアルの種類やユーザ教育の有無、法律向けや官公庁向けドキュメント対応とか必要か、報告義務などはどれだけ必要か、など。

業務プロセスのコンサルテーション、法律要件があるとか、ＣＤ-ＲＯＭつけるとか、紙だしするとか、セミナーやるとか、そういうのもこの段階で確認し、費用をちゃんと盛り込む。

#見積もり書に載せるべき情報
全体金額、対応内訳（細かすぎないこと）、作業開始日、納品日、全体スケジュール（報告会などがある場合はそれも記載）、納品物一覧（請負なら必要）、アサイン人数（SESの場合必要。バイネームはダメ）

#アウトプットが決まらない場合の見積もりや契約の進め方
特に更改や新規開発などの場合、請負とはいえ当初は何がどこまで出来るの分からず、アウトプットのボリュームが決められないことが多々ある。
その場合は、調査工程と実装～リリース工程で請負契約を分割する。
もしくはプロトタイプを挟むなどする。

前工程のアウトプットにプロトタイピング結果を含め、次の工程の見積もりのインプットとする。

ユーザの反応を見ながら・・とか非常に柔軟にアウトプットが変わる前提ならむしろスパイラル開発やアジャイル・スクラムなどがよいのでは？と思う。それならSES。
~~自力でエンジニアを指揮できず稟議に時間かかる日本では流行りにくいのでは？と個人としては思うけど、不勉強で申し訳ないのですが、今スクラムはどうなってるんだろうか…~~

#見積もり金額がお客さんの想定内なのか謎な場合の対応
1.気にしないで会議に持ってく
2.会議前に費用感を直接聞く
3.会議に松竹梅の案を持って行く

下に行くほど手間がかかる。
お客様との信頼関係が有りかつ相見積もりじゃないなら２のように聞いてみるのが手っ取り早い。
１の場合、想定の桁が一つ違ったりすると場が無駄になることもあるので、だいたい３も一緒に考慮する。


#リスク回避の為のチェックポイント
見積もりにはかならず以下を考慮する。
　
   1.工期の1/2はテストに使うくらい余裕があるスケがよい
   2.ドキュメントの粒度、テストの粒度を予め確認して計算する
   3.進捗報告工数、レビュー工数と管理工数をちゃんと入れる（請負は必須）
   4.リスクを数字にして、元の値との掛け算の係数とする　
　　リスク係数:デフォルトのリスク工数(1.2とか)、アサインメンバーの技術野の習熟度、お客の連携の遅さとか
   5.お客さんマターの再見積もり条項をつける。
　　　例えば、開発環境を期日よりもかなり遅れて渡された時とかに「この状況に該当したので、再見積をさせて頂けますでしょうか」ってなる。

1とか、あまりにも夢見ていないか？と思われそうであるが、少しでも連携先があるようなシステムなら連携テストなどやると一月とかすぐ吹っ飛ぶので、これくらいがちょうどいい。
またこれは単なる経験上だが、テスト期間は1/3を割ると炎上することが多い。

2と3は意外と考慮が甘いことが多いから、必要ならフォーマットまで作って考えるのがいい。

4と5は、各社色々味付けがあると思いますからそこに倣う感じで。




また思いだしたら書きます。
SharepointOnline＋PowerAutomateを無料で試す方法はあるのか確認した

SharepointOnline＋PowerAutomate（とちょっぴりTeams）でなんか作るとか運用する羽目になり、家でちょっと触ってみたいと思って色々調べたメモ。

#いきなり結論
（Teamsの裏にある）Sharepointと、PowerAutomate（というかPowerPlatform）はそれぞれ無料で借りれるけど、それをくっつけようとすると、__認証する際に強制的にログアウトする仕組みの模様。__
よって自分が調べた範囲では__完全無料は難しそう__に思えます。
そう感じた一番の理由は、Sharepointにリストを作って、普通にリストの画面から、PowerAutomateのフロー作成を呼び出したら、こんな感じでして。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218283/6970eac9-1333-e38a-9747-d238cda16284.png)
(Teams裏にある)Sharepointだけ、弾かれちゃってます。

もし完全無料で出来たよ！と言う方がいたら是非教えて頂きたいです。

#ちなみにやったこと

無料版Teamsを動かす方法は以下。

 0. MSに捧げるメールアドレスを用意する。Microsoftアカウントに登録済み。
 0. [MS Teamsのwebで無料サインアップ](https://www.microsoft.com/ja-jp/microsoft-teams/log-in)する。__この際に必ず「会社・組織用」を選択する。__
 0. TeamsのWeb版にそのままログインする。と、自動的にチームサイトが出来ている。
 0. デスクトップで使いたい場合はTeamsWeb版のサイト上にあるダウンロードボタンから、クライアントをダウンロードすること。どうもこうしないと対象のチャンネルにアクセスできない？制御が物理ファイル的にかかっているようです。めんどくさければweb版を使いましょ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218283/1f5374fe-d031-06ca-7228-a9b853f0b7e5.png)

Teamsのほぼフル機能が使えますし裏側にあるsharepointも使えますが、前述の通り認証系で弾かれる模様。他にもいくつか機能限定がされている模様ですが、普通に使う分には充分です。すごいなぁ。

以下はPowerApps開発者アカウントを作る方法。
自分は使って良い組織メアドがなくて出来なかったです。

 0. PowerPlatformのwebサイトにアクセスし、同じメールアドレスで無料サインアップする。可能なら[半永久的に利用可能なPowerApps開発者プランをとる](https://docs.microsoft.com/ja-jp/powerapps/maker/developer-plan)。こちらが出来ない場合は無料試用版で30日間使用する方を選択する。

ちなみにシェアポとPowerAppsのあいの子、Sharepointリストのフォームカスタマイズも、カスタマイズを加えようとした瞬間に強制サインアウトしてしまう作りになっていました。ワーオｗ


o365系は、環境を整えるコストや初学者の基盤理解の学習コストが高いですね～。。

こつこつ試している基本的なPowerAutomateの作成方法は別途まとめ予定。
PodcastへのSmart App Bannerを表示する

# この記事の対象とする人

自分でPodcastサイトを運営しているPodcaster

# Smart App Bannerとは

Smart App Banner(スマートバナーとも言うらしい)というのは、iPhoneからWebサイトを見たときに、上に表示されているアプリダウンロードのバナーみたいなやつのこと。
↓こんなやつ
![IMG_4580.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602340/2cf691b1-e8ef-6788-84fb-ae2982648dd2.jpeg)


この記事では、Smart App Bannerの「自分のPodcastへのリンク」版を表示させます。
↓上の方に表示されているやつです。
![IMG_4579.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602340/2a7280d9-c6f4-35a7-4438-614020f56c06.png)

押すと、Podcastアプリで、自分のPodcastが開きます。

# 実装

先に、Apple Podcastでの配信URLを確認しておく。
`https://podcasts.apple.com/podcast/watahari/id1540412220` のようなURLのはず[^1]。この最後の部分の`id`に続く１０桁数字をコピーしておく。

そして、head内に、下記のmetaタグを書けばOK。

```html:hoge.html
<meta name="apple-itunes-app" content="app-id=(さっきメモした数字)">
```

上述の例であれば、こんな感じになる。

```html:hoge.html
<meta name="apple-itunes-app" content="app-id=1540412220">
```

アプリダウンロードのSmart App Bannerと同じ書式で、表示させたいアプリのIDを入れるところを、PodcastのIDにするだけで、不思議なことにPodcastへのリンクになる。

表示確認は実機iPhone/iPadじゃないとできないことに注意。
私は（どうせmetaタグだし試しに本番反映してやろう・・・）と思ってやってみたら、一発でうまくいって嬉しかったです。

# 参考

公式ドキュメント[^2]ではPodcastでの例は見当たらなかったが、下記のようなサイトで発見され紹介されていた。

https://martech.zone/itunes-podcast-smart-app-banner/

https://blog.pacific-content.com/optimize-your-podcast-website-for-ios-with-a-single-line-of-code-cf56a7a3f486

[^1]: 私の配信している[Podcastまみむメモ](https://podcasts.apple.com/podcast/watahari/id1540412220)です！聴いてね;)

[^2]: https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
広い視野と考察力を学ぶ推理番組がお好きでしょ？

エンジニアだけに限った話ではありませんが、仕事をするうえで最も必要な要素は、「広い視野」と「考察力」であると考えます。

チームワークとしての協調性、技術に対する順応・応用、問題発生時の対応など、あらゆる場面で「広い視野」と「考察力」は生きてきますし、とても重要です。

しかし、誰も「広い視野」と「考察力」を磨くことに目を向けていないように見受けられます。ほとんどのエンジニアは、技術やコミュニケーションを磨くことばかり考えているように見受けられます。

技術やコミュニケーションだけがエンジニアの資質なのか。

エンジニアとしての「多様性」を見出すためにも「広い視野」と「考察力」が必要になってくるのです。


#広い視野と考察力とは
広い視野と考察力とは、事象の流れを観察し、様々な角度で予測することで、物事の解決に向けた最適解を見つけ出すことであると、ここでは定義させてください。

個人的な意見ですが、これらの力を身につけた人は、

　・「スタート」「ゴール」が常に明確になっています。
　・上がだめなら下から読むことをします。
　・会話の言葉尻を絶対に見逃しません。

のようなことが自然とできているようになっていると感じます。

また、広い視野と考察力とは「人の習慣づいた思考に準拠するもの」であると考えています。
そのため、意識してどうにかなるモノではなく、普段から自然に行えるように習慣化することで、その効果を発揮してくれるものであると考えられます。

**そして、広い視野と考察力には「常識となったNOをYESに変える力」が秘められていると考えています。**

#広い視野と考察力を磨くには
私たちにとって重要な「広い視野」「考察力」を磨くにはどうしたらよいのでしょうか。

流行りのクイズ・謎解き番組で磨けるのではと思った方は「不正解」です。

私達を取り巻く環境で起こる事象は、クイズ・謎解き番組のような完結した内容ではなく、時系列も長く複雑なものとなっています。欲しいのは「瞬発力」ではなく「持久力」です。

では一体何なのか。

[広い視野で考察する力を養う推理番組](https://www.zenbaren.jp/stress-tv-housoukinshi/ "広い視野で考察する力を養う推理番組")

興味があれば上記リンクをご参照ください。
gsonでパースするときに DateTimeParseException になったメモ

gson で 日付をパースするときに原因不明のエラーになってハマりました

```
java.time.format.DateTimeParseException: 
Text '2021-01-11T09:59:01' could not be parsed at index 19
```

もともと swagger から自動生成したコードで、
org.threeten.bp.OffsetDateTime が使われていたので、 java.time.OffsetDateTime に変更してもエラーは変わらず。

java.time.LocalDateTime に変更してもエラーは変わらず。

散々ハマってしまったけど、解決方法は下記でした
gson に設定する LocalDateTimeTypeAdapter の formatter の指定が下記になっていた

```java
DateTimeFormatter.ISO_OFFSET_DATE_TIME
```

これを下記にしたら無事にパースできました

```java
DateTimeFormatter.ISO_LOCAL_DATE_TIME
```

上記は、java.time.OffsetDateTime に変更している前提です。
OffsetDateTime のままだとやはりパースできず下記エラーになります。（オフセット部分がないので）

```
Unable to obtain ZoneOffset from TemporalAccessor:
 {},ISO resolved to 2021-08-27T16:10:23 of type java.time.format.Parsed
```

threeten.bp パッケージ (ThreeTen Backport) は DateTime の機能をJava 6でも使えるように移植したものなので、今となっては使う必要性はないと思います。
MurmurHash v3 の Node.js 実装の処理速度を比較 → murmurhash-js が速い

短い ASCII 文字列の手軽なハッシュ値を欲しかったので、[murmurhash v3](https://sites.google.com/site/murmurhash/) の pure JavaScript 実装の npm モジュールを4種ピックアップして、ベンチマーク比較してみました。
結論としては、手元では [murmurhash-js](https://www.npmjs.com/package/murmurhash-js) が速かった。実装も[めちゃくちゃシンプル](https://github.com/mikolalysenko/murmurhash-js/blob/master/murmurhash3_gc.js)。

| package | Dependents | Weekly Downloads | ops/sec |
|---|---|---|---|
| [murmurhash](https://www.npmjs.com/package/murmurhash) | 151 | 231,041 | 1,796,786 |
| [imurmurhash](https://www.npmjs.com/package/imurmurhash) | 502 | 19,599,975 | 21,301,152 |
| [murmurhash3js](https://www.npmjs.com/package/murmurhash3js) | 64 | 73,494 | 50,456,677 |
| [murmurhash-js](https://www.npmjs.com/package/murmurhash-js) | 128 | 466,189 | 67,474,068 |

グラフが長いほうが速い。こんなに違いが出るとは。

![murmur3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12419/3576ea97-54ad-1e72-e29c-a9145e4e5994.png)

環境や入力する文字数によっては結果が違ったり、サポートする文字種や入力データ形式も異なると思いますので、使う用途に応じて再ベンチマークした方が良いかと。

```js
#!/usr/bin/node

console.log(process.title, process.version, require("os").cpus().pop().model);

const Benchmark = require("benchmark");
const murmurhash = require("murmurhash");
const imurmurhash = require("imurmurhash");
const murmurhash3js = require("murmurhash3js");
const murmurhash_js = require("murmurhash-js");

const text = "string";

const suite = new Benchmark.Suite();

suite.add("murmurhash", () => murmurhash.v3(text));
suite.add("imurmurhash", () => imurmurhash(text).result());
suite.add("murmurhash3js", () => murmurhash3js.x86.hash32(text));
suite.add("murmurhash-js", () => murmurhash_js.murmur3(text));

suite.on("cycle", event => console.log(String(event.target)));
suite.on("complete", () => console.log('Fastest is ' + suite.filter('fastest').map('name')));

suite.run({async: true});
```

手元の環境 node v16.6.1 Apple M1 での実行結果

```
node v16.6.1 Apple M1
murmurhash x 1,796,786 ops/sec ±1.69% (80 runs sampled)
imurmurhash x 21,301,152 ops/sec ±0.37% (99 runs sampled)
murmurhash3js x 50,456,677 ops/sec ±0.33% (96 runs sampled)
murmurhash-js x 67,474,068 ops/sec ±0.42% (96 runs sampled)
Fastest is murmurhash-js
```
【Docker】 docker image の大きさを小さくする(その２)

# はじめに

前回はC言語のソースビルドについてdocker image の size を小さくする方法をメモリました。

https://qiita.com/XPT60/items/7ef151371522bd54b438

今回は python についてです。忘れないうちにメモメモします。

# 内容

## 削減方法

docker image を build と実行用に分けるのは同じ考え方です。こちらのサイトで学ばせていただきました。

https://pythonspeed.com/articles/multi-stage-docker-python/

ここで紹介されているのは、

1. pip install --user を使う
--user オプションをつけると、ホームディレクトリの.local に全ての必要なファイルがインストールされる。ので、これをコピーすればよい。

1. virtualenv を使う
virtualenv で使用したディレクトリをコピーし、PATHを通す。

というものです。いずれもビルド用イメージでpip や virtualenv を実行し、実行用イメージに必要なものだけ持ってくるという作戦です。


## 実行例

おじさんは頑張って動かしたので、こちらにメモします。Flask を使いました。以下のファイルが全て同じフォルダにあれば、再現できるかと思います。

```
.
├── main.py
├── multi-stage.Dockerfile
├── requirements.txt
└── single-stage.Dockerfile
```

### 通常のコンテナを作成

まず、実行するpython スクリプト。

```Python:main.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)
```

pip でインストールするためのあれ。

```requirements.txt
Flask==2.0.1
```

そして、普通の?Dockerfile

```Dockerfile:single-stage.Dockerfile
FROM ubuntu:18.04

RUN apt-get update && apt-get install -y python3 python3-pip

COPY requirements.txt .
RUN python3 -m pip install --user -r requirements.txt
RUN ls /root/.local/


COPY main.py /root/app/main.py
EXPOSE 8080

CMD ["python3", "/root/app/main.py"]
```

もちろん動きます。

```
$ docker build -f single-stage.Dockerfile -t py-single .
$ docker run -p 5000:8080 --rm py-single
```

ブラウザを立ち上げても良いですが、別のterminal で

```
$ curl localhost:5000
Hello World!
```

となります。

### マルチステージ

ここで、Dockerfile を変更します。pip install するとき、--user オプションをつけると、user の　~/.local/ というディレクトリにpython module が保存されます。なので、それをコピーする作戦です。

```Dockerfile:multi-stage.Dockerfile
FROM ubuntu:18.04 AS compile-image

RUN apt-get update && apt-get install -y python3 python3-pip

COPY requirements.txt .
RUN python3 -m pip install --user -r requirements.txt
RUN ls /root/.local/

FROM ubuntu:18.04 AS build-image
COPY --from=compile-image /root/.local /root/.local
RUN apt-get update && apt-get install -y python3

COPY main.py /root/app/main.py
EXPOSE 8080

CMD ["python3", "/root/app/main.py"]
```

同様に実行できます。

### イメージサーズの比較

私の所では、こんな感じでした。

```
REPOSITORY                    TAG               IMAGE ID       CREATED         SIZE
py-multi                      latest            bbdb7bfc547b   2 days ago      140MB
py-single                     latest            a16f61635f65   2 days ago      487MB
```

小さくなりました。良かった良かった。

# まとめ

python でも docker image の size を小さくする方法を調査し、動作確認した。pip install するとき、--user オプションをつけて、~/.local をコピーすればよい。

更に考えられるこの先の調査は

- pip でそもそも何がインストールされるのか、を理解できれば、必要なファイルを細かく選別できて、もっと小さくできるかもしれない
- virtualenv も試す

ですが、とりあえず目的が達せられそうなので、これで良しとしました。
(2021/08/26 翌日の深夜1:22)
マネージャーの役割を引き継ぐときに、メンバから聞いた”継続して欲しいこと”

# はじめに
昔(2018年12月くらいに）、体制変更で業務が変わるかもしれないので、マネージャーの役割を引き継ごうとしました。
今まで取り組んだことで、価値のないこと（価値を感じてもらえていないこと）は自分がいなくなれば、自然とやられなくなります。
今まで取り組んだことで、価値があることでも、やる人がいなくなれば、やられなくなる可能性があります。

そこで、メンバに『私が抜けても継続して欲しいことある？』と質問してみました。
自分が取り組んだことで、メンバに価値があったこと／価値を感じてないことがよくわかり、有益でした。
自分は価値があると思っていたのに、意見がなかったこともあります・・・

# 継続してほしいこと一覧
## 情報共有
* みんなが見えるところに日報を書く
* 日報を利用し困っていることの打ち上げ
* チームまたいだ全体の情報共有会
* 協力会社リーダも情報共有会に参加
* slack（Mattermost）の活用
* 上位マネージャとメンバの直接コミニュケーション（相談、アドバイス）
* メンバの意見を真摯に聞き、事実を確認、アイデアを引き出す
* 振り返り
* 改善事例の共有会

## 新技術導入
* gitの利用を進める
* LightningReviewとslackを活用した欠陥クローズの毎日継続フォロー
* 新しいツールの積極的導入を続ける
* 世の中の一般的な（認められている）技法を取り入れる
* 社内の他業務から積極的に情報収集
* 社外から積極的に情報収集

## 価値の低い面倒なことに時間を使わせない
* 週報はマネージャーだけが書き、メンバは書かない
* 飲み会はマネージャーが企画・運営する
* 無駄なことはすぐやめる・すぐやめさせる、やめるために各方面とすぐ調整
* 定例会では進捗を確認せず計画を共有する、進捗は日報で把握する

## 風土・習慣
* PDCAでもCAPDoでもなくDoCAP！とりあえず早くやってみてそのあとに実績をもとに計画を立てる
* 会議で即決、できる限り持ち帰らない
* 大きな声、元気
* 一週間の連続有休取得

## 体制
* 案件の規模・難易度・期間等に応じて、チーム構成を臨機応変に変更、要員計画として見えるようにし展開
* 業務内のスーパーマン（自動化エンジニア、ドメイン知識を一番持ってる人）のリソースを全チームで共有し、誰でもスーパーマンに助けてもらえる
* ニコカレ・業務負荷を毎週確認し、ヒアリングし、問題があれば考慮して体制を定義

# おわりに
プロジェクトマネージャーとして、メンバに『私が抜けても継続して欲しいことある？』と聞いてみるのは、有益です。
マネージャーは、書籍から学ぶことより、メンバから学ぶことのほうが多いかもしれません。
プロジェクトの失敗をふりかえり対策するアプローチより、よかったことを洗い出し継続するアプローチほうが、もしかしたらプロジェクトの成功確率を上げやすいかもしれません。検討された失敗に対する対策は、その時点では効果があるかは定かではありませんので。

# 参考文献

https://blog.tinect.jp/?p=63962

https://qiita.com/kaizen_nagoya/items/81265788e7cf8f7b230b

# 関連記事
https://qiita.com/kazuo_reve/items/6976029e72763ea73245

# フィードバックツイート
自分が考えていなかったアプローチを、twitterでblanite氏から示していただいた。確かにやってみると良さそう。

> 逆に、自分がチームに入ったとき「前のマネージャがやっていて良かったから継続してほしいこと」を聞いて続けるようにすると良いよね。
> 着任後、チームの雰囲気を前向きかつメンバの発信型で掴むことにも活きるのでいいこと尽くめだわ。

https://twitter.com/blanite/status/1433426973120811021
FrameとBoundsの違いを理解する

# はじめに
UIKitを触ったときに`frame`とか`boudns`とかで情報が散乱していました。
今回は`frame`, `bounds`について情報を整理します。

# 環境
`Swift 5.4.2`
`Xcode 12.5.2`
`macOS Big Sur 11.5.2`


# `frame`と`bounds`の違い
画像で説明したらわかりやすかったです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551042/0b036886-a1e5-a891-0cbc-672a562588a2.jpeg" width="300" />
`frame`の場合、起点がルートビューの左上です。
一方、`bounds`の起点は、自身の左上でした。

## frameの使用例

```swift:ViewController.swift
// X方向の最小値を取得する
view.frame.minX

// X方向の中央値を取得する
view.frame.midX

// X方向の最大値を取得する
view.frame.maxX

// NavigationBarのHeightを取得する
navigationController?.navigationBar.frame.height

// TabBArのHeightを取得する
tabBarController?.tabBar.frame.height
```

## `bounds`の使用例

```swift:ViewController.swift
// スクリーンのサイズを取得する
UIScreen.main.bounds
```

## NavigationBarの下にボタンを追加する
`frame`を使って、NavigationBarの下にボタンを追加してみます。

```swift:ViewController.swift
// NavigationBarのy座標を取得します
let navBarBottom = navigationController.navigationBar.frame.maxY

// Viewの幅を利用して、ボタンの幅を取得します
let buttonWidth = view.bounds.width - 20

// UIButtonを定義する際に、NavigationBarのbottomを利用します
let button = UIButton(frame: CGRect(x: 10, y: navBarBottom + 10, width: buttonWidth, height: 50))

...

view.addSubview(button)
```



# 参考リンク
https://qiita.com/st43/items/3802624d15a8dded8169#safearea%E3%81%AE%E5%8F%96%E5%BE%97
話をうまくまとめるPREP法

話をうまくまとめられず、相手に自分の話を理解してもらえない場合は「PREP法」を使うことで、相手に分かりやすい形で考えを伝えられるようになります。

#PREP法とは？

「PREP法」とは、文章をわかりやすく表現するためのものです。
また「PREP法」は相手に話を伝えるのにも役立つ話の組み立てかたです。

PREPは以下の頭文字をとったモノです。

Point（結論）
Reson（理由）
Example(事例、具体例)
Point（結論）


今の自分の趣味というテーマにそってPREP法で組み立てると以下のような形になります。

Point（結論）
私の趣味はサウナです。

Reson(理由)
サウナで汗を流すことで、日々のストレスが発散されます。それが気持ちよくてハマっています。

Example（事例、具体例）
実家の近くに温泉施設があり、その温泉施設のスチームサウナでサウナデビューしました。
サウナの効果的な入り方はサウナに７分、水風呂１分、外気浴５分を３セットすることです。

Point(結論)
サウナによってストレスが軽減され、気持ちよく眠れるようになり、毎朝スックリした気持ちで仕事に挑むことができるようになりました。
Linux  MySQLインストール 備忘録

##ダウンロードとインストール

```:サーバーのパッケージインデックスを更新
sudo apt update
```
```:MySQLをインストール
sudo apt install mysql-server
```

```:セキュリティ設定
sudo mysql_secure_installation

secure enough. Would you like to setup VALIDATE PASSWORD component?
Press y|Y for Yes, any other key for No: Y

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG:
2

rootユーザーのパスワードを設定

password: 100
パスワードの強さチェックを確認の後、Y を入力してスクリプトを継続します。
```
## Remove anonymous users? 匿名ユーザーを削除しましか？
>匿名ユーザーは名前を持たない、パスワードも設定されていないユーザーです。匿名ユーザーを使えば、ユーザー名やパスワードなしでMySQLに問題なく接続できてしまうということです。
【中略】
MySQLの安全性を高めるためにも、<b>匿名ユーザーは削除しておくべき</b>です。
http://lovee7.blog.fc2.com/blog-entry-25.html

```:匿名ユーザーの排除
Remove anonymous users? (Press y|Y for Yes, any other key for No)
Y
```

##MySQLログインと設定
```:ログイン
sudo mysql -u root
```
```:ワードプレス用のデータベース作成
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
```
```:ユーザーネームとホスト名
CREATE USER 'wordpressuser'@'%' IDENTIFIED WITH mysql_native_password BY 'パスワード';
```
###ホスト名
「\`%\`」の部分がホスト名。「\`%\`」のワイルドカードはホスト名を省略していることを表す。
→`wordpressuser` = `wordpressuser`@`%`

```:権限付与
GRANT ALL ON wordpress.* TO 'wordpressuser'@'%';
```
権限の種類

|項目|
|----|
|<b>グローバルレベル(G)</b><br>全てのデータベースに適用:mysql.user テーブル<br>GRANT ALL ON *.* TO user;<br>GRANT SELECT, INSERT ON *.* TO user;<br>||
|<b>データベースレベル(D)<br>特定のデータベース内の全てのオブジェクトに適用:mysql.db テーブル<br>GRANT ALL ON db_name.* TO user;</b><br>GRANT SELECT, INSERT ON mydb.* TO user;<br>||
|<b>テーブルレベル(T)</b><br>特定のテーブル内の全てのカラムに適用:mysql.tables_priv テーブル<br>GRANT ALL ON db_name.table_name TO user;<br>GRANT SELECT, INSERT ON db_name.mytbl TO user;<br>||
|<b>カラムレベル(C)</b><br>特定テーブル内の単一カラムに適用:mysql.columns_priv テーブル<br>GRANT SELECT (col1), INSERT (col1, col2) ON db_name.table_name TO user;|



```:権限のフラッシュ
FLUSH PRIVILEGES;
```

```:ユーザー一覧
SELECT User, Host FROM mysql.user;
```

```:ルートユーザーでログイン
sudo mysql
```
##データベースとテーブル定義例
https://qiita.com/YuichiTanaka007/items/affb74f92a848ad84ab0#source%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB-%E4%B8%AD%E8%BA%AB%E3%81%AE%E4%BE%8B%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%A8%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E5%AE%9A%E7%BE%A9



参考

https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-on-ubuntu-20-04-with-a-lamp-stack-ja

https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04-ja

https://dev.mysql.com/doc/refman/5.6/ja/account-names.html

https://www.dbonline.jp/mysql/user/index5.html
[シリーズ 全2回] docker-compose を使って即座に dnsmasq を立てる

# 概要

docker-compose を使って即座に dnsmasq を構築する手順を記している.
ここでは、私にとって最短手番での dnsmasq の起動手順を記す.

なお、本手順は物理ホストの 53番ポートを解放済みであることを前提にしている.

|コンテンツ|実装時期|
|:--|:--|
|[[01] docker-compose を使って即座に dnsmasq を構築する (構築手順のみ)](https://qiita.com/robozushi10/items/372785e962d282c6110f)|2021/08|
|[[02] docker-compose を使って即座に dnsmasq を立てる (解説)](https://qiita.com/robozushi10/items/bc455bff0899d2d73f58)|2021/08|