OmniAICreator/Qiita-1.07M huggingface dataset:
[Google Cloud][Gemini]gemini-pro-experimental使ってみた

# はじめに
Gemini Pro Experimaentalが来たという話を聞いたので、使ってみました

https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/gemini-experimental

# コンソール利用
gemini-flash-experimental、gemini-pro-experimentalが選択できるようになってます

![スクリーンショット 2024-08-29 22.14.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3866216/eb5a7af0-a51e-9093-69a4-69f3cd1def0d.png)

experimentalを選択すると、リージョンが選択できなくなります。（理由はわからず。
Funcitonsの項目が追加されています。

![スクリーンショット 2024-08-29 22.51.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3866216/f6a0df9f-6bcb-af76-4c0d-266df0884a2b.png)

# API利用

```bash
#/bin/bash

LOCATION="us-central1"
MODEL_ID="gemini-pro-experimental" #モデル変えるだけ！
PROJECT_ID="test-project"
GENERATE_RESPONSE_METHOD="generateContent"

curl \
-X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/publishers/google/models/${MODEL_ID}:${GENERATE_RESPONSE_METHOD} -d \
$'{
  "contents": [
    {
    "role": "user",
    "parts": { "text": "Hello!" }
    },
    {
    "role": "model",
    "parts": { "text": "Argh! What brings ye to my ship?" }
    },
    {
    "role": "user",
    "parts": { "text": "Wow! You are a real-life pirate!" }
    }
  ],
  "safety_settings": {
    "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
    "threshold": "BLOCK_LOW_AND_ABOVE"
  },
  "generation_config": {
    "temperature": 0.9,
    "topP": 1,
    "candidateCount": 1,
    "maxOutputTokens": 2048
  }
}'
```
(参考)

https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-chat-prompts-gemini?hl=ja#gemini-chat-samples-drest:~:text=%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9-,curl%20%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E4%BE%8B,-LOCATION%3D%22us
 
:::note info
東京リージョン(asia-northeast1)にリクエストすると404になりました。
us-central1はとりあえず使えるようです。
東京リージョンはもう少し待ちましょう。
:::

# 最後に
まだあまり触れてないので、触ったら所感等更新したいと思います。
使い捨てメールアドレスか判定する 薄い npm パッケージを作った


## はじめに

使い捨てメールアドレスか判定する 薄い npm パッケージを作成しました。

https://github.com/mziyut/disposable-email-domains-js

https://www.npmjs.com/package/disposable-email-domains-js

## 使い捨てメールアドレス とは

捨てアドなどと呼ばれる、一時的な使用を目的としたメールアドレスのこと。

https://wa3.i-3-i.info/word110724.html

## 作った背景

使い捨てメールアドレス一覧を GitHub 上にまとめてくださっている方が居ました。

https://github.com/disposable-email-domains/disposable-email-domains

README.md には、Python, PHP, Go, Ruby on Rails, Node.js など様々な言語で使用する方法が記載されています。
一部の言語では Package, Module として公開され簡単に使うことができます。

https://pypi.org/project/disposable-email-domains/

https://packagist.org/packages/elliotjreed/disposable-emails-filter

https://github.com/rocketlaunchr/anti-disposable-email

https://github.com/oesgalha/disposable_mail

Node.js は package がなかったので作成することにしました。

https://github.com/mziyut/disposable-email-domains-js

https://www.npmjs.com/package/disposable-email-domains-js

## disposable-email-domains-js の使い方

npm に package を公開しているので install すればすぐに利用できます。

```bash
npm install disposable-email-domains-js

# or

yarn add disposable-email-domains-js
```

install したら pacakge を呼び出して判定するだけです。

```javascript:JavaScript
var disposableEmailDomains = require('disposable-email-domains-js');

disposableEmailDomains.isDisposableEmailDomain('example.com'); // return false
```

```typescript:TypeScript
import { isDisposableEmail } from 'disposable-email-domains-js';

isDisposableEmailDomain('example.com'); // return false
```

### ドメインリストの更新

ドメインリストは引き続き参照元のパッケージが更新されたら追従する予定です。
もし追加したいドメインがあれば `disposable-email-domains` に Pull request を作成してください。

https://github.com/disposable-email-domains/disposable-email-domains

更新は Dependabot で検知するように設定しています。

https://github.com/mziyut/disposable-email-domains-js/blob/8841bcd7c1288717c4db2b17d8dd71776386ef82/.github/dependabot.yml#L19-L26

https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
宝くじ (paizaランク C 相当) Rust(43)

paiza×Qiita記事投稿キャンペーン「プログラミング問題をやってみて書いたコードを投稿しよう！」
https://qiita.com/official-events/9ab96aa95d62fe3cbdd7

初心者向けDランク〜最難関Sランクまで全25問（すべて無料）
https://paiza.jp/pages/campaign/paiza-qiita

宝くじ (paizaランク C 相当)
https://paiza.jp/works/mondai/d_rank_skillcheck_archive/square

宝くじ (paizaランク C 相当) C++(57)
https://qiita.com/kaizen_nagoya/items/f1cf332b2235028fa47b

Rubyの資料は出てくるのに、、、
C++の時どうしたかわすれちゃった。

明日以降、ちゃんと調べてやりなおし。

Rustの資料がまだベータ版でないのが原因かどうかもよくわからず彷徨っている。
【殿堂入りキャンペーン】N倍の文字列 (paizaランク D 相当) Rust(42)

paiza×Qiita記事投稿キャンペーン「プログラミング問題をやってみて書いたコードを投稿しよう！」
https://qiita.com/official-events/9ab96aa95d62fe3cbdd7

初心者向けDランク〜最難関Sランクまで全25問（すべて無料）
https://paiza.jp/pages/campaign/paiza-qiita

【殿堂入りキャンペーン】N倍の文字列 (paizaランク D 相当)
https://paiza.jp/works/mondai/d_rank_skillcheck_archive/square/edit?language_uid=rust

【殿堂入りキャンペーン】N倍の文字列 (paizaランク D 相当) C++(55)
https://qiita.com/kaizen_nagoya/items/5b9d56fd2bca32bd7dbf

```rust:ast.rs
#include <iostream>
using namespace std;
int main(int argc, char ** argv){
    // 自分の得意な言語で
    // Let's チャレンジ！！
    int i;
    cin >>i;
    for (i; i--;i <= 0){
    cout << "*";
    }
    cout<< endl;
    return 0;
}
```

あれ、C++のソースが残ってる。

>コード実行結果： 実行結果ステータス	compilation error
コンパイル時エラーメッセージ	
```rust:error
error: expected one of `!` or `[`, found `include`
 --> Main.rs:1:2
  |
1 | #include <iostream>
  |  ^^^^^^^ expected one of `!` or `[`

error: aborting due to previous error
```
提出コードのアウトプット	
期待する出力	****

出てるのはRustのエラー。
途方に暮れて、明日やり直す。
関数

=IF(OR(LEFT(A2, 3)="src", LEFT(A2, 4)=" src"), B1+1, B1)


=IF(OR(LEFT(C83, 3)="src", LEFT(C83, 4)=" src"), 
    MID(C83, SEARCH("¥impl¥", C83)+6, SEARCH("ServiceImpl.java:", C83) - SEARCH("¥impl¥", C83)-6), 
    IF(ISNUMBER(SEARCH("this.", C83)), 
        MID(C83, SEARCH("this.", C83) + 5, FIND("Dao", C83, SEARCH("this.", C83)) + 3 - SEARCH("this.", C83) - 5), 
        IF(ISNUMBER(FIND("=", C83)), 
            MID(C83, FIND("=", C83) + IF(MID(C83, FIND("=", C83) + 1, 1) = " ", 2, 1), FIND(".", C83, FIND("=", C83)) - FIND("=", C83) - IF(MID(C83, FIND("=", C83) + 1, 1) = " ", 2, 1)),
            IF(ISNUMBER(SEARCH("return ", C83)),
                MID(C83, SEARCH("return ", C83) + 7, FIND(".", C83, SEARCH("return ", C83)) - SEARCH("return ", C83) - 7),
                ""
            )
        )
    )
)


=IF(LEFT(C83, 3)="src", 
    MID(C83, SEARCH("¥impl¥", C83)+6, SEARCH("ServiceImpl.java:", C83) - SEARCH("¥impl¥", C83)-6), 
    IF(ISNUMBER(SEARCH("this.", C83)), 
        MID(C83, SEARCH("this.", C83) + 5, FIND("Dao", C83, SEARCH("this.", C83)) + 3 - SEARCH("this.", C83) - 5), 
        IF(ISNUMBER(FIND("=", C83)), 
            MID(C83, FIND("=", C83) + IF(MID(C83, FIND("=", C83) + 1, 1) = " ", 2, 1), FIND(".", C83, FIND("=", C83)) - FIND("=", C83) - IF(MID(C83, FIND("=", C83) + 1, 1) = " ", 2, 1)),
            IF(ISNUMBER(SEARCH("return ", C83)),
                MID(C83, SEARCH("return ", C83) + 7, FIND(".", C83, SEARCH("return ", C83)) - SEARCH("return ", C83) - 7),
                ""
            )
        )
    )
)
プロジェクトの管理とワークフローの基礎: 実務未経験エンジニアのためのガイド

## はじめに

実務未経験のエンジニアにとって、プロジェクト管理とワークフローの理解は、チーム開発における重要なステップです。このガイドでは、基本的なプロジェクト管理の概念やツール、実践的なワークフローの進め方を解説します。これらの知識を身につけることで、プロジェクトをスムーズに進行させ、チームとの連携を強化することができます。

## 1. プロジェクト管理とは？
プロジェクト管理は、特定の目標を達成するために、リソースやタスクを計画・実行・監視・完了するプロセスです。開発プロジェクトにおいては、スケジュール管理、タスクの割り当て、リスク管理などが含まれます。プロジェクト管理の基本的なフレームワークを理解することで、効率的な進行が可能になります。

## 2. タスク管理ツールの紹介
タスク管理ツールは、プロジェクトの進行を整理し、チームメンバー間でのタスクの可視化を助けます。以下は初心者エンジニアにおすすめのタスク管理ツールです：

- GitHub Projects: タスク管理やプロジェクトの進捗を視覚的に追跡できるカンバンボード形式のツールで、リポジトリと連携して効率的なプロジェクト管理をサポートします。
- Trello: 視覚的にタスクを管理できるカンバンボード形式のツール。タスクを「To Do」「In Progress」「Done」などのステージに分けて整理します。
- JIRA: ソフトウェア開発チームに特化したタスク管理ツール。アジャイル開発のスプリントやバックログ管理が可能です。
- Asana: プロジェクト全体を視覚化し、チームの進捗をリアルタイムで追跡できます。


これらのツールを使いこなすことで、タスクの漏れを防ぎ、効率的なプロジェクト進行を実現できます。

※ シンシアではJIRA・Github Projectsを使っています

## 3. ワークフローの基本
ワークフローとは、タスクが完了するまでの一連のステップを指します。以下に、基本的なワークフローの構造を紹介します。

タスクの作成: プロジェクトの目標に基づき、必要なタスクをリストアップします。
タスクの割り当て: 各タスクを適切なチームメンバーに割り当てます。割り当て時には、各メンバーのスキルセットや現在のタスク負荷を考慮しましょう。
進捗の追跡: タスク管理ツールを用いて、各タスクの進捗をリアルタイムで監視します。必要に応じて優先順位の変更やリソースの再配分を行います。
レビューとフィードバック: タスクが完了したら、他のメンバーや上司によるレビューを受けます。フィードバックを元に、改善点を次のプロジェクトに活かします。

## 4. アジャイル開発とスクラム
アジャイル開発は、変化に柔軟に対応するための開発手法です。スクラムはその一つで、短期間のスプリントと呼ばれる作業サイクルでプロジェクトを進行します。以下はスクラムの基本構成です：

プロダクトバックログ: プロジェクト全体の要件やタスクのリストです。優先順位に基づいて整理されます。
スプリントバックログ: スプリントごとに完了すべきタスクのリストです。
デイリースクラム: 毎日行われる短いミーティングで、各メンバーが現在の進捗を共有します。
スプリントレビュー: スプリントの最後に、完了したタスクをチームやステークホルダーに発表します。
スプリントレトロスペクティブ: スプリントの振り返りを行い、次のスプリントへの改善点を話し合います。
アジャイルとスクラムの理解は、迅速で効率的な開発を実現し、チーム全体の生産性を向上させることができます。

## 5. 実際のプロジェクトでの応用
プロジェクト管理とワークフローの知識を実際のプロジェクトで応用することで、経験を積むことができます。最初は小規模なプロジェクトから始め、徐々に複雑なプロジェクトに挑戦していくと良いでしょう。また、オープンソースプロジェクトに参加することで、他のエンジニアとの協力や実践的な経験を積むこともおすすめです。

## まとめ
プロジェクト管理とワークフローの基本を理解し、実際の開発に応用することは、エンジニアとしてのキャリアの第一歩です。タスク管理ツールの活用やアジャイル開発の実践を通じて、効率的にプロジェクトを進行させるスキルを磨いていきましょう。
社内でSSHを使用してGitHubリポジトリをクローンする方法

# はじめに
企業環境では、セキュリティ上の理由からインターネットへの直接アクセスが制限されていることがよくあります。このような状況で、リモートサーバにホストされているGitHubリポジトリをクローンする必要がある場合、SSHのローカルホストフォワーディング機能を使用すると便利です。この記事では、その方法を詳しく説明します。

# 前提条件
1. ローカルマシンにSSHクライアントがインストールされていること
1. リモートサーバへのSSHアクセス権限があること
1. リモートサーバがGitHubにアクセスできること
1. GitHubアカウントとアクセスしたいリポジトリへの権限があること

# 手順
### 1. SSHローカルフォワーディングの設定
まず、ローカルマシンからリモートサーバへのSSH接続を確立し、ポートフォワーディングを設定します。
```bash
ssh <username>@<remote-server> -L 8080:localhost:80
```
:::note warn
usernameはリモートサーバのユーザー名に、remote-serverはリモートサーバのアドレスに置き換えてください。
:::

### 2. GitHubでのトークン作成
右上のプロフィールアイコンをクリックし、Settingsを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/90687b76-953a-b261-097b-8115f297c84c.png)

左サイドメニューからDeveloper settingsをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/b4ed5ff1-42b7-2508-eb3b-e1e97feb823c.png)

Personal access tokens > Tokens (classic)を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/07414576-c758-bd6c-f6ba-851c8e9bc60e.png)

Generate new token (classic)をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/35818eb3-1ead-e0ba-01b7-dc10054a4fbd.png)

repoにチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/c2c4eb62-c22e-ab55-0728-0b4bec8daf9f.png)

Generate tokenをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760374/5a37e8fb-2dbc-de88-73e1-47a6f4fb3776.png)

トークンが表示されたら、忘れずにコピーして安全な場所に保存してください。

### 3. リポジトリのクローン
ポートフォワーディングが確立された状態で、以下のコマンドを使用してリポジトリをクローンします：
```bash
git clone -c "http.proxy=127.0.0.1:8080" http://<username>:<token>/<repository>.git
```
:::note warn
usernameにはGitHubのユーザー名を、repositoryにはクローンしたいリポジトリ名を入力してください。また、tokenには先ほど生成したトークンを入力してください。
:::

# 注意点
1. **SSHセッションの維持**：このセットアップはSSHセッションがアクティブな間のみ機能します。セッションが切断されると、再度接続を確立する必要があります
1. **セキュリティポリシーの確認**：企業のセキュリティポリシーに違反しないよう、必ず適切な許可を得てから実行してください

# まとめ
SSHローカルフォワーディングを使用することで、直接のインターネットアクセスが制限された環境でも、安全にGitHubリポジトリをクローンすることができます。この方法は、企業のセキュリティ要件を満たしつつ、効率的な開発作業を可能にします。
Eメールアドレス (paizaランク D 相当) Rust(41)

paiza×Qiita記事投稿キャンペーン「プログラミング問題をやってみて書いたコードを投稿しよう！」
https://qiita.com/official-events/9ab96aa95d62fe3cbdd7

初心者向けDランク〜最難関Sランクまで全25問（すべて無料）
https://paiza.jp/pages/campaign/paiza-qiita

Eメールアドレス (paizaランク D 相当) 
https://paiza.jp/works/mondai/d_rank_skillcheck_archive/email_address/edit?language_uid=rust

Eメールアドレス (paizaランク D 相当) C++(54)
https://qiita.com/kaizen_nagoya/items/13789a46684099da4378

```rust:email.rs
// 自分の得意な言語で
// Let's チャレンジ！！
fn main(){
  println!("paiza＠example.com”);
}
```

>コード実行結果： 実行結果ステータス	compilation error
コンパイル時エラーメッセージ
```rust:error
error[E0765]: unterminated double quote string
 --> Main.rs:4:12
  |
4 |     println!("paiza＠example.com”);
  |  ____________^
5 | | }
  | |__^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0765`.
```
提出コードのアウトプット	
期待する出力	paiza@example.com

```
// 
// email2.rs (c) Dr. Kiyoshi Ogawa
fn main(){
  println!("paiza＠example.com");
}
```

>コード実行結果： 実行結果ステータス	Wrong Answer
提出コードのアウトプット	paiza＠example.com
期待する出力	paiza@example.com

// https://qiita.com/kaizen_nagoya/items/346ed4bf272e26b58df0
// email2.rs (c) Dr. Kiyoshi Ogawa
fn main(){
  println!("paiza@example.com");
}

コード実行結果： 実行結果ステータス	Success
提出コードのアウトプット	paiza@example.com
期待する出力	paiza@example.com
【VRChat】Genericアバターのセットアップ手順

# 概要
Blenderから出力したfbxを、VRChatのGenericアバターにする手順を紹介します。

### ポイント
- アバターのルートオブジェクトにはAnimatorとVRC Avatar Descriptorが必要
- 自分でFXレイヤーなどをいじらない人には関係ありませんが、**アバターのルートオブジェクトのAnimatorのAvatar欄が空のままだと、ExpressionMenuが機能しないので注意**

# 環境
- Blender 3.6
- Unity 2022.3.22f1
- VRChat SDK - Base 3.6.1
- VRChat SDK - Avatars 3.6.1

# 手順

## fbxの用意
今回は例としてBlenderのSzanneをそのままアバターとして利用します。
fbxの出力設定は画像の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/5e628e27-9317-6c1b-3511-6753297d23cd.png)

## fbxのセットアップ
UnityにSzanneのfbxをインポート後、
fbxのRig設定のAvatar Definitionで「Create From This Model」を選択しApplyボタンを押す。
その後fbx内に人の形をしたAvatar(今回はSzannneAvatarという名前)が生成されていればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/4c2b49ef-dd61-c1e1-4ffd-fc217186effe.png)



## アバターのルートオブジェクト作成
1. Hierarchyを右クリック→Create Emptyで空オブジェクトを生成。"AvatarRoot"という名前にする

2. Inspector一番下のAdd Compornentボタンから「Animator」と「VRC Avatar Descriptor」を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/cd7469f5-b13d-dcbe-a48c-565d21608f10.png)
<br>
3. Animatorに先ほど生成したAvatarを設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/0c603071-e9c0-a6ce-de15-fa170e20955f.png)

:::note warn

自分でFXレイヤーなどをいじらない人には関係ありませんが、
**このAvatar欄が空だと、ExpressionMenuが機能しないので注意**
:::



## モデルの配置
1. アバターのルートオブジェクト"AvatarRoot"にSzanneのfbxをドラッグ&ドロップし、子オブジェクトにする
2. 子オブジェクトのAnimatorコンポーネントをRemove Componentで消去する
2. 子オブジェクトのTransformやマテリアルをお好みに設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/f38a8543-1e6c-048c-e048-e9137c2d0de0.png)


## ビューポイントの調整
アバターのルートオブジェクト"AvatarRoot"のVRC Avatar Descriptor内のViewPositionで調整
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/e10ea1de-a327-f2f1-db69-9522a7f790cc.png)

## アップロード
他のアバターと同様に、VRChat Control PanelからOnline Publishingのチェックを入れ、「Build&Publish」ボタンを押してアップロード完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/580519ff-4318-72d6-2845-4e667fae0ec1.png)

:::note info
Validationsに赤いビックリマークが出ていてアップロードできない場合は「AutoFix」を押すことで自動で修正してくれる場合があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864812/2b550b02-9be9-9846-0541-09fdf00a66aa.png)
:::
【CSS】3点リーダーを実装する

## 概要

この記事では、テキストの行末を3点リーダー（`・・・`）で切り取る方法について説明します。1行のテキストと複数行のテキストで、それぞれ3点リーダーを適用する方法を紹介します。

## 実装

### 1行のテキストに3点リーダーを適用する

1行のテキストが要素の幅を超える場合、その部分を省略して3点リーダーを表示するには、以下の CSS プロパティを使用します。

```css
{
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
```

- `overflow: hidden;`
コンテンツが親要素を超えた場合、見切れた部分を表示しません。

- `text-overflow: ellipsis;`
コンテンツが切り取られる場合、その末尾に `・・・` を表示します。

- `white-space: nowrap;`
テキストを1行に制限し、改行を無効にします。

----

### 複数行のテキストに3点リーダーを適用する

複数行のテキストが指定した行数を超える場合、3点リーダーを表示するには、以下の CSS プロパティを使用します。以下は、2行目の末尾に3点リーダーを適用する例です。


```css
{
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
```

- `display: -webkit-box;`
flex プロパティのベンダープレフィックスです。

- `-webkit-box-orient: vertical;`
コンテンツを縦方向に配置します。

- `webkit-line-clamp: 2;`
表示する行数を制限します。この例では2行まで表示し、3行目以降は切り取ります。

- `overflow: hidden;`
コンテンツが親要素を超えた場合、見切れた部分を表示しません。

## Tailwind CSS を使用する場合

Tailwind CSS を使って、3点リーダーを簡単に実装することも可能です。`line-clamp` ユーティリティを使うと、指定した行数を超えたテキストに自動的に3点リーダーを付けることができます。

```html
<div class="line-clamp-2">
  aaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaa・・・
</div>
```

## 参照

https://qiita.com/yoshida-hi/items/055c36e015f0bf8fe4f6

https://tailwindcss.com/docs/line-clamp

https://github.com/tailwindlabs/tailwindcss-line-clamp
クライアント認証を有効にしたサイトで特定のパスだけ認証不要とする

はじめに
--------

このようなシチュエーションはあまりないような気がしますが、実際これを設定する必要があり、暫く試行錯誤してしまったのでまとめておきます。

* Apache+SSLで、ユーザー毎のクライアント証明書による認証を有効化
* 当該サーバー上の特定のパスだけは、認証を外して誰でもアクセスできるようにする

前提
----

Apacheでは、このような設定をしているものとします。典型的な設定先は `/etc/httpd/conf.d/ssl.conf` あるいは `/etc/apache2/sites-available/default-ssl.conf` となるでしょう。

```apache:ssl.conf
<VirtualHost _default_:443>

# サーバー証明書の設定
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

# クライアント認証局(自己署名)の設定
SSLCACertificateFile /etc/pki/local/ca/cert.pem
SSLVerifyClient require
SSLVerifyDepth 10
SSLCARevocationCheck leaf
SSLCARevocationFile /etc/pki/local/ca/crl.pem

</VirtualHost>
```

この設定によって、`/etc/pki/local/ca/cert.pem`で署名したクライアント証明書をインストールしていない端末からはアクセスできなくなります。[^1]

[^1]: SSLエラーになります。サーバー側でのハンドシェークエラーなので、無視してアクセスすることはできません。

やりたいこと
------------

新しくアクセスを許可するユーザーのために作った証明書をダウンロードしてもらうパスを同じサーバー上に用意するようなシチュエーション、を考えます。

> https\://example.com/client_certs/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

のような長いURLでアクセスさせる感じです。

失敗例
------

LocationなりDirectoryなりのディレクティブで制御すればいいのよね、ということで、安易に

```apache:ssl.conf
<Location />
  SSLVerifyClient require
</Location>

<Location /client_certs/>
  SSLVerifyClient none
</Location>
```

としてみましたが、ダメでした。

他に、

```apache:ssl.conf
<If "%{REQUEST_URI} !~ m!^/client_certs/!">
  SSLVerifyClient require
</If>
```

も試してみました。これもダメ。

落ち着いて考えればわかるのですが、証明書の検証とクライアント認証は、SSLハンドシェーク時に行うので、ハンドシェーク後のHTTP GET等のコマンドを与えた後に分岐するような設定は効果がないのです。

https://stackoverflow.com/questions/69006640/can-sslverifyclient-be-specified-in-location-xxxx

クライアントIPアドレスのような、ハンドシェーク時にわかる値であれば、後者のやり方で制御できます。

https://www.konekuri.com/173/

考察
----

結局のところ、証明書なしで接続を許すリソースが存在する以上、ハンドシェーク時には証明書があってもなくても繋がる状態にする必要があります。

```apache:ssl.conf
SSLVerifyClient optional
```

で、証明書が必要なリソースについては、別の手段でアクセス制御をかけることになります。

今回は`SSLRequire`が使えそうです。クライアント証明書のDNのベース部分(例えばC/ST/O)を固定にして、ユーザー毎にその下(CN等)を変えるような運用を想定すると、`SSL_CLIENT_S_DN_O`を条件にすれば、「クライアント認証を必須とするパス」を定義可能となるはずです。[^2]

[^2]: もちろん、DNの条件を揃えた適当な証明書ではそれを用いたハンドシェークができないので、正規の証明書である必要があります。

https://qiita.com/kite_999/items/0638e750da69105b598c

成功例
------

これでうまくいきました。ついでにいうと、証明書なしでアクセスした際に、SSLエラーでなくForbiddenが出るようになったので、これはこれでわかりやすい気がします。[^3][^4]

[^3]: 一度Apacheに接続を許してるのは大丈夫なのか、という議論はあるかもしれませんが、制限対象のリソースにはアクセスできないので問題ないでしょう。
[^4]: なお、CRLに突っ込んで証明書を無効化した場合はハンドシェークに失敗するようで、SSLエラーとなります。

```apache:ssl.conf
SSLVerifyClient optional

<If "%{REQUEST_URI} !~ m!^/client_certs/!">
  SSLRequire %{SSL_CLIENT_S_DN_O} eq "Your Company"
</If>
```

おわりに
--------

本件、ちょっと必要にかられてこういうのを作っていて、はて、と止まってしまったやつです。せっかくなので切り出して記事化してみました。

https://github.com/jado4810/privcert/

もし興味あればどうぞ。特に、なんとなく勢いでI18nなyml書いちゃったのだけど、多分ものすごく不自然な翻訳になってるだろうから(特に中台韓独[^5])、このへんフィードバックいただける奇特な方がおられましたら嬉しいです。もちろん翻訳の追加も大歓迎。需要あるかはわかりませんが…

[^5]: ようするに日英以外全部じゃねーか…
バッチファイルによる名前変更について　ドラッグドロップ編

# はじめに
投稿者はプライベート用事により大量のPDFファイルを使用することがあります。
これらを一定の決まったファイル名にすることがあるのですが、いちいち「右クリック→名前の変更」や「F2キー」を対応するとかなり面倒なことになります。
　例：時間がかかる、ミスタイプの発生
　例：一定でないファイル名

というわけで、今回はドラッグドロップによるファイル名の記名を行っていきたいと思います。

# 前置き
・PDFファイルの名前変更を取り扱います。
※PDFファイル以外でも使用できると考えております(未検証)。

# 本題
## その1 固定の名前に変更
例：○○.pdfを「hogehoge.pdf」に変更
```bat:cmd01.bat
@echo off
ren %1 hogehoge.pdf
```
### 使用方法
・適当なバッチファイルに保存する
・保存したバッチファイルに、名前変更したいPDFファイルをドラッグドロップ
### 解説
`@echo off`
<details><summary>詳細</summary>

`@echo off`：起動したcmd.exeに表示させなくする
ややうっとうしいので固定で入れました。お好みで。
参考：https://qiita.com/Sanada-code/items/5d21d885b732e6fb0d03

</details>

`ren`
<details><summary>詳細</summary>

`ren`：リネーム(名前変更)のコマンド。`rename`でも同様。それぞれ大文字でも同様。
参考：https://www.javadrive.jp/command/file/index3.html
</details>

`%1`
<details><summary>詳細</summary>

`%1`：引数1つ目　ドラッグドロップした場合はファイル名(パス付き)が入ってます。
参考：https://www.javadrive.jp/command/bat/index6.html
</details>
要するに「どこにあろうが○○.pdfをhogehoge.pdfに変更する」というバッチができました。

## その2 後連番つきの名前に変更
例：○○.pdf、△△.pdfを「hogehoge_1.pdf」「hogehoge_2.pdf」に変更
```bat:cmd02.bat
@echo off
setlocal enabledelayedexpansion

set num=1
for %%f in (%*) do (
    ren %%f hogehoge_!num!%%~xf
    set /a num=num+1
)

endlocal
```
### 使用方法
・適当なバッチファイルに保存する
・保存したバッチファイルに、名前変更したいPDFファイルををすべて選択してドラッグドロップ
※基本的に「並べ替え→本バッチを起動」の順で起動することを推奨。また、ドラッグドロップの際は選択したファイルのうち一番上をドラッグすることを推奨します。それ以外の動作を行った場合、連番の挙動は保証できません。
### 解説
`setlocal enabledelayedexpansion`、`endlocal`
<details><summary>詳細</summary>

`setlocal enabledelayedexpansion`、`endlocal`：遅延環境変数
魔境です。各自ぐぐっていただいた方が早いと思います。
ちなみに今回は連番用の変数`num`を遅延環境変数として扱っております。
参考：https://qiita.com/tana_tomo_1025/items/7f824a154f004f610386
</details>

`for`
<details><summary>詳細</summary>

`for`：繰り返し
魔境です。各自ぐぐっていただいた方が早いと思います。
ちなみに今回の文章だとループ変数が`%f`となっております。
参考：https://qiita.com/plcherrim/items/67be34bab1fdf3fb87f9
</details>

`%*`
<details><summary>詳細</summary>

`%*`：引数*つ目　for文の中に入れてあるこの書き方の場合、「引数をすべて処理する」という意味になります。
</details>

`%%~xf`
<details><summary>詳細</summary>

`%%~xf`：変数`%f`からファイル名の拡張子を抽出
ここのサイトがわかりやすいです。
参考：https://rainbow-engine.com/batch-get-filepath-filename/
</details>

`set /a`
<details><summary>詳細</summary>

`set /a`：変数への代入
ここを見てください。
参考：https://qiita.com/plcherrim/items/9cba5a42273e10915c8f
</details>


要するに「どこにあろうが○○.pdfその他を一括でhogehoge_*.pdfに変更する」というバッチができました。
~~正直遅延環境変数にする必要なさそうなのですが(※未検証)、まあこれで思った通り動くから良いか…~~

# あとがき
投稿者はこれを300ファイルくらい作って活用しております。
不明点・誤字・誤り等ございましたらコメントください。お待ちしております。

# 今後の予定
・バッチファイルによる名前変更について　ダブルクリック編
グローバル IP がなくても任意の TCP サーバーを公開したい！

## 概要

　この記事は手元のマシンで建てている任意の TCP サーバー (HTTP や SSH に限らない) をグローバル IP に公開する方法を記したものです。筆者がとあるゲームサーバーを手元のマシンで建てていたのですが、固定グローバル IP がない環境のため固定された IP アドレスでサーバーを公開できるようにしたためその構成をざっくり書き残します。

## 方法 1: DDNS

　１つ目の方法は DDNS (Dynamic DNS) を使用する方法で、(1) ドメイン名でサーバーを公開してよく、(2) 固定ではなくても良いのでグローバル IPv4 アドレスに直接公開でき、(3) レジストラが何かしら自動でレコードを更新する方法を提供している場合に使用できます。
 　ゲームによっては IP アドレスしか受け付けない (ドメイン名ではアクセスできない) 場合があるため今回は見送りましたが、一般家庭で Minecraft サーバーを建てるなどであれば十分であると思われます。

## 方法 2: P2P・トンネル

　2 つ目の方法は P2P やトンネルを使う方法です。Hamachi など何かしら P2P での接続を確立するためのサービスがあればサーバーを公開することができます。この場合は NAT 抜けもできるので多重ルーターや IPv6 回線しかない場合でも使用できます。今回は Cloudflare Tunnel を使用する予定だったのですが、Cloudflare Tunnel では任意の TCP 通信を行う場合クライアント側にも cloudflared サービスを立ち上げる必要があるため見送りました。

 ## 方法3: トンネル+踏み台サーバー

　先程のトンネルに加えクライアントの代わりに踏み台サーバーを置き、踏み台サーバーの Global IPv4 アドレスを用いてサーバーを公開する方法です。この方法の概略を下図に示します。この方法ではホストがどの IP にあってもよく (NAT 抜けも可能)、起動するマシンに cloudflared さえ入っていればどのマシンで起動してもよいため利便性も高いです。今回はこの方法に落ち着きました。
 
 ![cloudflare-tunnel.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1291112/229fff20-8147-86dc-e684-d39e65a48588.png)

### 構築手順

　詳しい部分は覚えていないので他の記事に譲ります。今回は remotely managed tunnel で設定しました。

1. Cloudflare Zero Trust コンソールからトンネルを作成する
    - このとき適当なドメインで public トンネルを作成する (認証は必要なので public で問題なし)
    - 最終的にサーバーを公開するドメインとは別です
2. 自宅マシンに cloudlared をインストール
    - 1 の作成中に cloudflared のインストールを促されると思います
3. 踏み台サーバーに cloudflared をインストール
    - `cloudflared access tcp` とかでクライアントとして自宅マシンに疎通できるはずです
4. 踏み台サーバーでポートフォワーディングの設定をする
    - iptables, redir などで転送 (私は楽なので redir を使いました)
        - 転送先を cloudflared が待ち受けるポートにする
    - 必要に応じて ufw などの設定もする

　　以上の手順で踏み台サーバーへの通信を自宅マシンに流すことが可能になります。よいサーバーライフを！
【JUnit】Controllerのテスト

## はじめに
コントローラテストについて、何を確認しているのか、どうやって書くのかを理解するために記事を書いていければと思います！

## Controllerクラスでは何のテストをする？
Controllerクラスでは何のテストをするのか、どのような状態になればOKなのかについて理解していきます。

**1.HTTPリクエストとレスポンスの確認**
Controllerが正しいHTTPリクエストを受け取り、期待されるレスポンス（ステータスコードなど）を返すかどうかをテストします。

**2.ルーティングの確認**
各メソッドが正しいURLにマッピングされているかをテストします。
たとえば、@GetMapping, @PostMapping などのアノテーションが正しく設定されているかどうかを確認します。

**3.サービス呼び出しの確認**
Controllerがサービスを適切に呼びだせているかを確認します。
期待される引数がサービスに渡されて、サービスからの結果を正しく受け取れているかを確認します。

**4.例外処理の確認**
コントローラが特定の例外を処理しているかを確認します。
サービス層で例外が発生した場合にコントローラが適切なエラーレスポンスを返すかを確認します。
    
**5.リクエストパラメータのバリデーション**
リクエストパラメータやリクエストボディが正しくバリデーションされているかを確認します。
不正なデータが送信され時に適切なエラーメッセージやステータスコードが返されるかを確認します。

**6.レスポンスの内容確認**
コントローラーが正しい形式（例えばJSONやXML）でデータを返しているか、またその内容が期待通りであるかを確認します。

**7.ヘッダーの確認**
HTTPヘッダーが正しく設定されているかを確認します。

**8.リダイレクトの確認**
コントローラが適切にリダイレクトを行っているかを確認します。

## Controllerクラステストの簡単な例

```java :HotelController
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/hotels")
public class HotelController {

    @GetMapping("/info")
    public String getHotelInfo(String id, Model model) {
        model.addAttribute("id", id);
        return "hotelInfo";
    }
}
```
```java:  HotelControllerTest
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@SpringBootTest
public class HotelControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetHotelInfo() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/hotels/info") 
                .param("id", "123")) ...1⃣
                .andExpect(MockMvcResultMatchers.status().isOk()) ...2⃣
                .andExpect(MockMvcResultMatchers.view().name("hotelInfo")) ...3⃣
                .andExpect(MockMvcResultMatchers.model().attribute("message", "Hotel info for id: 123")); ...4⃣
    }

    @Test
    public void testGetHotelInfo_NoId() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/hotels/info"))
                .andExpect(MockMvcResultMatchers.status().is4xxClientError()); // IDパラメータなしのため、エラーを期待
    }
}

```

**【解説】**
- MockMvcとは：コントローラーテストをするためのサポートツールでMockMvcを使用すると実際にサーバーを立ち上げることなくHTTPリクエストとレスポンスの処理をシミュレーションしてテストすることができる

```
< HTTPリクエストとレスポンスの確認 >
1⃣：/hotels/info エンドポイントへのGETリクエストを送信し、id パラメータに 123 を設定
2⃣：レスポンスのステータスコードが 200 OK であることを確認

< レスポンスの内容確認 >
3⃣：コントローラーが返すビューの名前が hotelInfo であることを確認
4⃣：モデルに含まれる属性 id の値が 123 であることを確認
```

testGetHotelInfo_NoId()では、パラメータを設定していません。
このメソッドではパラメータなしのリクエストに対してエラーが発生することを確認しています。
具体的には、4xx エラーステータスが返されることを期待しています。


## さいごに
以上が、簡単なJUnitコントローラーテストについての説明ですが、１つずつかみ砕いて理解していくとあまり噛めることはない気がしました。
テストコードを書くことが少ないので、量をこなして慣れていきたいです。
Railsでimportmapが読み込めないエラーの発生と解決

## エラーの発生

rails7を使っていると、ブラウザにて以下のエラーが発生した。身に覚えがない。

```text
An import map is added after module script load was triggered.
Uncaught TypeError: Failed to resolve module specifier "application". Relative references must start with either "/", "./", or "../".
```

importmapが読み込めていないらしい。
だがimportmapに手は加えておらず、viewのjavascript_importmap_tagsを設置する位置をheadタグの一番上に変えたりもしたが効果なし。
調べてもそれらしきものは見当たらず、結果として数時間かけてなんとか解決法できたので備忘録も兼ねて書き残す。

## 試したこと

importmapについて色々調べたが、それらしい情報がない。
が、よくよく見てみればエラーは常に出ているわけではなかった。

- ルートページでのみ発生
- リロード時のみ発生。他ページからの遷移では起こらず

ということだったので、ルーティングかビューが問題なのだろうと推測。
試しにindex.html.erbをまっさらにしてみたところ、エラー起こらず！
だんだんと探索個所を絞ることで原因を特定できた。

## 原因

やっぱりimportmapは何も問題なかった。原因は「javascript_include_tag」。
importmapとjavascript_include_tagが競合したためだろう。

ルートページのみ個別にjsを読み込みたかったので使ってみたのだ。
importmapを使っているのに、よくわからず調べて出てきたものをそのまま使ったがよくなかった。
ネットの情報はrails7より前のものも多くあるため、分からずにコピペで使うのは危険！という話。

## 解決法

javascript_import_module_tagを使用する。
使用方法は以下の記事が分かりやすかった。

https://zenn.dev/yama525/articles/8537244b477059

## まとめ

分からないものを分からないまま使うのは、やめよう！　はい。

調べたかが悪かったのかもしれないというのはさておき、こういうのを残せば後々同じミスした人が出たときに見つけられるのかな～と思ったり。

## おまけ

> - リロード時のみ発生。他ページからの遷移では起こらず

ちなみになんで他ページからの遷移では起こらなかったかというと、別ページを開いた際にimportmapが読み込まれ、turboが動き出したためだった。
つまりは特に本件に関りはないが、これが手がかりだ！といろいろ考えてたのは内緒。
SQLでテーブル定義を変更しよう

## テーブル定義の修正
今回は以下のようなテーブル定義の変更を行うよ。
* テーブル作成
* テーブル名変更
* カラム名変更
* カラム追加
* カラム削除
* デフォルト値の設定
* NOT NULL制約
* プライマリーキー追加
* テーブル削除

### テーブル作成
まずは以下のような`parson`テーブルを作成するよ。
| id | name | age |
|:-:|:-:|:-:|
|   |   |   |
```sql
CREATE TABLE parson (id INTEGER, name VARCHAR(100), age INTEGER);
```
### テーブル名変更
先ほど作成した`parson`テーブルを`parson_detail`テーブルに変更するよ。
```sql
ALTER TABLE parson RENAME TO parson_detail;
```

### カラム名変更
`parson_detail`テーブルの列名`name`を`first_name`に変更するよ。
```sql
ALTER TABLE parson_detail RENAME COLUMN name TO first_name;
```
| id | first_name | age |
|:-:|:-:|:-:|
|   |   |   |
### カラム追加
次にカラムの追加を行うよ。今回は`parson_detail`テーブルの列名`first_name`の後に`last_name`を追加するよ。
```sql
ALTER TABLE parson_detail ADD last_name VARCHAR(100) AFTER first_name;
```
| id | first_name | last_name | age |
|:-:|:-:|:-:|:-:|
|   |   |   |   |
### カラム削除
今度は`parson_detail`テーブルから列名`age`を削除するよ。
```sql
ALTER TABLE parson_detail DROP age;
```
| id | first_name | last_name |
|:-:|:-:|:-:|
|   |   |   |
### デフォルト値の設定
次にデフォルト値の設定を行うよ。
ひとまず、`parson_detail`テーブルに列名`age`を追加した以下のようなテーブルを用意してね。
| id | first_name | last_name | age |
|:-:|:-:|:-:|:-:|
|   |   |   |   |

ここで、`age`にデフォルトで`0`を入れるようにするよ。
```sql
ALTER TABLE parson_detail ALTER COLUMN age SET DEFAULT 0;
```
### NOT NULL制約
今度は、指定した列名に対して`NOT NULL制約`をつけるよ。
ここでは`first_name`はNULLでないようにするよ。
```sql
ALTER TABLE parson_detail ALTER COLUMN first_name VARCHAR(100) SET NOT NULL;
```

### プライマリーキー追加
プライマリーキーへの変更をするよ。
```sql
ALTER TABLE parson_detail ADD PRIMARY KEY (id);
```

### テーブル削除
最後に`parson_detail`テーブルを削除するよ。
```sql
DROP TABLE parson_detail;
```

## まとめ
テーブル定義修正のあれこれを書いたよ。まだほかにもあるけれどまずはこれを押さえておこう。
海外の謎業者に無料でクローズドテスト20人分を代行してもらえた話

## 前置き
GooglePlayストアにアプリをリリースする際、2023年11月13日以降に新規に作成した個人用デベロッパーアカウントの場合は
**「20人以上のテスターが14日以上連続でクローズドテストに参加」**
といった要件を満たす必要があります。

## 発見
この問題、海外の人たちはどう対処しているのだろうと気になって「android closed test」でググると3ページ目ぐらいで下記のサイトを見つけました。

Closed App Testing
https://www.closedapptesting.com/

一言で言うと、これは
**Androidアプリのクローズドテスト代行に特化したサイト**でした。
ちなみに、あまりにも簡素な作りだったので私の第一印象は「怪しい」でした。

## 料金について
・1アプリまでは無料で20人分代行してもらえる
・2つ目のアプリからはスタンダードプランに乗り換える必要がある
・スタンダードプランの場合、テスター1人につき0.25€（※）を支払う
ということが書かれています。
※日本円にすると約40円（2024年8月29日のレートで換算）

## お願いしてみた結果
はい、この記事のタイトル通り無料で20人分を代行してもらえました。
最初に一度催促をしてからは途切れることなく、「20人での14日間のオプトイン」という不可能に近いミッションをこの謎業者はコンプリートしてくれました。
正直、途中で離脱されたり最終的にお金を請求されたりするんだろうなと疑ってかかっていたので拍子抜けでした。
まだ完全に信用したわけではありませんが、今は感謝しかありません。
このサイトを利用してみたいという方は以下の内容も参考にしていただければと思います。

## リクエスト後の状況
クローズドテストのリクエストをするとダッシュボード画面に入れるようになります。
そのダッシュボード画面には「確認に最大48時間かかる。問題がある場合はメールしてください。」といった旨の文言が書かれています。
そして丸2日待って何も動きはありませんでした。
というか自動送信メールすら来ませんでした。

## 催促の方法
以下の方法で催促しました。
1.ダッシュボード画面で画像赤枠部分をクリック
![ss1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3844767/ae32c9d8-1633-db41-266f-cfb011044d91.png)

2.下記のポップアップが出てくるのでテンプレート部分をコピペ、そこに書かれたメールアドレスにメールを送る
![ss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3844767/7579e644-e7dd-41bb-28ca-6c892182fddf.png)

私の場合、この翌日に20人分のクローズドテストが始まりました。

## 最後に
サイトの利用は自己責任でお願いします。
1アプリまでとは言え、無料なのは何か裏がある可能性もあります。
これは完全な憶測ですが、例えばリリース前のアプリのアイディアや技術を盗むことをもう一つの目的としているなんてことも考えられますよね。
それとフィードバックをもらえないというのも踏まえて利用を検討されるのがよろしいかと思います。
そこが国内のマーケットなどでお金を払ってお願いする場合との一番の違いですね。

## 最後の最後に（コミュニティの紹介）
Xに相互クローズドテストを目的としたコミュニティが存在するので紹介させてください。

▼Android クローズドテスト コミュニティ
https://x.com/Android189473

私がこのコミュニティを知ったのは業者にリクエストした後だったのでタイミングが合いませんでしたが、次回からは利用させていただこうと思っています。
謎業者を紹介しておいてなんですが、やっぱりお金をかけずに安心してお願いできるのが理想ですから。
個人開発者の方は是非最初の選択肢として検討してみてください。
TypeScriptにおける型安全性: ! と ? 演算子について

# はじめに

6年くらい、PythonとPHP中心に活動してましたが、最近TypeScriptの開発も副業でアサインさせていただき、勉強した内容の備忘録を記載させていただきます。

## 型安全性: ! と ? 演算子について
TypeScriptは、JavaScriptに型安全性を提供し、開発者が実行時のエラーを防ぎ、より信頼性の高いコードを書くことを可能にします。特に、null や undefined の値を扱う際に役立つ2つの重要な演算子が、非nullアサーション演算子（!）とオプショナルチェーン演算子（?.）です。この記事では、info.user!.name と info.user?.email という例を使って、これらの演算子がどのように機能するのか、また値が undefined や null だった場合のについて記載します。

## 非nullアサーション演算子 (!)
非nullアサーション演算子 (!) は、特定の変数が null または undefined ではないと確信している場合に使用します。この演算子を使うことで、TypeScriptに対して「この値は必ず存在する」と伝え、型チェックをバイパスすることができます。

```.tsx
const info = getUserInfo(); // info.user は null または undefined の可能性がある
const userName = info.user!.name;
```
この例では、info.user!.name という記述により、info.user が null や undefined でないことを保証しています。これにより、TypeScriptはエラーを出さずに name プロパティにアクセスできるようになります。

### info.user が null または undefined だった場合の動作
もし info.user が実際には null または undefined であった場合、このコードは実行時エラーを引き起こします。具体的には、「undefined のプロパティ ‘name’ を読み取ることができません」というエラーが発生し、アプリケーションがクラッシュする可能性があります。

```.tsx
function getUserInfo(): { user?: { name: string } } {
    return { user: undefined }; // user が undefined であることをシミュレート
}

const info = getUserInfo();
const userName = info.user!.name; // 実行時エラー: Cannot read property 'name' of undefined
```

### info.user!.name を使うべき場合
- info.user が確実に null や undefined ではないと確信している場合。
- TypeScriptの型推論が、info.user が非nullであることを認識できないが、プログラムのロジック上、安全であると分かっている場合。

## オプショナルチェーン演算子 (?.)
オプショナルチェーン演算子 (?.) は、null または undefined である可能性のあるオブジェクトのプロパティに安全にアクセスするための演算子です。この演算子を使うことで、オブジェクトが存在しない場合でもエラーを発生させることなく処理を進めることができます。

```.tsx
const info = getUserInfo(); // info.user は null または undefined の可能性がある
const userEmail = info.user?.email;
```

この場合、info.user?.email は「info.user が存在する場合は email を返し、存在しない場合は undefined を返す」という意味になります。

### info.user が null または undefined だった場合の動作:
もし info.user が null または undefined であれば、info.user?.email の式は undefined を返し、エラーは発生しません。これにより、アプリケーションがクラッシュすることなく、コードは安全に動作します。

```.tsx
function getUserInfo(): { user?: { email: string } } {
    return { user: undefined }; // user が undefined であることをシミュレート
}

const info = getUserInfo();
const userEmail = info.user?.email; // undefined を返し、エラーは発生しない
console.log(userEmail); // 出力: undefined
```

- info.user が null または undefined である可能性があり、その場合でも安全に処理を行いたい場合。
- null または undefined の値が存在する可能性がある状況で、実行時のクラッシュを避けたい場合。

## まとめ

**1. 安全性と保証:**
- info.user!.name は、info.user が常に存在することを前提にしており、型安全性チェックを無視します。
- info.user?.email は、info.user が存在しない場合でも安全に処理を行うため、より安全なにあたいが存在しない場合の対処ができます。
	
**2. エラーハンドリング:**
- info.user!.name は、info.user が null または undefined であれば、実行時エラーを引き起こします。
- info.user?.email は、info.user が null または undefined であれば、undefined を返してエラーを回避します。

**3. 想定される使用ケース:**
- info.user!.name は、info.user が常に存在すると確信している場合に使用します。
- info.user?.email は、info.user が存在しない可能性がある状況でも安全に処理したい場合に使用します。
ViteからRsbuild(Rspack)に移行してみる


# はじめに

Rspack 1.0 の発表されたついでに、ビルドツールをViteからRsbuildに移行してみる

Rsbuildについて

https://rsbuild.dev/

Rspack v1.0について

https://rspack.dev/blog/announcing-1-0

# 移行

今回はVueのプロジェクトを移行してみる

下記の公式ガイドがとても親切でわかりやすい
https://rsbuild.dev/guide/migration/vite
今回はVueなので下記を利用
https://rsbuild.dev/plugins/list/plugin-vue

vite.config.jsからrsbuild.config.mjsに変更する

```diff_javascript:vite.config.js
-import { fileURLToPath, URL } from 'node:url'
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'

-export default defineConfig({
-  plugins: [
-    vue(),
-  ],
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  }
-})
```

```javascript:rsbuild.config.mjs
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from '@rsbuild/core';
import { pluginVue } from '@rsbuild/plugin-vue';

export default defineConfig({
  plugins: [pluginVue()],
  html: {
    template: './index.html',
  },
  source: {
    entry: {
      index: './src/main.js',
    },
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
});
```

ビルドエントリは
Vite index.html => Rsbuild src/index.js　になるため、上記のように記述する
https://rsbuild.dev/guide/migration/vite#build-entry

index.html の 下記は削除
```diff_html:index.html
-  <script type="module" src="/src/main.js"></script>
```

ベースURLは 
Viteだと　import.meta.env.BASE_URL　
Rsbuildだと　process.env.ASSET_PREFIX になる
https://rsbuild.dev/guide/advanced/env-vars#processenvasset_prefix

package.jsonも変更

```diff_json:package.json
  "scripts": {
-     "dev": "vite",
-    "build": "vite build",
-    "preview": "vite preview"
    "dev": "rsbuild dev",
    "build": "rsbuild build",
    "preview": "rsbuild preview"
  }
```


# ビルド速度について

ファイルが少ないVueのプロジェクトで移行して全く参考にならないので

正確なベンチマークは下記を参考にしてください。🙇‍♂️

https://github.com/rspack-contrib/performance-compare?tab=readme-ov-file#build-tools-performance-compare

(個人の感想)

移行してみた体感としてはnpm run devの速度は変わらないが🤔、、、

npm run buildの速度が速くなった😄

# さいごに

RspackはBreakthrough of the Year Awardを受賞していたり、人気が出ているので

今後も注目していきたい。あと、Rspackの蟹のキャラクターがとても可愛い!!🦀

https://osawards.com/javascript/


追記、最近だとFarm🧑‍🌾っていうのも速いらしい

https://www.farmfe.org/
ping について

お疲れさまです！
未経験で参画してから３ヶ月経ちました。わからないことだらけですが、ラッキーな事に構築作業の補助をさせていただいてます。
最近は飛び交う宇宙語をスピードラーニングの如く聞き、後々勉強してるとこれを言っていたのかー！という伏線回収が楽しみです。
今日は恐らく一日千回は聞いているpingについてご紹介します。

# pingとは
ネットワーク上でホスト間の通信状況を確認するためのネットワークの基本的なコマンドです。
ICMP（Internet Control Message Protocol）を使って、指定したIPアドレスまたはホスト名にエコーリクエストパケットを送信し、その応答を待つことで、ネットワークの接続性をチェック出来ます。
ちなみにpingの由来は音響ソナーです。水中で物体を探すために音波を発してその反響を利用して位置を特定します。pingコマンドは１９８３年にマイク・ムース氏によって開発されました。彼はネットワークのトラブルシューティングや接続確認に役立つツールを作りたいと考え、この名前をつけたそうです

ping【ホスト名またはIPアドレス】

ping www.google.co.jp

と入力すると、Googleのサーバーに対してリクエストを送信し、その応答を受け取ることが出来ます。
応答があるということはネットワーク接続が正常であるということが確認出来ます。


# 標準ping
標準pingは最も基本的なpingコマンドの実行方法です。
ホストにエコーリクエストを送信し、応答の時間を測定するだけのシンプルなものです。
一般的に4回のリクエストをし、それに対する応答時間を示します。
こちらは成功例です。
ルーターからPCにpingを飛ばしてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782993/548aa906-1522-1e51-0045-94fab0f43cb4.png)

ping応答メッセージ一覧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782993/a0c04b3a-90e0-1b40-596a-db42ff1ecba0.png)

# 拡張ping
拡張pingとはより詳細なオプションを提供するping機能です。
例えば送信するパケットの数やサイズを指定したり、タイムアウトの設定、TTL（Time to Live）値の指定などが可能です。拡張pingを使用することで、特定の条件での応答を確認し、ネットワークのトラブルシューティングを行う際に役立ちます。

ルーターからPCに拡張pingをしてみます。
pingと入力したらenterを押すと、プロトコルなど色々聞いてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3782993/7edaf14b-aa5f-1af6-23fe-3ef0b3262077.png)

拡張pingの一部で、さらに詳細な情報を提供する詳細pingがあります。
パケットの送信間隔やより多くの送信オプションを設定し、個別のホストへのリクエストの応答をさらに深掘りして調査することが出来ます。
これにより、ネットワークの性能問題や以上の診断がより具体的に行えるようになります。
詳細pingは一般的に高度なユーザーやネットワーク管理者向けの機能です。

# まとめ
ネットワーク初心者が初めて使うコマンドはpingだと思います。
ネットワークとはデバイス同士が相互に接続し、情報を効率的にやりとりするための基盤です。
なので現場で何かトラブルが発見された際はまずpingで確認して！と言われます。
参画前に勉強していた時はこんなコマンドあるんだ程度でしたが、超重要なコマンドでした。
次回はICMPについてまとめたいと思います。
ここまで読んでくださりありがとうございます！
ゼロから始めるプログラム学習（C#）_002

##    はじめに

 プログラミング言語 __<font color="blue">　【C#『シーシャープ』】</font>__ を使用して
 プログラムを最初から学習するための記載。 


　　　前回：ゼロから始めるプログラム学習（C#）_001
　　　https://qiita.com/nekoozi/items/33057a27b09213618c85


###### なお、演算子については、以下を参照
https://qiita.com/nano357/items/87278d3e36636df8a6d8


## <font color="green">変数</font>
プログラム内でデータを一時的に保存するためのもの
--------------------------------------------------------------


### 変数の宣言の構文

>データ型 変数名;

データ型 は、変数が保持するデータの種類を指定します。

変数名 は、変数を識別するための名前です。識別子の命名規則に従って名前を付けます。

### 識別子の命名規則

英字またはアンダースコア _ で始めることができます。

英字、数字、アンダースコアを組み合わせて使用できます。

大文字と小文字は区別されます。
```C#
using System;

namespace MyFirstCSharpProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            // int型の変数を宣言して初期化
            int myNumber = 42;

            // string型の変数を宣言して初期化
            string myName = "John";

            // コンソールに変数の値を表示
            Console.WriteLine($"My number is

````
なお、予約語は、プログラムの構文や意味に影響を与えるもので、
変数名やクラス名として使用することはできません。

https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/


## <font color="green">定数</font>
定数（Constants『コンスタント』）は、
プログラム内で変更されない値を表すものです。


### 定数の宣言の構文

>const データ型 定数名 = 初期値;

データ型 は、定数が保持するデータの型を指定します。

定数名 は、定数を識別するための名前です。

初期値 は、定数の初期値を指定します。

###### 定数はプログラム内で変更されない値を表すために使用されます。以下は、定数を使う一般的なケースです
````
    数学的な定数:
        数学的な定数（例: π、e）は、プログラム内で変更されることがないため、
        定数として宣言されます。
        例えば、円周率 π を計算するプログラムでは、
        const double Pi = 3.14159; のように定数を使用します。

    物理的な定数:
        物理的な定数（例: 重力加速度、光速）も同様に定数として宣言されます。
        これらの値は科学的な計算やシミュレーションで使用されます。

    マジックナンバーの代替:
        プログラム内で何度も使用される特定の値（マジックナンバー）は、
        定数として宣言することで可読性を向上させます。
        例えば、const int MaxRetryCount = 3; のように、
        リトライ回数の上限を定数として宣言できます。

    プログラムの設定値:
        プログラムの設定やパラメータに関連する値は、定数として宣言されることがあります。
        例えば、データベース接続文字列やタイムアウト値を定数として管理できます。
````

###### 以下は、定数を使ったシンプルなサンプルプログラムです。このプログラムでは、月の数を表す定数を宣言しています。
```C#
using System;

namespace ConstantsExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 月の数を表す定数を宣言
            const int Months = 12;

            // 定数を使ってメッセージを表示
            Console.WriteLine($"1年は{Months}ヶ月です。");
        }
    }
}
````



--------------------------------------------------------------


　　　次回：ゼロから始めるプログラム学習（C#）_003
　　　https://qiita.com/nekoozi/items/08a97f0df4668d638481


## 参考文献

C#関連情報サイト様

   https://qiita.com/YutaManaka/items/62dda256bb7ba6c08399

   https://codic.jp/engine
開発者ツール使ってると目に付く__loopupGetter__ってなんだ!?

開発者ツールで入力候補に`__loopupGetter__`や`__defineSetter__`等のなんだか没そうなメゾッドがあります。
これってなんでしょう???
## これは？
オブジェクトに自動でつくメゾッドです。
正確にはObject.prototypeで宣言されたメゾッドです。
## ことわり
これらのメゾッドは全て非推奨です。また、代替はあるため使わないで下さい。
## \_\_defineGetter\_\_
そのオブジェクトのゲッター、内容を取得する関数を宣言します。
```js:例
let a = {}
a.__defineGetter__("b",()=>"c")
console.log(a.b) //c
```
```js:代替
let a = {}
Object.defineProperty(a,"b",{
    get:()=>"c"
})
console.log(a.b) //c
```
## \_\_defineSetter\_\_
そのオブジェクトのセッター、内容を入力する関数を宣言します。
```js:例
let a = {}
let _b = ""
a.__defineSetter__("b",val=>{_b = "The " + val; return true})
a.__defineGetter__("b",()=>_b)
a.b = "End"
console.log(a.b) //The End
```
```js:代替
let a = {}
let _b = ""
Object.defineProperty(a, "b", {
    get: ()=>_b,
    set: val=>{_b = "The" + val;return true}
})
a.b = "End"
console.log(a.b) //TheEnd
```
## \_\_lookupGetter\_\_
そのオブジェクトのゲッター関数を取得します。関数宣言がなければundefinedです。
```js:例
let a = {}
a.__defineGetter__("b", ()=>29)
console.log(a.__lookupGetter__("b"))
//()=>29
```
```js:代替
let a = {}
Object.defineProperty(a,"b",{
    get:()=>29
})
console.log(Object.getOwnPropertyDescriptor(a,"b").get)
//()=>29
```
## \_\_lookupSetter\_\_
そのオブジェクトのセッター関数を取得します。関数宣言がなければundefinedです。
```js:例
let a = {}
let __b = ""
a.__defineSetter__("b",val=>{__b="The "+val;return true})
console.log(a.__lookupSetter__("b"))
//val=>{__b="The "+val;return true}
```
```js:代替
let a = {}
let __b = ""
Object.defineProperty(a,"b",{
    set:val=>{__b = "The " + val;return true}
})
console.log(Object.getOwnPropertyDescriptor(a,"b").set)
//val=>{__b = "The " + val;return true}
```
## 仕様について

[https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupGetter__](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupGetter__)

これによると仕様書では実装の要求はないようですが、主要なブラウザーでは
このすべてが実装されており、削除される可能性は低いそうです。しかし、代替はありますし
仕様的に非推奨なので`Object.xxx`のやつを使いましょう。
fairseqのhydra_integrationを読んだメモ

# Hydra Integration

[https://github.com/facebookresearch/fairseq/blob/main/docs/hydra_integration.md](https://github.com/facebookresearch/fairseq/blob/main/docs/hydra_integration.md)

- Hydraはオープンソースフレームワークで、研究開発をシンプルにする
- 主な特徴は、動的に階層的なコンフィギュレーションを作成し、コンフィギュレーションファイルやコマンドラインによってそれを上書きできること
- 名前の由来は多数の似たジョブを実行する能力から、たくさんの頭を持つヒドラから

Motivation

- 最近までfairseqのすべてのコンポーネントは共有のargsネームスペースを通して設定されていた
- コンポーネントは自身のadd_argsメソッドを宣言してargparseパーサを更新し、他のコンポーネントの引数と名前が衝突しないようにする
- これはfairseqが大きくなり他のアプリと統合されるにつれて問題になった
- 各コンポーネントを設定する方法を決めるために、a) どんなargsがこのコンポーネントによって追加されたかを調べて、b) コードを読んで他で追加されたどの共有引数を使っているかを把握する必要があった
- モデルを再現するには、何十ものコマンドラインスイッチを含むコマンドを共有する必要があった
- 上記のようなモデルは後方互換のためまだサポートされているが、将来非推奨です
- fairseqの新しいコンポーネントは、そのコンポーネントを設定するのに必要な全パラメータをカプセル化するデータクラスを作ります
- そのデータクラスはコンポーネントと一緒に登録され、このコンフィグレーションオブジェクトを構築し、コンポーネントのコンストラクタに提供します
- パラメーターの共有はオプションでまだ機能するが、"source of truth"を明示的に指し示す必要があることに注意（下記の継承の例を参照）
- これらの変更はfairseqのコンポーネントをより独立かつ再利用可能にします
- コンポーネントを作るには、そのデータクラスをイニシャライズしていくつかのデフォルトを上書きするだけでよい
- 階層化された YAML 設定ファイルによって、fairseq を完全に、あるいは部分的に設定できるようになりました
- 可用性高まる

Creating or migrating components

- 新しいコンポーネントはデータクラスを提供する必要があります
- このデータクラスは通常コンポーネントと同じファイルに配置され、引数として `register_*()` 関数に渡されます
- すべてのfairseqアプリケーションに存在するべきTop-level configはglobal configファイルに置かれ `FairseqConfig`  オブジェクトに追加される
- 各データクラスは、NamedTupleに似た、古いデータ・オブジェクトで、 `@dataclass` デコレータでデコレートされ通常 `FairseqDataclass` を継承する
- 各フィールドは型を持たなければならず、一般的にはメタデータ（ヘルプ文字列など）とデフォルト値を持つ
- 各フィールドのデータ型として許されるのは、プリミティブ型またはその他のコンフィグ・オブジェクトのみである

**Inheriting values**

- コンポーネントによっては、値を共有する必要がある。例えば、学習率スケジューラとオプティマイザは、ともに学習率の初期値を知っている必要があるかもしれない
- デフォルトで同じ階層の別のconfigからその値を継承するフィールドを宣言することができます
- `II("")` は変数埋め込みのシンタックスシュガーとして使用できる

Tasks and Models

- TaskやModelの作成は以前度同じく動作しますが、 `LegacyFairseq*` ベースクラスから継承したレガシーな実装は除きます
- 新しいコンポーネントは `FairseqTask` と `FairseqModel` を継承しデータクラスを `register_*()` 関数へ与えます

Other Components

- 他のコンポーネントは以前と同じように動作しますが、コンストラクタの引数としてコンフィギュレーション・データクラスを取るようになりました
- 新しいコンポーネントのセットに対して新しいレジストリを追加する場合は、fairseq/dataclass/config.pyのFairseqConfigオブジェクトに追加する必要があります

Training with fairseq-hydra-train

- Hydraによるコンフィグレーションの柔軟性を完全に活用するために、 `fairseq-hydra-train` で新しいモデルを訓練したいかもしれない
- `fairseq-train` のようなレガシーCLIツールはまだサポートされているが徐々に非推奨になる
- 起動時にHydraは必要なすべてのデータクラスの階層を含む設定オブジェクトを作成し、そのデフォルト値をコードに入力します
- デフォルト値は `farseq/config`にあるYAMLファイルの値で上書きされ、それからさらにコマンドライン引数の値で上書きされる
- ユースケース
    - **Override default values through command line**
    - **Replace bundled configs with an external config**
        - `farseq/config` のconfigは使われずexternal configで上書きしない限り各データクラスのデフォルトが使われる
        - modelやdatasetなどトップレベルのフィールドの名前を持つmain config fileとして同じ場所にディレクトリ構造を作り、トップレベルのconfig fileの特定のセクションを入力する意味のある名前でconfig fileを置き換えることで、configを分割することもできる
        - その後、コマンドラインで正しい設定を指定したり、メイン設定のデフォルトを指定したり、あるいはスイープとしてそれらすべてを起動することもできます（この方法についてはHydraのドキュメントを参照してください）
    - **Add an external config directory to Hydra search path**
        - これにより、デフォルトのコンフィギュレーション（バンドルされているコンフィギュレーション・ファイルの使用を含む）を組み合わせながら、コンフィギュレーションの一部に独自のコンフィギュレーション・ファイルを指定することができる。
GCPでSSHのportを変更したときのハマりポイント

環境はUbuntu 24.04 LTS Minimalです

### 要約

/usr/lib/systemd/system/ssh.service の以下を変更しないと /etc/ssh/sshd_config の設定が正しくよみこまれない
```
#EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D
```

```sh
sudo systemctl daemon-reload
sudo systemctl restart ssh
sudo systemctl status ssh
```
でログを確認してください

### その他

#### /etc/ssh/sshd_config の設定例
```
Port ${YOUR_PORT}
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2
```

#### ファイヤーウォールの設定
ソースフィルターは0.0.0.0/0を指定
優先度は65534などにしておく(65535にすると適切に動かない)
TCPの${YOUR_PORT}を指定する

VM内のファイヤーウォールはどうやらないようです(iptables, nft, ufw, firewalldが動いていないことを確認しました)

#### チェック
以下で試せます
```sh
ssh your_name@vm_ip -p your_port -i ~/.ssh/id_your_key
```

うまくいったら ~/.ssh/config に以下のように設定しましょう
```
Host gcp
    Hostname vm_ip 
    User your_name
    Port your_port
    IdentityFile .ssh/id_your_key
```

#### おまけ
VM作成の際に
セキュリティ > 手動で生成した SSH 認証鍵の追加
で公開鍵の追加ができますがGoogleカウントのユーザー名と自分のPCのアカウント名が異なっている場合、ただしく認証できません
なので鍵を追加する際にユーザー名を合わせるように修正しましょう

#### GCP Consoleからのアクセス
VMインスタンスのページに飛んで
ブラウザウィンドウでカスタムポートを開く
をクリックしてください
画像生成AIの活用法と著作権問題について調べてみました

# 1.はじめに

どうも、ARIの名古屋支社に勤務している愛知県民こと、
新藏（にいくら）と申します♪
(/・ω・)/

前回の記事では、画像生成AIの技術について記載しました。

https://qiita.com/Aichi_Lover/items/997dec9a535b0744b93f

**そこで今回は、画像生成AIの使用用途や注意点について記事にしたいと思います！**
画像生成AIに興味のある方や、使ったことがある人の参考になれば幸いです。
(*^^)v

# 2. 画像生成AIの活用法
画像生成AIの活用法は、イラスト作成やロゴの作成等多岐に渡ります。

その反面、絵を描く仕事をする人からすると、
**画像生成AIは仕事を奪う敵**と認識されている印象があります。

自分が調べた画像生成AIの活用法の中で、
画像生成AIの強みを活かしていると感じた例を下記にあげます。

- **広報の人が、自社の商品の写真とAI生成画像を組み合わせてプロモーションに使用する**
- **犯罪に巻き込まれた人が、犯人の顔を思い出すのに使用する**
- **絵を描く人が、草案や背景を作成するために使用する**

:::note info
個人の意見ですが、画像生成AIはあくまで既存の画像を学習して出力しているので、
人が描く絵のような無から生み出すクリエイティブさはないと考えていて、
上記の例は人とAIが住み分けできている活用例のため、良いと感じました。
:::

# 3. 画像生成AIと著作権問題
そんな便利な画像生成AIですが、著作権問題がどうなっているのか気になったので、
学習面と生成面から調べてみました。

## 3.1.画像生成AIの学習時の著作権について

文化庁が2023年に作成した以下の資料を読みました。
https://www.bunka.go.jp/seisaku/chosakuken/pdf/93903601_01.pdf

資料によると、
>AI開発のための情報解析のように、著作物に表現された思想又は感情の享受を目的としない利用行為は、
原則として著作権者の許諾なく行うことが可能です（権利制限規定）。

とのことなので、AIの学習行為は違法となる「複製」や「公衆送信」ではなく、
「情報解析のための利用」＝「著作物に表現された思想又は感情の享受 を目的としない利用」
にあたり、**基本的には著作権者の許諾なく使えるそうです。**

:::note warn
「基本的には」なので例外もあると思います・・・
:::

## 3.2.画像生成AIの生成時の著作権について
3.1で紹介した資料を確認すると、
AIを用いて生成した画像が著作権侵害となるかどうかの判断は、
人が描いた場合と同様に判断されるとのことです。

>この著作権侵害の要件として、裁判例では、
① 「後発の作品が既存の著作物と同一、又は類似していること」（類似性）
② 「既存の著作物に依拠して複製等がされたこと」（依拠性）
……の両方を満たすことが必要とされています。

つまり、**AI生成画像であっても著作権侵害と判断される可能性があります。**

AI生成画像は基本的に著作物に該当しませんが、
創作意図がある場合は著作物に該当すると考えられるとのことです。

# 4.画像生成AIで著作権侵害しないために気を付けるポイント
3章では著作権自体について説明したので、
本章では画像生成AIで著作権侵害をしないために、
気を付けるポイントについてまとめたいと思います。

結論としては以下の4点がポイントと感じました。

* **漫画やキャラクターなどのIP（知的財産）を使用しない**
* **現役作家の名前を使用してAI画像を作成しない**
※死後70年を経過していれば著作権切れになります

* **実在の有名人の名前を使用しない**
※ディープフェイクが作成され、広まるとその人の仕事を邪魔してしまう可能性がある
* **できるだけ非営利目的で使用する**

参考：先読み!ITxビジネス講座　画像生成AI 　深津貴之著

:::note info
知り合いに画像生成AIを使う機会の多い人や法律に詳しい人がいれば、
まずは相談するのも1つの手と感じました。
:::

# 5.まとめ
ここまで読んで下さり、ありがとうございます！！！
(^^)

今回は画像生成AIの著作権に対する注意点について調べました。
上記の内容に気をつけつつ、仕事の資料に入れるイメージ画像や勉強の覚書用で、
有効活用したいと感じました

引き続きよろしくお願いいたします♪
(:3_ヽ)_