OmniAICreator/Qiita-1.07M huggingface dataset:
raspberrypi上のproxmoxのバージョンを6から7に上げる

raspberrypiにproxmoxを入れて運用していてバージョンが6.x系バージョンだったので7.x系に上げることにしました。
再インストールでもいいですが楽しそうなので（？）アップデートを試してみようと思います。
#やってみて
結構手こずりました（自分の場合は丸1日程）。ですがVMの構成ファイル等をそのまま引き継げるので楽といえば楽です（バックアップを取ればいいという話はさておき）。
##1,リポジトリの書き換え＆OSのバージョンアップ
proxmox6.x系はDebian10(buster)をベースとしており、7.x系はDebian11(bullseye)をベースにしているためリポジトリも対応したものに書き換える必要があります。

```/etc/apt/sources.list(変更前)
deb http://deb.debian.org/debian buster main contrib non-free
deb http://deb.debian.org/debian-security buster/updates main contrib non-free
deb http://deb.debian.org/debian buster-updates main contrib non-free

~~中略~~
```

```/etc/apt/sources.list(変更後)
deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://security.debian.org bullseye-security main contrib non-free
deb http://deb.debian.org/debian bullseye-updates main contrib non-free

~~中略~~
```

基本的にはbusterの部分をbullseyeに書き換えるだけで問題ありませんが、セキュリティーリポジトリのレイアウトが変わっているので注意が必要です。（自分はこの部分でだいぶ手こずりました）

:::note info
http://deb.debian.org/debian-security/がhttp://security.debian.orgに、buster/updatesがbullseye-securityになっています。
:::

proxmox関連のリポジトリも一度削除します。（後で内容の違うものを書き足すため）

```
sudo rm /etc/apt/sources.list.d/pimox.list
sudo rm /etc/apt/sources.list.d/pve-enterprise.list
```
環境によって違うかもしれないのでそこは適正読み替えてください。

ここまで済んだらOSのバージョンアップをします。

```
sudo apt update
sudo apt dist-upgrade
sudo apt autoremove
sudo reboot
```
こうするとDebianのバージョンが上がりSSHの接続が可能になる…はずですがここで問題が発生しました。

###IPアドレスが降ってきていない

アクセスできないのでルーターからIPアドレスの割当を確認したところ、IPアドレスが割り当てられていませんでした。
ログを見ると`Failed to start DHCP Client Daemon`の文字が。

調べたところdhcpのユニットファイルの書き換えが必要な模様。

```conf:/etc/systemd/system/dhcpcd.service.d/wait.conf（変更前）
[Service]
ExecStart=
ExecStart=/usr/lib/dhcpcd5/dhcpcd -q -w
```
```conf:/etc/systemd/system/dhcpcd.service.d/wait.conf(変更後）
[Service]
ExecStart=
ExecStart=/usr/sbin/dhcpcd -q -w
```
これで再起動をかければ無事上がってくるようになります。

##2.proxmoxの再インストール
ようやくproxmoxのインストールに入れます。

```
sudo su
echo "deb https://raw.githubusercontent.com/pimox/pimox7/master/ dev/" > /etc/apt/sources.list.d/pimox.list
curl https://raw.githubusercontent.com/pimox/pimox7/master/KEY.gpg | apt-key add -
apt update
apt install pve-manager
reboot
```
`proxmox-ve`でもいいと思いますが以前問題があると聞いたので`pve-manager`をインストールしています。（現在どうなっているかは不明です）
また自分の環境では問題ありませんでしたがネットワークが切れるとあるのでコンソールへの直アクセスかscreenなどの使用をおすすめします。

##3.ブリッジの再作成
何故かブリッジが消えていたので再作成します。

この部分は[wancom](https://qiita.com/wancom)さんという方が詳しく解説なさっているのでぜひそちらをご覧ください。
[ネットワークを使用するためブリッジを追加する](https://qiita.com/wancom/items/b62ac44e6c9f0d1c4048#%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%83%96%E3%83%AA%E3%83%83%E3%82%B8%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)

#総括
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1648552/6ced948c-5d7f-8909-9de4-8b0201e77c41.png)
こうして無事proxmox6.x系から7.x系へのバージョンアップが完了しました。
Debianのバージョンアップに伴って色々と躓くところはありましたが、自分の環境ではパフォーマンスの低下などは現状なくよかったです。

もしミス・訂正等あればお知らせください。
【ABC109】C - SkipをC++を使って解説 

#はじめに
　皆さんこんにちは！ 競プロの問題の理解を深めるために解説記事を書いていこうと思います！今回は **ABC109 C** です！
 気に入っていただけたら**LGTM**して頂けるとうれしいです！
何かあれば**コメント**や[Twitter](https://twitter.com/python05238919)からお願いいたします!

#[C - Skip](https://atcoder.jp/contests/abc109/tasks/abc109_c)
####問題概要
 座標$X$から$N$個の座標$x_i$を以下の条件で1回以上訪れるとき
　・座標$x$から座標$x + D$に移動する
　・座標$x$から座標$x - D$に移動する
このときの最大値$D$を求めよ．
####制約
・$1 \leq N \leq 10^5$
・$1 \leq X \leq 10^9$
・$1 \leq x_i \leq 10^9$
・$x_i$はすべて異なる
・$x_1, x_2, ..., x_N \not= 0$
##考察
####ポイント
 **各座標の差から最大値の$D$を求める**
####解説
 各座標をそれぞれ移動するのでそれぞれの座標との距離が必要であると考えられます．それぞれの距離での**最大公約数**を求めることによって座標を$D$だけで移動することができます．
　入力例2では

```C:
3 81
33 105 57
//計算
81 - 33 = 48
81 - 105 = -24
81 - 57 = 24
33 - 105 = -72
33 - 57 = -24
105 - 57 = 48
```
となり絶対値をとって最大公約数を求めると`24`となり，出力と一致します．

それでは実装していきましょう．
#実装
　最大公約数は`gcd`がC++のライブラリにあるので使いましょう．
　互いの距離を一直線で順番になるように`sort`を使っています．
##正解コード

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

int main()
{
    //入力
    int N;
    cin >> N;
    long long ans, X;
    cin >> X;
    vector<long long> x(N); 
    for(int i = 0; i < N; i++){
        cin >> x[i];
    }
    //ソートして順番通りにする
    sort(x.begin(), x.end());
    //初期値はXとxの最小値
    ans = abs(X - x[0]);
    //N個分繰り返す
    for(int i = 1; i < N; i++){
        ans = gcd(ans, x[i] - x[i-1]);
    }
    //出力
    cout << ans << endl;
}
```
macOSアプリでDockのアイコンをバウンスさせる

# 概要
- 遊びでDock上のアイコンをバウンスさせてみる

![Dec-27-2021 20-32-29](https://i.imgur.com/3TTPzyo.gif)

# 参考
- [How can we bounce the dock icon on the Mac?](https://stackoverflow.com/questions/2026936/how-can-we-bounce-the-dock-icon-on-the-mac)
- [requestUserAttention:](https://developer.apple.com/documentation/appkit/nsapplication/1428358-requestuserattention?language=objc)

# 挙動
- アプリのウィンドウがアクティブのときはバウンスはされない
    - 本来の用途が非アクティブの場合にアプリへの注意を引くためだからだと思う
- Dockアイコンにフォーカスを当てるとバウンスが止まる
- アイコンをクリックして他のウィンドウにフォーカスするとバウンスが再開する

# Code

```swift
Timer.scheduledTimer(timeInterval: 1,
                     target: self,
                     selector: #selector(self.timerUpdate),
                     userInfo: nil, repeats: true)

@objc func timerUpdate() {
    NSApp.requestUserAttention(.criticalRequest)
}
```
【Nuxt.js・Firebase】firestoreからIDをもとにデータを取得する

#はじめに
こんにちは。
こちらの記事では、firestoreからIDをもとにデータの取得を行う方法を記しています。
誤っている点がございましたらコメントいただけると幸いです。

##やりたいこと
Cloud Firestore に登録されたデータを、自動生成されるIDをもとに取得して画面に描画させたい。


##実装
クリックイベントで、firestoreからIDをもとにデータを取得する処理を記述している。


```html:template
<template>
  <p>{{ content }}</p>
  <button @click="show">データを表示</button>
</template>
```

```javascript:script
<script>
export default {
  data: function () {
    return {
      content: ""
    }
  },
  methods: {
    async show(){
      const uid = this.$store.getters.user.uid;
      const doc = await this.$firestore.collection("contents").doc(uid).get();
      const data = doc.data();
      this.content = data.contents;
    }
  }
}
```

 * `store/index.js`で、gettersにIDをセットして呼び出しています。
 * `plugins/firebase.js`で、firestoreをinjectしているので、`this.$firestore`で呼び出せるようにしている。（injectについては、下記の記事で紹介しています。）
 * 取得したデータを、dataプロパティのcontentにセットして描画しています。


https://qiita.com/YuStarrr/items/e6821add123d3cb0631a

<br>


#おわりに
ここまでfirestoreからIDをもとにデータの取得する方法についてまとめました。
かなり簡単に描いたので、firestoreの詳しい記事に関しては学習を進めながらアウトプットしていきます。

以上、最後まで読んでいただきありがとうございました！
よければLGTMを押してくれると嬉しいです！
npx create-react-app 出来ない時

# エラーが出た

いつものように、React アプリを npx で立ち上げようとしたら、エラーが出た。

```bash
$ npx create-react-app my-app
Need to install the following packages:
  create-react-app
Ok to proceed? (y) y

You are running `create-react-app` 4.0.3, which is behind the latest release (5.0.0).

We no longer support global installation of Create React App.

Please remove any global installs with one of the following commands:
- npm uninstall -g create-react-app
- yarn global remove create-react-app

The latest instructions for creating a new app can be found here:
https://create-react-app.dev/docs/getting-started/
```

## 指示通りやるも、どっちもうまく行かない

とりあえず、指示に従う。

```
$ npm uninstall -g create-react-app

up to date, audited 1 package in 237ms

found 0 vulnerabilities
```
ダメ。

```
$ yarn global remove create-react-app
[1/2] 🗑  Removing module create-react-app...
error This module isn't specified in a package.json file.
info Visit https://yarnpkg.com/en/docs/cli/global for documentation about this command.
```
ダメ。


global にいないんじゃ仕方ないね。


## つーか、いまインストールしたのが古いとは？！

最初に聞かれたじゃないですか。

```
Need to install the following packages:
  create-react-app
```

React 公式にも、

> When you run npx create-react-app my-app it automatically installs the latest version of Create React App. - via. [Updating to New Releases](https://create-react-app.dev/docs/updating-to-new-releases)

と、最新バージョンがインストールされるって書いてある。エラーメッセージおかしくない？


## キャッシュをクリアする

```bash
$ npx clear-npx-cache
```

うまく行った。人に聞けない時期に、謎エラーは起こりますよね。


## まとめ

> We no longer support global installation of Create React App.

と言っているように、global インストールはサポートしなくなったので、global にインストールされている場合は、

- npm uninstall -g create-react-app
- yarn global remove create-react-app

等を試みて削除。```$ which create-react-app``` で見つかる場合は、それも内容確認して削除。
その上で、キャッシュが悪さしているようなので、

```bash
$ npx clear-npx-cache
```
これで改めて、```npx create-react-app my-app``` すると、うまく行く。```yarn create react-app my-app```だと、出ないのかな。

ついでに、node, npm, yarn とかのバージョンは（慎重に上げて）新年を迎えましょう。


では、良いお年を。
[Swift] DispatchGroup の実行途中で enter 数と leave 数が同じになっても残りの enter が実行されるか調べた時のメモ

結果：実行される

---

実行したコード：

```swift
import Foundation

let dispatchGroup = DispatchGroup()

for i in 0..<3 {
    
    print(i, "enter start")
    dispatchGroup.enter()
    print(i, "enter did")
    
    DispatchQueue.main.async {
        
        DispatchQueue.main.async {
            
            print(i, "- inner enter start")
            dispatchGroup.enter()
            print(i, "- inner enter did")
            
            sleep(1)
            
            print(i, "- inner leave start")
            dispatchGroup.leave()
            print(i, "- inner leave did")
            
        }
        
        print(i, "leave start")
        dispatchGroup.leave()
        print(i, "leave  did")
    }

    sleep(1)

}

dispatchGroup.notify(queue: .main) {
    print("notify")
}
```

結果：

```
0 enter start
0 enter did
1 enter start
1 enter did
2 enter start
2 enter did
0 leave start
0 leave  did
1 leave start
1 leave  did
2 leave start
2 leave  did
0 - inner enter start
0 - inner enter did
0 - inner leave start
0 - inner leave did
1 - inner enter start
1 - inner enter did
1 - inner leave start
1 - inner leave did
2 - inner enter start
2 - inner enter did
2 - inner leave start
2 - inner leave did
notify
```

まとめると、

1. 外側が全部 enter
1. 外側が全部 leave
1. 内側がそれぞれ　enter → leave
1. notify

途中で enter 数 == leave 数　になっても、
まだ enter していない部分があれば
notify されないみたい。
M1 MacにMultipassで仮想環境を構築する

m1のMacでVagrantが動かないため、multipassで仮想環境を作りました。
現在multipassのインスタンス（ゲストOS）の中でdocker-composeを起動してRails開発してますが、今のところ特に不自由なく開発できています。
以下、Vagrantで行っていた操作や設定をmultipassでどのように実現するかという点に絞って構築手順を残したいと思います。

## インストール
まずはbrewでmultipassをインストールします。

```shell
brew install --cask multipass
```

## インスタンスの作成
次にインスタンスを新規作成します。`vagrant up`に相当するコマンドです。

```shell
multipass launch --name myapp --mem 8G --disk 20G --cloud-init cloud-init.yml
```
`--name`には起動するインスタンスに付ける名前を指定します。
`--mem`と`--disk`はインスタンスに割り当てるリソースの指定です。Vagrantではインスタンスに割り当てるリソースをVagrantfileに記述しましたが、multipassではlaunchコマンド実行時にオプションで指定します。

プロビジョニングはcloud-initというツールで行います。
cloud-init用の設定ファイルを作成して、`--cloud-init`にそのファイルを指定します。
例えば、dockerとdocker-composeを使えるようにしたい場合は、cloud-init.ymlは以下のような内容になります。

```yml:cloud-init.yml
#cloud-config
timezone: Asia/Tokyo

apt:
  sources:
    docker.list:
      source: deb [arch=arm64 signed-by=$KEY_FILE] https://download.docker.com/linux/ubuntu $RELEASE stable
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

packages:
  - make
  - docker-ce
  - docker-ce-cli
  - docker-compose

system_info:
  default_user:
    groups: [docker]
```

## データの同期
ホストOS（インスタンスの外）とインスタンスの間でデータを同期する場合、VagrantではVagrantfileにmountの設定を書いてました。
multipassの場合、launchした後に別途コマンドを実行する必要があります。

```shell
multipass mount . myapp:/mnt
```
これでカレントディレクトリがインスタンスの/mntにマウントされます。
Vagrantのようにlaunch時にマウントできたらよいのですが、調べた限り現時点ではできないようです。[githubのissue](https://github.com/canonical/multipass/issues/833)にも上がってました。

## インスタンスにSSH接続する

`vagrant ssh`に相当するコマンドです。

```shell
multipass shell myapp
```

## ホストOSからインスタンス内のサーバにアクセスする

VagrantではVagrantfileに`config.vm.network "forwarded_port", guest: 3000, host: 3000`のような設定を書くことで、ホストOSのブラウザからインスタンス内のサーバにアクセスできました。
しかしmultipassにはホストOSとインスタンスをポートフォワーディングする機能がありません。
以下のように手動でsshポートフォワーディングします。

```shell
sudo ssh -i /var/root/Library/Application\ Support/multipassd/ssh-keys/id_rsa \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-L 3000:localhost:3000 \
ubuntu@$(multipass info myapp | grep IPv4 | awk '{print $2}')
```

これを実行した後に、ホストOSのブラウザから`http://localhost:3000`にアクセスすると、インスタンス内で3000番ポートをlistenしてるサーバに繋がります。

`-i /var/root/Library/Application\ Support/multipassd/ssh-keys/id_rsa`
はmultipassをMacにインストールした時にここにkeyが保存されるのでそれを指定しています。
`-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`
は未知のサーバにアクセスする時に出る警告等を無視するオプション。
`-L 3000:localhost:3000`がポートフォワーディングの設定です。
「SSHポートフォワーディング」でググると情報が見つかります。

最後の`$(multipass info myapp | grep IPv4 | awk '{print $2}')`ですが、これはインスタンスのIPを取得しています。
インスタンス起動時に毎回自動でIPが振られるので、`multipass info`コマンドでIPを取得して接続先を決定しています。

## 最後に
今までずっとVagrantを使ってましたが、multipassはVagrantと操作感も似ていてシンプルですぐに馴染めました。
Vagrantから乗り換えるなら学習コストも低くてとてもおすすめです。

m1 Macに対応した仮想環境としては他にはUTMという選択肢もあります。
個人的にはGUI環境の操作が馴染めませんでしたが、multipassがubuntuの仮想環境しか作れないので、それ以外のOSが必要な場合はUTMが良さそうです。
リープチェア 2021

# 中古のリープチェア 2021

リープチェアとはアメリカ Steelcase社の椅子、ワーキングチェア。

## 人間工学的にデスクや椅子の高さを決める順序

椅子は作業環境の一部。
影響大 > 椅子 >> ディスプレイ > KB,マウス > 机 > 影響小

1. 椅子の座面の高さを適切にする。ひざ下がまっすぐ立って足とお尻で体重を分散させる。
1. 肘掛の高さを適切にする。肘が90度の状態でちょうど肘掛が来る高さ。
1. キーボードとマウスの高さを決める。快適な高さは人によるが、地上から 62cm など、書き物机に置いたのでは高すぎる高さになるはず。
1. 机の高さを決める。書き物をしないならキーボードが適切な高さに来る机にするか、キーボード台（引き出し式、アーム式）が適切な高さに設置できる高さの机。
1. ディスプレイの高さを決める。椅子に正しく座って目線+3cm程度にディスプレイの上辺が来ると良い。ディスプレイを見るときは水平から下向きに目線を動かす。

過去に IBM や富士通や Microsoft や EIZO (Nanao) が人間工学的に正しい作業姿勢のガイドラインを公表していた。 archive.org で探すと見つかる。キーワード「VDT作業」

## 椅子の座面高さ

椅子は使う人の身体に合わせる。

165cm 以上の身長の人はほぼ無視できるが、欧米の椅子を日本人が使うと、足が届かない問題や肩の高さが合わない問題が発生する（こともある）。

身長 155cm の人の多くは膝が 90度で下腿部・ひざ下の寸法が 38 - 39cm 前後になる。オフィスで靴を履いているときは、1-2cm 高さがますので座面高 400mm からの椅子でも問題が出ないが、裸足・靴下・スリッパでリモートワークするときに問題になる。

大半のワーキングチェアは、420mm または 400mm までしか下がらない。

例外的に、「女性のため」などのキャッチのついたイトーキ「カシコチェア」などがある。
## リープチェア

リープチェアで調べると、

1. 無印
1. HD
1. HD2
1. V2

の話題が出てくる。しかし、2021年に新品で買えるのは V2 のみ。
中古で出回っているのも主に V2. 初代や HD/HD2 はすでに中古マーケットにない。（リースバック品の会社の事務椅子としては古すぎるため）

## 代理店

- 内田洋行
- WSI (Steelcase)
- くろがね

の３社が輸入販売している。
それぞれ仕様が異なるバリエーションを持っているようだ。
WSI にオーダーすれば希望の仕様の組み合わせの椅子が手に入るはず。

## リープチェア V2 の座面高さ

同じ名前の「リープチェアV2」の新品には大きく分けて 3種類のバリエーションがある。（ファブリックやランバーサポート違いの掛け算で SKU はもっと多い）

- 394 - 521mm **155cmの人にはこれ**
- 400 - 524mm
- 411 - 535mm

内訳:

- 394 - 521mm
    - USオリジナル
        - K-46216179
        - K-46216179P
        - K-46216179CS
- 400 - 524mm
    - APモデル (Asia-Pacific) マレーシア工場？
        - プラチナ フレーム
- 411 - 535mm
    - APモデル
        - ブラック フレーム （少し廉価）

安さにつられてブラックフレームのモデルを買うと、身長が低いと足が届かないようです。

中古で買う場合は、よくよく調べて買うか、実物に座ってこの実物をくださいと言える店舗で買いましょう。

## 結論

おおよそ 160cm 未満の身長の方は身体にあう **USオリジナル** モデルを買いましょう。


## 座れるところ （試座と言っても営業さんには通じない）

- IDC 大塚家具 新宿ショールーム
    - https://www.idc-otsuka.jp/showroom/shinjuku
- Workaholic (JR浅草橋)
    * http://iamworkaholic.jp/
    * 要予約。2名まで 3300円/2時間。
- Workspace Solutions Inc (WSI) ショールーム 広尾
    - 要予約。
    - https://www.wsi.jp/showroom/


## 後記

めちゃめちゃ調べたけれど中古のある商品がどんなモデルでどんなサイズなのかさっぱりわからなかったので、判明したことをまとめて公開します。

まちがいがあるならご指摘ください。
REST API を使って Google Apps Script をさらにもうひと工夫する

学校法人角川ドワンゴ学園でプログラミング講師をしている @ohataken です。プログラミングの教材を制作したり、生徒からの技術的な質問や相談を解決するのが主な仕事です。とにかく Google Apps Script をよく使っています。

# Google Apps Script について

Google Apps Script とは、Google ドキュメントとか Google スプレッドシートなどの資料をプログラミング言語(JavaScript)から読み出したり、書き換えたりすることができる実行環境です。実際に Google Apps Script を書くということはブラウザ上のエディタから JavaScript を書く、ということになります。

さらに「トリガー」という機能があり、たとえば1日に1回とか1時間に1回、関数を実行するように設定しておくことができます。自分でサーバーサイドの開発をする場合であれば cron を動かしたりする必要があるところを、トリガー機能がすでに組み込まれています。ここがとても便利に感じていて、気に入っているところです。

よくある使い方の例として、なにかデータを取得して、整形して、 Google スプレッドシートに書き出す、というのがあると思います。そのケースで便利な関数について、 [今年のアドベントカレンダーの7日目](https://qiita.com/ohataken/items/1e9479ce5c3c32292bbd)に書きました。

# ひとつの Google スプレッドシートに、ひとつの Google Apps Script

独自のノウハウとして「ひとつの Google スプレッドシートに、ひとつの Google Apps Script」というルールを設けて運用しています。いうまでもなくシンプルになるので、問題が起きた時の切り分けも易しくなります。また、 Google Apps Script のトリガーには1プロジェクトあたり 20 までという制約があるため、 Google Apps Script は大きいひとつのプロジェクトが頑張って働くよりも、小さいシンプルなプロジェクトがたくさん働くようなイメージのほうが合います。

# いくつもの Google Apps Script を管理するための REST API 

小さいシンプルな Google Apps Script をたくさん動かすためには、プロジェクトをコピーしたり編集したりする機能が欲しくなります。が「Google Apps Script の内容を書き換える Google Apps Script」というのは、そういう API がないので書けません。この点は、複数の Google ドキュメントやスプレッドシートを書き換えることができるのと対照的に思います。

なので Google Apps Script の枠組みから離れて、 REST API を利用します。最近は Google Apps Script くらいは自分で書ける方も少なからず見受けられますが、 REST API となるとハードルが高まるのではないでしょうか？Webプログラマーとしては、このあたりが面白い

[Manage scripts programmatically with the REST API](https://developers.google.com/apps-script/api/concepts) 

# Google Apps Script の projects.getContent と projects.updateContent を利用して、コードを上書きコピーする

[projects.getContent](https://developers.google.com/apps-script/api/reference/rest/v1/projects/getContent) と [projects.updateContent](https://developers.google.com/apps-script/api/reference/rest/v1/projects/updateContent) という API を使って「元になるプロジェクトの内容を、他のプロジェクトに上書きする」という機能をつくることができます。

私は Ruby を使って実装しました。

```ruby
service = Google::Apis::ScriptV1::ScriptService.new
service.authorization = credentials # credentials を指定する

content = service.get_project_content(source_id) # source_id はコピー元プロジェクトの ID
service.update_project_content(dest_id, content) # dest_id はコピー先プロジェクトの ID
```

という感じで、 API クライアントのオブジェクトをそのまま使い回す形で書くことができて、実装が楽でした。

# ScriptProperties について

ここまでで、Google Apps Script をコピーするコードを書くことができましたが、もともと実現したかった「ひとつの Google Apps Script につきひとつの Google スプレッドシート」というルールをまっとうするために、スプレッドシートの URL や ID といったマジックナンバーをどう持つか、という問題がでてきます。

Google Apps Script は、 Properties という簡素なデータベースも備えています。(Properties Service
)[https://developers.google.com/apps-script/reference/properties] データベースというよりは、書き換え可能な環境変数というつもりくらいがちょうどいいと思います。

この仕組みをつかって、マジックナンバーをコード外に押しやることができます。

ブラウザ上のエディタから以下のような関数を、関数名を指定して実行することで設定します。

```js
function setProperties() {
  const props = PropertiesService.getScriptProperties();
  props.setProperties({
    "SPREADSHEET_ID" : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  });
}
```

この作業だけはいちいち手作業でやらざるを得ず、課題として残っています。REST API から Properties を書き換えできたらいいのに、と思います。
Slidevを使ってスライド作成

# 触ってみた理由
トレンド入りしていたので気になった＆これから会社でスライドの機会が増えそうだったので

# 諸々

- git: https://github.com/slidevjs/slidev
- docs : https://sli.dev/

# スタート

```
npm init slidev
```
必要情報を入れて好きな yarnやら npmやら選んだら勝手に立ち上がる

# css
cssモジュール
https://windicss.org/

```css
class="mt-[30px]"
```
などである程度のスタイルは適応できそう
Terraform(HCL)でのデバッグとコーディングで気を付けたいこと

# はじめに

Terraformの構文エラー等でつまずいたときに気を付けたいことなどをまとめます
「気づき」などがあれば追記していこうと思います

## 気を付けたいこと

### `state`ファイルを確認する

別リソースなどの値の参照でうまくいっていないときは、`state`ファイルを覗いてみます
オブジェクトだと思っていると、途中に配列が挟まっていたりするかもしれません

### `terraform console`で出力を確認する

```hcl
$ terraform console # consoleモードに入る

$ exit              # consoleモードの終了
```

例えば、`concat`関数の動作を`console`内で確認できます

```hcl
> concat(["1", "2"], ["3", "4", "5"])
[
  "1",
  "2",
  "3",
  "4",
  "5",
]
```

## 分かりやすい記述をする

HCL（HashiCorp Configuration Language）は`dynamic`や`for_each`などを多用すると理解しにくくなると思います
次に読む人が分かりやすいように意識したいポイントも書いてみます

### あえて変数化しない

変更される頻度の低い部分は変数化しないほうが読みやすいと感じます

### 必ずフォーマットする

VSCodeでの場合、`Shift` + `Alt` + `F` でフォーマットしてくれます
こまめに実行しておきましょう

> [HashiCorp Terraform \- Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform)

### `variable`ブロックを`object`タイプで管理する

`variable`ブロックは変数をなんでもつっこめます
、、、が、詰め込みすぎると逆に分かりにくくなります

1つの`variable`ブロックに同じアプリの設定はすべて格納しておいたり、
tfファイルを別々に分けておくと読みやすさを維持できます

変数名も同じ系統の変数は同じ文字を使っておけば、ぱっと見で変数をグループ化してみることができます
変数名が`vnet`から始まれば仮想ネットワークの、`snet`から始まればサブネットの変数であることが想像できます
`object`で指定しておけば、`string`や`list()`など異なる型の値も同じ`variable`ブロックで管理できます

```hcl
variable "network" {
  type = object({
    vnet_name                                 = string
    vnet_address_space                        = list(string)
    vnet_peering_name                         = string
    vnet_peering_allow_virtual_network_access = bool
    snet_public_name                          = string
    snet_public_address_prefixes              = list(string)
    snet_private_name                         = string
    snet_private_address_prefixes             = list(string)
  })

  default = {
    ...
  }
}
```

### 変数`var`と`locals`を組み合わせる

```hcl
resource "resource" "label" {
  ip_ranges = [
    var.ip_address_1,
    var.ip_address_2,
    var.ip_address_3,
    var.ip_address_4,
  ]
}
```

リストを指定したい場合、`resource`ブロックに記載するとぱっと見で理解しずらくなります
かといって、変数を変数のなかで指定できません
このときは`local`変数を使えばすっきりします

```hcl
locals {
  ip_ranges = [
    var.ip_address_1,
    var.ip_address_2,
    var.ip_address_3,
    var.ip_address_4,
  ]
}

resource "resource" "label" {
  ip_ranges = local.ip_ranges
  # localsの参照をするときは、local("s"を含まない)で指定します
}
```
【IQ Bot】フィールド項目のマッピング方法　その２：相対座標によるマッピング

# はじめに

[IQ Bot](https://qiita.com/IQ_Bocchi/items/36dbe9adc4b8e70437bf)でフィールド項目をマッピングする３つの方法のうち、この記事では「相対座標によるマッピング」について解説します。

#相対座標によるマッピングとは

相対座標によるマッピングは、**ラベルと値の位置関係を人が明示的に指定する**やりかたです。

以下の動画で行っているマッピングの方法が、相対座標によるマッピングです。

（最新バージョンのIQ Botには、「Draw」というボタンはありません。「フィールド値」にカーソルが当たった状態でマッピング領域を選択する操作になります）
![値の学習_後半.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/10bc7013-0e36-94e8-df37-e7e22514ebd9.gif)

動画の字幕からは、「ほしい値が一部しかとれていない場合」や「IQ Botが値を間違って取得してきた場合」のみに適用する方法かのように見えてしまうかもしれませんが、実際には違います。

実はフィールド項目に関しては、**相対座標によるマッピングを行った方がいい場合がほとんど**になります。

# 相対座標によるマッピングのメリット

相対座標によるマッピングを行うことで、フローティングによるマッピングの問題点を回避することができます。

以下の記事で「フローティングの注意点」の具体例に示した帳票を、相対座標でマッピングする場合を具体的に見てみましょう。

https://qiita.com/IQ_Bocchi/items/f5f6104997adfd2153a3

## 具体例

請求書１では、送料の値をIQ Botが自動取得してくれています。
ですが、相対座標によるマッピングをする際は、**あえて送料の入るべき範囲を手動で明示的にマッピング**します。

![01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/a68b6bb0-2a26-0660-f350-b01e44bb839e.jpeg)

すると、IQ Bot は「送料」というラベルの位置に対して、明示的にマッピングされた位置関係にある項目を認識しようとします。

これによって、請求書２のような帳票でも、正しい値を認識することができます。

より正確に言えば、このケースはマッピングされた場所に値はないので、値がないことを正当に認識することができます。

![02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/60d564ed-3798-58d9-14f7-537251596352.jpeg)

## 相対座標によるマッピングの応用

[フローティングによるマッピングの解説](https://qiita.com/IQ_Bocchi/items/f5f6104997adfd2153a3)では、以下のような手書き帳票の具体例も挙げました。

![08.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/19d9c7e3-0f7b-0a7f-d86b-3b58daa59f37.jpeg)

このケースでも、相対座標によるマッピングが有効ですが、少し応用的なマッピングを考える必要があります。

おそらく多くの人が直感的に思い浮かべるのは、以下のようなマッピングだと思います。

![03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/23557670-6a17-76c9-d203-6f52a8331148.jpeg)

こうしておけば、フローティングでは正しく取得できなかった帳票２の「どこでも太郎」さんのケースに関しては、正しく値を取得することができます。

ところが、ラベルと値がくっついてしまっていた帳票３の「どこでも次郎」さんのケースは、このやりかたでは正しく取得できません。
![04.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/3f2954cf-3cf5-19fb-836c-19f67db10e87.jpeg)

このようなケースに関しては、以下の記事で解説した方法でマッピングをする必要があります。

https://qiita.com/IQ_Bocchi/items/2c4e6465c885009283a9

今回の帳票に関しては、以下のようにマッピングをします。

![05.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/bd3bd91e-5977-5810-68f6-17d8376cb7e9.jpeg)

こうしておくと、下記のどのパターンであっても正しく値を取得することができます。

![06.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/68e82951-c113-e5da-6e60-fca10ed19128.jpeg)

# まとめ

以上が、相対座標によるマッピング方法の解説になります。

IQ Bot を使ってフィールド項目をマッピングする場合、最も実用的なのが、この相対座標によるマッピング方法です。

実際には、以前に解説した[フローティング](https://qiita.com/IQ_Bocchi/items/f5f6104997adfd2153a3)と、もうひとつ、固定座標によるマッピングの方法（記事は準備中）がありますが、筆者が実際にIQ Bot を使うときは、フィールドのほとんど全項目を相対座標でマッピングしています。
【kintone】テーブルの入力内容の重複チェックの方法を紹介

##はじめに
kintoneのテーブルの入力内容の重複チェックの方法について紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/13bcdbf8-04e7-c8dc-affe-6c43736924a2.png)
このようにテーブル内に1つの項目がある状態を前提としています。
今回はルックアップを使用しているため、同じデータを取得してしまうケースが発生することを考慮し重複チェックを入れました。

##kintoneでの挙動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/9cd32451-9f74-efa0-299d-a477b7fb71b1.png)
1行目と3行目に同じ値、2行目と4行目は空で入力して保存ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/191f902c-2dfb-062c-e054-ad476f5501cc.png)
値が重複している行に対してエラーを表示します。
空白行は同じ入力内容ではありますが、この行はスルーします。
通常kintoneでは保存時のエラーの場合はモーダルでもエラーを表示していますが、このカスタマイズは難易度が高いため行っていません。

## サンプルコード
```javascript 

(function () {
  'use strict';

  // 定数の定義
  const ERROR_DUPLICATE_MESSAGE = '値が重複しています。';

  /*
   * 保存前イベント
   */
  kintone.events.on([
    'app.record.create.submit',
    'app.record.edit.submit'
  ], function (event) {
    // 申請NOのバリデーションチェック
    return validApplyNo(event);
  });

  /*
  * 申請NOのバリデーションチェック
  */
  function validApplyNo(event) {
    const record = event.record;
    // 保存しようとしている申請NOリスト
    const tableApplytNoList = record['テーブル'].value;
    // バリデーションチェック用の申請NOリスト
    let applyNoList = [];
    for (let i = 0; i < tableApplytNoList.length; i++) {
      // エラーを一度初期化する
      record['テーブル'].value[i].value['申請NO'].error = null;
      // 値が空ではないかつ既に申請NOが存在していた場合、エラー
      if (
        tableApplytNoList[i].value['申請NO'].value !== undefined
        && applyNoList.includes(tableApplytNoList[i].value['申請NO'].value)) {
        // 値が重複しているインデックスと現在のインデックスをリストに追加
        const indexList =
          [applyNoList.lastIndexOf(tableApplytNoList[i].value['申請NO'].value), i]
        // 値が重複している2つの行に対してエラーを設定
        for (let n in indexList) {
          record['テーブル'].value[indexList[n]].value['申請NO'].error =
            ERROR_DUPLICATE_MESSAGE;
        }
      }
      applyNoList.push(tableApplytNoList[i].value['申請NO'].value);
    }
    return event;
  }
})();
```

### 解説
**applyNoListについて**
バリデーションチェックのために作成した変数です。tableApplytNoListの値をチェックした後に全ての値はここに入っていきます。

**条件式部分**

```javascript
tableApplytNoList[i].value['申請NO'].value !== undefined
```
値が空の場合はスルーするチェックです。

```javascript
applyNoList.includes(tableApplytNoList[i].value['申請NO'].value)
```

値が重複している場合はエラーとするため、applyNoListに既に値が含まれているかのチェックです。


**条件式内の処理**

```javascript
// 値が重複しているインデックスと現在のインデックスをリストに追加
const indexList =
  [applyNoList.lastIndexOf(tableApplytNoList[i].value['申請NO'].value), i]
```
値が重複している2つの行に対してエラーを追加するため、indexListという変数に重複しているインデックスを追加します。

##さいごに
上記参考になれば幸いです:point_up_tone1:
よりよい方法が他にあれば紹介してください！
【Unity】UniTaskを使って音声の再生終了イベントを設定する

音声以外にも再生待ちに使えると思います。

```csharp:AudioPlayAsync.cs
using UnityEngine;
using UnityEngine.Events;
using Cysharp.Threading.Tasks;

public class AudioPlayAsync : MonoBehaviour
{
    [SerializeField]
    private UnityEvent OnPlayComplete;

    /// <summary>
    // ゲームオブジェクトにアタッチされたAudioSourceの音声を再生し、
    // 再生終了後にUnityEventを実行する。
    /// </summary>
    /// <returns></returns>
    public async UniTask PlayAudioAsync()
    {
        AudioSource _audioSource = gameObject.GetComponent<AudioSource>();
        _audioSource.Play();

        // 再生待ち
        await UniTask.WaitUntil(() => _audioSource.isPlaying);

        // 再生終了待ち
        await UniTask.WaitWhile(() => _audioSource.isPlaying);

        // 登録したUnityEventを実行
        OnPlayComplete.Invoke();
    }
}
```
【IQ Bot】フィールド項目のマッピング方法　その１：フローティング

# はじめに

[IQ Bot](https://qiita.com/IQ_Bocchi/items/36dbe9adc4b8e70437bf)でフィールド項目をマッピングする３つの方法のうち、この記事では「フローティング」について解説します。

#フローティングによるマッピングとは

IQ Botは以下のように、ラベルを指定すると、それに紐づく値を自動で取得してきます。

![値の学習_前半.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/070afc17-23f5-b06c-8923-3f580b718a35.gif)

この、自動取得したマッピングを変えずにそのままにしておくのが「フローティング」のやりかたです。

動画の字幕には「値の場所が正しい場合は、このままでOK」と書いてありますが、**フローティングの考え方をしっかり理解した上でないと、つまずく原因になるので注意が必要です＞＜！**

#フローティングでマッピングすることの意味

フローティングでマッピングするということは、**「どんな場合でも、IQ Botが自動取得してきた値の位置を信じます」**という意味になります。

## 具体例

例えば、帳票の一部が以下のようになっている請求書があったとします。
![01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/1165bb21-b000-1681-7931-47b60b1dfdbc.jpeg)

IQ Botで読み取ると、以下のような[SIR](https://qiita.com/IQ_Bocchi/items/1e0a36fb69393106d025)が生成されました。

![02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/10f07d7b-8bd1-debc-bd8b-3ff8ea691dce.jpeg)

この中で、「送料」をラベルとして選択すると、おそらく以下のように、送料の右側の「1,000円」をIQ Botが送料に紐づく値として自動取得してくるはずです。

![03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/7c3f421f-5b54-ace7-4675-f6ccf070ad4b.jpeg)

これをそのまま放っておけば、フローティングによるマッピングをしたということになります。

## フローティングの注意点

フローティングによるマッピングは、一見すると楽ですが、注意が必要な場合もあります。

### 具体例１

先ほどと同じ請求書で、今度は送料の欄が空白になっている帳票が来ました。

![04.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/ed2d11a2-b2dc-dc15-f33f-d982bea0822a.jpeg)

するとIQ BotのSIRは以下のように生成されます。
![05.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/10e938b2-f05a-40b4-73d5-ce1b903c3617.jpeg)

IQ Botは送料のラベルに紐づく値を探しに行こうとしますが、最もそれらしい右側には値がありません。
そこで、下にある「合計」という文字を値として拾ってきてしまう場合があります。

![06.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/7279caa9-9010-21a5-536c-007e56b44ecb.jpeg)

フローティングによるマッピングは、**「どんな場合でも、IQ Botが自動取得してきた値の位置を信じます」**という意味ですが、このケースは、IQ Bot が自動取得した値の位置を信じてはいけませんよね。

なので、このケースはフローティングによるマッピングが適さないケースであるといえます。

### 具体例２

例えば手書きの帳票で、氏名欄が以下のように書かれてくるとします。
![07.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/80825cf6-aa9a-7efb-d1c4-555c0264f0ad.jpeg)

手書きの帳票は活字帳票と違って、値の書かれる位置が安定しません。
SIRの括られ方も、以下のように変わることがあります。
![08.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/19d9c7e3-0f7b-0a7f-d86b-3b58daa59f37.jpeg)

これをフローティングでマッピングすると、例えば以下のような問題が出てきてしまいます。

![09.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/21ff9094-a8f6-8c46-b548-fbbd1057517a.jpeg)

# フローティングの問題点の回避策

以上のような、フローティングによるマッピングの問題点を回避するための方法が、「相対座標によるマッピング」です。

https://qiita.com/IQ_Bocchi/items/1909bc427bacf2f840e9

筆者が実際にIQ Bot を使うときは、フィールドのほぼ全項目を相対座標でマッピングしています。

ぜひ、上記のリンクをご参照ください。
【Project Euler】Problem 66: ディオファントス方程式

* 本記事は[ProjectEuler](https://projecteuler.net/about)の「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。

### 問題：ディオファントス（ペル）方程式

原文 [Problem 66:Diophantine equation](https://projecteuler.net/problem=66)

**問題の要約：　以下の不定方程式で$D<10^3$で$x$の最小解の最大値を求めよ**

$\ \ \ \ \ \displaystyle \large x^2-Dy^2=1$

原文ではディオファントス方程式となっていますが、その中のペル方程式を扱ってますので、以下ペル方程式と呼びます。

まず[過去の投稿「ペル型方程式をsympyのdiop_DNで解く」](https://qiita.com/masa0599/items/162c7e1989ae9a653a0c)でsympyのdiop_DNを紹介しましたが、それがそのまま使って$N=7$とすると$D=5$が求まります。ただこれも$N=1000$とすると42秒と1分ルールには収まるもののあまり速くないので。別解を考えます。

```python
from sympy.solvers.diophantine.diophantine import diop_DN

N,mx = 7, 0
for D in range(2,N+1):
  (x,y) = diop_DN(D,1)[0]
  if x > mx: 
    mx, mxD = x, D
print(mxD, mx)
#5 9
```

### ペル方程式の基本解を平方根の連分数展開から求める

以前の投稿[【Project Euler】Problem 64: 平方根の連分数](https://qiita.com/masa0599/items/44b89e5f0bbfb0450523)でも参照した文献「[平方根の連分数とペル方程式 有澤健治著](https://leo.aichi-u.ac.jp/~keisoken/research/books/book51/book51.pdf)」の87ページにある以下の定理によりペル方程式の解を平方根の連分数展開を使って求めることが出来るということです。

> **定理 2a.** 
> ペル方程式 $p^2-mq^2=1$
> $n=\lfloor\sqrt{m}\rfloor $、$\sqrt{m}$の連分数展開を$[n,n_1,n_2,\dots,]$、$l$を連分数の循環部分の周期とする。
> そこで$p_k/q_k=[n,n_1,n_2,\dots ,n_{kl-1}]$ $(k=1,2,\dots)$とすると
> $p_k^2-mq_k^2=(-1)^{kl}$である

従ってまず$D$の連分数展開を求めて循環部分の周期が**偶数**ならば$(-1)^{l}=1$となるので、$k=1$として

* $p_1/q_1=[n,n_1,n_2,\dots ,n_{l-1}]$

となり$p_1, q_1$がペル方程式の解となります。

循環部分の周期が**奇数**ならば、$(-1)^{kl}=1$となる最小の$k$は$2$なので

* $p_2/q_2=[n,n_1,n_2,\dots ,n_{2l-1}]$

の$p_2, q_2$が解となります。


$D$の連分数展開は[【Project Euler】Problem 64: 平方根の連分数](https://qiita.com/masa0599/items/44b89e5f0bbfb0450523)で作った関数**cfsqrt**を使い、連分数から分数に変換する関数を**cont2frac**とすると、ペル方程式の最小解を求める関数**pellEq**は以下のようになります。これを**diop_DN**の代わりに使うと$N=1000$でも1秒以内に求めることが出来ました。

```python
# Solve Pell Equation
def pellEq(D):
  cf = cfsqrt(D) # continued fraction of square root of D (Problem 64)
  if len(cf) == 1: return (1,0)  ## square numbers
  cf1 = [cf[0]]+cf[1]+cf[1][:-1] if len(cf[1])%2 == 1 else [cf[0]]+cf[1][:-1]
  return cont2frac(cf1)

# Convert continued fraction to fraction
def cont2frac(cont):
    num, den = 1, 0
    for u in reversed(cont):
      num, den = den + num*u, num
    return num, den
```
【Automation Anywhere】Bot Agent のアンインストール - デバイス接続がうまくいかない場合

#はじめに

過去に [Automation360](https://qiita.com/IQ_Bocchi/items/e5c95bd74cae45d1c586)を動かしたことのあるPCで、別のControl Room にデバイス接続しようとすると、エラーになることがあります。

エラーとなるのは、過去にインストールした古いBot AgentがPCに残っている場合が多いです。

その場合、古いBot Agentを削除（アンインストール）する必要があります。

このページでは、インストール済みのBot Agentをアンインストールする方法を説明します。

#Bot Agentのアンインストール

### Step1 「アプリと機能」を起動する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/8ede5270-4816-baa3-6253-4679f083c33d.png)

### Step2 Bot Agentをアンインストールする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/a9c02d44-22bd-6f8e-8761-7cb00fa1c271.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/57324631-304f-fe8e-2686-fe13afa34b67.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/275b8c8f-b698-dc62-ebdf-d1fc3f642ed1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/5103e122-9cb5-e3bd-1a26-bfa0ac88ff0c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587845/e704b2e8-89e9-f266-427a-d639c03f075a.png)

以上で Bot Agent のアンインストールは完了です。

# どうして古いBot Agent をアンインストールしなければいけないの？

作業の手順は上記のとおりですが、どうして古い Bot Agent の削除が必要なのか、理由が知りたい方のために書いておきます。

### Bot Agentは、複数の Control Room との接続を想定していないから

Automation Anywhere を使う場合、１台のControl Roomにすべての実行・開発端末を接続して管理することが一般的です。

実行端末や開発端末を複数の Control Room に接続して使うことは、実際の業務では考えにくいため、ひとつのBot Agentが複数のControl Room に接続することを想定しない作りになっています。

ですが、例えば過去にフリートライアルを使っていて、その際にPCとフリートライアルのControl Roomを接続し、その同じPCでCommunity Editionや製品版のControl Roomと接続をしようとすると、あたかも上記の「考えにくい」状況が起きているふうになってしまいます。

新しいControl Roomは、PC上にBot Agentがいることを検知しているのに、そのBot Agentの接続先は別のControl Roomになっている。

だから接続がうまくいかない、というわけです。

なのでこの場合は、古いBot Agentを削除し、改めて新しい Control Room から Bot Agent を入れ直す必要があります。

### 古いBot Agentは、新しいバージョンへの上書きができない仕様になっているから

PCに残っている Bot Agent が古い場合、接続先の Control Room はひとつしかなくても、接続がエラーになる可能性があります。

特に Control Room がクラウドの場合（Community Editionはクラウドです）、Control Room 側のバージョンは日々アップグレードしています。それに伴い、Control Roomと互換性のあるBot Agentのバージョンも変わっていきます。

Control Room と Bot Agent のバージョンの対応は、下記の公式ドキュメントに記載があります。

https://docs.automationanywhere.com/ja-JP/bundle/enterprise-v2019/page/enterprise-cloud/topics/deployment-planning/on-prem-install/cloud-bot-agent-compatibility.html

最近のバージョンでは、Control Room 側で Bot Agent が自分と互換性のないバージョンだと検知すると、実行時等に自動で Bot Agent を更新する仕組みができているのですが、古い Bot Agent は自動更新に対応しておらず、手でアンインストール　→　再インストールの流れが必要になる場合があります。


#最後に

この記事は、「はじめてのAA」シリーズからリンクされています。

https://qiita.com/IQ_Bocchi/items/8d28f12256037b5f9058

Automation Anywhere を使い始めたばかりの方はもちろん、これから使ってみようと思っている方や、ずっと使っているけど改めて基本を学びたい方も、ぜひ上記のリンクをチェックしてみてください。
コマンドを打つとチャンネルを作ってくれるdiscordbotをPythonで作ろう #4  ~おまけ~

[前回](https://qiita.com/peco_2282/items/56ec9a81a5e99c7a8959)までは 
テキストチャンネル作成：　`guild.create_text_channel()`

ボイスチャンネル作成　：　`guild.create_voice_channel()`

カテゴリ作成　　　　　：　`guild.create_category()`

の3つに触れました。

ついでなのでチャンネル削除についても作ってみたいと思います。

# チャンネルを削除する

`.chdel` の後に

- チャンネルidを入力しない場合：　そのチャンネルを削除

- チャンネルidを入力した場合　：id指定されたチャンネルを削除
- idを入力したが存在しない場合：削除しない

このパターンで行きます。

コードです。

```python
# 新しくモジュールをインポートします
from asyncio import sleep


if message.content.startswith('.chdel'):
    try:
        msg = message.content.split()
        if len(msg) == 1:
            await message.channel.send(f'{message.author.mention} が チャンネル : {message.channel}を 10秒後に 削除します。')
            await sleep(10)
            await message.channel.delete()

        else:
            ch_id = int(msg[1])
            del_channel = message.guild.get_channel(ch_id)
            if del_channel is None:
                await message.channel.send('チャンネルが見つかりません。')

            else:
                await message.channel.send(f'{message.author.mention} が チャンネル : {del_channel}を 10秒後に 削除します。')
                await sleep(10)
                await del_channel.delete()
                await message.channel.send(f'{message.author.mention} が チャンネル{del_channel} を削除しました。')
    
    except:
        await message.channel.send(f'{message.author.mention}, エラーです。')
```

## 解説

[`.delete()`](https://discordpy.readthedocs.io/ja/latest/api.html#discord.TextChannel.delete)
を使います。

---

`if len(msg) == 1:` から

これは上のmsgが `.chdel` の1つだったらという意味です。

この場合、メッセージを送信したチャンネルを削除します。

```python
await sleep(10)
await message.channel.delete()
```

10秒待ってからメッセージを送信したチャンネルを削除します。

`else:` 以下

- if の条件に当てはまらなかったら(チャンネルidが入力されていなかったら)
もしその中で指定したチャンネルが見つからなかった場合、

 > チャンネルが見つかりません。

と返します。

- もしその中で指定したチャンネルが見つかった場合、
`del_channel = message.guild.get_channel(ch_id)` にそのチャンネルのデータが入ります。

```python
await sleep(10)
await del_channel.delete()
```

`del_channel` を10秒後に削除します。
Kotlinで地図アプリ（ゴミ拾いアプリ）作成。（5日目）

Kotlinで制作している、GoogleMapを使った、自作のゴミ拾いアプリ。制作5日目。

#これまでの経過

1日目：Android Studio + Kotlin で開発環境作成。GoogleMapAPIアクセスキー取得。エミュで動作。
2日目：地図にマーカー表示・カスタム方法。マップ表示形式の変更。
3日目：現在地を取得し、地図上に表示。クリックイベント。
4日目：移動する際にマップがスクロールしなかった問題を修正。

※毎日やってる事
・Kotlin言語の勉強
・Android Studioの使用方法
・アイコンの作成方法など基本的なアプリの作り方など


#本日の課題

回収したゴミの種類を大まかに「大」「中」「小」に分け、ボタンクリックで、位置情報を表示し、GETメソッドで、サーバーへデータを送信。

通信ライブラリとしてとりあえずvolleyを使用。

スマホから送信されたデータを、受信するサーバーは、有料契約済みのロリポップを使用。




#通信ライブラリ volley 

Volleyの利点

:::note info
・ネットワーク リクエストの自動スケジューリング。
・複数のネットワークの並行接続。
・標準の HTTP キャッシュ コヒーレンシを備えた透過的ディスク / メモリ レスポンス キャッシュ。
・リクエストの優先順位付けのサポート。
・Cancellation request API。単一のリクエストのキャンセル、ブロックの設定、キャンセル リクエストの範囲の設定ができます。
・容易なカスタマイズ（再試行、バックオフなど）。
・強力な順序付けにより、ネットワークから非同期に取得されるデータを UI に正しく簡単に入力できます。
・デバッグツールとトレースツール。
:::




https://developer.android.com/training/volley?hl=ja

公式マニュアルに従い、build.gradleにimplementation を追加

```gradle:build.gradle
dependencies {
    implementation 'com.android.volley:volley:1.1.1'
```


#Volleyでシンプルな処理を実行テスト


https://developer.android.com/training/volley/simple?hl=ja

マニフェストに android.permission.INTERNET パーミッションを追加

```xml:AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
```

プロジェクトのクリーンアップ
[Build] -> [Clean Project]


```kotlin:MapsActivity.kt
import com.android.volley.Response
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley

....
    val queue = Volley.newRequestQueue(this)
    val url = "https://www.xxxxxxx.com/p/index.php?DATA=ゴミ拾いアプリ"
    lateinit var ResError:String

    val stringRequest = StringRequest(Request.Method.GET, url,
        Response.Listener<String> { response ->
            // Display the first 500 characters of the response string.
            //textView.text = "Response is: ${response.substring(0, 500)}"
        },
        Response.ErrorListener { ResError = "That didn't work!" })
    queue.add(stringRequest)

```


とりあえず、GETデータ受信側のサーバで、リクエストデータを受け取り、記録するだけのシンプルなコードを作成。

```php:index.php
<?php
if(isset($_GET['DATA']) && $_GET['DATA'] != ''){
    file_put_contents("./data.txt", $_GET['DATA']);
?>
```

実行後、サーバー上のファイルを確認

```txt:data.txt
ゴミ拾いアプリ
```


今日はここまで。
Device42でlog4jが入っているサーバーを探す

##はじめに

2021年、最後の最後で登場した脆弱性問題であるLog4Shellについてのお話です。
Log4Shellって何？という方は以下の記事をご参照ください。

https://www.ipa.go.jp/security/ciadr/vul/alert20211213.html

弊社では自社導入しているDevice42が日常的に機器情報を自動収集しています。
今回はその収集された情報から`log4j`が入っているサーバーを探す作業を行いましたので、その備忘録をここに残します。
※なおDevice42はPostgresSQLを使用していますので、後述するSQLクエリはPostgresSQLでの記述になっています。

Device42については以下で紹介していますので、ご興味のある方は是非。

https://qiita.com/daijiro_ABY3/items/0f68081239aaf0acdf5c


##①使用中のソフトウェア情報を利用した検出方法
ひとつ目は関連性のあるソフトウェア名から対象機器を選別する方法です。
自動収集で`使用中のソフトウェア（自動収集されたソフトウェア情報）`の情報がとれている場合、以下のSQLで`log4j`と関連のあるソフトウェア名で検索することで対象の機器を検出することができます。

```
WITH	javacomp AS (
	SELECT
		sf1.software_pk,
		sf1.name,
		sf1.server_software_fk
	FROM view_software_v1 sf1 -- ソフトウェア名の検索条件指定
	WHERE LOWER(sf1.name) LIKE '%java%'
	OR LOWER(sf1.name) LIKE '%log4j%'
)
SELECT
	si1.device_fk AS "デバイスID",
	d.name AS "デバイス名",
	javacomp.name AS "ソフトウェア名"
FROM	javacomp
INNER JOIN	view_softwareinuse_v1 AS si1	ON	javacomp.software_pk = si1.software_fk
LEFT JOIN	view_device_v2 AS d	ON	si1.device_fk = d.device_pk
ORDER BY "デバイスID" ASC
```

これで`java`と`log4j`が使用中のソフトウェアとしてインストールされているデバイス（機器）が出力されます。

ただしこのやり方はあくまでソフトウェア名での検索をかけているため、後述の手法と比べると正確性で劣ります。
上記の例でいえば`java`と名の付く大量のソフトウェアコンポーネント群が抽出対象に引っかかってしまうので、出力結果をさらに精査する必要が出てきます。

##②サービスインスタンス情報を利用した検出方法
こちらの手法が本命となります。
Device42ではオプションのADMライセンスをつけることによって、サービスインスタンスのパス（環境変数）情報を取得してくれます。

![log4j01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2393822/d01e8f14-cfc3-0bf6-d752-dfef35bd65f3.png)
GUIで見るとこの部分です。
![log4j_03.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2393822/0c23d911-0590-9a39-f617-77a77378e863.png)

SQLクエリで抽出する場合は以下のように記述します。

```
SELECT
	d.name AS "デバイス名",
	s.displayname AS "サービスインスタンス名",
	si.cmd_paths AS "コマンドパス"
FROM	view_serviceinstance_v2 AS si
LEFT JOIN	view_device_v2 AS d	ON	d.device_pk = si.device_fk
LEFT JOIN	view_service_v2 AS s	ON	s.service_pk = si.service_fk
WHERE	(si.cmd_paths::text like '%log4j%')
```

パスに`log4j`が含まれているサービスインスタンスと、そのサービスが入っているデバイスを出力することができました。

##まとめ
①の使用中のソフトウェア単位で脆弱性を探し出す場合には有効ですが、今回のように対象ソフトウェア群が広範囲にわたる場合にはあまり有効な手法とは言えません。
最終的には②のサービスインスタンス情報から探し出す方法で、より正確な対象機器一覧を抽出できました。

これはlog4jはあくまでライブラリのため、ソフトウェア名で検索をかけても直接は出てこないことの方が多いからです
サービスのパスであればlog4jを使用している全く異なる名前のソフトウェアでも検出ができます

今回のように影響範囲が膨大な脆弱性ですと、都度各機器にログインして確認を行うというのはかなり手間がかかることかと思われます。
こういうときこそCMDBツールの利点を生かしていきたいですね。
[findコマンド]複数の候補からいずれかの拡張子を持つファイルを検索する

拡張子jpeg、jpg、pngのファイルを検索する

```bash
find . -regextype posix-extended -regex ".*\.(jpeg|jpg|png)"
```

拡張子ごとにファイル数を表示する

```bash
find . -regextype posix-extended -regex ".*\.(jpeg|jpg|png)" | sed 's/.*\.//' | sort | uniq -c
```
2021年の振り返り、2022年への想い

このQiitaのブログは全く個人的な備忘録です。まあ、逆に言えば仕事関係は契約的に書けないわけですがね。本記事は2021年の個人的な感想になります。これ以上読むことはお勧めしません。


2021年はハッキリ言ってあまり大きな出会いはありませんでした。出会い系サイトの話ではなく、技術的な出会いの話です。過去の話で言えばHasskellとの出会いは大きなものでした。C言語やPrologで始まった私のプログラマ人生において、それは同窓会で初恋の人に出会ったような甘苦くもどこか爽やかな気分にしてくれるものでした。数学での挫折。Elixir/PhoenixやElmとの出会いも忘れられません。ディープラーニングの基礎も面白いものでした。

しかし、2021年はハッキリ言ってあまり大きな出会いはありませんでした。あえて言えば、小さなことですが、Pythonのasyncio、それを基礎とするFastAPIなどかな。それと最近の話ではVue3の Composition APIのコンポジション関数が面白いと思いました。オブジェクト指向の限界を関数型で乗り越える、なんてね。ReactよりVue3の方が良く思えてきました。

さて、本当に振り返ることはこれで終わり。本当に少ないです。2022年は新しいElixir/Phoenixに触りたいと思います。ストップしている確率・統計学の勉強も再開させます。あまり多くを求めません。あとは暖かい春が早く来ることを願うだけです。終わり。
GitHub flowについて

# GitHub flowとは？

- ブランチがベースとなった軽量な開発フローのこと
- 誰にとっても使いやすい

# GitHub flowの流れ

1. ブランチを作成する
1. 変更を加える
1. プルリクエストを作成する
1. レビューをしてもらう
1. プルリクエストをマージする
1. ブランチを消去する

## 1. ブランチを作成する

- 自分のリポジトリ上で作成する
- 短くて分かりやすいブランチ名が良い
- ブランチを作ることで
    - 他に影響を与えずに作業が出来る
    - 他者からレビューをもらえる

## 2. 変更を加える

- ブランチ
    - 安全に変更が出来る場
    - もしミスしてもやり直せる
        - リモートのブランチにマージするときは注意
    - 機能に応じて分離させると良い
- コミット
    - 変更を記録すること
    - 他者に分かりやすいコミットメッセージを残すと良い
    - それぞれのコミットは独立していると良い


## 3. プルリクエストを作成する

- 変更した点を他者からフィードバックをもらうため
    - マージする前にレビューを貰う必要あり
- 書き方は人やチームによる

## 4. レビューをしてもらう

- レビュアーは質問やコメント、提案などを残す
    - 特定の行にコメントすることも出来る
    - 画像やコードもOK

## 5. プルリクエストをマージする

- プルリクエストが承認された場合にマージする
    - コメントやコミットの履歴は保持される
- 誰が見ても分かりやすい内容にすると良い
- コンフリクトがある場合は、それを対処する

## 6. ブランチを消去する

- プルリクエストをマージした後にブランチを消去
    - そのブランチでの作業が完了したことを示す
    - 誤って古いブランチを使用することを防ぐ

# 参考資料

https://docs.github.com/en/get-started/quickstart/github-flow

# おわりに
流れ自体はかなりシンプルですが、ブランチの切り方やプルリクエストの内容などが難しいなと感じています。

ここまで読んでいただき、ありがとうございました。
何かありましたらコメントをお願いします。
CentOS7にPython3仮想環境(venv)をセットアップする

# ポイント

CentOS 7のPython 3.6において、ensurepipの問題で仮想環境(venv)がうまく入りませんでした。
仮想環境作成時に `--without-pip` オプションを付けてまず仮想環境を作成し、続いてpipをインストールすることで無事に作成できました。

## 通常の手順でやってみる(失敗する)

```bash
$ mkdir virtual
$ python3 -mvenv virtual
Error: Command '['/home/user/virtual/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1.
```

私の環境が悪いのか、ensurepip周りでエラーが発生して仮想環境が構築されません。
ensurepipだけを読み込んでみるとsetuptoolsがないぞと言われます。

```bash
$ /home/user/virtual/bin/python3 -Im ensurepip
Traceback (most recent call last):
  File "/usr/lib64/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib64/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/lib64/python3.6/ensurepip/__main__.py", line 5, in <module>
    sys.exit(ensurepip._main())
  File "/usr/lib64/python3.6/ensurepip/__init__.py", line 232, in _main
    default_pip=args.default_pip,
  File "/usr/lib64/python3.6/ensurepip/__init__.py", line 129, in _bootstrap
    shutil.copy(whl, tmpdir)
  File "/usr/lib64/python3.6/shutil.py", line 245, in copy
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib64/python3.6/shutil.py", line 120, in copyfile
    with open(src, 'rb') as fsrc:
FileNotFoundError: [Errno 2] No such file or directory: '/usr/lib64/python3.6/ensurepip/_bundled/setuptools-40.6.2-py2.py3-none-any.whl'
```

ensurepipに_bundled/setuptoolsのバージョンがハードコードされてるからそれを書き換えたらうまくいくよ！という[記事](https://stackoverflow.com/questions/59855477/venv-fails-in-centos-ensurepip-missing)があったのですが、そもそも `/usr/lib64/python3.6/ensurepip/_bundled` ディレクトリが存在していない…。

仕方がないので、 `pip` を入れずに構築し、その後で `pip` をインストールするという作戦を試みました。

## pipを後からインストール

```bash
$ mkdir virtual
$ python3 -mvenv virtual --without-pip
$ . virtual/bin/activate
(virtual)$ curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py
(virtual)$ python3 get-pip.py
```

これで無事にインストールできました。

# おまけ

普段からきっちりvenvしてなかった私が悪いのですが、.localにいろいろパッケージが入ってしまっていて、それが悪さをして困ったことが起こります。特にGoogle APIはなかなかのツワモノで、.localと干渉してインストールできない、できても動かない、という感じでハマります。

私の環境ではvenvをactivateしてもうまくいかないケース(もう環境がぐちゃぐちゃなのかも)がありましたが、その場合はactivateして、さらに仮想環境のpip3を指定することで無事に動きました。

```bash
$ . virtual/bin/activate
(virtual)$ ./virtual/bin/pip3 install google-api-python-client google-auth-oauthlib
```
Gitでfile permissionが差分として検出された時

<h1>はじめに</h1>
gitでfile permissionの変更が検出され、少し手を焼いたので調べたことと対処方法を載せておきます

OS：Windows-WSL(Ubuntsu 20.0.4)
VScode + forkでgit周りの作業をしています

<h1>結論</h1>
`git config --local core.fileMode false`
で解決します

<h1>症状</h1>
いつも通りmasterを`git pull`しようと思ったら、

```terminal
error: Your local changes to the following files would be overwritten by merge:
```
とエラーが出てきました。これは、
「localにあるファイルが変更されてて、mergeする時に上書きされてしまうで」
ということなので、`git stash`なり、`git add -> git commit`すればいいのですが、`git stash`も
`discard change`もできないので困りました。ファイルの差分をVScodeで見ても何も変更がありません。forkで確認すると、ありがたいことに以下の記述が。

```terminal
changed file mode 100755 → 100644
```
chmodコマンドを使用した覚えはないものの、file permission(読み込み限定とか、変更できるユーザーが限定されるとか、そういうやつ)が書き換わっていたようです。

<h1>解決策</h1>
少しググってみると、同じような症状に会った人の英語記事が出てきたので読みました。ざっくり要約すると
<h4>①Gitはファイルの所有者の変更のみ検出する
②ファイルをcheck out した時に、PC環境によってはfile permissionが変わり得る
③Gitの設定で検出をオフにできる
③repoのcontributerにWindowsユーザーがいるときはオフにしといた方がいい（おまけ）</h4>

ということでした。pullしたリポジトリは自分だけWindowsメインで開発しているので、それが原因でfile permissionが書き換わっていたようです。

ということで、ターミナルでlocalの設定を調べると

```terminal
$ git config --get --local  core.filemode
true
```
localではfile permissionの変更が検出されるようになっていました。これを変えてあげればいいので

```terminal
$ git config --local core.fileMode false
$ git config --get --local  core.filemode
false
```
これでいいですね。僕の環境では実行直後にlocal changesが綺麗になくなりました。

<h1>参考資料</h1>
https://medium.com/@tahteche/how-git-treats-changes-in-file-permissions-f71874ca239d
vue3 で laravelmix element plus をインストール

element plus をインスコ

```

npm install element-plus --save

```


webpackに追加

```webpack.mix.js

mix.webpackConfig(
{
    module:
    {
        rules:
        [
            {
                test: /\.mjs$/,
                resolve: { fullySpecified: false },
                include: /node_modules/,
                type: "javascript/auto"
            }
        ]
    },
}
);



```

絶対パスで読み込みましょう

```app.js

import ElementPlus from '/var/www/html/your/node_modules/element-plus/'
import '/var/www/html/your/node_modules/element-plus/dist/index.css'

app.use(ElementPlus)
app.mount('#app');

```

以上