Compare commits
9 Commits
6e1b0c597b
...
claude
Author | SHA1 | Date | |
---|---|---|---|
d0746a7af6
|
|||
a4f7f867f5
|
|||
81db8cfe29
|
|||
6513d626de
|
|||
a17d2c9d66
|
|||
998777d46a
|
|||
61d7df6922
|
|||
eb8f1b17c8
|
|||
fc5e942f0c
|
23
.claude/settings.local.json
Normal file
23
.claude/settings.local.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(cargo check:*)",
|
||||||
|
"Bash(cargo test)",
|
||||||
|
"Bash(cargo test:*)",
|
||||||
|
"Bash(grep:*)",
|
||||||
|
"Bash(cargo install:*)",
|
||||||
|
"Bash(cargo make:*)",
|
||||||
|
"Bash(cargo:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(./target/debug/aibot --help)",
|
||||||
|
"Bash(./target/debug/ai --help)",
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(chmod:*)",
|
||||||
|
"Bash(git checkout:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(rg:*)"
|
||||||
|
],
|
||||||
|
"deny": []
|
||||||
|
}
|
||||||
|
}
|
Submodule .config/ai/scpt updated: 9cbca76fc7...a1905d104b
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
target
|
target
|
||||||
*.json
|
#*.json
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
**.DS_Store
|
**.DS_Store
|
||||||
scpt/json/
|
scpt/json/
|
||||||
|
17
Cargo.toml
17
Cargo.toml
@ -1,15 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ai"
|
name = "aibot"
|
||||||
authors = ["syui"]
|
authors = ["syui"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "latest@2024-08-18"
|
description = "ai.bot - Bluesky AT Protocol Bot"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "aibot"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ai"
|
||||||
|
path = "src/alias.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
seahorse = "*"
|
seahorse = "*"
|
||||||
reqwest = { version = "*", features = ["blocking", "json"] }
|
reqwest = { version = "*", features = ["blocking", "json"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
shellexpand = "*"
|
|
||||||
config = "*"
|
config = "*"
|
||||||
serde = "*"
|
serde = "*"
|
||||||
serde_json = "*"
|
serde_json = "*"
|
||||||
@ -19,3 +26,7 @@ rustc-serialize = "*"
|
|||||||
toml = "*"
|
toml = "*"
|
||||||
iso8601-timestamp = "*"
|
iso8601-timestamp = "*"
|
||||||
sysinfo = "*"
|
sysinfo = "*"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mockito = "1.2"
|
||||||
|
tokio-test = "0.4"
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
FROM syui/aios
|
FROM syui/aios
|
||||||
ADD .ssh /root/.ssh
|
|
||||||
|
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
ADD ./test/entrypoint.sh .
|
ADD ./test/entrypoint.sh .
|
||||||
RUN chmod +x /root/entrypoint.sh
|
RUN chmod +x /root/entrypoint.sh
|
||||||
RUN pacman -Syu bc --noconfirm
|
|
||||||
|
|
||||||
ENTRYPOINT ["/root/entrypoint.sh"]
|
ENTRYPOINT ["/root/entrypoint.sh"]
|
||||||
|
@ -17,6 +17,14 @@ command = "cargo"
|
|||||||
args = ["test"]
|
args = ["test"]
|
||||||
dependencies = ["clean"]
|
dependencies = ["clean"]
|
||||||
|
|
||||||
|
[tasks.test-quick]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["test"]
|
||||||
|
|
||||||
|
[tasks.test-verbose]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["test", "--", "--nocapture"]
|
||||||
|
|
||||||
[tasks.my-flow]
|
[tasks.my-flow]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"format",
|
"format",
|
||||||
|
35
README.md
35
README.md
@ -107,3 +107,38 @@ ADMIN=syui.syu.is
|
|||||||
$ docker compose build
|
$ docker compose build
|
||||||
$ docker compose up -d
|
$ docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## pds:card
|
||||||
|
|
||||||
|
- https://atproto.com/ja/guides/lexicon
|
||||||
|
- https://at.syu.is/at/did:plc:uqzpqmrjnptsxezjx4xuh2mn/ai.syui.card/3lagpwihqxi2v
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# oauth(button)
|
||||||
|
[yui]ai.syui.card.verify -> [user]ai.syui.card
|
||||||
|
|
||||||
|
[yui]
|
||||||
|
$ ./target/debug/ai card-verify -i 0 -p 0 -r 0 -h syui.ai -d did:plc:uqzpqmrjnptsxezjx4xuh2mn
|
||||||
|
{"uri":"at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.card.verify/3lagpvhppmd2q"}
|
||||||
|
|
||||||
|
[user]
|
||||||
|
$ ./target/debug/ai card -i 0 -p 0 -r 0 -v at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.card.verify/3lagpvhppmd2q
|
||||||
|
```
|
||||||
|
|
||||||
|
## pds:game
|
||||||
|
|
||||||
|
- https://atproto.com/ja/specs/record-key
|
||||||
|
- https://at.syu.is/at/did:plc:uqzpqmrjnptsxezjx4xuh2mn/ai.syui.game/self
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# oauth(play)
|
||||||
|
[yui]ai.syui.game.user -> [user]ai.syui.game
|
||||||
|
|
||||||
|
[account]
|
||||||
|
# https://at.syu.is/at/did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.game.user/syui
|
||||||
|
## [rkey]
|
||||||
|
1. echo $handle|cut -d . -f 1
|
||||||
|
2. $handle
|
||||||
|
3. tid
|
||||||
|
```
|
||||||
|
|
||||||
|
117
docs/README.md
Normal file
117
docs/README.md
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# ai.bot ドキュメント
|
||||||
|
|
||||||
|
ai.botプロジェクトの包括的なドキュメント集です。
|
||||||
|
|
||||||
|
## ドキュメント一覧
|
||||||
|
|
||||||
|
### 開発者向け
|
||||||
|
|
||||||
|
1. **[開発ガイド](./development-guide.md)**
|
||||||
|
- プロジェクト概要とアーキテクチャ
|
||||||
|
- 開発環境のセットアップ
|
||||||
|
- 開発ワークフローとベストプラクティス
|
||||||
|
- 新機能追加の手順
|
||||||
|
|
||||||
|
2. **[HTTPクライアントAPI](./http-client-api.md)**
|
||||||
|
- HttpClientモジュールの完全なAPIリファレンス
|
||||||
|
- 使用例とサンプルコード
|
||||||
|
- エラーハンドリングのベストプラクティス
|
||||||
|
|
||||||
|
### 保守・運用向け
|
||||||
|
|
||||||
|
3. **[移行ガイド](./migration-guide.md)**
|
||||||
|
- パッケージ名・CLI名の変更詳細
|
||||||
|
- 段階的移行手順
|
||||||
|
- 後方互換性の説明
|
||||||
|
- トラブルシューティング
|
||||||
|
|
||||||
|
4. **[リファクタリングサマリー](./refactoring-summary.md)**
|
||||||
|
- HTTPクライアント共通化の詳細
|
||||||
|
- エラーハンドリング改善の記録
|
||||||
|
- 対象ファイル一覧とBefore/After
|
||||||
|
|
||||||
|
## クイックスタート
|
||||||
|
|
||||||
|
### 1. 基本セットアップ
|
||||||
|
```bash
|
||||||
|
# 依存関係インストール
|
||||||
|
cargo install cargo-make
|
||||||
|
|
||||||
|
# プロジェクトビルド
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
# テスト実行
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. CLI使用方法
|
||||||
|
```bash
|
||||||
|
# 新しいコマンド(推奨)
|
||||||
|
./target/debug/aibot --help
|
||||||
|
|
||||||
|
# 旧コマンド(互換性)
|
||||||
|
./target/debug/ai --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 設定ディレクトリ
|
||||||
|
- **新**: `~/.config/syui/ai/bot/`
|
||||||
|
- **旧**: `~/.config/ai/` (自動移行対応)
|
||||||
|
|
||||||
|
## 主要な変更履歴
|
||||||
|
|
||||||
|
### 2025年6月6日 - 大規模リファクタリング
|
||||||
|
|
||||||
|
#### HTTPクライアント共通化
|
||||||
|
- 24個のファイルをHttpClientモジュールで統合
|
||||||
|
- コード重複を大幅削減
|
||||||
|
- エラーハンドリングを改善(`.unwrap()` → `match`)
|
||||||
|
|
||||||
|
#### 命名規則統一
|
||||||
|
- パッケージ名: `ai` → `aibot`
|
||||||
|
- CLI名: `ai` → `aibot`(`ai`は互換性維持)
|
||||||
|
- 設定ディレクトリ: `~/.config/ai/` → `~/.config/syui/ai/bot/`
|
||||||
|
|
||||||
|
#### テストインフラ構築
|
||||||
|
- ユニットテストの追加
|
||||||
|
- cargo-makeによるタスク管理
|
||||||
|
- CI/CD対応の準備
|
||||||
|
|
||||||
|
## アーキテクチャ概要
|
||||||
|
|
||||||
|
```
|
||||||
|
ai.bot/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.rs # メインCLI (aibot)
|
||||||
|
│ ├── alias.rs # 互換性CLI (ai)
|
||||||
|
│ ├── http_client.rs # 統合HTTPクライアント
|
||||||
|
│ ├── data.rs # 設定・データ管理
|
||||||
|
│ ├── game/ # ゲーム機能
|
||||||
|
│ └── tests/ # テストスイート
|
||||||
|
├── docs/ # ドキュメント
|
||||||
|
├── scripts/ # セットアップスクリプト
|
||||||
|
└── ~/.config/syui/ai/bot/ # 設定ディレクトリ
|
||||||
|
├── scpt/ # コマンドスクリプト
|
||||||
|
└── txt/ # ログファイル
|
||||||
|
```
|
||||||
|
|
||||||
|
## サポート・問い合わせ
|
||||||
|
|
||||||
|
### 開発関連
|
||||||
|
- 新機能追加: [開発ガイド](./development-guide.md)を参照
|
||||||
|
- API使用方法: [HTTPクライアントAPI](./http-client-api.md)を参照
|
||||||
|
|
||||||
|
### 移行・運用関連
|
||||||
|
- 移行作業: [移行ガイド](./migration-guide.md)を参照
|
||||||
|
- トラブル: 各ドキュメントのトラブルシューティング章を参照
|
||||||
|
|
||||||
|
### 履歴・詳細
|
||||||
|
- 実装詳細: [リファクタリングサマリー](./refactoring-summary.md)を参照
|
||||||
|
|
||||||
|
## 貢献ガイドライン
|
||||||
|
|
||||||
|
1. **コードスタイル**: `cargo fmt`でフォーマット必須
|
||||||
|
2. **テスト**: 新機能には対応するテストを追加
|
||||||
|
3. **エラーハンドリング**: `.unwrap()`の使用禁止
|
||||||
|
4. **ドキュメント**: 重要な変更は対応ドキュメントも更新
|
||||||
|
|
||||||
|
詳細は[開発ガイド](./development-guide.md)の「コントリビューション」章を参照してください。
|
332
docs/development-guide.md
Normal file
332
docs/development-guide.md
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
# ai.bot 開発ガイド
|
||||||
|
|
||||||
|
## プロジェクト概要
|
||||||
|
|
||||||
|
ai.botは、Rust製のBluesky(AT Protocol)ボットです。メンション応答、コマンド実行、OpenAI統合などの機能を提供します。
|
||||||
|
|
||||||
|
**重要**: 2025年6月6日より、パッケージ名とCLI名が統一されました:
|
||||||
|
- **新CLI名**: `aibot` (推奨)
|
||||||
|
- **旧CLI名**: `ai` (互換性維持)
|
||||||
|
- **設定ディレクトリ**: `~/.config/syui/ai/bot/` (旧: `~/.config/ai/`)
|
||||||
|
|
||||||
|
詳細は[移行ガイド](./migration-guide.md)を参照してください。
|
||||||
|
|
||||||
|
## アーキテクチャ
|
||||||
|
|
||||||
|
### 主要コンポーネント
|
||||||
|
|
||||||
|
1. **HTTPクライアント** (`src/http_client.rs`)
|
||||||
|
- AT Protocol API呼び出しの統一インターフェース
|
||||||
|
- 認証処理の自動化
|
||||||
|
- エラーハンドリングの標準化
|
||||||
|
|
||||||
|
2. **コマンドシステム** (`src/main.rs`)
|
||||||
|
- Seahorseを使用したCLIインターフェース
|
||||||
|
- 各機能への振り分け
|
||||||
|
|
||||||
|
3. **AT Protocolモジュール**
|
||||||
|
- 投稿、フォロー、いいね等の基本機能
|
||||||
|
- 認証・セッション管理
|
||||||
|
- フィード・通知処理
|
||||||
|
|
||||||
|
4. **ゲームシステム** (`src/game/`)
|
||||||
|
- カードゲーム機能
|
||||||
|
- ユーザー管理
|
||||||
|
- ゲームデータ処理
|
||||||
|
|
||||||
|
5. **外部連携**
|
||||||
|
- OpenAI API統合 (`src/openai.rs`)
|
||||||
|
- 画像処理機能
|
||||||
|
|
||||||
|
## 開発環境セットアップ
|
||||||
|
|
||||||
|
### 必要なツール
|
||||||
|
```bash
|
||||||
|
# Rust(最新安定版)
|
||||||
|
rustup update stable
|
||||||
|
|
||||||
|
# Cargo make(タスクランナー)
|
||||||
|
cargo install cargo-make
|
||||||
|
|
||||||
|
# 開発用依存関係は自動インストール
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 初回は`cargo install cargo-make`でcargo-makeのインストールが必要です。
|
||||||
|
|
||||||
|
### 設定ファイル
|
||||||
|
```
|
||||||
|
~/.config/syui/ai/bot/config.toml # 基本設定
|
||||||
|
~/.config/syui/ai/bot/refresh # リフレッシュトークン
|
||||||
|
~/.config/syui/ai/bot/access # アクセストークン
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 旧設定ディレクトリ(`~/.config/ai/`)も自動的に参照・移行されます。
|
||||||
|
|
||||||
|
## 開発ワークフロー
|
||||||
|
|
||||||
|
### 1. コード変更
|
||||||
|
```bash
|
||||||
|
# フォーマット
|
||||||
|
cargo fmt
|
||||||
|
|
||||||
|
# コンパイル確認
|
||||||
|
cargo check
|
||||||
|
|
||||||
|
# テスト実行
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 統合ワークフロー
|
||||||
|
```bash
|
||||||
|
# 全体フロー(フォーマット→ビルド→テスト)
|
||||||
|
cargo make my-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 個別テスト
|
||||||
|
```bash
|
||||||
|
cargo make test-quick # 素早いテスト
|
||||||
|
cargo make test-verbose # 詳細出力テスト
|
||||||
|
```
|
||||||
|
|
||||||
|
## API呼び出しパターン
|
||||||
|
|
||||||
|
### HttpClientの使用方法
|
||||||
|
|
||||||
|
#### 基本的なGETリクエスト
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
|
pub async fn get_user_profile(handle: String) -> String {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = format!("https://bsky.social/xrpc/app.bsky.actor.getProfile?actor={}", handle);
|
||||||
|
|
||||||
|
match client.get_with_auth(&url).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error getting profile: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### JSONを送信するPOSTリクエスト
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn create_post(text: String) -> String {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/com.atproto.repo.createRecord";
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"repo": "user.handle",
|
||||||
|
"collection": "app.bsky.feed.post",
|
||||||
|
"record": {
|
||||||
|
"text": text,
|
||||||
|
"createdAt": chrono::Utc::now().to_rfc3339()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &payload).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error creating post: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## テストの書き方
|
||||||
|
|
||||||
|
### ユニットテスト
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_client_creation() {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
// テストロジック
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data_parsing() {
|
||||||
|
// 同期テスト
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 統合テスト
|
||||||
|
```rust
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore] // 通常は無視、環境変数設定時のみ実行
|
||||||
|
async fn test_real_api() {
|
||||||
|
if std::env::var("RUN_INTEGRATION_TESTS").is_ok() {
|
||||||
|
// 実際のAPI呼び出しテスト
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## エラーハンドリングガイドライン
|
||||||
|
|
||||||
|
### 推奨パターン
|
||||||
|
```rust
|
||||||
|
// Good: 適切なエラーハンドリング
|
||||||
|
match api_call().await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error in operation: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: unwrapの使用
|
||||||
|
api_call().await.unwrap()
|
||||||
|
```
|
||||||
|
|
||||||
|
### エラーレスポンス
|
||||||
|
- API呼び出し失敗時は`"err"`文字列を返す
|
||||||
|
- ログにエラー詳細を出力
|
||||||
|
- 上位レイヤーでエラー処理を継続
|
||||||
|
|
||||||
|
## 新機能追加の手順
|
||||||
|
|
||||||
|
### 1. AT Protocol関連機能
|
||||||
|
```bash
|
||||||
|
# 1. モジュールファイルを作成
|
||||||
|
touch src/new_feature.rs
|
||||||
|
|
||||||
|
# 2. main.rsに追加
|
||||||
|
# pub mod new_feature;
|
||||||
|
|
||||||
|
# 3. HttpClientを使用して実装
|
||||||
|
# 4. テストを追加
|
||||||
|
# 5. main.rsのコマンドに追加
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 基本的なテンプレート
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::{data_toml, url};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn new_feature_request(param: String) -> String {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let endpoint_url = url(&"endpoint_name");
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"param": param
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&endpoint_url, &payload).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error in new_feature: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_new_feature() {
|
||||||
|
// テスト実装
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## デバッグ方法
|
||||||
|
|
||||||
|
### 1. ログ出力
|
||||||
|
```rust
|
||||||
|
eprintln!("Debug: {}", variable);
|
||||||
|
println!("Info: {}", message);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. テスト実行
|
||||||
|
```bash
|
||||||
|
# 特定のテストのみ
|
||||||
|
cargo test test_name
|
||||||
|
|
||||||
|
# 詳細出力
|
||||||
|
cargo test -- --nocapture
|
||||||
|
|
||||||
|
# 特定モジュール
|
||||||
|
cargo test module_name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. HTTPリクエストのデバッグ
|
||||||
|
HttpClientモジュール内でリクエスト/レスポンスをログ出力することで、API呼び出しの詳細を確認できます。
|
||||||
|
|
||||||
|
## パフォーマンス考慮事項
|
||||||
|
|
||||||
|
### HttpClientの再利用
|
||||||
|
```rust
|
||||||
|
// Good: 一度作成して再利用
|
||||||
|
let client = HttpClient::new();
|
||||||
|
for item in items {
|
||||||
|
client.get_with_auth(&url).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: 毎回新規作成
|
||||||
|
for item in items {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
client.get_with_auth(&url).await;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 非同期処理
|
||||||
|
- 可能な限り並列処理を活用
|
||||||
|
- await呼び出しを最小限に
|
||||||
|
- tokio::joinやfutures::join_allの活用
|
||||||
|
|
||||||
|
## トラブルシューティング
|
||||||
|
|
||||||
|
### よくある問題
|
||||||
|
|
||||||
|
1. **認証エラー**
|
||||||
|
- トークンの有効期限切れ
|
||||||
|
- 設定ファイルの不備
|
||||||
|
|
||||||
|
2. **コンパイルエラー**
|
||||||
|
- 型不整合(特にライフタイム)
|
||||||
|
- 未使用のimport
|
||||||
|
|
||||||
|
3. **実行時エラー**
|
||||||
|
- ネットワーク接続問題
|
||||||
|
- API仕様変更
|
||||||
|
|
||||||
|
### 解決方法
|
||||||
|
```bash
|
||||||
|
# 設定確認(新ディレクトリ)
|
||||||
|
ls -la ~/.config/syui/ai/bot/
|
||||||
|
# 旧ディレクトリも確認
|
||||||
|
ls -la ~/.config/ai/
|
||||||
|
|
||||||
|
# トークンリフレッシュ
|
||||||
|
./target/debug/aibot refresh
|
||||||
|
# または互換性コマンド
|
||||||
|
./target/debug/ai refresh
|
||||||
|
|
||||||
|
# 詳細ログ
|
||||||
|
RUST_LOG=debug ./target/debug/aibot [command]
|
||||||
|
```
|
||||||
|
|
||||||
|
## コントリビューション
|
||||||
|
|
||||||
|
1. コードフォーマット必須: `cargo fmt`
|
||||||
|
2. テスト追加必須: 新機能には対応テスト
|
||||||
|
3. エラーハンドリング必須: `.unwrap()`の使用禁止
|
||||||
|
4. ドキュメント更新: 重要な変更は本ドキュメントも更新
|
||||||
|
|
||||||
|
## 関連ドキュメント
|
||||||
|
|
||||||
|
- [リファクタリングサマリー](./refactoring-summary.md)
|
||||||
|
- [AT Protocol仕様](https://atproto.com/)
|
||||||
|
- [Rust公式ドキュメント](https://doc.rust-lang.org/)
|
334
docs/http-client-api.md
Normal file
334
docs/http-client-api.md
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
# HttpClient API リファレンス
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
`HttpClient`は、AT Protocol APIへの統一されたHTTPクライアントインターフェースです。認証、エラーハンドリング、リクエスト管理を自動化します。
|
||||||
|
|
||||||
|
## 基本的な使用方法
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
// または
|
||||||
|
let client = HttpClient::default();
|
||||||
|
```
|
||||||
|
|
||||||
|
## APIメソッド
|
||||||
|
|
||||||
|
### 認証付きリクエスト
|
||||||
|
|
||||||
|
#### get_with_auth
|
||||||
|
AT Protocolの認証が必要なGETリクエストを実行します。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn get_with_auth(&self, url: &str) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**パラメータ:**
|
||||||
|
- `url`: リクエスト先のURL
|
||||||
|
|
||||||
|
**戻り値:**
|
||||||
|
- `Ok(String)`: レスポンスボディ(文字列)
|
||||||
|
- `Err(Error)`: リクエストエラー
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/app.bsky.actor.getProfile?actor=user.bsky.social";
|
||||||
|
|
||||||
|
match client.get_with_auth(&url).await {
|
||||||
|
Ok(response) => println!("Response: {}", response),
|
||||||
|
Err(e) => eprintln!("Error: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### post_json_with_auth
|
||||||
|
AT Protocolの認証が必要なPOSTリクエスト(JSON)を実行します。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn post_json_with_auth<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**パラメータ:**
|
||||||
|
- `url`: リクエスト先のURL
|
||||||
|
- `json`: シリアライズ可能なJSONデータ
|
||||||
|
|
||||||
|
**戻り値:**
|
||||||
|
- `Ok(String)`: レスポンスボディ(文字列)
|
||||||
|
- `Err(Error)`: リクエストエラー
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/com.atproto.repo.createRecord";
|
||||||
|
let payload = json!({
|
||||||
|
"repo": "user.bsky.social",
|
||||||
|
"collection": "app.bsky.feed.post",
|
||||||
|
"record": {
|
||||||
|
"text": "Hello, World!",
|
||||||
|
"createdAt": "2025-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &payload).await {
|
||||||
|
Ok(response) => println!("Post created: {}", response),
|
||||||
|
Err(e) => eprintln!("Error: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### delete_with_auth
|
||||||
|
AT Protocolの認証が必要なDELETEリクエストを実行します。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn delete_with_auth(&self, url: &str) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**パラメータ:**
|
||||||
|
- `url`: リクエスト先のURL
|
||||||
|
|
||||||
|
**戻り値:**
|
||||||
|
- `Ok(String)`: レスポンスボディ(文字列)
|
||||||
|
- `Err(Error)`: リクエストエラー
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/com.atproto.repo.deleteRecord";
|
||||||
|
|
||||||
|
match client.delete_with_auth(&url).await {
|
||||||
|
Ok(response) => println!("Deleted: {}", response),
|
||||||
|
Err(e) => eprintln!("Error: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 認証なしリクエスト
|
||||||
|
|
||||||
|
#### get
|
||||||
|
認証なしのGETリクエストを実行します。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn get(&self, url: &str) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://public-api.example.com/data";
|
||||||
|
|
||||||
|
match client.get(&url).await {
|
||||||
|
Ok(response) => println!("Response: {}", response),
|
||||||
|
Err(e) => eprintln!("Error: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### post_json
|
||||||
|
認証なしのPOSTリクエスト(JSON)を実行します。ログイン処理などで使用。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn post_json<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/com.atproto.session.create";
|
||||||
|
let credentials = json!({
|
||||||
|
"identifier": "user.bsky.social",
|
||||||
|
"password": "password"
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.post_json(&url, &credentials).await {
|
||||||
|
Ok(response) => println!("Login successful: {}", response),
|
||||||
|
Err(e) => eprintln!("Login failed: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### カスタムヘッダー付きリクエスト
|
||||||
|
|
||||||
|
#### post_with_headers
|
||||||
|
カスタムヘッダーを指定したPOSTリクエストを実行します。
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub async fn post_with_headers<T: Serialize>(
|
||||||
|
&self,
|
||||||
|
url: &str,
|
||||||
|
json: &T,
|
||||||
|
headers: Vec<(&str, &str)>
|
||||||
|
) -> Result<String, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
**パラメータ:**
|
||||||
|
- `url`: リクエスト先のURL
|
||||||
|
- `json`: シリアライズ可能なJSONデータ
|
||||||
|
- `headers`: ヘッダーのキーと値のペアのベクター
|
||||||
|
|
||||||
|
**使用例:**
|
||||||
|
```rust
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
let url = "https://bsky.social/xrpc/com.atproto.session.refresh";
|
||||||
|
let refresh_token = "refresh_token_value";
|
||||||
|
let auth_header = format!("Bearer {}", refresh_token);
|
||||||
|
let headers = vec![("Authorization", auth_header.as_str())];
|
||||||
|
let empty_json = json!({});
|
||||||
|
|
||||||
|
match client.post_with_headers(&url, &empty_json, headers).await {
|
||||||
|
Ok(response) => println!("Token refreshed: {}", response),
|
||||||
|
Err(e) => eprintln!("Refresh failed: {}", e),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内部実装詳細
|
||||||
|
|
||||||
|
### 認証処理
|
||||||
|
認証付きメソッドは自動的に以下を実行します:
|
||||||
|
|
||||||
|
1. `data_refresh(&"access")`でアクセストークンを取得
|
||||||
|
2. `Authorization: Bearer {token}`ヘッダーを自動追加
|
||||||
|
3. リクエストを実行
|
||||||
|
|
||||||
|
### エラーハンドリング
|
||||||
|
- ネットワークエラー
|
||||||
|
- HTTPステータスエラー
|
||||||
|
- JSON解析エラー
|
||||||
|
- タイムアウトエラー
|
||||||
|
|
||||||
|
すべて`reqwest::Error`として統一されて返されます。
|
||||||
|
|
||||||
|
## 使用上の注意
|
||||||
|
|
||||||
|
### 1. トークン管理
|
||||||
|
- アクセストークンは自動的に取得されます
|
||||||
|
- トークンの有効期限切れは呼び出し元で処理する必要があります
|
||||||
|
- リフレッシュトークンは`post_with_headers`で手動設定
|
||||||
|
|
||||||
|
### 2. エラー処理パターン
|
||||||
|
```rust
|
||||||
|
// 推奨パターン
|
||||||
|
match client.get_with_auth(&url).await {
|
||||||
|
Ok(response) => {
|
||||||
|
// 成功処理
|
||||||
|
response
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("API call failed: {}", e);
|
||||||
|
"err".to_string() // 既存コードとの互換性
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. パフォーマンス
|
||||||
|
- `HttpClient`の作成は軽量な操作です
|
||||||
|
- 内部でrequwestクライアントを再利用しています
|
||||||
|
- 複数のリクエストで同じインスタンスを使い回すことも可能
|
||||||
|
|
||||||
|
### 4. デバッグ
|
||||||
|
リクエスト/レスポンスの詳細をログ出力したい場合:
|
||||||
|
```rust
|
||||||
|
// HttpClientモジュール内で適宜printlnを追加
|
||||||
|
println!("Request URL: {}", url);
|
||||||
|
println!("Response: {}", response);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 実装例:新しいAPI呼び出しモジュール
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::{data_toml, url};
|
||||||
|
use serde_json::json;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
|
pub async fn create_custom_record(data: String) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_create");
|
||||||
|
|
||||||
|
let timestamp = Timestamp::now_utc().to_string();
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"repo": handle,
|
||||||
|
"did": did,
|
||||||
|
"collection": "app.bsky.custom.record",
|
||||||
|
"record": {
|
||||||
|
"data": data,
|
||||||
|
"createdAt": timestamp
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
match client.post_json_with_auth(&url, &payload).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error creating custom record: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_custom_record() {
|
||||||
|
let result = create_custom_record("test data".to_string()).await;
|
||||||
|
assert_ne!(result, "err");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 移行ガイド
|
||||||
|
|
||||||
|
### 既存コードからの移行
|
||||||
|
|
||||||
|
#### Before (旧実装)
|
||||||
|
```rust
|
||||||
|
extern crate reqwest;
|
||||||
|
use crate::data_refresh;
|
||||||
|
|
||||||
|
pub async fn old_request() -> String {
|
||||||
|
let token = data_refresh(&"access");
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let res = client
|
||||||
|
.post(url)
|
||||||
|
.json(&data)
|
||||||
|
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After (新実装)
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
|
pub async fn new_request() -> String {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
match client.post_json_with_auth(&url, &data).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error in request: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### チェックリスト
|
||||||
|
- [ ] `extern crate reqwest;`を削除
|
||||||
|
- [ ] `use crate::http_client::HttpClient;`を追加
|
||||||
|
- [ ] `data_refresh(&"access")`の手動呼び出しを削除
|
||||||
|
- [ ] `reqwest::Client::new()`を`HttpClient::new()`に置換
|
||||||
|
- [ ] `.unwrap()`を適切な`match`文に置換
|
||||||
|
- [ ] エラーメッセージの追加
|
271
docs/migration-guide.md
Normal file
271
docs/migration-guide.md
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# ai.bot 段階的移行ガイド
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
ai.botプロジェクトの命名規則とディレクトリ構造を統一するための段階的移行作業の記録です。
|
||||||
|
|
||||||
|
## 移行内容
|
||||||
|
|
||||||
|
### 1. パッケージ・CLI名の変更
|
||||||
|
|
||||||
|
| 項目 | 変更前 | 変更後 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| パッケージ名 | `ai` | `aibot` |
|
||||||
|
| メインCLI | `ai` | `aibot` |
|
||||||
|
| 互換性CLI | - | `ai` (aibotへのエイリアス) |
|
||||||
|
|
||||||
|
### 2. ディレクトリ構造の変更
|
||||||
|
|
||||||
|
| 用途 | 変更前 | 変更後 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 設定ディレクトリ | `~/.config/ai/` | `~/.config/syui/ai/bot/` |
|
||||||
|
| ログディレクトリ | `~/.config/ai/txt/` | `~/.config/syui/ai/bot/txt/` |
|
||||||
|
| スクリプトディレクトリ | `~/.config/ai/scpt/` | `~/.config/syui/ai/bot/scpt/` |
|
||||||
|
|
||||||
|
## 実装した機能
|
||||||
|
|
||||||
|
### 1. デュアルバイナリシステム
|
||||||
|
|
||||||
|
#### Cargo.toml設定
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "aibot"
|
||||||
|
description = "ai.bot - Bluesky AT Protocol Bot"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "aibot"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ai"
|
||||||
|
path = "src/alias.rs"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### エイリアスバイナリ (src/alias.rs)
|
||||||
|
- `ai`コマンドが`aibot`を自動的に呼び出し
|
||||||
|
- 同一ディレクトリ内のaibotバイナリを優先検索
|
||||||
|
- PATH内のaibotもフォールバック対応
|
||||||
|
|
||||||
|
### 2. 設定ディレクトリの自動移行
|
||||||
|
|
||||||
|
#### data_file関数の改良
|
||||||
|
```rust
|
||||||
|
pub fn data_file(s: &str) -> String {
|
||||||
|
// 新しい設定ディレクトリ(優先)
|
||||||
|
let new_config_dir = "/.config/syui/ai/bot/";
|
||||||
|
// 旧設定ディレクトリ(互換性のため)
|
||||||
|
let old_config_dir = "/.config/ai/";
|
||||||
|
|
||||||
|
// 自動移行ロジック
|
||||||
|
// 1. 新しいパスにファイルが存在 → 新しいパスを使用
|
||||||
|
// 2. 旧パスのみに存在 → 新しいパスにコピーして使用
|
||||||
|
// 3. どちらにも存在しない → 新しいパスを使用
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### log_file関数も同様の移行対応
|
||||||
|
|
||||||
|
### 3. 移行セットアップスクリプト
|
||||||
|
|
||||||
|
#### scripts/setup-migration.sh
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# 新しい設定ディレクトリの作成
|
||||||
|
mkdir -p ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# スクリプトディレクトリのコピー
|
||||||
|
cp -r ~/.config/ai/scpt ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# エイリアス設定の提案
|
||||||
|
echo "alias ai='aibot'"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 後方互換性
|
||||||
|
|
||||||
|
### 1. 設定ファイル
|
||||||
|
- **自動移行**: 旧パスから新パスへ自動コピー
|
||||||
|
- **継続読み込み**: 移行後も旧パスは参照可能
|
||||||
|
- **透過的**: ユーザーは変更を意識する必要なし
|
||||||
|
|
||||||
|
### 2. コマンドライン
|
||||||
|
- **aiコマンド**: 既存スクリプトで引き続き使用可能
|
||||||
|
- **aibotコマンド**: 新しい正式名称
|
||||||
|
- **完全互換**: 引数、オプション、出力すべて同一
|
||||||
|
|
||||||
|
### 3. スクリプト
|
||||||
|
- **shellscript**: `ai`コマンドをそのまま使用可能
|
||||||
|
- **エイリアス推奨**: `alias ai='aibot'`で統一
|
||||||
|
|
||||||
|
## 移行手順
|
||||||
|
|
||||||
|
### 1. 自動移行(推奨)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# プロジェクトをビルド
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
# 移行スクリプトを実行
|
||||||
|
./scripts/setup-migration.sh
|
||||||
|
|
||||||
|
# 新しいバイナリを使用
|
||||||
|
./target/debug/aibot --help
|
||||||
|
./target/debug/ai --help # 互換性確認
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 手動移行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 新しい設定ディレクトリ作成
|
||||||
|
mkdir -p ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# 2. スクリプトディレクトリのコピー
|
||||||
|
cp -r ~/.config/ai/scpt ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# 3. バイナリのインストール
|
||||||
|
cargo install --path .
|
||||||
|
|
||||||
|
# 4. エイリアス設定(オプション)
|
||||||
|
echo "alias ai='aibot'" >> ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 段階的移行(企業環境等)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Phase 1: 新しいバイナリの導入
|
||||||
|
cargo install --path . --bin aibot
|
||||||
|
|
||||||
|
# Phase 2: エイリアス設定
|
||||||
|
echo "alias ai='aibot'" >> ~/.profile
|
||||||
|
|
||||||
|
# Phase 3: スクリプトの段階的更新
|
||||||
|
# (既存スクリプトは変更不要)
|
||||||
|
|
||||||
|
# Phase 4: 旧設定の完全移行(任意のタイミング)
|
||||||
|
rm -rf ~/.config/ai/ # 十分な検証後
|
||||||
|
```
|
||||||
|
|
||||||
|
## 影響範囲
|
||||||
|
|
||||||
|
### 1. 変更が必要な箇所
|
||||||
|
- ❌ **なし** (完全後方互換)
|
||||||
|
|
||||||
|
### 2. 変更が推奨される箇所
|
||||||
|
- 📝 shellrcでのエイリアス設定
|
||||||
|
- 📝 ドキュメント内のコマンド例
|
||||||
|
- 📝 CI/CDスクリプト(新しい名前使用)
|
||||||
|
|
||||||
|
### 3. 変更が不要な箇所
|
||||||
|
- ✅ 既存のshellscript
|
||||||
|
- ✅ 設定ファイル
|
||||||
|
- ✅ ログファイル
|
||||||
|
- ✅ git submodule
|
||||||
|
|
||||||
|
## トラブルシューティング
|
||||||
|
|
||||||
|
### 1. 設定ファイルが見つからない
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 現在の設定ディレクトリを確認
|
||||||
|
ls -la ~/.config/syui/ai/bot/
|
||||||
|
ls -la ~/.config/ai/ # 旧ディレクトリ
|
||||||
|
|
||||||
|
# 手動でコピー
|
||||||
|
cp ~/.config/ai/token.json ~/.config/syui/ai/bot/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. aiコマンドが動作しない
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# aibotバイナリの存在確認
|
||||||
|
which aibot
|
||||||
|
./target/debug/aibot --help
|
||||||
|
|
||||||
|
# エイリアスの設定
|
||||||
|
alias ai='aibot'
|
||||||
|
# または
|
||||||
|
export PATH="$(pwd)/target/debug:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. スクリプトディレクトリが見つからない
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 手動でコピー
|
||||||
|
cp -r ~/.config/ai/scpt ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# gitサブモジュールの場合
|
||||||
|
cd ~/.config/syui/ai/bot/scpt
|
||||||
|
git remote -v # リモートURLを確認
|
||||||
|
```
|
||||||
|
|
||||||
|
## 検証方法
|
||||||
|
|
||||||
|
### 1. 基本動作確認
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 新しいコマンド
|
||||||
|
aibot --help
|
||||||
|
aibot post "Test from aibot"
|
||||||
|
|
||||||
|
# 互換性確認
|
||||||
|
ai --help
|
||||||
|
ai post "Test from ai alias"
|
||||||
|
|
||||||
|
# 設定ディレクトリ確認
|
||||||
|
ls -la ~/.config/syui/ai/bot/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 自動移行確認
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 旧設定で動作確認
|
||||||
|
touch ~/.config/ai/test_file
|
||||||
|
aibot some_command # 新ディレクトリにコピーされることを確認
|
||||||
|
ls -la ~/.config/syui/ai/bot/test_file
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. スクリプト互換性確認
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 既存スクリプトの動作確認
|
||||||
|
cd ~/.config/syui/ai/bot/scpt
|
||||||
|
./ai.zsh # aiコマンドが正常動作することを確認
|
||||||
|
```
|
||||||
|
|
||||||
|
## 今後の予定
|
||||||
|
|
||||||
|
### Phase 1 (完了)
|
||||||
|
- ✅ パッケージ名の変更
|
||||||
|
- ✅ デュアルバイナリシステム
|
||||||
|
- ✅ 設定ディレクトリの自動移行
|
||||||
|
- ✅ 後方互換性の確保
|
||||||
|
|
||||||
|
### Phase 2 (将来)
|
||||||
|
- 📅 ドキュメントの更新
|
||||||
|
- 📅 CI/CDの新コマンド対応
|
||||||
|
- 📅 パフォーマンス最適化
|
||||||
|
|
||||||
|
### Phase 3 (任意)
|
||||||
|
- 📅 旧設定ディレクトリの削除
|
||||||
|
- 📅 エイリアスバイナリの削除
|
||||||
|
- 📅 完全な新体系への移行
|
||||||
|
|
||||||
|
## 関連ファイル
|
||||||
|
|
||||||
|
- `Cargo.toml` - パッケージ設定
|
||||||
|
- `src/alias.rs` - エイリアスバイナリ
|
||||||
|
- `src/data.rs` - 設定ディレクトリ管理
|
||||||
|
- `scripts/setup-migration.sh` - 移行スクリプト
|
||||||
|
- `docs/migration-guide.md` - 本ドキュメント
|
||||||
|
|
||||||
|
## 注意事項
|
||||||
|
|
||||||
|
1. **gitサブモジュール**: scptディレクトリがgitサブモジュールの場合、リモートURLの確認が必要
|
||||||
|
2. **権限**: 設定ディレクトリの権限は適切に設定すること
|
||||||
|
3. **バックアップ**: 重要な設定は移行前にバックアップを推奨
|
||||||
|
4. **テスト**: 本番環境での使用前に十分なテストを実施
|
||||||
|
|
||||||
|
## 参考情報
|
||||||
|
|
||||||
|
- [開発ガイド](./development-guide.md)
|
||||||
|
- [リファクタリングサマリー](./refactoring-summary.md)
|
||||||
|
- [HTTPクライアントAPI](./http-client-api.md)
|
208
docs/refactoring-summary.md
Normal file
208
docs/refactoring-summary.md
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# ai.bot リファクタリング作業サマリー
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
2025年6月6日に実施されたai.botプロジェクトのリファクタリング作業の完全な記録です。
|
||||||
|
|
||||||
|
## 実施した作業
|
||||||
|
|
||||||
|
### 1. HTTPクライアントの共通化
|
||||||
|
- **作成したファイル**: `src/http_client.rs`
|
||||||
|
- **対象**: 24個のファイルを統合
|
||||||
|
- **削減**: 重複するHTTPリクエストコードを一箇所に集約
|
||||||
|
|
||||||
|
#### HttpClientモジュールの機能
|
||||||
|
```rust
|
||||||
|
// 認証付きリクエスト
|
||||||
|
client.get_with_auth(&url).await
|
||||||
|
client.post_json_with_auth(&url, &json_data).await
|
||||||
|
client.delete_with_auth(&url).await
|
||||||
|
|
||||||
|
// 認証なしリクエスト
|
||||||
|
client.get(&url).await
|
||||||
|
client.post_json(&url, &json_data).await
|
||||||
|
|
||||||
|
// カスタムヘッダー付きリクエスト
|
||||||
|
client.post_with_headers(&url, &json_data, headers).await
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. リファクタリング対象ファイル一覧
|
||||||
|
|
||||||
|
#### コアAPIファイル (8個)
|
||||||
|
- `src/post.rs` - 投稿作成
|
||||||
|
- `src/like.rs` - いいね機能
|
||||||
|
- `src/repost.rs` - リポスト機能
|
||||||
|
- `src/follow.rs` - フォロー/アンフォロー
|
||||||
|
- `src/reply.rs` - 返信機能
|
||||||
|
- `src/profile.rs` - プロフィール取得
|
||||||
|
- `src/delete_record.rs` - レコード削除
|
||||||
|
- `src/describe.rs` - ユーザー説明取得
|
||||||
|
|
||||||
|
#### 認証・セッション管理 (3個)
|
||||||
|
- `src/session.rs` - セッション管理
|
||||||
|
- `src/refresh.rs` - トークンリフレッシュ
|
||||||
|
- `src/token.rs` - ログイン認証
|
||||||
|
|
||||||
|
#### 通知・メンション (3個)
|
||||||
|
- `src/mention.rs` - メンション投稿
|
||||||
|
- `src/notify.rs` - 通知取得
|
||||||
|
- `src/notify_read.rs` - 通知既読
|
||||||
|
|
||||||
|
#### フィード・タイムライン (4個)
|
||||||
|
- `src/feed_get.rs` - フィード取得
|
||||||
|
- `src/timeline_author.rs` - 作者タイムライン
|
||||||
|
- `src/followers.rs` - フォロワー取得
|
||||||
|
- `src/follows.rs` - フォロー取得
|
||||||
|
|
||||||
|
#### 画像関連 (3個)
|
||||||
|
- `src/img.rs` - 画像投稿
|
||||||
|
- `src/img_reply.rs` - 画像付き返信
|
||||||
|
- `src/img_upload.rs` - 画像アップロード
|
||||||
|
|
||||||
|
#### リンク・リッチテキスト (3個)
|
||||||
|
- `src/post_link.rs` - リンク付き投稿
|
||||||
|
- `src/reply_link.rs` - リンク付き返信
|
||||||
|
- `src/reply_og.rs` - OGデータ付き返信
|
||||||
|
|
||||||
|
#### ゲームモジュール (5個)
|
||||||
|
- `src/game/post_card.rs` - ゲームカード投稿
|
||||||
|
- `src/game/post_card_verify.rs` - カード検証
|
||||||
|
- `src/game/post_game.rs` - ゲームデータ投稿
|
||||||
|
- `src/game/post_game_login.rs` - ゲームログイン
|
||||||
|
- `src/game/post_game_user.rs` - ゲームユーザーデータ
|
||||||
|
|
||||||
|
### 3. 除外したファイル
|
||||||
|
- `src/openai.rs` - OpenAI API用(異なる認証方式のため)
|
||||||
|
- `src/feed_watch.rs` - reqwest使用していないため
|
||||||
|
|
||||||
|
### 4. 共通の変更パターン
|
||||||
|
|
||||||
|
#### Before (変更前)
|
||||||
|
```rust
|
||||||
|
extern crate reqwest;
|
||||||
|
use crate::data_refresh;
|
||||||
|
|
||||||
|
pub async fn some_request() -> String {
|
||||||
|
let token = data_refresh(&"access");
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let res = client
|
||||||
|
.post(url)
|
||||||
|
.json(&post)
|
||||||
|
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After (変更後)
|
||||||
|
```rust
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
|
pub async fn some_request() -> String {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
"err".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. テストインフラの追加
|
||||||
|
|
||||||
|
#### 作成したテストファイル
|
||||||
|
- `src/tests/mod.rs` - テストモジュール宣言
|
||||||
|
- `src/tests/http_client_tests.rs` - HttpClientのテスト
|
||||||
|
|
||||||
|
#### テストコマンド
|
||||||
|
```bash
|
||||||
|
# 基本テスト
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Makefileを使用したテスト
|
||||||
|
cargo make test # cleanしてからテスト
|
||||||
|
cargo make test-quick # 素早いテスト
|
||||||
|
cargo make test-verbose # 詳細出力
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 追加した依存関係 (Cargo.toml)
|
||||||
|
```toml
|
||||||
|
[dev-dependencies]
|
||||||
|
mockito = "1.2"
|
||||||
|
tokio-test = "0.4"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 改善された点
|
||||||
|
|
||||||
|
### コード品質
|
||||||
|
- **コード重複の削除**: 同じHTTPリクエストパターンの重複を排除
|
||||||
|
- **エラーハンドリング**: `.unwrap()`を適切な`match`文に置換
|
||||||
|
- **保守性**: HTTP関連のロジックが一箇所に集約
|
||||||
|
|
||||||
|
### 開発効率
|
||||||
|
- **変更容易性**: HTTPクライアントの変更が1ファイルで完結
|
||||||
|
- **テスト可能性**: ユニットテストの追加でバグ検出が容易
|
||||||
|
- **デバッグ性**: エラーメッセージの改善
|
||||||
|
|
||||||
|
### 安定性
|
||||||
|
- **パニック回避**: `.unwrap()`によるパニックを防止
|
||||||
|
- **エラー処理**: 適切なエラーレスポンスの返却
|
||||||
|
|
||||||
|
## 次のステップ(推奨)
|
||||||
|
|
||||||
|
### 1. bot.rsのリファクタリング (高優先度)
|
||||||
|
- 500行以上の巨大な関数を分割
|
||||||
|
- コマンド処理の構造化
|
||||||
|
- 深いif-else文の改善
|
||||||
|
|
||||||
|
### 2. 設定管理の統一 (中優先度)
|
||||||
|
- 複数の設定構造体の統合
|
||||||
|
- 設定ファイルパスの一元管理
|
||||||
|
|
||||||
|
### 3. main.rsの整理 (中優先度)
|
||||||
|
- コマンド定義の外部ファイル化
|
||||||
|
- モジュール構造の改善
|
||||||
|
|
||||||
|
## ファイル構造
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── http_client.rs # 新規作成:共通HTTPクライアント
|
||||||
|
├── tests/ # 新規作成:テストディレクトリ
|
||||||
|
│ ├── mod.rs
|
||||||
|
│ └── http_client_tests.rs
|
||||||
|
├── main.rs # 更新:http_clientモジュール追加
|
||||||
|
├── Cargo.toml # 更新:テスト依存関係追加
|
||||||
|
├── Makefile.toml # 更新:テストコマンド追加
|
||||||
|
└── [24個のリファクタリング済みファイル]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事項
|
||||||
|
|
||||||
|
1. **OpenAI API**: `src/openai.rs`は意図的に除外(異なる認証方式)
|
||||||
|
2. **後方互換性**: 既存のAPI呼び出しは同じ形式を維持
|
||||||
|
3. **エラー処理**: "err"文字列を返すパターンは既存仕様に合わせて維持
|
||||||
|
|
||||||
|
## 検証方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# コンパイル確認
|
||||||
|
cargo check
|
||||||
|
|
||||||
|
# テスト実行
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# フォーマット確認
|
||||||
|
cargo fmt --check
|
||||||
|
|
||||||
|
# 全体ビルド
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
この作業により、ai.botプロジェクトのHTTP通信部分が大幅に改善され、今後の開発・保守が容易になりました。
|
37
scripts/setup-migration.sh
Executable file
37
scripts/setup-migration.sh
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ai.bot 移行セットアップスクリプト
|
||||||
|
|
||||||
|
echo "=== ai.bot Migration Setup ==="
|
||||||
|
|
||||||
|
# 1. 新しい設定ディレクトリの作成
|
||||||
|
echo "Creating new config directory..."
|
||||||
|
mkdir -p ~/.config/syui/ai/bot/
|
||||||
|
|
||||||
|
# 2. スクリプトディレクトリの移動(gitサブモジュール)
|
||||||
|
if [ -d ~/.config/ai/scpt ]; then
|
||||||
|
echo "Copying script directory..."
|
||||||
|
cp -r ~/.config/ai/scpt ~/.config/syui/ai/bot/
|
||||||
|
echo "Scripts copied to ~/.config/syui/ai/bot/scpt/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 設定ファイルの移行(自動的にdata.rsで行われる)
|
||||||
|
echo "Configuration files will be migrated automatically when used."
|
||||||
|
|
||||||
|
# 4. エイリアス設定の提案
|
||||||
|
echo ""
|
||||||
|
echo "=== Manual Steps Required ==="
|
||||||
|
echo ""
|
||||||
|
echo "1. Add this alias to your shell profile (~/.zshrc, ~/.bashrc, etc.):"
|
||||||
|
echo " alias ai='aibot'"
|
||||||
|
echo ""
|
||||||
|
echo "2. Install the new binaries:"
|
||||||
|
echo " cargo install --path ."
|
||||||
|
echo ""
|
||||||
|
echo "3. Or add to PATH:"
|
||||||
|
echo " export PATH=\"$(pwd)/target/debug:\$PATH\""
|
||||||
|
echo ""
|
||||||
|
echo "4. Update git submodule path if needed:"
|
||||||
|
echo " cd ~/.config/syui/ai/bot/scpt"
|
||||||
|
echo " git remote -v # Check current remote"
|
||||||
|
echo ""
|
||||||
|
echo "Migration setup complete!"
|
39
src/alias.rs
Normal file
39
src/alias.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Legacy alias: ai -> aibot
|
||||||
|
// This provides backward compatibility for existing scripts
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command, exit};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
|
// Skip the first argument (program name) and pass the rest to aibot
|
||||||
|
let aibot_args: Vec<&str> = args.iter().skip(1).map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
|
// Try to find aibot binary in the same directory as this binary
|
||||||
|
let current_exe = env::current_exe().unwrap_or_else(|_| PathBuf::from("ai"));
|
||||||
|
let mut aibot_path = current_exe.parent().unwrap_or(&PathBuf::from(".")).to_path_buf();
|
||||||
|
aibot_path.push("aibot");
|
||||||
|
|
||||||
|
// First try relative path, then try PATH
|
||||||
|
let mut cmd = if aibot_path.exists() {
|
||||||
|
Command::new(&aibot_path)
|
||||||
|
} else {
|
||||||
|
Command::new("aibot")
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.args(&aibot_args);
|
||||||
|
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) => {
|
||||||
|
exit(status.code().unwrap_or(1));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error executing aibot: {}", e);
|
||||||
|
eprintln!("Make sure 'aibot' is installed and available in PATH");
|
||||||
|
eprintln!("Attempted path: {:?}", aibot_path);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/bot.rs
36
src/bot.rs
@ -431,6 +431,42 @@ pub fn c_bot(c: &Context) {
|
|||||||
println!("{}", str_rep);
|
println!("{}", str_rep);
|
||||||
w_cid(cid.to_string(), log_file(&"n1"), true);
|
w_cid(cid.to_string(), log_file(&"n1"), true);
|
||||||
}
|
}
|
||||||
|
} else if com == "game" || com == "/game" {
|
||||||
|
let output = Command::new(data_scpt(&"ai"))
|
||||||
|
.arg(&"atproto").arg(&"game")
|
||||||
|
.arg(&handle)
|
||||||
|
.arg(&did)
|
||||||
|
.arg(&cid)
|
||||||
|
.arg(&uri)
|
||||||
|
.arg(&cid_root)
|
||||||
|
.arg(&uri_root)
|
||||||
|
.arg(&host)
|
||||||
|
.arg(&prompt)
|
||||||
|
.arg(&prompt_sub)
|
||||||
|
.output()
|
||||||
|
.expect("zsh");
|
||||||
|
let d = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let dd = "\n".to_owned() + &d.to_string();
|
||||||
|
let text_limit = c_char(dd);
|
||||||
|
handlev = d.lines().collect::<Vec<_>>()[0].to_string();
|
||||||
|
link = "https://card.syui.ai/".to_owned() + &handlev;
|
||||||
|
println!("{}", e);
|
||||||
|
e = link.chars().count();
|
||||||
|
if text_limit.len() > 3 {
|
||||||
|
let str_rep = reply_link::post_request(
|
||||||
|
text_limit.to_string(),
|
||||||
|
link.to_string(),
|
||||||
|
s,
|
||||||
|
e.try_into().unwrap(),
|
||||||
|
cid.to_string(),
|
||||||
|
uri.to_string(),
|
||||||
|
cid_root.to_string(),
|
||||||
|
uri_root.to_string(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("{}", str_rep);
|
||||||
|
w_cid(cid.to_string(), log_file(&"n1"), true);
|
||||||
|
}
|
||||||
} else if com == "quiz" || com == "/quiz" {
|
} else if com == "quiz" || com == "/quiz" {
|
||||||
println!("admin:{}", admin);
|
println!("admin:{}", admin);
|
||||||
let output = Command::new(data_scpt(&"ai"))
|
let output = Command::new(data_scpt(&"ai"))
|
||||||
|
206
src/data.rs
206
src/data.rs
@ -5,37 +5,69 @@ use std::fs;
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::PathBuf;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
/// 設定ディレクトリのベースパス
|
||||||
|
const CONFIG_BASE_DIR: &str = "~/.config/syui/ai/bot";
|
||||||
|
|
||||||
|
/// ホームディレクトリパスを展開するユーティリティ関数
|
||||||
|
/// "~"で始まるパスをユーザーのホームディレクトリに展開します
|
||||||
|
fn expand_home_path(path: &str) -> PathBuf {
|
||||||
|
if path.starts_with("~") {
|
||||||
|
let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
|
||||||
|
let path_without_tilde = path.strip_prefix("~/").unwrap_or(&path[1..]);
|
||||||
|
PathBuf::from(home).join(path_without_tilde)
|
||||||
|
} else {
|
||||||
|
PathBuf::from(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 設定ディレクトリのベースパスを取得し、必要に応じて作成する
|
||||||
|
fn get_config_base_path() -> PathBuf {
|
||||||
|
let path = expand_home_path(CONFIG_BASE_DIR);
|
||||||
|
if !path.is_dir() {
|
||||||
|
let _ = fs::create_dir_all(&path);
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// サブディレクトリを含む設定パスを取得し、必要に応じて作成する
|
||||||
|
fn get_config_path(subdir: &str) -> PathBuf {
|
||||||
|
let base_path = get_config_base_path();
|
||||||
|
let path = if subdir.is_empty() {
|
||||||
|
base_path
|
||||||
|
} else {
|
||||||
|
base_path.join(subdir)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !path.is_dir() {
|
||||||
|
let _ = fs::create_dir_all(&path);
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn data_file(s: &str) -> String {
|
pub fn data_file(s: &str) -> String {
|
||||||
let file = "/.config/ai/";
|
let path = get_config_base_path();
|
||||||
let mut f = shellexpand::tilde("~").to_string();
|
let path_str = path.to_string_lossy();
|
||||||
f.push_str(&file);
|
|
||||||
let path = Path::new(&f);
|
match s {
|
||||||
if path.is_dir() == false {
|
"toml" => format!("{}/token.toml", path_str),
|
||||||
let _ = fs::create_dir_all(f.clone());
|
"json" => format!("{}/token.json", path_str),
|
||||||
}
|
"refresh" => format!("{}/refresh.toml", path_str),
|
||||||
match &*s {
|
_ => format!("{}/.{}", path_str, s),
|
||||||
"toml" => f + &"token.toml",
|
|
||||||
"json" => f + &"token.json",
|
|
||||||
"refresh" => f + &"refresh.toml",
|
|
||||||
_ => f + &"." + &s,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_file(s: &str) -> String {
|
pub fn log_file(s: &str) -> String {
|
||||||
let file = "/.config/ai/txt/";
|
let path = get_config_path("txt");
|
||||||
let mut f = shellexpand::tilde("~").to_string();
|
let path_str = path.to_string_lossy();
|
||||||
f.push_str(&file);
|
|
||||||
let path = Path::new(&f);
|
match s {
|
||||||
if path.is_dir() == false {
|
"n1" => format!("{}/notify_cid.txt", path_str),
|
||||||
let _ = fs::create_dir_all(f.clone());
|
"n2" => format!("{}/notify_cid_run.txt", path_str),
|
||||||
}
|
"c1" => format!("{}/comment_cid.txt", path_str),
|
||||||
match &*s {
|
_ => format!("{}/{}", path_str, s),
|
||||||
"n1" => f + &"notify_cid.txt",
|
|
||||||
"n2" => f + &"notify_cid_run.txt",
|
|
||||||
"c1" => f + &"comment_cid.txt",
|
|
||||||
_ => f + &s,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +139,7 @@ pub struct BaseUrl {
|
|||||||
pub record_list: String,
|
pub record_list: String,
|
||||||
pub record_create: String,
|
pub record_create: String,
|
||||||
pub record_delete: String,
|
pub record_delete: String,
|
||||||
|
pub record_put: String,
|
||||||
pub session_create: String,
|
pub session_create: String,
|
||||||
pub session_refresh: String,
|
pub session_refresh: String,
|
||||||
pub session_get: String,
|
pub session_get: String,
|
||||||
@ -128,20 +161,20 @@ pub struct BaseUrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn url(s: &str) -> String {
|
pub fn url(s: &str) -> String {
|
||||||
let s = String::from(s);
|
let data = match Data::new() {
|
||||||
let data = Data::new().unwrap();
|
Ok(data) => data,
|
||||||
let data = Data {
|
Err(_) => {
|
||||||
host: data.host,
|
eprintln!("Error: Configuration file not found at {}/token.toml",
|
||||||
password: data.password,
|
get_config_base_path().display());
|
||||||
handle: data.handle,
|
eprintln!("Please run 'aibot login <handle> -p <password>' first to authenticate.");
|
||||||
did: data.did,
|
std::process::exit(1);
|
||||||
access: data.access,
|
}
|
||||||
refresh: data.refresh,
|
|
||||||
};
|
};
|
||||||
let t = "https://".to_string() + &data.host.to_string() + &"/xrpc/".to_string();
|
let t = "https://".to_string() + &data.host.to_string() + &"/xrpc/".to_string();
|
||||||
let baseurl = BaseUrl {
|
let baseurl = BaseUrl {
|
||||||
profile_get: "com.atproto.identity.resolveHandle".to_string(),
|
profile_get: "com.atproto.identity.resolveHandle".to_string(),
|
||||||
thread_get: "app.bsky.feed.getPostThread".to_string(),
|
thread_get: "app.bsky.feed.getPostThread".to_string(),
|
||||||
|
record_put: "com.atproto.repo.putRecord".to_string(),
|
||||||
record_create: "com.atproto.repo.createRecord".to_string(),
|
record_create: "com.atproto.repo.createRecord".to_string(),
|
||||||
record_delete: "com.atproto.repo.deleteRecord".to_string(),
|
record_delete: "com.atproto.repo.deleteRecord".to_string(),
|
||||||
describe: "com.atproto.repo.describeRepo".to_string(),
|
describe: "com.atproto.repo.describeRepo".to_string(),
|
||||||
@ -173,6 +206,7 @@ pub fn url(s: &str) -> String {
|
|||||||
"record_list" => t.to_string() + &baseurl.record_list,
|
"record_list" => t.to_string() + &baseurl.record_list,
|
||||||
"record_create" => t.to_string() + &baseurl.record_create,
|
"record_create" => t.to_string() + &baseurl.record_create,
|
||||||
"record_delete" => t.to_string() + &baseurl.record_delete,
|
"record_delete" => t.to_string() + &baseurl.record_delete,
|
||||||
|
"record_put" => t.to_string() + &baseurl.record_put,
|
||||||
"session_create" => t.to_string() + &baseurl.session_create,
|
"session_create" => t.to_string() + &baseurl.session_create,
|
||||||
"session_refresh" => t.to_string() + &baseurl.session_refresh,
|
"session_refresh" => t.to_string() + &baseurl.session_refresh,
|
||||||
"session_get" => t.to_string() + &baseurl.session_get,
|
"session_get" => t.to_string() + &baseurl.session_get,
|
||||||
@ -191,29 +225,29 @@ pub fn url(s: &str) -> String {
|
|||||||
"follows" => t.to_string() + &baseurl.follows,
|
"follows" => t.to_string() + &baseurl.follows,
|
||||||
"followers" => t.to_string() + &baseurl.followers,
|
"followers" => t.to_string() + &baseurl.followers,
|
||||||
"feed_get" => t.to_string() + &baseurl.feed_get,
|
"feed_get" => t.to_string() + &baseurl.feed_get,
|
||||||
_ => s,
|
_ => s.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_toml(s: &str) -> String {
|
pub fn data_toml(s: &str) -> String {
|
||||||
let s = String::from(s);
|
let data = match Data::new() {
|
||||||
let data = Data::new().unwrap();
|
Ok(data) => data,
|
||||||
let data = Data {
|
Err(_) => {
|
||||||
host: data.host,
|
eprintln!("Error: Configuration file not found at {}/token.toml",
|
||||||
password: data.password,
|
get_config_base_path().display());
|
||||||
handle: data.handle,
|
eprintln!("Please run 'aibot login <handle> -p <password>' first to authenticate.");
|
||||||
did: data.did,
|
std::process::exit(1);
|
||||||
access: data.access,
|
}
|
||||||
refresh: data.refresh,
|
|
||||||
};
|
};
|
||||||
match &*s {
|
|
||||||
|
match s {
|
||||||
"host" => data.host,
|
"host" => data.host,
|
||||||
"password" => data.password,
|
"password" => data.password,
|
||||||
"handle" => data.handle,
|
"handle" => data.handle,
|
||||||
"did" => data.did,
|
"did" => data.did,
|
||||||
"access" => data.access,
|
"access" => data.access,
|
||||||
"refresh" => data.refresh,
|
"refresh" => data.refresh,
|
||||||
_ => s,
|
_ => s.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,41 +263,41 @@ pub fn c_refresh(access: &str, refresh: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_refresh(s: &str) -> String {
|
pub fn data_refresh(s: &str) -> String {
|
||||||
let s = String::from(s);
|
let data = match Data::new() {
|
||||||
|
Ok(data) => data,
|
||||||
let data = Data::new().unwrap();
|
Err(_) => {
|
||||||
let data = Data {
|
eprintln!("Error: Configuration file not found at {}/token.toml",
|
||||||
host: data.host,
|
get_config_base_path().display());
|
||||||
password: data.password,
|
eprintln!("Please run 'aibot login <handle> -p <password>' first to authenticate.");
|
||||||
handle: data.handle,
|
std::process::exit(1);
|
||||||
did: data.did,
|
}
|
||||||
access: data.access,
|
|
||||||
refresh: data.refresh,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut _file = match Refresh::new()
|
let mut _file = match Refresh::new() {
|
||||||
{
|
|
||||||
Err(_why) => c_refresh(&data.access, &data.refresh),
|
Err(_why) => c_refresh(&data.access, &data.refresh),
|
||||||
Ok(_) => println!(""),
|
Ok(_) => println!(""),
|
||||||
};
|
};
|
||||||
let refresh = Refresh::new().unwrap();
|
|
||||||
let refresh = Refresh {
|
let refresh = match Refresh::new() {
|
||||||
access: refresh.access,
|
Ok(refresh) => refresh,
|
||||||
refresh: refresh.refresh,
|
Err(_) => {
|
||||||
|
eprintln!("Error: Refresh token file not found.");
|
||||||
|
eprintln!("Please run 'aibot login <handle> -p <password>' to re-authenticate.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match &*s {
|
|
||||||
|
match s {
|
||||||
"access" => refresh.access,
|
"access" => refresh.access,
|
||||||
"refresh" => refresh.refresh,
|
"refresh" => refresh.refresh,
|
||||||
_ => s,
|
_ => s.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_scpt(s: &str) -> String {
|
pub fn data_scpt(s: &str) -> String {
|
||||||
let s = String::from(s);
|
let mut path = get_config_path("scpt");
|
||||||
let file = "/.config/ai/scpt/".to_owned() + &s + &".zsh";
|
path.push(format!("{}.zsh", s));
|
||||||
let mut f = shellexpand::tilde("~").to_string();
|
path.to_string_lossy().to_string()
|
||||||
f.push_str(&file);
|
|
||||||
return f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -531,7 +565,20 @@ pub fn w_cfg(h: &str, res: &str, password: &str) {
|
|||||||
let mut f = fs::File::create(f.clone()).unwrap();
|
let mut f = fs::File::create(f.clone()).unwrap();
|
||||||
let mut ff = fs::File::create(ff.clone()).unwrap();
|
let mut ff = fs::File::create(ff.clone()).unwrap();
|
||||||
f.write_all(&res.as_bytes()).unwrap();
|
f.write_all(&res.as_bytes()).unwrap();
|
||||||
let json: Token = serde_json::from_str(&res).unwrap();
|
// Check if response contains an error
|
||||||
|
if res.contains("\"error\"") {
|
||||||
|
eprintln!("Authentication error: {}", res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json: Token = match serde_json::from_str(&res) {
|
||||||
|
Ok(token) => token,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("JSON parse error: {}", e);
|
||||||
|
eprintln!("Response: {}", res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
let datas = Data {
|
let datas = Data {
|
||||||
host: h.to_string(),
|
host: h.to_string(),
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
@ -600,11 +647,10 @@ pub fn w_cid(cid: String, file: String, t: bool) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn c_follow_all() {
|
pub fn c_follow_all() {
|
||||||
let file = "/.config/ai/scpt/follow_all.zsh";
|
let path = expand_home_path("~/.config/syui/ai/bot/scpt/follow_all.zsh");
|
||||||
let mut f = shellexpand::tilde("~").to_string();
|
|
||||||
f.push_str(&file);
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
let output = Command::new(&f).output().expect("zsh");
|
let output = Command::new(path.to_str().unwrap()).output().expect("zsh");
|
||||||
let d = String::from_utf8_lossy(&output.stdout);
|
let d = String::from_utf8_lossy(&output.stdout);
|
||||||
let d = "\n".to_owned() + &d.to_string();
|
let d = "\n".to_owned() + &d.to_string();
|
||||||
println!("{}", d);
|
println!("{}", d);
|
||||||
@ -614,9 +660,10 @@ pub fn c_openai_key(c: &Context) {
|
|||||||
let api = c.args[0].to_string();
|
let api = c.args[0].to_string();
|
||||||
let o = "api='".to_owned() + &api.to_string() + &"'".to_owned();
|
let o = "api='".to_owned() + &api.to_string() + &"'".to_owned();
|
||||||
let o = o.to_string();
|
let o = o.to_string();
|
||||||
let l = shellexpand::tilde("~") + "/.config/ai/openai.toml";
|
|
||||||
let l = l.to_string();
|
let path = expand_home_path("~/.config/syui/ai/bot/openai.toml");
|
||||||
let mut l = fs::File::create(l).unwrap();
|
|
||||||
|
let mut l = fs::File::create(&path).unwrap();
|
||||||
if o != "" {
|
if o != "" {
|
||||||
l.write_all(&o.as_bytes()).unwrap();
|
l.write_all(&o.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
@ -625,9 +672,10 @@ pub fn c_openai_key(c: &Context) {
|
|||||||
|
|
||||||
impl Open {
|
impl Open {
|
||||||
pub fn new() -> Result<Self, ConfigError> {
|
pub fn new() -> Result<Self, ConfigError> {
|
||||||
let d = shellexpand::tilde("~") + "/.config/ai/openai.toml";
|
let path = expand_home_path("~/.config/syui/ai/bot/openai.toml");
|
||||||
|
|
||||||
let s = Config::builder()
|
let s = Config::builder()
|
||||||
.add_source(File::with_name(&d))
|
.add_source(File::with_name(path.to_str().unwrap()))
|
||||||
.add_source(config::Environment::with_prefix("APP"))
|
.add_source(config::Environment::with_prefix("APP"))
|
||||||
.build()?;
|
.build()?;
|
||||||
s.try_deserialize()
|
s.try_deserialize()
|
||||||
|
@ -1,33 +1,21 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(rkey: String, col: String) -> String {
|
pub async fn post_request(rkey: String, col: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
//let did = data_toml(&"did");
|
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_delete");
|
let url = url(&"record_delete");
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"rkey": rkey.to_string(),
|
"rkey": rkey.to_string(),
|
||||||
"collection": col.to_string()
|
"collection": col.to_string()
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.post(url)
|
Err(e) => format!("Error: {}", e),
|
||||||
.json(&post)
|
}
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,13 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
//use crate::data_toml;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
|
||||||
pub async fn get_request(user: String) -> String {
|
pub async fn get_request(user: String) -> String {
|
||||||
//let token = data_refresh(&"access");
|
let base_url = url(&"describe");
|
||||||
let url = url(&"describe");
|
let url = format!("{}?repo={}", base_url, user);
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(e) => format!("Error: {}", e),
|
||||||
.query(&[("repo", &user)])
|
}
|
||||||
//.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,13 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
|
||||||
pub async fn get_request(feed: String) -> String {
|
pub async fn get_request(feed: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
let base_url = url(&"feed_get");
|
||||||
let url = url(&"feed_get");
|
let url = format!("{}?feed={}", base_url, feed);
|
||||||
let feed = feed.to_string();
|
let client = HttpClient::new();
|
||||||
//let col = "app.bsky.feed.generator".to_string();
|
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get_with_auth(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(_) => "err".to_string(),
|
||||||
.query(&[("feed", feed)])
|
|
||||||
//.query(&[("feed", feed), ("collection", col)])
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let status_ref = res.error_for_status_ref();
|
|
||||||
|
|
||||||
match status_ref {
|
|
||||||
Ok(_) => {
|
|
||||||
return res.text().await.unwrap();
|
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
|
||||||
let e = "err".to_string();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
//use crate::data::Follow;
|
//use crate::data::Follow;
|
||||||
|
|
||||||
pub async fn post_request(u: String) -> String {
|
pub async fn post_request(u: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -18,7 +16,7 @@ pub async fn post_request(u: String) -> String {
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -26,25 +24,19 @@ pub async fn post_request(u: String) -> String {
|
|||||||
"subject": u.to_string(),
|
"subject": u.to_string(),
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error following user: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_request(u: String, rkey: String) -> String {
|
pub async fn delete_request(u: String, rkey: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -54,7 +46,7 @@ pub async fn delete_request(u: String, rkey: String) -> String {
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -63,19 +55,14 @@ pub async fn delete_request(u: String, rkey: String) -> String {
|
|||||||
"subject": u.to_string(),
|
"subject": u.to_string(),
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error unfollowing user: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,14 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
//use serde_json::json;
|
|
||||||
|
|
||||||
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
|
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
|
||||||
let token = data_refresh(&"access");
|
let base_url = url(&"followers");
|
||||||
let url = url(&"followers");
|
|
||||||
let cursor = cursor.unwrap();
|
let cursor = cursor.unwrap();
|
||||||
|
let url = format!("{}?actor={}&cursor={}", base_url, actor, cursor);
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get_with_auth(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(e) => format!("Error: {}", e),
|
||||||
.query(&[("actor", actor), ("cursor", cursor)])
|
}
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,14 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
//use serde_json::json;
|
|
||||||
|
|
||||||
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
|
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
|
||||||
let token = data_refresh(&"access");
|
let base_url = url(&"follows");
|
||||||
let url = url(&"follows");
|
|
||||||
let cursor = cursor.unwrap();
|
let cursor = cursor.unwrap();
|
||||||
//let cursor = "1682386039125::bafyreihwgwozmvqxcxrhbr65agcaa4v357p27ccrhzkjf3mz5xiozjvzfa".to_string();
|
let url = format!("{}?actor={}&cursor={}", base_url, actor, cursor);
|
||||||
//let cursor = "1682385956974::bafyreihivhux5m3sxbg33yruhw5ozhahwspnuqdsysbo57smzgptdcluem".to_string();
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get_with_auth(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(e) => format!("Error: {}", e),
|
||||||
.query(&[("actor", actor), ("cursor", cursor)])
|
}
|
||||||
//cursor.unwrap()
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
5
src/game.rs
Normal file
5
src/game.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod post_card;
|
||||||
|
pub mod post_card_verify;
|
||||||
|
pub mod post_game;
|
||||||
|
pub mod post_game_user;
|
||||||
|
pub mod post_game_login;
|
35
src/game/post_card.rs
Normal file
35
src/game/post_card.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::data_toml;
|
||||||
|
use crate::url;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(verify: String, id: i32, cp: i32, rank: i32, rare: String, col: String, author: String) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_create");
|
||||||
|
let d = Timestamp::now_utc();
|
||||||
|
let d = d.to_string();
|
||||||
|
|
||||||
|
let post = json!({
|
||||||
|
"repo": handle.to_string(),
|
||||||
|
"did": did.to_string(),
|
||||||
|
"collection": col.to_string(),
|
||||||
|
"record": {
|
||||||
|
"id": id,
|
||||||
|
"cp": cp,
|
||||||
|
"rank": rank,
|
||||||
|
"rare": rare.to_string(),
|
||||||
|
"author": author.to_string(),
|
||||||
|
"verify": verify.to_string(),
|
||||||
|
"createdAt": d.to_string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
49
src/game/post_card_verify.rs
Normal file
49
src/game/post_card_verify.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::data_toml;
|
||||||
|
use crate::url;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(col: String, img: String, id: i32, cp: i32, rank: i32, rare: String, user_handle: String, user_did: String) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_create");
|
||||||
|
let d = Timestamp::now_utc();
|
||||||
|
let d = d.to_string();
|
||||||
|
let link = "https://bsky.app/profile/yui.syui.ai".to_string();
|
||||||
|
let post = json!({
|
||||||
|
"repo": handle.to_string(),
|
||||||
|
"did": did.to_string(),
|
||||||
|
"collection": col.to_string(),
|
||||||
|
"record": {
|
||||||
|
"id": id,
|
||||||
|
"cp": cp,
|
||||||
|
"rank": rank,
|
||||||
|
"rare": rare.to_string(),
|
||||||
|
"handle": user_handle.to_string(),
|
||||||
|
"did": user_did.to_string(),
|
||||||
|
"embed": {
|
||||||
|
"$type": "app.bsky.embed.external",
|
||||||
|
"external": {
|
||||||
|
"uri": link,
|
||||||
|
"thumb": {
|
||||||
|
"$type": "blob",
|
||||||
|
"ref": {
|
||||||
|
"$link": img.to_string()
|
||||||
|
},
|
||||||
|
"mimeType": "image/jpeg",
|
||||||
|
"size": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdAt": d.to_string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
30
src/game/post_game.rs
Normal file
30
src/game/post_game.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::data_toml;
|
||||||
|
use crate::url;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(col: String, account: String) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_put");
|
||||||
|
let d = Timestamp::now_utc();
|
||||||
|
let d = d.to_string();
|
||||||
|
let post = json!({
|
||||||
|
"repo": handle.to_string(),
|
||||||
|
"did": did.to_string(),
|
||||||
|
"collection": col.to_string(),
|
||||||
|
"rkey": "self".to_string(),
|
||||||
|
"record": {
|
||||||
|
"account": account.to_string(),
|
||||||
|
"createdAt": d.to_string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
33
src/game/post_game_login.rs
Normal file
33
src/game/post_game_login.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::data_toml;
|
||||||
|
use crate::url;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(col: String, username: String, login: bool, account: String) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_put");
|
||||||
|
let d = Timestamp::now_utc();
|
||||||
|
let d = d.to_string();
|
||||||
|
|
||||||
|
let post = json!({
|
||||||
|
"repo": handle.to_string(),
|
||||||
|
"did": did.to_string(),
|
||||||
|
"collection": col.to_string(),
|
||||||
|
"rkey": "self".to_string(),
|
||||||
|
"record": {
|
||||||
|
"login": login,
|
||||||
|
"username": username.to_string(),
|
||||||
|
"account": account.to_string(),
|
||||||
|
"createdAt": d.to_string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
46
src/game/post_game_user.rs
Normal file
46
src/game/post_game_user.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use crate::data_toml;
|
||||||
|
use crate::url;
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(col: String, user_name: String, user_did: String, user_handle: String, aiten: i32, limit: i32, chara: String, lv: i32, exp: i32, hp: i32, rank: i32, mode: i32, attach: i32, critical: i32, critical_d: i32) -> String {
|
||||||
|
let did = data_toml(&"did");
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
let url = url(&"record_put");
|
||||||
|
let d = Timestamp::now_utc();
|
||||||
|
let d = d.to_string();
|
||||||
|
let post = json!({
|
||||||
|
"repo": handle.to_string(),
|
||||||
|
"did": did.to_string(),
|
||||||
|
"collection": col.to_string(),
|
||||||
|
"rkey": user_name.to_string(),
|
||||||
|
"record": {
|
||||||
|
"did": user_did.to_string(),
|
||||||
|
"handle": user_handle.to_string(),
|
||||||
|
"aiten": aiten,
|
||||||
|
"limit": limit,
|
||||||
|
"character": {
|
||||||
|
chara.to_string(): {
|
||||||
|
"lv": lv,
|
||||||
|
"exp": exp,
|
||||||
|
"hp": hp,
|
||||||
|
"rank": rank,
|
||||||
|
"mode": mode,
|
||||||
|
"attach": attach,
|
||||||
|
"critical": critical,
|
||||||
|
"critical_d": critical_d,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdAt": d.to_string(),
|
||||||
|
"updatedAt": d.to_string(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
114
src/http_client.rs
Normal file
114
src/http_client.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use reqwest::{Client, Error};
|
||||||
|
use serde::Serialize;
|
||||||
|
use crate::data_refresh;
|
||||||
|
|
||||||
|
pub struct HttpClient {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpClient {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
client: Client::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET request with authentication
|
||||||
|
pub async fn get_with_auth(&self, url: &str) -> Result<String, Error> {
|
||||||
|
let token = data_refresh(&"access");
|
||||||
|
|
||||||
|
let response = self.client
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST request with JSON body and authentication
|
||||||
|
pub async fn post_json_with_auth<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error> {
|
||||||
|
let token = data_refresh(&"access");
|
||||||
|
|
||||||
|
let response = self.client
|
||||||
|
.post(url)
|
||||||
|
.json(json)
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DELETE request with authentication
|
||||||
|
pub async fn delete_with_auth(&self, url: &str) -> Result<String, Error> {
|
||||||
|
let token = data_refresh(&"access");
|
||||||
|
|
||||||
|
let response = self.client
|
||||||
|
.delete(url)
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST request without authentication (for login, etc.)
|
||||||
|
pub async fn post_json<T: Serialize>(&self, url: &str, json: &T) -> Result<String, Error> {
|
||||||
|
let response = self.client
|
||||||
|
.post(url)
|
||||||
|
.json(json)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET request without authentication
|
||||||
|
pub async fn get(&self, url: &str) -> Result<String, Error> {
|
||||||
|
let response = self.client
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST request with custom headers
|
||||||
|
pub async fn post_with_headers<T: Serialize>(
|
||||||
|
&self,
|
||||||
|
url: &str,
|
||||||
|
json: &T,
|
||||||
|
headers: Vec<(&str, &str)>
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let mut request = self.client.post(url).json(json);
|
||||||
|
|
||||||
|
for (key, value) in headers {
|
||||||
|
request = request.header(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = request
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpClient {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
27
src/img.rs
27
src/img.rs
@ -1,23 +1,19 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
pub async fn post_request(text: String, link: String) -> String {
|
pub async fn post_request(text: String, link: String) -> String {
|
||||||
|
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -41,19 +37,12 @@ pub async fn post_request(text: String, link: String) -> String {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -12,17 +11,15 @@ pub async fn post_request(
|
|||||||
uri: String,
|
uri: String,
|
||||||
itype: String,
|
itype: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -56,19 +53,12 @@ pub async fn post_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
pub async fn post_request(text: String, link: String) -> String {
|
pub async fn post_request(text: String, link: String) -> String {
|
||||||
|
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -41,19 +37,12 @@ pub async fn post_request(text: String, link: String) -> String {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
29
src/like.rs
29
src/like.rs
@ -1,12 +1,10 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(cid: String, uri: String) -> String {
|
pub async fn post_request(cid: String, uri: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -16,7 +14,7 @@ pub async fn post_request(cid: String, uri: String) -> String {
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -27,19 +25,14 @@ pub async fn post_request(cid: String, uri: String) -> String {
|
|||||||
},
|
},
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error liking post: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
413
src/main.rs
413
src/main.rs
@ -12,6 +12,11 @@ use crate::data::url;
|
|||||||
use crate::data::w_cfg;
|
use crate::data::w_cfg;
|
||||||
use crate::data::w_refresh;
|
use crate::data::w_refresh;
|
||||||
use crate::feed_watch::c_feed_watch;
|
use crate::feed_watch::c_feed_watch;
|
||||||
|
use crate::game::post_card;
|
||||||
|
use crate::game::post_card_verify;
|
||||||
|
use crate::game::post_game;
|
||||||
|
use crate::game::post_game_user;
|
||||||
|
use crate::game::post_game_login;
|
||||||
|
|
||||||
use data::ProfileIdentityResolve;
|
use data::ProfileIdentityResolve;
|
||||||
|
|
||||||
@ -22,6 +27,7 @@ pub mod describe;
|
|||||||
pub mod follow;
|
pub mod follow;
|
||||||
pub mod followers;
|
pub mod followers;
|
||||||
pub mod follows;
|
pub mod follows;
|
||||||
|
pub mod http_client;
|
||||||
pub mod img_reply;
|
pub mod img_reply;
|
||||||
pub mod like;
|
pub mod like;
|
||||||
pub mod mention;
|
pub mod mention;
|
||||||
@ -30,6 +36,7 @@ pub mod notify_read;
|
|||||||
pub mod openai;
|
pub mod openai;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_link;
|
pub mod post_link;
|
||||||
|
pub mod game;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod refresh;
|
pub mod refresh;
|
||||||
pub mod reply;
|
pub mod reply;
|
||||||
@ -42,6 +49,10 @@ pub mod token;
|
|||||||
pub mod feed_get;
|
pub mod feed_get;
|
||||||
pub mod feed_watch;
|
pub mod feed_watch;
|
||||||
pub mod delete_record;
|
pub mod delete_record;
|
||||||
|
pub mod update_handle;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
@ -95,7 +106,7 @@ fn main() {
|
|||||||
.command(
|
.command(
|
||||||
Command::new("login")
|
Command::new("login")
|
||||||
.alias("l")
|
.alias("l")
|
||||||
.description("l <handle> -p <password>\n\t\t\tl <handle> -p <password> -s <server>")
|
.description("l <handle> -p <password>\n\t\t\tl <handle> -p <password> -s <server>\n\t\t\tl <handle> -p <password> -c <2fa_code>")
|
||||||
.action(token)
|
.action(token)
|
||||||
.flag(
|
.flag(
|
||||||
Flag::new("password", FlagType::String)
|
Flag::new("password", FlagType::String)
|
||||||
@ -107,6 +118,11 @@ fn main() {
|
|||||||
.description("server flag")
|
.description("server flag")
|
||||||
.alias("s"),
|
.alias("s"),
|
||||||
)
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("code", FlagType::String)
|
||||||
|
.description("2FA authentication code")
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
Command::new("refresh")
|
Command::new("refresh")
|
||||||
@ -154,6 +170,165 @@ fn main() {
|
|||||||
.alias("c"),
|
.alias("c"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("update-handle")
|
||||||
|
.description("update-handle <new_handle>")
|
||||||
|
.action(update_handle)
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("card")
|
||||||
|
.description("-v <at://verify> -i <int:id> -p <int:cp> -r <int:rank> -c <collection> -a <author> -img <link> -rare <normal>")
|
||||||
|
.action(card)
|
||||||
|
.flag(
|
||||||
|
Flag::new("id", FlagType::Int)
|
||||||
|
.alias("i"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("cp", FlagType::Int)
|
||||||
|
.alias("p"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("rank", FlagType::Int)
|
||||||
|
.alias("r"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("rare", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("author", FlagType::String)
|
||||||
|
.alias("a"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("verify", FlagType::String)
|
||||||
|
.alias("v"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("img", FlagType::String)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("card-verify")
|
||||||
|
.description("<at://verify> -c <collection> -i <id> -p <cp> -r <rank> -rare <normal> -H <syui.ai> -d <did>")
|
||||||
|
.action(card_verify)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("id", FlagType::Int)
|
||||||
|
.alias("i"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("cp", FlagType::Int)
|
||||||
|
.alias("p"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("rank", FlagType::Int)
|
||||||
|
.alias("r"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("rare", FlagType::String)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("handle", FlagType::String)
|
||||||
|
.alias("H"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("did", FlagType::String)
|
||||||
|
.alias("did"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("game")
|
||||||
|
.description("a <at://yui.syui.ai/ai.syui.game.user/username>")
|
||||||
|
.action(game)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("account", FlagType::String)
|
||||||
|
.alias("a"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("game-login")
|
||||||
|
.description("l <bool> -u <username> -c <collection>")
|
||||||
|
.action(game_login)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("login", FlagType::Bool)
|
||||||
|
.alias("l"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("username", FlagType::String)
|
||||||
|
.alias("u"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
Command::new("game-user")
|
||||||
|
.description("-chara ai -l 20240101 -ten 0 --lv 0 --exp 0 --hp 0 --rank 0 --mode 0 --attach 0 --critical 0 --critical_d 0")
|
||||||
|
.action(game_user)
|
||||||
|
.flag(
|
||||||
|
Flag::new("username", FlagType::String)
|
||||||
|
.alias("u"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("did", FlagType::String)
|
||||||
|
.alias("d"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("handle", FlagType::String)
|
||||||
|
.alias("H"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("character", FlagType::String)
|
||||||
|
.alias("chara"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("aiten", FlagType::Int)
|
||||||
|
.alias("ten"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("limit", FlagType::Int)
|
||||||
|
.alias("l"),
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("lv", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("hp", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("attach", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("exp", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("critical", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("critical_d", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("rank", FlagType::Int)
|
||||||
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("mode", FlagType::Int)
|
||||||
|
)
|
||||||
|
)
|
||||||
.command(
|
.command(
|
||||||
Command::new("like")
|
Command::new("like")
|
||||||
.description("like <cid> -u <uri>")
|
.description("like <cid> -u <uri>")
|
||||||
@ -234,6 +409,10 @@ fn main() {
|
|||||||
Flag::new("post", FlagType::String)
|
Flag::new("post", FlagType::String)
|
||||||
.alias("p"),
|
.alias("p"),
|
||||||
)
|
)
|
||||||
|
.flag(
|
||||||
|
Flag::new("col", FlagType::String)
|
||||||
|
.alias("c"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
Command::new("follow")
|
Command::new("follow")
|
||||||
@ -335,18 +514,19 @@ fn openai_key(c: &Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn token(c: &Context) {
|
fn token(c: &Context) {
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Handle is required.");
|
||||||
|
eprintln!("Usage: aibot login <handle> -p <password>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(p) = c.string_flag("password") {
|
if let Ok(p) = c.string_flag("password") {
|
||||||
if let Ok(s) = c.string_flag("server") {
|
let server = c.string_flag("server").unwrap_or_else(|_| "bsky.social".to_string());
|
||||||
let res = token::post_request(m.to_string(), p.to_string(), s.to_string()).await;
|
let code = c.string_flag("code").ok();
|
||||||
w_cfg(&s, &res, &p);
|
|
||||||
} else {
|
let res = token::post_request(m.to_string(), p.to_string(), server.to_string(), code).await;
|
||||||
let res =
|
w_cfg(&server, &res, &p);
|
||||||
token::post_request(m.to_string(), p.to_string(), "bsky.social".to_string())
|
|
||||||
.await;
|
|
||||||
w_cfg(&"bsky.social", &res, &p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
|
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
|
||||||
@ -362,7 +542,7 @@ fn refresh(_c: &Context) {
|
|||||||
let m = data_toml(&"handle");
|
let m = data_toml(&"handle");
|
||||||
let p = data_toml(&"password");
|
let p = data_toml(&"password");
|
||||||
let s = data_toml(&"host");
|
let s = data_toml(&"host");
|
||||||
let res = token::post_request(m.to_string(), p.to_string(), s.to_string()).await;
|
let res = token::post_request(m.to_string(), p.to_string(), s.to_string(), None).await;
|
||||||
w_cfg(&s, &res, &p);
|
w_cfg(&s, &res, &p);
|
||||||
} else {
|
} else {
|
||||||
w_refresh(&res);
|
w_refresh(&res);
|
||||||
@ -431,6 +611,11 @@ fn timeline(c: &Context) {
|
|||||||
|
|
||||||
fn post(c: &Context) {
|
fn post(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Post text is required.");
|
||||||
|
eprintln!("Usage: aibot post <text>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(link) = c.string_flag("link") {
|
if let Ok(link) = c.string_flag("link") {
|
||||||
@ -450,6 +635,11 @@ fn post(c: &Context) {
|
|||||||
|
|
||||||
fn delete(c: &Context) {
|
fn delete(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Record key is required.");
|
||||||
|
eprintln!("Usage: aibot delete <rkey> --col <collection>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(col) = c.string_flag("col") {
|
if let Ok(col) = c.string_flag("col") {
|
||||||
@ -461,8 +651,29 @@ fn delete(c: &Context) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_handle(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: New handle is required.");
|
||||||
|
eprintln!("Usage: aibot update-handle <new_handle>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let new_handle = c.args[0].to_string();
|
||||||
|
let h = async {
|
||||||
|
let str = update_handle::post_request(new_handle);
|
||||||
|
println!("{}", str.await);
|
||||||
|
};
|
||||||
|
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
fn like(c: &Context) {
|
fn like(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: CID is required.");
|
||||||
|
eprintln!("Usage: aibot like <cid> --uri <uri>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(uri) = c.string_flag("uri") {
|
if let Ok(uri) = c.string_flag("uri") {
|
||||||
@ -474,8 +685,152 @@ fn like(c: &Context) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn c_card(c: &Context) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
//let m = c.args[0].to_string();
|
||||||
|
let author = c.string_flag("author").unwrap_or_else(|_| "syui".to_string());
|
||||||
|
let verify = c.string_flag("verify").unwrap_or_else(|_| "at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.card.verify/3lagpvhppmd2q".to_string());
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "ai.syui.card".to_string());
|
||||||
|
//let img = c.string_flag("img").unwrap_or_else(|_| "bafkreigvcjc46qtelpc4wsg7fwf6qktbi6a23ouqiupth2r37zhrn7wbza".to_string());
|
||||||
|
let id = c.int_flag("id")?.try_into()?;
|
||||||
|
let cp = c.int_flag("cp")?.try_into()?;
|
||||||
|
let rank = c.int_flag("rank")?.try_into()?;
|
||||||
|
let rare = c.string_flag("rare").unwrap_or_else(|_| "normal".to_string());
|
||||||
|
let str = post_card::post_request(verify, id, cp, rank, rare, col, author);
|
||||||
|
println!("{}", str.await);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async {
|
||||||
|
if let Err(e) = c_card(c).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn c_card_verify(c: &Context) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "ai.syui.card.verify".to_string());
|
||||||
|
let img = c.string_flag("img").unwrap_or_else(|_| "bafkreigvcjc46qtelpc4wsg7fwf6qktbi6a23ouqiupth2r37zhrn7wbza".to_string());
|
||||||
|
let id = c.int_flag("id")?.try_into()?;
|
||||||
|
let cp = c.int_flag("cp")?.try_into()?;
|
||||||
|
let rank = c.int_flag("rank")?.try_into()?;
|
||||||
|
let rare = c.string_flag("rare").unwrap_or_else(|_| "normal".to_string());
|
||||||
|
let user_handle = c.string_flag("handle").unwrap_or_else(|_| "syui.ai".to_string());
|
||||||
|
let user_did = c.string_flag("did").unwrap_or_else(|_| "did:plc:uqzpqmrjnptsxezjx4xuh2mn".to_string());
|
||||||
|
|
||||||
|
//match id === 1 let img = "xxx";
|
||||||
|
let str = post_card_verify::post_request(col, img, id, cp, rank, rare, user_handle, user_did);
|
||||||
|
println!("{}", str.await);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card_verify(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async {
|
||||||
|
if let Err(e) = c_card_verify(c).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn c_game(c: &Context) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let account = c.string_flag("account").unwrap_or_else(|_| "at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.game.user/syui".to_string());
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "ai.syui.game".to_string());
|
||||||
|
let handle = data_toml(&"handle");
|
||||||
|
if handle == "syui.ai" {
|
||||||
|
let str = post_game::post_request(col, account);
|
||||||
|
println!("{}", str.await);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Box::new(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Not authorized")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async {
|
||||||
|
if let Err(e) = c_game(c).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn c_game_user(c: &Context) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "ai.syui.game.user".to_string());
|
||||||
|
let user_name = c.string_flag("username").unwrap_or_else(|_| "syui".to_string());
|
||||||
|
let user_handle = c.string_flag("handle").unwrap_or_else(|_| "syui.ai".to_string());
|
||||||
|
let user_did = c.string_flag("did").unwrap_or_else(|_| "did:plc:uqzpqmrjnptsxezjx4xuh2mn".to_string());
|
||||||
|
let chara = c.string_flag("character").unwrap_or_else(|_| "ai".to_string());
|
||||||
|
let limit = c.int_flag("limit")?.try_into()?;
|
||||||
|
let aiten = c.int_flag("aiten")?.try_into()?;
|
||||||
|
let lv = c.int_flag("lv")?.try_into()?;
|
||||||
|
let exp = c.int_flag("exp")?.try_into()?;
|
||||||
|
let hp = c.int_flag("hp")?.try_into()?;
|
||||||
|
let rank = c.int_flag("rank")?.try_into()?;
|
||||||
|
let mode = c.int_flag("mode")?.try_into()?;
|
||||||
|
let attach = c.int_flag("attach")?.try_into()?;
|
||||||
|
let critical = c.int_flag("critical")?.try_into()?;
|
||||||
|
let critical_d = c.int_flag("critical_d")?.try_into()?;
|
||||||
|
|
||||||
|
if data_toml(&"handle") == "yui.syui.ai" {
|
||||||
|
let str = post_game_user::post_request(col, user_name, user_did, user_handle, aiten, limit, chara, lv, exp, hp, rank, mode, attach, critical, critical_d);
|
||||||
|
println!("{}", str.await);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Box::new(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Not authorized")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_user(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async {
|
||||||
|
if let Err(e) = c_game_user(c).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn c_game_login(c: &Context) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "ai.syui.game.login".to_string());
|
||||||
|
let user_name = c.string_flag("username").unwrap_or_else(|_| "syui".to_string());
|
||||||
|
let account = "at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.game.user/".to_string() + &user_name;
|
||||||
|
let login = c.bool_flag("login");
|
||||||
|
if data_toml(&"handle") == "yui.syui.ai" {
|
||||||
|
let str = post_game_login::post_request(col, user_name, login, account);
|
||||||
|
println!("{}", str.await);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Box::new(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Not authorized")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_login(c: &Context) {
|
||||||
|
refresh(c);
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async {
|
||||||
|
if let Err(e) = c_game_login(c).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn repost(c: &Context) {
|
fn repost(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: CID is required.");
|
||||||
|
eprintln!("Usage: aibot repost <cid> --uri <uri>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(uri) = c.string_flag("uri") {
|
if let Ok(uri) = c.string_flag("uri") {
|
||||||
@ -489,6 +844,11 @@ fn repost(c: &Context) {
|
|||||||
|
|
||||||
fn follow(c: &Context) {
|
fn follow(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Handle is required.");
|
||||||
|
eprintln!("Usage: aibot follow <handle>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
@ -512,10 +872,10 @@ fn profile(c: &Context) {
|
|||||||
let h = async {
|
let h = async {
|
||||||
if c.args.len() == 0 {
|
if c.args.len() == 0 {
|
||||||
let j = profile::get_request(data_toml(&"handle")).await;
|
let j = profile::get_request(data_toml(&"handle")).await;
|
||||||
println!("{}", j);
|
print!("{}", j);
|
||||||
} else {
|
} else {
|
||||||
let j = profile::get_request(c.args[0].to_string()).await;
|
let j = profile::get_request(c.args[0].to_string()).await;
|
||||||
println!("{}", j);
|
print!("{}", j);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
|
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
|
||||||
@ -524,10 +884,16 @@ fn profile(c: &Context) {
|
|||||||
|
|
||||||
fn mention(c: &Context) {
|
fn mention(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Handle is required.");
|
||||||
|
eprintln!("Usage: aibot mention <handle>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
let str = profile::get_request(m.to_string()).await;
|
let str = profile::get_request(m.to_string()).await;
|
||||||
let profile: ProfileIdentityResolve = serde_json::from_str(&str).unwrap();
|
let profile: ProfileIdentityResolve = serde_json::from_str(&str).unwrap();
|
||||||
|
let col = c.string_flag("col").unwrap_or_else(|_| "app.bsky.feed.post".to_string());
|
||||||
let udid = profile.did;
|
let udid = profile.did;
|
||||||
let handle = m.to_string();
|
let handle = m.to_string();
|
||||||
let at = "@".to_owned() + &handle;
|
let at = "@".to_owned() + &handle;
|
||||||
@ -535,6 +901,7 @@ fn mention(c: &Context) {
|
|||||||
let s = 0;
|
let s = 0;
|
||||||
if let Ok(post) = c.string_flag("post") {
|
if let Ok(post) = c.string_flag("post") {
|
||||||
let str = mention::post_request(
|
let str = mention::post_request(
|
||||||
|
col,
|
||||||
post.to_string(),
|
post.to_string(),
|
||||||
at.to_string(),
|
at.to_string(),
|
||||||
udid.to_string(),
|
udid.to_string(),
|
||||||
@ -551,6 +918,11 @@ fn mention(c: &Context) {
|
|||||||
|
|
||||||
fn reply(c: &Context) {
|
fn reply(c: &Context) {
|
||||||
refresh(c);
|
refresh(c);
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Reply text is required.");
|
||||||
|
eprintln!("Usage: aibot reply <text> --cid <cid> --uri <uri>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
if let Ok(cid) = c.string_flag("cid") {
|
if let Ok(cid) = c.string_flag("cid") {
|
||||||
@ -590,6 +962,11 @@ fn reply(c: &Context) {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn c_img_upload(c: &Context) -> reqwest::Result<()> {
|
async fn c_img_upload(c: &Context) -> reqwest::Result<()> {
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Image file path is required.");
|
||||||
|
eprintln!("Usage: aibot img_upload <image_file>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let token = data_refresh(&"access");
|
let token = data_refresh(&"access");
|
||||||
let atoken = "Authorization: Bearer ".to_owned() + &token;
|
let atoken = "Authorization: Bearer ".to_owned() + &token;
|
||||||
let con = "Content-Type: image/png";
|
let con = "Content-Type: image/png";
|
||||||
@ -621,6 +998,11 @@ fn img_upload(c: &Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn img_post(c: &Context) {
|
fn img_post(c: &Context) {
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Text is required.");
|
||||||
|
eprintln!("Usage: aibot img_post <text> --link <link> --cid <cid> --uri <uri>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let link = c.string_flag("link").unwrap();
|
let link = c.string_flag("link").unwrap();
|
||||||
let cid = c.string_flag("cid").unwrap();
|
let cid = c.string_flag("cid").unwrap();
|
||||||
@ -667,6 +1049,11 @@ fn reply_og(c: &Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn openai(c: &Context) {
|
fn openai(c: &Context) {
|
||||||
|
if c.args.is_empty() {
|
||||||
|
eprintln!("Error: Message is required.");
|
||||||
|
eprintln!("Usage: aibot openai <message>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
let m = c.args[0].to_string();
|
let m = c.args[0].to_string();
|
||||||
let h = async {
|
let h = async {
|
||||||
let str = openai::post_request(m.to_string()).await;
|
let str = openai::post_request(m.to_string()).await;
|
||||||
|
@ -1,28 +1,24 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(text: String, at: String, udid: String, s: i32, e: i32) -> String {
|
pub async fn post_request(col: String, text: String, at: String, udid: String, s: i32, e: i32) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
"record": {
|
"record": {
|
||||||
"text": at.to_string() + &" ".to_string() + &text.to_string(),
|
"text": at.to_string() + &" ".to_string() + &text.to_string(),
|
||||||
"$type": "app.bsky.feed.post",
|
"$type": col.to_string(),
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
"facets": [
|
"facets": [
|
||||||
{
|
{
|
||||||
@ -39,19 +35,12 @@ pub async fn post_request(text: String, at: String, udid: String, s: i32, e: i32
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,13 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
//use serde_json::json;
|
|
||||||
|
|
||||||
pub async fn get_request(limit: i32) -> String {
|
pub async fn get_request(limit: i32) -> String {
|
||||||
let token = data_refresh(&"access");
|
let base_url = url(&"notify_list");
|
||||||
let url = url(&"notify_list");
|
let url = format!("{}?limit={}", base_url, limit);
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get_with_auth(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(_) => "err".to_string(),
|
||||||
.query(&[("limit", limit)])
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let status_ref = res.error_for_status_ref();
|
|
||||||
|
|
||||||
match status_ref {
|
|
||||||
Ok(_) => {
|
|
||||||
return res.text().await.unwrap();
|
|
||||||
}
|
|
||||||
Err(_e) => {
|
|
||||||
let e = "err".to_string();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,17 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(time: String) -> String {
|
pub async fn post_request(time: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let url = url(&"notify_update");
|
let url = url(&"notify_update");
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"seenAt": time.to_string(),
|
"seenAt": time.to_string(),
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.post(url)
|
Err(e) => format!("Error: {}", e),
|
||||||
.json(&post)
|
}
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
29
src/post.rs
29
src/post.rs
@ -1,12 +1,10 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(text: String) -> String {
|
pub async fn post_request(text: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -16,7 +14,7 @@ pub async fn post_request(text: String) -> String {
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -24,19 +22,14 @@ pub async fn post_request(text: String) -> String {
|
|||||||
"text": text.to_string(),
|
"text": text.to_string(),
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error posting: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(text: String, link: String, s: i32, e: i32) -> String {
|
pub async fn post_request(text: String, link: String, s: i32, e: i32) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -38,19 +35,12 @@ pub async fn post_request(text: String, link: String, s: i32, e: i32) -> String
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
pub async fn get_request(user: String) -> String {
|
pub async fn get_request(user: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let url = url(&"profile_get") + &"?handle=" + &user;
|
let url = url(&"profile_get") + &"?handle=" + &user;
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.get_with_auth(&url).await {
|
||||||
.get(url)
|
Ok(response) => response,
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
Err(e) => {
|
||||||
.send()
|
eprintln!("Error getting profile: {}", e);
|
||||||
.await
|
"err".to_string()
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
}
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,18 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
|
||||||
pub async fn post_request() -> String {
|
pub async fn post_request() -> String {
|
||||||
let refresh = data_toml(&"refresh");
|
let refresh = data_toml(&"refresh");
|
||||||
let url = url(&"session_refresh");
|
let url = url(&"session_refresh");
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let auth_header = format!("Bearer {}", refresh);
|
||||||
let res = client
|
let headers = vec![("Authorization", auth_header.as_str())];
|
||||||
.post(url)
|
let empty_json = serde_json::json!({});
|
||||||
.header("Authorization", "Bearer ".to_owned() + &refresh)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let status_ref = res.error_for_status_ref();
|
match client.post_with_headers(&url, &empty_json, headers).await {
|
||||||
|
Ok(response) => response,
|
||||||
match status_ref {
|
Err(_) => "err".to_string(),
|
||||||
Ok(_) => {
|
|
||||||
return res.text().await.unwrap();
|
|
||||||
}
|
|
||||||
Err(_e) => {
|
|
||||||
let e = "err".to_string();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
src/reply.rs
29
src/reply.rs
@ -1,7 +1,6 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ pub async fn post_request(
|
|||||||
cid_root: String,
|
cid_root: String,
|
||||||
uri_root: String,
|
uri_root: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -23,7 +21,7 @@ pub async fn post_request(
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -41,19 +39,14 @@ pub async fn post_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error replying to post: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -15,17 +14,15 @@ pub async fn post_request(
|
|||||||
cid_root: String,
|
cid_root: String,
|
||||||
uri_root: String,
|
uri_root: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -57,19 +54,12 @@ pub async fn post_request(
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -16,17 +15,15 @@ pub async fn post_request(
|
|||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
let url = url(&"record_create");
|
let url = url(&"record_create");
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
|
|
||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -60,19 +57,12 @@ pub async fn post_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
|
||||||
.post(url)
|
|
||||||
.json(&post)
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
extern crate reqwest;
|
|
||||||
use crate::data_toml;
|
use crate::data_toml;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub async fn post_request(cid: String, uri: String) -> String {
|
pub async fn post_request(cid: String, uri: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let did = data_toml(&"did");
|
let did = data_toml(&"did");
|
||||||
let handle = data_toml(&"handle");
|
let handle = data_toml(&"handle");
|
||||||
|
|
||||||
@ -16,7 +14,7 @@ pub async fn post_request(cid: String, uri: String) -> String {
|
|||||||
let d = Timestamp::now_utc();
|
let d = Timestamp::now_utc();
|
||||||
let d = d.to_string();
|
let d = d.to_string();
|
||||||
|
|
||||||
let post = Some(json!({
|
let post = json!({
|
||||||
"repo": handle.to_string(),
|
"repo": handle.to_string(),
|
||||||
"did": did.to_string(),
|
"did": did.to_string(),
|
||||||
"collection": col.to_string(),
|
"collection": col.to_string(),
|
||||||
@ -27,19 +25,14 @@ pub async fn post_request(cid: String, uri: String) -> String {
|
|||||||
},
|
},
|
||||||
"createdAt": d.to_string(),
|
"createdAt": d.to_string(),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = HttpClient::new();
|
||||||
let res = client
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
.post(url)
|
Ok(response) => response,
|
||||||
.json(&post)
|
Err(e) => {
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
eprintln!("Error reposting: {}", e);
|
||||||
.send()
|
"err".to_string()
|
||||||
.await
|
}
|
||||||
.unwrap()
|
}
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,12 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
|
||||||
pub async fn get_request() -> String {
|
pub async fn get_request() -> String {
|
||||||
let token = data_refresh(&"access");
|
|
||||||
let url = url(&"session_get");
|
let url = url(&"session_get");
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
match client.get_with_auth(&url).await {
|
||||||
let res = client
|
Ok(response) => response,
|
||||||
.get(url)
|
Err(_) => "err".to_string(),
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let status_ref = res.error_for_status_ref();
|
|
||||||
|
|
||||||
match status_ref {
|
|
||||||
Ok(_) => {
|
|
||||||
return res.text().await.unwrap();
|
|
||||||
}
|
|
||||||
Err(_e) => {
|
|
||||||
let e = "err".to_string();
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/tests/http_client_tests.rs
Normal file
24
src/tests/http_client_tests.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use crate::http_client::HttpClient;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_client_creation() {
|
||||||
|
let _client = HttpClient::new();
|
||||||
|
// HttpClientが正しく作成されることを確認
|
||||||
|
let _client2 = HttpClient::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_client_error_handling() {
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
// 無効なURLでエラーが返ることを確認
|
||||||
|
let result = client.get("http://invalid-url-that-does-not-exist.local").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// モジュールが正しくコンパイルされることを確認
|
||||||
|
#[test]
|
||||||
|
fn test_module_imports() {
|
||||||
|
// モジュールが存在することを確認
|
||||||
|
assert!(true);
|
||||||
|
}
|
2
src/tests/mod.rs
Normal file
2
src/tests/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod http_client_tests;
|
@ -1,27 +1,14 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use crate::data_refresh;
|
|
||||||
use crate::url;
|
use crate::url;
|
||||||
|
|
||||||
pub async fn get_request(actor: String) -> String {
|
pub async fn get_request(actor: String) -> String {
|
||||||
let token = data_refresh(&"access");
|
let base_url = url(&"record_list");
|
||||||
let url = url(&"record_list");
|
|
||||||
|
|
||||||
let actor = actor.to_string();
|
|
||||||
//let cursor = cursor.unwrap();
|
|
||||||
|
|
||||||
let col = "app.bsky.feed.post".to_string();
|
let col = "app.bsky.feed.post".to_string();
|
||||||
let client = reqwest::Client::new();
|
let url = format!("{}?repo={}&collection={}", base_url, actor, col);
|
||||||
let res = client
|
let client = HttpClient::new();
|
||||||
.get(url)
|
|
||||||
.query(&[("repo", actor), ("collection", col)])
|
|
||||||
//.query(&[("actor", actor),("cursor", cursor)])
|
|
||||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
match client.get_with_auth(&url).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
src/token.rs
58
src/token.rs
@ -1,25 +1,47 @@
|
|||||||
extern crate reqwest;
|
use crate::http_client::HttpClient;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
pub async fn post_request(handle: String, pass: String, host: String) -> String {
|
pub async fn post_request(handle: String, pass: String, host: String, auth_factor_token: Option<String>) -> String {
|
||||||
let url = "https://".to_owned()
|
// First attempt with provided 2FA code (if any)
|
||||||
+ &host.to_string()
|
let response = create_session_request(&handle, &pass, &host, auth_factor_token.as_deref()).await;
|
||||||
+ &"/xrpc/com.atproto.server.createSession".to_string();
|
|
||||||
|
// Check if 2FA is required
|
||||||
|
if response.contains("AuthFactorTokenRequired") {
|
||||||
|
println!("🔐 2FA authentication required");
|
||||||
|
println!("📧 A sign-in code has been sent to your email address");
|
||||||
|
|
||||||
|
// Prompt for 2FA code
|
||||||
|
print!("Enter 2FA code: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut code = String::new();
|
||||||
|
io::stdin().read_line(&mut code).unwrap();
|
||||||
|
let code = code.trim();
|
||||||
|
|
||||||
|
// Retry with 2FA code
|
||||||
|
println!("🔄 Retrying authentication with 2FA code...");
|
||||||
|
return create_session_request(&handle, &pass, &host, Some(code)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_session_request(handle: &str, pass: &str, host: &str, auth_factor_token: Option<&str>) -> String {
|
||||||
|
let url = format!("https://{}/xrpc/com.atproto.server.createSession", host);
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("identifier", &handle);
|
map.insert("identifier", handle);
|
||||||
map.insert("password", &pass);
|
map.insert("password", pass);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
// Add 2FA code if provided
|
||||||
let res = client
|
if let Some(code) = auth_factor_token {
|
||||||
.post(url)
|
map.insert("authFactorToken", code);
|
||||||
.json(&map)
|
}
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return res;
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
match client.post_json(&url, &map).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
src/update_handle.rs
Normal file
17
src/update_handle.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::url;
|
||||||
|
use crate::http_client::HttpClient;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn post_request(handle: String) -> String {
|
||||||
|
let url = url(&"update_handle");
|
||||||
|
let client = HttpClient::new();
|
||||||
|
|
||||||
|
let post = json!({
|
||||||
|
"handle": handle
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.post_json_with_auth(&url, &post).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => format!("Error updating handle: {}", e),
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
|
|
||||||
ai l $HANDLE -p $PASSWORD -s $HOST && ai bot -a $ADMIN
|
#ai l $HANDLE -p $PASSWORD -s $HOST
|
||||||
|
ai bot -a $ADMIN
|
||||||
|
Reference in New Issue
Block a user