OmniAICreator/Qiita-1.07M huggingface dataset:
【CocoaPods】「zsh: command not found pod」エラーに遭遇した際の解決策

## はじめに
この記事では、CocoaPodsをインストールした後に遭遇するかもしれない「command not found: pod」のエラーメッセージについて説明します。
この問題に直面した際の対処法を、経験から学んだ解決策を基に紹介します。なお、ここで紹介する方法はあくまで一例ですので、状況に応じて適宜参考にしてください。

また、特定のバージョンのCocoaPodsをインストールする方法についても触れます。

CocoaPodsについての詳細は、[公式](https://cocoapods.org/)を参照してください。


## 問題の発生
CocoaPodsをインストールしたにも関わらず、pod コマンドを実行しようとすると、以下のようなエラーメッセージが表示されることがあります。
```zsh
command not found pod
```

## 環境
- Apple M2 Pro
- macOS Sonoma 14.2.1

## 対処法
### 1. 現状の確認
```zsh
which gem
/usr/bin/gem

which ruby
/usr/bin/ruby
```

### 2. rbenvをインストール
rbenv を使用すると、プロジェクトごとに異なるRubyのバージョンを簡単に切り替えることができます。
Homebrewを使ってrbenv と ruby-build（Rubyのバージョンをインストールするためのrbenvのプラグイン）をインストールします。
```zsh
brew update
brew install rbenv ruby-build
```



インストールが完了したらrbenvで管理されているrubyを確認してみます。以下のように表示されれば一旦OKです。
```zsh
rbenv versions
* system
```

### 3. インストールできる Ruby のバージョン確認
次に、利用可能なRubyのバージョンを調べます。
```zsh
rbenv install -l
3.0.6
3.1.4
3.2.2
jruby-9.4.5.0
mruby-3.2.0
picoruby-3.0.0
truffleruby-23.1.2
truffleruby+graalvm-23.1.2
```

### 4. 使用するRubyバージョンを設定
適切なRubyのバージョンを選択した後、そのバージョンのRubyをrbenvを使ってインストールします。
以下のコマンドは、3.2.2バージョンのRubyをインストールする例です。
```zsh
rbenv install 3.2.2
```

Rubyのインストールが完了したら、rbenvで管理されているRubyのバージョンを確認して、正しくインストールされていることを確かめます。

```zsh
rbenv versions
* system
  3.2.2
```

### 5. 利用する Ruby のバージョン変更
インストールしたRubyのバージョンをデフォルトとして設定するには、rbenvを使用してグローバルなバージョンを変更します。
以下のコマンドは、全体のデフォルトRubyバージョンを3.2.2に設定する例です。
```zsh
rbenv global 3.2.2
```
この設定が反映されたかどうかを確認するために、再度`rbenv versions`を実行します。
3.2.2の前に`*`が表示されていれば、そのバージョンがグローバルに設定されていることを意味します。
```zsh
rbenv versions
  system
* 3.2.2 (set by /Users/****/.rbenv/version)
```

### 6. シェル設定の更新
システムが新しいRubyバージョンを認識できるように、シェルの設定ファイル.zshrcを編集します。以下のコマンドで.zshrcファイルを開きます。
```zsh
vim ~/.zshrc
```

ファイルを開いたら、rbenvを初期化する以下の行を追加または確認します。これは、~/.rbenvディレクトリが存在する場合にのみ、rbenvのパスを環境変数に追加し、rbenvを初期化するためのものです。


```zsh
[[ -d ~/.rbenv  ]] && \
  export PATH=${HOME}/.rbenv/bin:${PATH} && \
  eval "$(rbenv init -)"
```
これにより、~/.rbenvが存在しない場合のエラーを防ぎ、rbenvが正しく機能するようになります。

編集が完了したら、まず、`escキー`を押してインサートモードを終了します。
次に、`:wq`と入力してEnterキーを押します。これにより、変更が保存されVimエディタが閉じます。

### 7. 設定の反映
.zshrcの編集が完了したら、変更を適用するためにシェルを再起動するか、以下のコマンドで設定を読み込みます。
```zsh
source ~/.zshrc
```

### 8. 設定の最終確認
全ての設定が正しく行われたかを確認するために、rbenvが管理するrubyとgemが正しく参照されているかをチェックします。
```zsh
which ruby

/Users/****/.rbenv/shims/ruby
```

```zsh
which gem

/Users/****/.rbenv/shims/gem
```

### 9. CocoaPodsのインストール
最後に、Rubyのパッケージ管理システムであるgemを使用して、CocoaPodsをインストールします。ここでは、特定のバージョン1.13.0をインストールする例を示します。

```zsh
sudo gem install cocoapods -v 1.13.0
```

インストールが完了したら、以下のコマンドでCocoaPodsのバージョンを確認します。

```zsh
pod --version 
1.13.0
```
出力結果が1.13.0と表示されれば、CocoaPodsが正しくインストールされています。

## 参考

https://nishinatoshiharu.com/manage-ruby-with-rbenv/
Swift Style Guide リンク集

# Swift Style Guide リンク集

## ~~いつもの~~ Swift.org

* [API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/)

## Swift Style Guide

* [Google](https://google.github.io/swift/)
* [Airbnb](https://github.com/airbnb/swift)
* [Kodeco](https://github.com/kodecocodes/swift-style-guide)
* [LinkedIn](https://github.com/linkedin/swift-style-guide)
* [Eureka](https://github.com/eure/swift-style-guide)

<details><summary>古い・メンテされてなさそう</summary><div>

* [GitHub](https://github.com/github/swift-style-guide)
* [Recruit Lifestyle](https://github.com/recruit-lifestyle/swift-style-guide)
* [Wantedly](https://qiita.com/susieyy/items/f71435cc962e70d81b37)
* [スマートテック・ベンチャーズ](https://github.com/SmartTechVentures/swift-style-guide)

</div></details>

## あとがき

現在担当しているプロジェクトでSwiftのコーディング規約を更新したく、調べていたのでその参考資料をまとめました。
古くなりメンテされてないGuideが散見され悲しい気持ちになりました。
天下のGoogle様がGuideを出してくれてるので長いものに巻かれたい人は嬉しいかも。
GitHubのGuideは何故更新されない（アーカイブされた）のでしょう、手を加える必要がないくらい完璧なものになったのか、別の優れたGuideが出来たのでしょうか。

他に良さげな規約、情報がありましたらお知らせください。

### 参考

https://qiita.com/KentaKudo/items/b48762a0d6adfbf62579
Djangoの学習③ view、templatesの作成

## この記事は個人で振り返る用の記事になります。

- djangoでページを作る際の手順

 ![スクリーンショット 2024-02-21 18.10.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724774/e80ddde0-b4e8-2035-bf14-7b583fa55ce5.png)

① viewの作成

```python:views.py
from django.shortcuts import render
# ListViweをimport（CRUD機能は大体これからimportする）
from django.views.generic import ListView
# モデルも使用できるようにしておく
from blog.models import Post

class PostListView(ListView):
  # モデルの継承
  model = Post
  # HTMLを指定
  template_name = 'blog/post_list.html'
```

②URLの設定

```python:config/urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 「''」トップページにアクセスがあったら、
    # 「'blog.urls'」でblog（アプリ）配下のurlsに飛ばしてあげる
    path('', include('blog.urls'))
]
```

- アプリ配下でurls.pyのファイルを構築する

```python:アプリ/urls.py
from django.contrib import admin
from django.urls import path
from blog.views import PostListView

urlpatterns = [
    path('', PostListView.as_view(), name='post-list')
]

```

③テンプレートhtmlの作成
アプリ/templates/アプリ名/html
この階層でテンプレートを作成する。

---

### オブジェクトの取り出し方などまとめ


```python
# よく使用するのはfor文
{% for 変数名　in object_list %}
{{ 変数名.オブジェクトの中身 }}
{% endfor %}

# 変数名.オブジェクトの中身 | truncatechars:30　で文字数制限も可能
```
node.jsのimport文でつまずいた話

# node.jsのimport文でつまずいた話
vue.jsの入門中にimport文でつまずいたので備忘としてメモる。

## エラーコード
```getting_started.js
import { creatApp } from "https://unpkg.com/vue@3/dist/vue.global.js"
import App from './App.vue'
const app = creatApp({
    App
})
```
出たエラー↓
```
(node:23304) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
c:\Users\shotaro\Documents\作業場.oridinal\0001_program\js\projects\vue_getting_started\getting_started.js:1
import { creatApp } from 'vue'
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.11.1
```
## 解決法
エラーが起きたファイルの拡張子を.mjsにする。
```getting_started.mjs
import { creatApp } from "https://unpkg.com/vue@3/dist/vue.global.js"
import App from './App.vue'
const app = creatApp({
    App
})
```
コードの内容は一切変わっていないが、拡張子が.mjsになったので、エラーが出なくなる。
API gateway (Cognito Authorizer) を使ったM2M認証

# 実現したいこと

* API gatewayでユーザーログイン認証ではなく、マシン間 (machine to machine：M2M) 認証を行いたい。
* 接続元クライアントごとに利用できるAPIを制御したい

### APIキーと使用量プランは認証には使えない
API Gatewayには、APIキーと使用量プランという機能があり、APIキーをもつクライアントのみがアクセス可能なAPIを作成でき、APIリクエストのスロットリングができます。
しかし、公式docにもある通り認証認可には別の方法を考える必要があります。
> API キーを、API へのアクセスを制御するための認証または承認に使用しないでください。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-api-usage-plans.html#apigateway-usage-plans-best-practices

### Amazon Cognito の　Client Credentials Grant　を利用する方法
公式doc上にて、API gatewayでのアクセス制御方法として、IAMロールとポリシー、Lambda オーソライザー、Amazon Cognito ユーザープールなどが記載されています。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-control-access-to-api.html

Amazon Cognito ユーザープールというとユーザーログイン認証に利用するイメージが強いですが、Cognitoを認可サーバとして利用して、M2M認証を行う（OAuth2.0のClient Credentials Grantを利用）方法があります。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/federation-endpoints-oauth-grants.html

# 仕組み
Amazon Cognito ユーザープール (OAuth 2.0 Client credentials grant) と Amazon API Gateway (Cognito Authorizer) を利用して実装するサンプルコードが aws-samples で公開されています。

https://github.com/aws-samples/amazon-cognito-and-api-gateway-based-machine-to-machine-authorization-using-aws-cdk/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644638/bccb0b5a-5b29-5817-f866-44a449c97f91.png)

1. クライアントID、クライアントシークレットのペアを用いて、トークンを発行
2. APIアクセス with トークン
3. Cognito Authorizer　でトークンを検証
4. Lambda　funcrion へのリクエストを許可


図中に記載の Custom Scopes を利用して、Cognito に登録したアプリケーションクライアント（接続元のシステムごと）にAPIのリソース・パスごとに認可の制御ができます。

### 実現イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644638/b41e5150-ecc0-383c-de67-9de51ec389c7.png)

* Amazon Cognitoのリソースサーバにてカスタムスコープを発行し、アプリケーションクライアントに紐づけることができる
* アプリケーションクライアントは紐づけられたカスタムスコープにのみアクセスが許可される
* Amazon API gateway の Cognito オーソライザーでリソース/パスごとに許可するスコープを指定できる
    * 参考）　[OAuth 2.0 スコープとリソースサーバーでの API 認可](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html)


# 参考
Client Credentials Grant を利用したやってみた記事がありましたので、参考として貼っておきます。

https://qiita.com/sugimount-a/items/938bcb4420a118fab1aa

https://qiita.com/poruruba/items/3681f1e1f8e72fcaadfa

https://dev.classmethod.jp/articles/cognito-m2m-auth-with-cdk/
Route53 DNSクエリログに表示される値について

## 概要

下記記事でRoute53にてDNSクエリログを出力できるように設定した。ログ出力内容について触れていなかったのでこちらの記事で別途まとめてみる。

https://qiita.com/miriwo/items/a45c924143ed057c0c88

## 内容

下記のドキュメントに記載されている内容をまとめてみる。

https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/query-logs.html#query-logs-format

説明には下記のログを使用する。このログで出力されている内容がそれぞれどのようなものであるのかを確認する。

```
1.0 2017-12-13T08:16:02.130Z Z123412341234 example.com A NOERROR UDP Region 192.168.1.1 -
```

| ログの内容 | 項目名 | 備考 |
| --- | --- | --- |
| 1.0 | ログ形式バージョン |  |
| 2017-12-13T08:16:02.130Z | クエリのタイムスタンプ | UTC |
| Z123412341234 | ホストゾーンID |  |
| example.com | クエリ名 | 名前解決を試みたドメイン |
| A | クエリタイプ | クエリで指定されたDNSレコードのタイプ |
| NOERROR | レスポンスコード | クエリ応答のステータスコード<br>NOERRORなのでエラーはなかったということ |
| UDP | レイヤー 4 プロトコル | クエリに使用されたプロトコル<br>TCP or UDP |
| Region | クエリに応答したRoute53のエッジロケーション | 3文字のコードと任意の数字で構成される<br>3文字のコードはエッジロケーション近くにある空港の国際航空運送協会が定めた空港コード（例えば成田空港なら`NRT`） |
| 192.168.1.1 | DNSリゾルバーIPアドレス | 名前解決を行ったDNSサーバーのアドレス |
| - | EDNS クライアントサブネット | DNSリゾルバーがリクエストを発信したクライアントのIPアドレスを取得できている場合　に限って表示<br>今回はハイフンなので取得できていない |
オーバーロード

オーバーロードはコンストラクタやメソッドを同じ名前で多重定義する事です。

ただし、オーバーロードには条件があります。
その条件とは引数の型、数、それらの順番のいずれかが異なっている事です。
逆に、全く同じ引数を用いたオーバーロードは出来ないとも言えます。
Astroの開発環境で下部のツールバー(devToolbar)を消す方法

Astroの開発環境では画面の下部にツールバー(devToolbar)が表示されます。

![astoro toolbar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559347/4486c604-1874-ca2c-a5bd-2b3a3b5d9d9c.png)

とても便利な機能ですが、消したい場面もあるかと思います。
本記事ではツールバーを消す方法について解説していきます。

https://astro.build/

## Astroのツールバー(devToolbar)を削除する方法
ターミナルで下記のコマンドを実行します。
```
astro preferences disable devToolbar
```

```
// npm
npm run astro preferences disable devToolbar
```

```
// bun
bun astro preferences disable devToolbar
```

```
// pnpm
pnpm astro preferences reset devToolbar   
```

## ツールバー(devToolbar)の設定を戻す方法
ツールバーを再度表示する場合は下記のコマンドを実行します。

```
astro preferences reset devToolbar
```
```
// npm
npm run astro preferences reset devToolbar
```

```
// bun
bun astro preferences reset devToolbar
```

```
// pnpm
pnpm astro preferences reset devToolbar   
```

## 参考

https://docs.astro.build/en/reference/cli-reference/#astro-preferences
firebaseで "CAPTCHA_CHECK_FAILED : Hostname match not found"が出たとき

# 背景
firebase authenticationでSMS認証をしたい。
SMSメールを送る際にreCAPTCHAが必要

# CAPTCHA_CHECK_FAILED : Hostname match not foundが出た
SMSメールを送信しようとすると以下のエラーが出た。
![スクリーンショット 2024-02-21 18.01.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689380/317a0848-6712-504a-6a6f-fe1012ff1387.png)


# 解決策
reCAPTCHAを使用するにはアプリをホストしているドメインを許可する必要がある。
承認済みドメインを登録する方法
#### firebaseコンソールの場合
Authentication > settings > 承認済みドメイン
「ドメインの追加」からドメインを追加して保存する。

#### Identity Platformの場合
設定 > セキュリティ 
「ドメインの追加」からドメインを追加して保存する。


# 終わりに
reCAPTCHAは難しい。。
[Zig] ダックタイピングのスゝメ

## はじめに

`zig`は`OOP`言語ではないため継承のような機構はなく、異なる2つの型を同一視する(`is-a`構造にする)ことはできません。

もしどうしても同一視したいシチュエーションに遭遇した場合は、動的束縛による`trait object`[^1] (`Rust`言語で見かけるやつ)を自前で実装する必要があります。

[^1]: どこかでallocator patterみたいな名称を見かけた（実際allocatorの実装で使用されている）が出所が見つからなかったため、trait objectという名前にしておいた。

例えば以下のように

```zig
pub const TraitObject = struct {
    const Self = @This();
    
    ptr: *anyopaque, // 同一視したい構造へのポインタ
    vtbl: struct {
        foo: *const fn (ctx: *anyopaque, p1: i32, p2: p32) bool,
        // 複数のメソッドをオーバーライドさせたい場合はその分だけ記述
        ....
    },

    pub fn foo(self: Self, p1: i32, p2: i32) bool {            
        return self.vtbl.foo(self.ptr, p1, p2); 
    }
};
```

というのを用意しておいた上で、

同一視したい型は、例えば以下のように記述します。

```zig
const SomeObject = struct {
    const Self = @This();

    pub fn (allocator: std.mem.Allocator) *Self {
        const self = try allocator.create(Self);
        self.* = .{
            // ...
        };
        return self;
    }

    pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
        allocator.destroy(self);
    }
    
    pub fn dispatchFoo(ptr: *anyopaque, p1: i32, p2:i32) bool {
       const self: *Self = @ptrCast(@alignCast(ptr);
       return self.foo(p1, p2);
    }

    pub fn foo(self: Self, p1: i32, p2:i32) bool {
       // ...
    }
}

// 使う側
pub fn main() !void {
    const allocator = std.mem.page_allocator;
    
    const o1 = try SomeObject.init(allocator);
    defer o1.deinit(allocator);
    
    const to1 = TraitObject{ .ptr = o1, .vtbl = .{ .foo = SomeObject.dispatchFoo } };

    const v1 = 10;
    const v2 = 20;
    
    std.debug.print("{}\n", .{to1.foo(v1, v2)});
}
```

上記例を見てもわかるように、結構めんどいです。
`foo`関数を呼びたいだけなのに、`anyopaque`から元の方に戻すキャストを挟むための関数が挟まっているあたりが特に [^2]。

[^2]: 必須ではないが、一枚挟んでおいた方がコードの見通しがちょっとだけ良くなる程度の話

異なる型を同一視したい理由は、条件により振る舞いを分岐したいからと思われます。
もし利用する型のライフサイクルがローカルスコープで完結する場合、`trait object`を用意しなくても、コンパイル時実行の機構によるダックタイピングを用いることでもう少し楽ができます。

## ダックタイピング

まずは記述例から。

```zig
// 呼び出し元で以下のような関数を提供する
fn invokeAction(comptime SomeType: type, allocator: std.mem.Allocator, p1: i32, p2: i32) !void {
    const obj = try SomeType.init(allocator);
    defer obj.deinit(allocator);
    std.debug.print("{}\n", .{obj.foo(p1, p2)});
}

// 使う側
pub fn main() void {
    const allocator = std.mem.page_allocator;
    
    const v1 = 10;
    const v2 = 20;

    try invokeAction(SomeObject, allocator, v1, v2);
}
```

`invokeAction`では`comptime`引数として、同一視したい型を受け取り、
その型の`init`、`deinit`、および`foo`を呼び出している。

このように記述すると、`zig`コンパイラは、暗黙に渡された型のメンバ制約をチェックし、そのメンバの存在を要請します [^3]（なければコンパイルエラー）。

[^3]: `F#`を知っていれば、静的に解決される型パラメーター(`SRTP`)を思い浮かべていただけると分かり良いかも

具体的には、以下のチェック。

* `SomeType`は`Allocator`を受け取り`SomeType`のポインタと`Error union`を返す`init`関数が提供されていること
* `SomeType`は`SomeType`型のポインタと`Allocator`を受け取り`void`を返す`deinit`関数が提供されていること
* `SomeType`は`SomeType`型のポインタと2つの`i32`の値を受け取り`bool`を返す`foo`関数が提供されていること

このように同一視したい型のライフサイクルがローカルスコープに閉じている場合、より簡易な記述を行うことができる。

上記例では、メンバ関数に対する制約を記述したが、同一のメンバフィールドを持つのであれば、フィールドもダックタイピングできる。

## trait objectはいらない子？

あくまでダックタイピングによる代用は同一視したい型がローカルスコープに閉じている場合にのみ行えることであり、フィールドやコレクションに保持するような、よりライフサイクルが長くなる場合や別の関数に渡す場合は、依然`trait object`が必要となります。

## まとめ

`comptime`つおい
LinuC Lv1 合格体験記

# LinuC Lv1（101試験、102試験）に合格した
先日、101試験と102試験に合格しLinuC Lv1の認定を取得することが出来た。
継続力があまりないので、合格までの学習方法をメモ代わりに残そうと思う。

# 受験目的
業務でサーバを操作することがあり、知識が足りていないと感じた。
サーバの操作や管理についての知識を問われるLinuCは不足している部分を学ぶことが出来ると考えたため、受験することにした。

# スコア
## 点数
合格ラインの480点に対してそれぞれ560点と586点とそこそこのスコア
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2130318/df50f9cb-e3e8-1664-8835-88ed00ae6be6.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2130318/7024b848-6b6d-11ad-b57a-4f99045e9a77.png)

## 正解率
セクションごとの正解率は101試験の「リポジトリとパッケージ管理」と102試験の「重要なシステムサービス」がそれぞれ37%、55%と低かった。
101試験
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2130318/adb3de73-19b7-0690-37b7-1e8671982307.png)
102試験
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2130318/628f92c5-a7fb-2721-2208-3b3324dbeab1.png)

# 学習方法
[LinuCレベル1 バージョン10.0 合格教本](https://amzn.asia/d/gqKJPZy)とUdemyの講座、[Ping-t](https://mondai.ping-t.com/)、[ウズカレテスト](https://test.uzuz-college.jp/home)の4つを使用。
最初にテキストを読み、問題を解く。
正答率の低い分野をUdemyの講座で復習、問題を解くことをひたすら繰り返した。

## 期間
受験を決めたのは昨年の11月頃だが、学習したのは1月末頃からだったので3~4週間程度。
1月28日にそれぞれ1回受験したものの、ほとんど学習できなかったためどちらも不合格だった。
そのあとは101と102をそれぞれ1週間ずつ学習した。

# 感想
## 学習内容について
想像よりも役に立つ知識を身に着けることができた。
まだまだ覚えきれていないところもあるので継続していく必要があると思う。
Lv2の合格を目指すかはわからないが、必要そうであれば学習しても良いと思った。

## 学習方法について
学習方法は悪くないものだった。
しっかり学習を行っていれば1回目で合格できたと思う。
計画を立てて学習を継続できるように習慣化していきたい。
ドラクエみたいなドットフォントをTextMeshProで作成（日本語も対応できるよう）※メモ

とりあえず忘れないうちに殴り書き

Unity2023.2.8.f1


使用フォントはこちら（ありがたし、使用方法には注意いたします。）
https://free-fonts.jp/category/dot/
↓
https://free-fonts.jp/nosutaru-dot/
↓
https://logotype.jp/nosutaru-dot.html

▼Window-->TextMeshPro-->Font Asset Creatorをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17904/5985f277-ea9c-eb09-c1b1-f19c1517e0db.png)

▼初回起動時、インポートするか聞いてくるのでインポートすると最低限フォルダ構成を作ってくれます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17904/e6184aad-d351-83f2-4e22-86a2c000ba63.png)

▼Fontsフォルダの下にダウンロードしてきたフォントをいれる***（ここ重要）***
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17904/dc6014a0-f3ed-88b6-40d9-9e29f1591a76.png)

▼下記画像のように設定をする
　●Sampling Point Sizeはフォント紹介のページに12ドットとあったので12でも良いかも
　　念のためつぶれないように14に設定
　●Packing Method
 　Fastでも大丈夫だと思うが数が多いので念のためOptimum
　●Atlas Resolution
   最大の(8192*8192)にしたがおそらくそんな巨大なサイズはいらない使用する文字数に合わせれば削減可能
　●Character Set
　　日本語の文字コードに対応させたいのでCustom Rangeに設定（要注意）
  　実際使う時は表示する文字数に対応する文字コードだけにした方が良い
　　Atlasに作成にとてつもない時間がかかるため
  ※日本語の文字コードはここら辺を参考にした方が良さそう
  　https://stackoverflow.com/questions/19899554/unicode-range-for-japanese
　●Render Mode
 　RASTER（この辺りは調べる必要がありそう、ドットフォントだとRASTERに設定する記事をちょこちょこみかける）
  
![フォントの設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17904/9831c859-2aeb-18bf-8f4c-479db7b457e1.png)

▼Generate Font Atlas をクリックしてAtlasが作成されるのを待つ
　Atlasのサイズが大きかったり、文字コードの数が多いと何時間もかかる

▼作成完了したものを使ってみたところひと通り表示された
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17904/6bd99eea-fd4b-17b3-6044-27a365ab777e.png)
Visual BasicでGitHub Copilot

C#の例は多いですが、Visual Basicではどうかと試してみました。
結論から言うと、Visual Basicでも全く問題なく利用できました。
適当にWindowsFormアプリを作成します。ここではTCP/IPソケット通信のアプリを作ってみました。Visual Studio 2022を使用しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/099cde28-0e9a-93e4-bdce-506f959c115f.png)

コメントを入力してみます。入力している途中から先読みしてコメントを保管してくれます。しかし、残念ながらここでは目的のコメントにはなっていません。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/60889a80-7733-7d44-4c8a-304c311371c5.png)

コメントを最後まで入力しEnterキー押すと、コードを提案してくれます。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/bcabf0bd-6904-867f-fb17-065456696364.png)

Tabキー押して採用します。次の処理のコードを書くためのコメントを入力しようと「’」を入力した時点で、「NetworkStreamを取得する」というコメントが提案されました。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/c6bd1c2e-a4c8-0e36-6f51-d3885d749061.png)

このコメントを採用してEnterキーを押すと、それに応じたコードがまた提案されます。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/ded8647f-f21f-e67b-7a7e-13445b8538f1.png)

こういった要領でコメントすらほとんど書くことなく、提案されたコメントとコードを繰り返し採用していくと、以下のような一連の処理のコードができあがりました。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94958/8e6679a6-c697-8114-95cd-4aa7e334de55.png)

もちろん、このまま使うことはできず細部の調整は必要ですが、何も調べずここまでコードができあがってくることに感動しました。前後のコードも考慮されて、先読みしてコードが補完されてくるため、既存コードの改修にも効果がありそうです。
HANA DB SYSTEMユーザのパスワード初期化

### 前置き
先日HANA DBインストール後にSYSTEMユーザのパスワードを誤って設定していたことがありました。
その設定したパスワードも不明な状況(一体どうやって2度入力のトラップを潜り抜けたのか、、、)で絶体絶命の中、調べるときちんとパスワード初期化の方法が用意されていたので、やってみました。

### パスワード初期化手順
#### ①HANAをインストールしているDBサーバにログイン
#### ②ユーザスイッチ
インストール時に作成される"sid"admユーザーへスイッチします。
※"sid"admでログインでも可。
```
su - godadm
```
#### ③HANA DB停止
HANAデータベースインスタンスを停止します。
※コマンド内のディレクトリとインスタンス番号は適宜、利用している環境のものに置き換えてください。
```
/usr/sap/GOD/HDB10/exe/sapcontrol -nr 10 -function StopSystem HDB
```
#### ④SYSTEMデータベースのnameserverを起動
SYSTEMデータベースのnameserverを起動するため、以下を実行する。

```
/usr/sap/GOD/HDB10/hdbenv.sh
/usr/sap/GOD/HDB10/exe/hdbnameserver -resetUserSystem
```


#### ⑤初期化後パスワードの入力
初期化後パスワードの入力待ちのプロンプトが表示されたら、入力します。
入力が完了するとSYSTEMユーザのパスワードが初期化され、nameserverが自動で停止します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582716/34945ff1-414c-8ba1-20bd-61615dca7430.png)
#### ⑥HANA DB起動
HANAデータベースインスタンスを起動します。
```
/usr/sap/GOD/HDB10/exe/sapcontrol -nr 10 -function StartSystem HDB
```
#### ⑦確認
お好みの方法でログイン確認を実施します。
自分は、HANA Studioで想定通りのパスワードでSYSTEMユーザとしてHANAにログインできるかを確認しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582716/3511ebc7-a215-c608-4b3c-d1d255b047f6.png)

### 締め
今回は以上です。
よろしければ「いいね」と「ストック」をよろしくお願いいたします。
ではでは。
anyenvでtscコマンドが効かない

## 経緯

typescriptを使ってみようとして、インストール

```
npm install -g typescript
```

インストールはおわったっぽい
```
$HOME/.anyenv/envs/nodenv/versions/xxx/bin/tsc -> $HOME/.anyenv/envs/nodenv/versions/xxx/lib/node_modules/typescript/bin/tsc
$HOME/.anyenv/envs/nodenv/versions/xxx/bin/tsserver -> $HOME/.anyenv/envs/nodenv/versions/xxx/lib/node_modules/typescript/bin/tsserver
+ typescript@5.3.3
added 1 package from 1 contributor in 0.797s
```

tscコマンドをたたくと
```
tsc xxx.ts
zsh: command not found: tsc
```

コマンドがみつからない

## 原因

パスの問題だけど下記コマンドで解決

```
nodenv rehash
```

## 参考ページ

[Anyenv(nodeenv)でTypeScriptを動かす](https://tech.noricgeographic.com/anyenvnodeenv%E3%81%A7typescript%E3%82%92%E5%8B%95%E3%81%8B%E3%81%99/)
Webhookを自分で実装してみる

# 趣旨 
サーバーを複数運用していたらサーバー間でのWebhook通信も必要になる瞬間が来るんじゃないかと思って先に調べておきました。
Webhook自体が単なる HTTP リクエストなので割と簡単にできるみたいだったので、共有しておきます。

# 準備

外部サーバーのエンドポイントを準備する: Webhook データを受け取るためのエンドポイントを外部サーバー上に準備します。このエンドポイントは、HTTP リクエストを受け取り、適切に処理するように構成されている必要があります。

サーバー上でスクリプトを作成する: 外部のエンドポイントに HTTP リクエストを送信する Python スクリプトを作成します。このスクリプトでは、requests ライブラリなどを使用して HTTP リクエストを行うことができます。

スケジュールされたタスクを設定する: 必要に応じて、サーバーのスケジュールされたタスク機能を使用して、スクリプトが定期的に実行されるように設定します。これにより、定期的にデータを送信する Webhook を実装できます。

# サンプルコード
```python
import requests

url = 'http://example.com/webhook-endpoint'

data = {'key': 'value'}

response = requests.post(url, json=data)

print(response.text)
```
sql select with array

```python
def index_indicator_summary(market):
    code_dict = {
        'US10YT': 'US10YTreasuryNote'}
    code_str = ', '.join([f"'{code}'" for code in code_dict.keys()])
    sql_str = f"""
        SELECT Z.code, Z.ind
               (SELECT JSON_AGG(JSON_BUILD_OBJECT('da', da::text, 'beta_equity', beta_equity))
                FROM index
                WHERE code = Z.code
                AND da BETWEEN NOW() - INTERVAL '90 days' AND NOW()) AS nav
        FROM index_i AS Z
        LEFT JOIN index_indicators_ext AS E ON Z.code = E.code AND Z.da = E.da
        WHERE Z.strategy = 'Backtest'
        AND Z.da = (SELECT MAX(da) FROM index_i WHERE code = 'US10YT')
        AND Z.code in ({code_str})
        ORDER BY Z.da DESC;
    """
    combined_data = dbfetchallDict(market, sql_str, "db")
    for d in combined_data:
        d['cname'] = code_dict[d['code']]
    return combined_data
```
G検定の試験日はいつ？勉強に必要な期間とスケジュール

初心者の人や初めてG検定を検討する人は、G検定の試験日やどう勉強したら良いか分からないでしょう。
そこで今回は今年のG検定の試験日や、試験日に合わせた勉強スケジュールを作ってみました。
G検定を受けたいと思っている人はぜひ参考にしてください。

<h2>2024年のG検定の試験日</h2>
2024年のG検定は、以下のスケジュールで6回開催されます。

* 1月13日
* 3月8日と9日（2日間開催）
* 5月11日
* 7月5日と6日（2日間開催）
* 9月7日
* 11月8日と9日（2日間開催）

G検定の受験申込受付期間は各試験日の約2ヶ月前から始まります。
E資格と比べて開催期間が多いのが特徴です。

<h3>2025年のG検定の試験日</h3>
2025年のG検定のスケジュールは2024年の情報発表時点ではまだ決定しておらず、詳細は2024年中に改めて発表される予定です。

<h2>G検定は試験日からどれくらい前に勉強を開始したら良い？</h2>

![G検定は試験日からどれくらい前に勉強を開始したら良い？](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3686535/abaef9ab-5e0a-0545-5244-fafc72bd9b08.jpeg)

G検定の勉強を始める最適な時期は、それぞれAIへの基礎知識や学習速度によって異なりますが、一般的には試験日の2〜3ヶ月前から勉強を開始するのがおすすめです。
しかし、AIやディープラーニングの分野が初めての人や初心者の方は、もう少し長い準備期間を確保しておくと安心でしょう。

<h2>G検定の試験日までのおすすめ勉強スケジュール</h2>
ここでは2024年11月8日のG検定を受けると仮定して、試験日までのおすすめ勉強スケジュールを作ってみました。

* 2024/8/1〜8/7：AIとディープラーニングの基礎を学んで自分の苦手を理解する
* 2024/8/10〜8/20：[G検定のセミナー](https://ai-kenkyujo.com/ai-g-kentei/)を受ける
* 2024/8/21〜8/30：ディープラーニングのフレームワークとツール学習をマスターする
* 2024/9/2〜9/20：セミナーでもらえるG検定試験の例題や自作のテストを作成して解く
* 2024/10/2〜10/16：最終的なG検定の復習
* 2024/10/16〜10/30：G検定試験前の準備・苦手の克服
* 2024/11/3〜11/7：最終的な自己テストのチェック

このスケジュールに沿ってG検定の勉強を行えば、2024年11月8日のG検定に向けて効率的かつ体系的に学習を進めることができます。
また、期間が空いている空き時間にはゆっくり休んだり、リフレッシュする期間にしてください。

<h3>G検定を独学する場合</h3>
上記はG検定をセミナーで学習する場合のスケジュールですが、独学で勉強しようとしている人は上記の他に

* ニューラルネットワークの深堀り
* ディープラーニングの応用分野学習
* 全トピックの復習と複雑なプロジェクト練習

このような学習も必要です。G検定はAIやディープラーニングの基本的な内容が多いですが、初心者でも必ず合格できるような資格ではないので、できればセミナーで学んでおくと良いでしょう。

[おすすめG検定セミナー](https://ai-kenkyujo.com/ai-g-kentei/)

<h2>G検定の試験日に向けた勉強のコツ</h2>

![G検定の試験日に向けた勉強のコツ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3686535/a9361dea-3dc3-5378-eda0-33bb6746edf9.jpeg)


G検定の試験日に向けてただ2〜3ヶ月前から勉強しようと思っても、なかなか勉強が進まなかったり挫折してしまう人はたくさんいます。
そこで下記コツを理解しながらG検定の試験に向けて勉強を進めていきましょう。

<h3>まず自分に必要な勉強ジャンルを考える</h3>
まず最初に、G検定に向けて自分には何が足りないのか、何を勉強するべきなのかきちんと確認しておきましょう。
G検定の試験範囲はセミナーで教えてもらえるので、その中で何が理解できないかしっかりメモしておくと良いです。

<h3>分からない点は最後に講師に質問する</h3>
G検定のセミナーは大人数で受講するのでなかなか質問できない時もあるでしょう。
その場合に備えてメモをとっておき、セミナー終了後に講師に質問すると良いです。
短時間なら1:1で話すことができるので、最後にしっかり分からない点を理解することができるでしょう。

<h3>復習をしっかり行おう</h3>
G検定のセミナーを受けた後は、しっかり理解できたと勘違いしてしまう人も多いです。
しかしG検定の試験日に近づくにつれてだんだん学習した内容を忘れてしまうので、何度も復習して覚えておくことも大事でしょう。
また、忘れてしまわないようにセミナー中やセミナー終了後すぐにノートにまとめておくのがとてもおすすめです。

<h2>G検定の試験日と試験日までのスケジュールについてまとめ</h2>
今回はG検定の今年の試験日と、試験日までにどうしたら良いかスケジューリングを考えてみました。
G検定はE資格より難易度が低いと言われていますが、合格率が100%あるわけではありません。
しっかり試験日までの計画をして、G検定を安心して受けれるように勉強を重ねていきましょう。
Webサイトを作ってみる「HTMLを書くぞ編」

# HTMLの中身を書く

HTMLを記述する際、メモ帳などを開き[名前を付けて保存]をします。
この際に＜ファイル名＞.htmlに保存することでwebサイトとして使うことができる。

## HTML文の基本形
基本の中身はこんな感じ

```html:index.html
<!DOCTYPE HTML>
<html>
    <head>
    </head>
    <body>
    </body>
</html>

```

### HTMLの基本ルール

・タグは<>でくくる
・タグの終わりは/を使う


### HTMLの解説

HTMLの中身は基本的に<>でくくる。

**!DOCTYPE HTML**:このドキュメントはHTMLだよと指定している。

**html** :ここから(html)ここまで(/html)がこのHTML文の中身になる。

**head** :Webサイトのベースの部分

**body** :webサイトの中身


## HTML文の基本形
このルールと中身を組むとこんな感じになる。

```html:index.html
<!DOCTYPE HTML>
<html>
    <head>
        <title>Webサイトタイトル</title>
    </head>
    <body>
        Hello HTML world!
    </body>
</html>

```
総当たりで全組み合わせのpairwise alignmentを行う

# はじめに
Pythonで<b>pairwise alignment(ペアワイズアラインメント)</b>を実装する方法はこちらに記しました。

https://qiita.com/kujira_0120/items/0f5bd413a40a419678c4


複数の配列があり、その中でどの配列同士の相同性が高いか検証したい。という目的のもと、**2つの配列間のアラインメントを総当たり形式で全組み合わせ**行う。その結果をヒートマップで可視化する。

出力例はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655949/4e505023-325c-dd98-341c-c3f1b0253d8e.png)

# 実装

### インポート
```python
from Bio import Align
from Bio import SeqIO
from Bio import SearchIO
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
```

### 関数の定義
アライメントの結果は`results（辞書）`および`df_results（データフレーム）`に格納する。
```python
def alignment (x, y, sequences, labels, results, df_result):

    aligner = Align.PairwiseAligner()

    # アラインメントパラメータを設定
    aligner.match_score = 2
    aligner.mismatch_score = -1
    aligner.mode = "global" #ローカルの場合は"local"

    seq_a = sequences[x]
    seq_b = sequences[y]
    
    alignments = aligner.align(seq_a, seq_b)
    alignment = alignments[0]
   

    length = alignment.shape[1]
    difference = np.array(alignment.aligned)[0][:, 1] - np.array(alignment.aligned)[0][:, 0]
    identity = np.sum(difference)
    ratio = identity / length*100

    dic_temp = {
        "a_label": labels[x],
        "b_label": labels[y],
        "score": alignments.score,
        "length": length,
        "identity": ratio
        }

    results.append(dic_temp)
    df_result.iloc[x,y] = ratio
    
    return results, df_result
```

### 配列とラベルの準備
アラインメントを行いたい<b>配列（塩基orアミノ酸どちらもOK）</b>をリスト型で`sequences`に入れる。
配列の<b>ラベル（遺伝子名など）</b>を`labels`に入れる。ここの順番が対応しているように。
```python
sequences = #ここに配列を入れる
labels = #ここにラベル名（遺伝子名など）を入れる
```

### 関数の実行
`for文`でループして全通りのアラインメントの実行と結果の記録を行う。
```python
num = len(sequences)
combinations = [(x, y) for x in range(num) for y in range(num) if y > x]

results = []
df_result = pd.DataFrame(columns=labels, index=labels)

for x, y in combinations:
    name_x = labels[x]
    name_y = labels[y]
           
    results, df_result = alignment(x, y, sequences, labels, results, df_result)

df_result.fillna('-')
df_result = df_result.astype(float)
```
### 結果の表示
```python
fig = plt.figure(figsize=(10,9))
ax = fig.add_subplot(1,1,1) 

sns.heatmap(df_result,
            cmap='Reds',
            annot=True, 
            #fmt="1.1f", #表示する桁数を指定する
            annot_kws={"fontsize":8}
           )

plt.show()
```
### 出力結果例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655949/9c754a5b-929f-6745-bd10-f818449e9550.png)
バッテリーエネルギー貯蔵システムのメーカー Web サイトを作成する

持続可能なエネルギーの解決策がますます受け入れられる現代において、Battery Energy Storage Systems（BESS）の役割はますます重要性を増しています。再生可能エネルギー革命の真っ只中に立ち、革新的で信頼性の高いエネルギー蓄積ソリューションの必要性がますます高まっています。この投稿では、Battery Energy Storage Systemメーカーのウェブサイトのビジョン、重要性、主要な側面、利点、エネルギーの景観への潜在的な影響に掘り下げます。[Webサイト](https://www.huntkeyenergystorage.com/)

はじめに

エネルギー生産、蓄積、消費のダイナミックな風景において、Battery Energy Storage Systemsはよりクリーンで効率的な未来に向けた移行において中心的な存在となっています。BESSメーカー向けの専用のウェブサイトは、単なるオンラインプレゼンス以上のものとなり、知識の普及、製品のショーケース、コラボレーションの促進のプラットフォームとなります。

ウェブサイトの目的

ウェブサイトの主な目的は、Battery Energy Storage Systemsに関する包括的な情報ハブとして機能することです。エンジニア、ビジネス、一般の人々に対して、エネルギー蓄積の重要性とメーカーが提供する先進的なソリューションについて教育することを目指しています。

ウェブサイトの主要な要素

技術情報:

さまざまなBESSテクノロジーの詳細な説明。
システムの複雑さに深く入り込むための技術仕様。
さまざまなシナリオでのBESSの成功した導入を示すケーススタディ。
製品の紹介:

メーカーのBattery Energy Storage Systemsの製品ラインを紹介するギャラリー。
各製品の説明で、主要な機能、効率、アプリケーションを強調。
特定の製品が固有のニーズに適しているかを探索するための対話型ツール。
教育リソース:

エネルギー蓄積の原則を説明する記事、ホワイトペーパー、ガイド。
より魅力的な学習体験のためのウェビナーとビデオコンテンツ。
Battery Energy Storage Systemsに関する一般的な質問に対処するFAQセクション。
ニュースとアップデート:

業界のニュース、技術の進歩、企業の最新情報を掲載した定期的に更新されるブログ。
ユーザーがエネルギー蓄積の最新トレンドとブレイクスルーについて常に情報を得るためのニュースレター購読。
コラボレーションと連絡先:

潜在的なクライアント、協力者、投資家のための情報。
製造業者との直接のコミュニケーションを確保するためのコンタクトフォーム。
業界内でのパートナーシップとコラボレーションの機会。
ウェブサイトの利点

グローバルなリーチ:

ウェブサイトは地理的な境界を超え、メーカーが世界的な観客にアプローチできるようにし、切り替え可能なエネルギーソリューションに関心を寄せるユーザーにリーチできます。
教育の影響:

教育のプラットフォームとして機能し、エネルギー蓄積の重要性と持続可能な開発におけるその役割についての認識を高めるのに寄与します。
市場のポジショニング:

プロフェッショナルによって設計されたウェブサイトはメーカーの信頼性を向上させ、Battery Energy Storage Systemsの分野での権威としての地位を築きます。
ビジネスの成長:

オンラインプレゼンスはリードジェネレーションとビジネスの成長を促進し、潜在的なクライアント、投資家、およびパートナーを引き寄せます。
エネルギー景観への潜在的な影響

Battery Energy Storage Systemメーカーのウェブサイトは、エネルギーの未来を形作るための変革的な変化を触媒として機能する可能性があります。知識ハブとして機能することで、より情報通のコミュニティを促進し、エネルギー転換を促進するコラボレーションとイノベーションを育むのに寄与します。

結論

総括すると、Battery Energy Storage Systemメーカーのためのウェブサイトの作成は、オンラインプレゼンスの枠を超え、エネルギーの未来を形作るための鍵となります。大衆に知識を提供することから、最先端の製品をショーケースするまで、このようなウェブサイトはポジティブな変化を促進するための触媒として機能し、メーカーを再生可能エネルギー革命の最前線に位置付けます。世界がクリーンエネルギーの時代を迎える中で、Battery Energy Storage Systemsとそれらを促進するプラットフォームが持つ役割は、確実に持続可能で堅牢なエネルギーの未来を形成する上で鍵となるでしょう。
TypeScriptで書いたLambdaを、node_modulesを含めてデプロイする

# したいこと
- TypeScriptで書いたLambdaをnode_modulesを含めてDeployしたい
- 長期使用する前提で、メンテナンスの手数やメンテ時のオペミスリスクを減らしておきたい
- CodeBuildを使用してCI/CDする前提


# 結論
- Build後 dist/内に node_modules を配置した上でzipにして、Deployする形式を採用

## 処理の流れ
### 事前準備
- Lambdaの容量制限を気にして node_modulesの容量を削減するため、処理に必要なモジュール以外は `npm install -D` で devDependencies として入れる
  - 処理に必要な内、例外的にaws-sdkは LambdaのNode.jsランタイム内に含まれているので devDependencies 側としてinstallする

### CodeBuildの処理
- `npm ci` で devDependencies込みでnode_modulesを入れる
- Testの実行
- TypeScriptをBuild
- （Build環境のtmp内の容量節約が必要な際は ここで一度 `rm -rf ./node_modules`）
- `cp package.json package-lock.json ./dist/{foo}` する
    -  （dist以下の階層は実態に合わせ適宜調整）
- `cd ./dist/{foo} && npm ci --omit=dev` で、dist内にdevDependenciesを除くnode_modulesを入れる
- `zip -r ${LAMBDA_NAME}.zip .` でZIP化
    -  （zipのファイル名とLambda名が異なると正しくDeployできないため注意）
- `aws lambda update-function-code --function-name ${LAMBDA_NAME} --zip-file fileb://${LAMBDA_NAME}.zip --publish` でLambdaに反映

## 経緯
- LambdaLayerでも実現できるが、先々メンテナンスの際にLambdaLayerの方もメンテの必要が出る
  - また、雑にやるとLambda側のバージョン指定とLambdaLayerのバージョン間で齟齬が発生するリスクがある
  - CodeBuildなどで LambdaとLambdaLayerを同時に更新するようにするなどの方法はあるが、手数的にはnode_modulesごと反映してしまうほうが楽だろうと判断


実際Lambda Layerのメンテナンスをどうしていることが多いのか気になりつつ 結論を出したのでメモ。
firebase functionsをfirebase deployする際にやるべきこと



以下の手順で、必要な権限をCloud Functionsのサービスアカウントに付与することができます。

1. Google Cloud Consoleにアクセス
[Google Cloud Console](https://console.cloud.google.com/)にアクセスし、対象のFirebaseプロジェクトを選択します。

2. IAM & 管理画面に移動
ナビゲーションメニューから「IAM & 管理」を選択し、「IAM」をクリックします。

3. Cloud Functionsのサービスアカウントを見つける
プロジェクトのIAMリストで、Cloud Functionsに関連付けられているサービスアカウントを探します。このアカウントは通常、@appspot.gserviceaccount.comのメールアドレスで終わるものです。

4. 必要なロールを追加
見つけたサービスアカウントの行にある「ペン」アイコン（編集ボタン）をクリックし、「ロールを追加」を選択します。検索ボックスに「Artifact Registry 読み取り」と入力し、結果からそれを選択して追加します。変更を保存します。

5. デプロイの再試行
必要な権限をサービスアカウントに付与した後、Firebase Cloud Functionsのデプロイを再度試行してください。
「sympyのPiecewise」を調べた。

# DOC

Represents an expression, condition pair.
https://docs.sympy.org/latest/modules/functions/elementary.html#piecewise

# sympyで

```python
from sympy import *
var('t',real=True)
y =Piecewise((t      ,t<  3),
             (3-(t-3),t<=12),
             (999,True     )
            )
print("# ",y)
print("# ",y.args[0])
print("# ",y.args[1])
print("# ",y.args[2])
print("# ",y.args   )
print("# ",len(y.args))
#  Piecewise((t, t < 3), (6 - t, t <= 12), (999, True))
#  (t, t < 3)
#  (6 - t, t <= 12)
#  (999, True)
#  ((t, t < 3), (6 - t, t <= 12), (999, True))
#  3
from sympy.plotting import plot
plot(0,3,y,(t,0,12),aspect_ratio=(1.0,1.0))
```

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109698/20c9a435-35b8-6486-f688-9908f78b981b.png)
IntelliJ IDEA で 複数のローカルブランチを まとめて削除する方法

1. IDE の Git タブ > Git ログの表示 を選択する
1. IDEのどこか(デフォルトなら下部)に、Git ログ画面が表示される
1. 左側にローカルブランチ（とリモートブランチ）の一覧が出る
1. Shift+クリックなどで複数選択をして、右クリック→削除 でOK

[感想]
これまで右下のブランチのところからポチポチやっていたわ...
GPT-4を使用できるAndroidアプリ

GPT-4は現在最も強力な大型モデルで、しかしOpenAIではPlus会員でなければGPT-4を使用することはできません。私たちは広告を通じて、誰でもGPT-4を使用できるように試みています。

アプリケーションは使いやすく、一つのダイアログボックスだけで、ネットワーク依存や任意の設定を必要とせず、開封すればすぐに使えます。

Google Playのアドレス：https://play.google.com/store/apps/details?id=me.app.free.gpt4
雀魂のスタンプをスクレイビングする

# はじめに

**雀魂のスタンプ**って汎用性高いですよね。
かわいい系シュール系なんでもござれで対局中以外にも使いたいなと思ってしまいます。

そこで、Pythonのスクレイビングを使って雀魂DBからスタンプ画像を収集しました。
なお、収集したスタンプは研究室slackチャンネルのカスタム絵文字にしれっと入れました（1500個ほど）

# 必要なもの
Pythonの実行環境
`bs4`などのライブラリは`pip install`で適宜インストールしてください

※スクレイビングについてはこちらの記事"[Pythonで手軽に始めるWebスクレイピング](https://qiita.com/Moh_no/items/a835f77b6b4e3972fbbe)"が分かりやすいです。

# サンプルコード
ChatGPTにやってもらいました。
- [雀魂DB](https://mahjongsoul.club/characters/emotes)のスタンプ一覧からページ番号を指定すると、そのページのスタンプ画像を1秒おきにダウンロードします。（負荷をかけないペース）
- サムネイルだと画質がよくないので、画像を開いた後のurlを引っ張っています。
- 現在14ページまでスタンプがあるので、まとめてダウンロードしたい方はコメントアウトしてあるfor文を回してください。


~~~python:bs4.py
# 1-14の数字nを入力すると，雀魂データベースのスタンプ一覧の
# nページにあるスタンプのpng画像を同じ階層フォルダにまとめてダウンロードします
# ダウンロードの間隔は1秒に設定しています．

import requests
from bs4 import BeautifulSoup
import os
import urllib.request
import time

N = int(input('page number: '))

# for N in range(1,15):
#     time.sleep(30.0)

# ウェブサイトのURL
if N==1:
    url = 'https://mahjongsoul.club/characters/emotes'
else:
    url = 'https://mahjongsoul.club/characters/emotes?sort_by=field_character_er_target_id&sort_order=ASC&page='+str(N-1)

# ウェブサイトからHTMLを取得
response = requests.get(url)
response.raise_for_status()  # ステータスコードが200でない場合は、エラーを発生させる

# BeautifulSoupオブジェクトを作成し、HTMLを解析
soup = BeautifulSoup(response.text, 'html.parser')

# divタグのclassがview-contentのものを探し、その中のimgタグを全て取得
images = soup.find('div', class_='view-content').find_all('img')

# urlをサムネから置き換える
image_links = []
for img in images:
    link = img['src']
    link = link.replace('/styles/thumbnail/public','') 
    link = link.split('?')[0]
    image_links.append(link)

# ダウンロードする画像を保存するフォルダを指定（存在しない場合は作成）
download_folder = 'downloaded_images'
if not os.path.exists(download_folder):
    os.makedirs(download_folder)


# 各画像URLから画像をダウンロードして保存
for link in image_links:
    # 画像データを取得
    file_name = 'jantama-' + link.split('/')[-1]
    file_path = os.path.join(download_folder, file_name)
    print(file_name, file_path)
    urllib.request.urlretrieve(link, file_path)
    time.sleep(1.0)
    
~~~
Kaggleはじめました

# Kaggleはじめました

## はじめに
春休み中に暇を持て余したため，情報工学知能情報分野専攻としていっちょやってやりますかといったノリでKaggleをはじめてみました．

備忘録として使用していきます．

## 環境構築
Kaggle公式がDockerImageを配布していたため，自分は使用しました．
別にKaggleNotebookでも問題ないです．DockerImageが思いのほか巨大だったので推奨しません．
``` 
docker pull gcr.io/kaggle-gpu-images/python:latest
```
## 工事中
Pendo PES Growth rateの計算

自分用メモシリーズ：

[PES Growth rate](https://support.pendo.io/hc/ja/articles/360054782691-%E3%83%97%E3%83%AD%E3%83%80%E3%82%AF%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B2%E3%83%BC%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E3%82%B9%E3%82%B3%E3%82%A2-PES#:~:text=%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82-,%E6%88%90%E9%95%B7,-%E6%88%90%E9%95%B7%E7%8E%87%E3%81%AF%E3%80%81%E7%89%B9%E5%88%A5) を求めるscript


```python
#QRIが中央値より小さい
def lessthan_med(QR_med, QR_min, QRI):
    return 50 - ((QR_med - QRI) / (QR_med - QR_min)) * 50

#QRIが中央値より大きい
def morethan_med(QR_med, QR_max, QRI):
    return 50 + ((QRI - QR_med) / (QR_max - QR_med)) * 50
QR_max = 5  # 最大値
QR_med = 1.5  # 中央値
QR_min = 0.5  # 最小値
QRI = 1.7 # 現在のQR: Dropped/(New + recovered)

if QRI > QR_med:
    GrowthRate = morethan_med(QR_med, QR_max, QRI)
else:
    GrowthRate = lessthan_med(QR_med, QR_min, QRI)
 
print (GrowthRate) # => 52.857142857142854
```

KBより抜粋
![CleanShotxuvxraa1@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227518/8734eeef-f3d9-908f-54ab-cf9b350dc216.png)
WinUI3でも付箋アプリを作りたい（下調べ）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3698535/05894c20-00ad-3fcb-606b-c9a69c9c958d.png)

# Windowsアプリを作るならWinUIも外せない

Flutter on Desktopはとっても簡単にデスクトップアプリが作れますが、Windowsネイティブでアプリを作るならWinUIも外せない。ということで使いこなせるかどうか触ってみました。ちなみに私はC#erなのでWinFormsでの業務アプリの開発経験があります。

# WinUI3にはデザイナーがないけどFlutterのウィジェットに慣れてきたのでさほど違和感はなし

プロジェクトをWinUI3でスキャフォールドしてコードを確認してみたところ、xamlとxaml.csで構成されていることがわかりました。Razor Pagesのcshtmlとcshtm.csの関係のhtmlcがxmlに置き換わったようなものなのでさほど違和感はありませんでした。WInForms時代に見たら混乱していたかも知れません。

# コードに触れてみる

``` MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();

        IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
        WindowId myWndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
        AppWindow appWindow = AppWindow.GetFromWindowId(myWndId);

        appWindow.Resize(new SizeInt32(400, 400));

        var presenter = appWindow.Presenter as OverlappedPresenter;
        presenter.IsResizable = false;
    }

    private async void myButton_ClickAsync(object sender, RoutedEventArgs e)
    {
        var cd = new ContentDialog
        {
            Title = "YoYo Sticky WINUI3",
            Content = "よよのふせん",
            CloseButtonText = "OK",
            XamlRoot = this.Content.XamlRoot
        };

        await cd.ShowAsync();

        myButton.Content = "Clicked";
    }
}
```


## MainWindow()

WindowsAPIを利用してMainWindowsを取得し、リサイズしてからさらにリサイズを禁止してみました。

## myButton_ClickAsync()

myButton_Clickを非同期に書き換えて、ContentDialogクラスをShowAsync()で表示してみました。

## 感想

Flutter on Desktopより、よりダイレクトにWindowsに触れている感があります。FlutterのパッケージはWin32APIなどをラップしたライブラリとして使えるようにしてくれますが、WinUIでは自分で実装していくことになるのでハードルはあがりますがやれることは確実に増えます。

今後FlutterとWinUiのどちらで開発するか悩むところですが、Flutterで馴染んだマテリアルデザインは捨てがたいので現状では決め切れていません。またFlutter Desktopビルドでは基本的にMobileビルドと同じデザイン・同じ挙動には出来ないためWinUIに対してのクロスプラットフォームのアドバンテージはありません。

いっぽうでFlutterはフロントエンドアプリの扱いのためCloud Firestoreの利用が標準でサポートされています。WinUIはフロント型でもホスト型でもないためCloud Firestoreを利用できるパッケージが存在しておらず（RealTimeDatabeseの方はあったはず）Cloud Functionで対応することになります。
クロスAWSアカウントでS3オブジェクトにアクセスする

## やりたいこと

AWSアカウントA：
・S3バケットにVPCエンドポイントしかアクセスできないように制限をかけています
・S3バケットにはRefererアクセス制限をかけています

AWSアカウントB：
EC2などからAWSアカウントAのS3バケットへアクセスしたい


## おまかの流れ
AWS記事に書いているとおり
https://repost.aws/ja/knowledge-center/s3-instance-access-bucket

## 大変なところ
S3バケットのポリシーを書く

この記事にはだいたい書いています
https://tech.nri-net.com/entry/considered_Amazon_s3_bucket_policies_by_scenario

## 実際にどうやって書くのか
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Deny get requests not through vpc endpoint",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": [
              "${s3_bucket_arn}",
              "${s3_bucket_arn}/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userid": [
                        "ABxxxxxxxxxxxxN:*",
                        "ACxxxxxxxxxxxxY:*"
                    ]
                },
                "StringNotEquals": {
                    "aws:sourceVpce": "vpce-xxxxxxxx"
                }
            }
        },
        {
            "Sid": "Deny get requests outside *.-hoge.com",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": [
                "${s3_bucket_arn}",
                "${s3_bucket_arn}/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userid": [
                        "ABxxxxxxxxxxxxN:*",
                        "ACxxxxxxxxxxxxY:*"
                    ],
                    "aws:Referer": [
                        "https://*-hoge.com/*"
                    ]
                }
            }
        },
        {
            "Sid": "Allow cross account access",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::xxxxxx:role/hage-role"
                ]
            },
            "Action": [
                "s3:ListBucket",
                "s3:GetObject"
            ],
            "Resource": [
                "${s3_bucket_arn}",
                "${s3_bucket_arn}/*"
            ]
        }
    ]
}
```

## どうやって情報を埋めるか
`aws:userid` は以下のコマンドを打って `RoleID` を転記だけ

```
aws iam get-role --profile xxxxxxx --role-name yyyyyyy
```

以上
２次関数の応用問題「20年 中央大 経済 2」をsympyと作図をやってみたい。

パイソニスタの方へ
・Piecewise関数にしました。(ver0.2)
Represents an expression, condition pair.
https://docs.sympy.org/latest/modules/functions/elementary.html#piecewise
・Heaviside関数の正しい?使い方を教えて下さい。(ver0.1)　　 　

# オリジナル　
数学入試問題 様

http://kamelink.com/blog/2021/04/04/1-4-20-tyuuoudai-keizai-2/

大学入試数学問題集成 様＞ 未登録

# sympyで
・Piecewise関数で
・ver0.2
```python
# ver0.2
from sympy import *
x=symbols('x',real=True)
var('SR,BS,BC,RC,PB',real=True)
rep={SR:x,BS:x/tan(60*pi/180),RC:x/tan(60*pi/180),BC:1}
sol=solve(Eq(BC,BS+SR+RC).subs(rep),x)[0]
y1=x**2
rep={PB:1-x}
y2=(x*PB*sin(60*pi/180)).subs(rep).factor()
y =Piecewise( (y1,x<sol),(y2,True))
print("# (1)",y)
print("# (2)",maximum(y,x,Interval(0,1)) )
# (1) Piecewise((x**2, x < -3 + 2*sqrt(3)), (-sqrt(3)*x*(x - 1)/2, True))
# (2) sqrt(3)/8
```

・ver0.1
・オリジナル 様を参考に
```python
# ver0.1
from sympy import *
x=symbols('x',real=True)
SR=x
BS=RC=x/tan(60*pi/180)
BC=1
eq=Eq(BC,BS+SR+RC)
sol=solve(eq,x)[0]
y1=x**2
PB=1-x
y2=(x*PB*sin(60*pi/180)).factor()
print("# (1)",y1,sol)
print("# (1)",y2)
# y =y1-Heaviside(x-sol,H0=0)*y1+Heaviside(x-sol,H0=0)*y2
# print("# (2)",maximum(y,x,Interval(0,1)))
print("# (2)",max( maximum(y1,x,Interval(0,sol)),maximum(y2,x,Interval(sol,1)) ))
# (1) x**2 -3 + 2*sqrt(3)
# (1) -sqrt(3)*x*(x - 1)/2
# (2) sqrt(3)/8
# 作図(グラフ)----------------------------------------------------------------------
from sympy.plotting import plot
p0=plot(0,sqrt(3)/8,(x,0,1),aspect_ratio=(1.0,1.0),show=False)
p0=plot(0 ,(x,0,1),aspect_ratio=(1.0,1.0),show=False)
p1=plot(y1,(x,0.0,sol),show=False)
p2=plot(y2,(x,sol,1.0),show=False) 
p0.extend(p1)
p0.extend(p2)
p0.show()
```

# 作図
![0000Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109698/4f3b4d09-90e6-c04e-4ac7-c188df224ec9.png)

# いつもの? sympyの実行環境と 参考のおすすめです。
[(テンプレート)](https://qiita.com/mrrclb48z/items/00dd08b0317069be9342)
TS 環境の作り方

こうやったら上手くいったよ、というメモ。

```sh
$ npm init
$ npm install typescript
$ npx tsc --init

# Here, update `package.json` and `tsconfig.json`

$ npx tsc
# コンパイルされたjsを実行する
$ npx tsc && npx node load.js
```

## Update `package.json`

```json
  "type": "module",
  "scripts": {
    "tsc": "npx tsc -p tsconfig.json",
  },  
```

## Update `tsconfig.json`

```json
    "target": "es2022",
    "module": "es2022",
    "moduleResolution": "Bundler",
```


## TODO

- `--sourcemap`。現状エラーメッセージはjsファイルのコードを示している。
BEM規則のルール


## BEMとは？
BEM（Block Element Modifier）とはCSSの設計思想の一つで、厳密な命名規則を定めることで、クラス名だけでチーム全体に共通の認識を持たせることができます。BEMは「Block」、「Element」、「Modifier」の頭文字を取った略語であり、サイト内の要素をそれぞれ「Block」、「Element」、「Modifier」に分離してクラス名を割り当てていきます。



## BEMの特徴

BEMを使うことで、以下の利点があります。

1. **コードの読みやすさ**: BEMはクラス名の命名規則を厳格に定めるため、コードが読みやすくなります。
2. **理解のしやすさ**: 命名規則を共有することで、チーム全体で共通の認識を持つことができます。
3. **メンテナンスの容易さ**: CSSを操作する際にもメンテナンスが楽になります。

　
**BEMの命名規則は以下のようになります。**

- BlockとElementはアンダースコア2つで区切ります。
- ElementとModifierはハイフン2つで区切ります。
- Block、Element、Modifierが複数単語になる場合、単語と単語の間はハイフン1つで区切ります。

[例えば、Blockには「menu」、Elementには「item」を使い、Modifierには「red」を使っています。このようにBEMを適用することで、クラス名が明確になり、保守性が向上します](https://qiita.com/takahirocook/items/01fd723b934e3b38cbbc)

### 例１ ちょっと残念な例：

以下はちょっと残念なBEMを使った例です。

```
 <nav class="header__menu">
      <ul class="header__menu-li">
          <li class="header__menu-li-item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="header__menu-li-item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="header__menu-li-item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="header__menu-li-item"><a href="<?php echo home_url(''); ?>"></a></li>
      </ul>
  </nav>
```

### 例：書き換え例

以下はBEMを適用した例です。

```
  <nav class="header__menu">
      <ul class="menu">
          <li class="menu__item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="menu__item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="menu__item"><a href="<?php echo home_url(''); ?>"></a></li>
          <li class="menu__item"><a href="<?php echo home_url(''); ?>"></a></li>
      </ul>
  </nav>
```

## 結論

BEMはチームでの作業においておすすめの設計思想であると言えます。

厳格な命名規則を持つことで、チーム全体が共通の認識を持ち、スムーズなコードの理解とメンテナンスが可能になります。こんなBEM活用してみてはいかがでしょうか！

## 参考

https://qiita.com/takahirocook/items/01fd723b934e3b38cbbc

https://freelance321.com/css3/bem-design/

https://tsudoi.org/weblog/4374/

https://necomesi.jp/blog/tsmd/posts/152
OpsRamp Logs - Kubernetes ログ/イベントのサンプル

本記事では、OpsRamp LogsのKubernetesについて説明します。

# 概要
OpsRamp Logsでは、Kubernetesのログおよびイベントを取り込むことができます。

::: note warn
Kubernetesインテグレーションを設定する必要があります。
[Kubernetes Integration](https://docs.opsramp.com/integrations/container-orchestration/kubernetes/)
:::

# 設定方法
OpsRamp Agentをデプロイすると自動的にKubernetesを検出し、ログおよびイベントを取り込みます。(制限していなければ)
ログは、以下に出力されたログファイルを参照します。
``` 
/var/log/pods/<pod-name>/<container-name>/*.log
```

イベントは、以下のイベントを取り込みます。
| Node | Pod | その他 |
|:-:|:-:|:-:|
| RegisteredNode | Failed | FailedBinding |
| RemovingNode | InspectFailed | FailedScheduling |
| DeletingNode | ErrimageNeverPull | SuccessfulCreate |
| TerminatingEvictedPod | OutOfDisk | FailedCreate |
| NodeReady | HostPortConflict | SuccessfulDelete |
| NodeNotReady | - | FailedDelete |
| NodeSchedulable | - | - |
| NodeNotSchedulable | - | - |
| CIDRNotAvailable | - | - |
| CIDRAssignmentFailed | - | - |
| Starting | - | - |
| KubeletSetupFailed | - | - |
| NodeSelectorMismatching | - | - |
| InsufficientFreeCPU | - | - |
| InsufficientFreeMemory | - | - |
| OutOfDisk | - | - |
| HostNetworkNotSupported | - | - |
| NilShaper | - | - |
| Rebooted | - | - |
| NodeHasSufficientDisk | - | - |
| NodeOutOfDisk | - | - |
| InvalidDiskCapacity | - | - |
| FreeDiskSpaceFailed | - | - |

- ログサンプル

![k8s-log.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344939/0b36771c-d4b2-97ea-bc3e-39b15a03f15c.png)

- イベントサンプル

![k8s-event.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344939/8304bfef-eb17-12ae-dd9b-5bfbd5be05d5.png)

---------------------------------------
[<img width = 250 height = 200 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344939/111de30a-4d33-aa33-5c7d-83d9015093e6.png">](https://www.vistanet.jp/knowledge-base/)
ATOMCamの画像をBlueskyへ投稿する

## 目的
ATOMCamを使った定点カメラの画像を定期的にBlueskyへPostしてみた。
カメラは空を写しているので雲が写っています。
https://bsky.app/profile/roxabot.bsky.social

## 環境
- マシン : Raspberry PI 4
- Python : 3.7.3
- ATOMCam : ATOMCam2

## ATOMCam
下記のatomcam_toolsを導入済み
https://github.com/mnakada/atomcam_tools/blob/main/README.md

## Blueskyのアプリパスワードを取得
設定 -> アプリパスワード からパスワードを取得しておく。

## python環境作成
Raspberry PI へ仮想環境を作成してatprotoモジュールをインストールする。
```
% python3 -m venv atproto
% source atproto/bin/activate
% sudo pip3 install atproto
```

## Blueskyへ投稿するスクリプト
Postしたいメッセージとローカルにある画像ファイルを引数としてBlueskyへPostするスクリプトです。
https://github.com/roxa-delphi/post_sns/blob/main/bs_postimage.py
コマンドラインから実行するときは下記のように実行します。
```
% atproto/bin/python3 bs_postimage.py "message" image_filename
```

## ATOMCamからの画像取得とBlueskyへのポスト
ATOMCamから画像を取得して"data/yymmdd/yymmdd_hhmmss.jpg"へ保存し、
それをBlueskyへポストしています。
https://github.com/roxa-delphi/post_sns/blob/main/get_atomcam.pl

## cronで実行する
10分毎にスクリプトを実行し、毎時0分にはBlueskyへ投稿しています。
毎時0分の設定は get_atomcam.pl 内の '$mm eq "00"' で実施してます。
```
0-59/10 * * * * cd /target-directory;./get_atomcam.pl
```

## 結果
Blueskyは下記で見れます。
https://bsky.app/profile/roxabot.bsky.social