feat: Initial v0.9 release with API Key authentication
## v0.9.20260325_144654 ### Features - API Key Authentication System - Job Worker System - V2 Backup Versioning ### Bug Fixes - get_processor_results_by_job column mapping Co-authored-by: OpenCode
This commit is contained in:
674
docs/DEMO_MANUAL.md
Normal file
674
docs/DEMO_MANUAL.md
Normal file
@@ -0,0 +1,674 @@
|
||||
# Momentry Core API 示範手冊
|
||||
|
||||
| 項目 | 內容 |
|
||||
|------|------|
|
||||
| 版本 | V1.0 |
|
||||
| 日期 | 2026-03-25 |
|
||||
| 狀態 | 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 快速開始
|
||||
|
||||
### Demo API Key
|
||||
|
||||
```
|
||||
API Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
|
||||
Key ID: muser_68600856036340bcafc01930eb4bd839
|
||||
過期日: 2027-03-25
|
||||
```
|
||||
|
||||
### 測試連線
|
||||
|
||||
```bash
|
||||
curl http://localhost:3002/health
|
||||
```
|
||||
|
||||
```json
|
||||
{"status":"ok","version":"0.1.0","uptime_ms":456464}
|
||||
```
|
||||
|
||||
### 測試認證
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||
http://localhost:3002/api/v1/videos | jq '.videos | length'
|
||||
```
|
||||
|
||||
```json
|
||||
13
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 環境 URL
|
||||
|
||||
| 環境 | URL | 用途 |
|
||||
|------|-----|------|
|
||||
| **本地開發** | `http://localhost:3002` | 本機開發測試 |
|
||||
| **外部訪問** | `https://api.momentry.ddns.net` | n8n/WordPress/curl 生產環境 |
|
||||
|
||||
---
|
||||
|
||||
## 端點總覽
|
||||
|
||||
| 方法 | 端點 | 說明 | 認證 |
|
||||
|------|------|------|------|
|
||||
| GET | `/health` | 健康檢查 | 公開 |
|
||||
| GET | `/health/detailed` | 詳細健康檢查 | 公開 |
|
||||
| POST | `/api/v1/register` | 註冊影片 | 需要 |
|
||||
| POST | `/api/v1/probe` | 探測影片資訊 | 需要 |
|
||||
| POST | `/api/v1/search` | 語意搜尋 | 需要 |
|
||||
| POST | `/api/v1/n8n/search` | n8n 格式搜尋 | 需要 |
|
||||
| POST | `/api/v1/search/hybrid` | 混合搜尋 | 需要 |
|
||||
| GET | `/api/v1/videos` | 列出所有影片 | 需要 |
|
||||
| GET | `/api/v1/lookup` | 查詢影片 UUID | 需要 |
|
||||
| GET | `/api/v1/progress/:uuid` | 處理進度 | 需要 |
|
||||
| GET | `/api/v1/jobs` | 任務列表 | 需要 |
|
||||
| GET | `/api/v1/jobs/:uuid` | 任務詳情 | 需要 |
|
||||
|
||||
---
|
||||
|
||||
## 1. curl 範例
|
||||
|
||||
### 基本格式
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
URL
|
||||
```
|
||||
|
||||
### 1.1 健康檢查(公開)
|
||||
|
||||
```bash
|
||||
# 基本健康檢查
|
||||
curl http://localhost:3002/health
|
||||
|
||||
# 詳細健康檢查(含服務狀態)
|
||||
curl http://localhost:3002/health/detailed
|
||||
```
|
||||
|
||||
### 1.2 列出影片
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||
http://localhost:3002/api/v1/videos | jq '.'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"videos": [
|
||||
{
|
||||
"uuid": "952f5854b9febad1",
|
||||
"file_name": "ExaSAN PCIe series - Director Ou Yu-Zhi Shares His Experience.mp4",
|
||||
"duration": 159.637188,
|
||||
"width": 640,
|
||||
"height": 360
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 搜尋影片
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "ExaSAN", "limit": 5}' \
|
||||
http://localhost:3002/api/v1/search | jq '.'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"uuid": "952f5854b9febad1",
|
||||
"chunk_id": "...",
|
||||
"text": "...",
|
||||
"score": 0.85,
|
||||
"start_time": 0.0,
|
||||
"end_time": 5.0
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"query": "ExaSAN",
|
||||
"took_ms": 123
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 查詢進度
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||
http://localhost:3002/api/v1/progress/952f5854b9febad1 | jq '.'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "952f5854b9febad1",
|
||||
"overall_progress": 67,
|
||||
"current_processor": "yolo",
|
||||
"processors": [
|
||||
{"name": "asr", "status": "completed"},
|
||||
{"name": "cut", "status": "completed"},
|
||||
{"name": "yolo", "status": "running"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. n8n 範例
|
||||
|
||||
### 2.1 HTTP Request 節點設定
|
||||
|
||||
```
|
||||
Method: POST
|
||||
URL: https://api.momentry.ddns.net/api/v1/search
|
||||
Authentication: None (使用 Header)
|
||||
|
||||
Headers:
|
||||
┌─────────────────────┬──────────────────────────────────────────────────┐
|
||||
│ Name │ Value │
|
||||
├─────────────────────┼──────────────────────────────────────────────────┤
|
||||
│ X-API-Key │ muser_68600856036340bcafc01930eb4bd839_... │
|
||||
│ Content-Type │ application/json │
|
||||
└─────────────────────┴──────────────────────────────────────────────────┘
|
||||
|
||||
Body Content (JSON):
|
||||
{
|
||||
"query": "{{ $json.search_term }}",
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 n8n 搜尋 Workflow
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Manual Trigger",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"position": [250, 300]
|
||||
},
|
||||
{
|
||||
"name": "Set Search Term",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"parameters": {
|
||||
"values": {
|
||||
"json": {
|
||||
"search_term": "ExaSAN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [450, 300]
|
||||
},
|
||||
{
|
||||
"name": "Search Videos",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.momentry.ddns.net/api/v1/search",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-API-Key",
|
||||
"value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"bodyContentType": "json",
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ { \"query\": $json.search_term, \"limit\": 5 } }}"
|
||||
},
|
||||
"position": [650, 300]
|
||||
},
|
||||
{
|
||||
"name": "Process Results",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"parameters": {
|
||||
"jsCode": "// Extract video results\nconst results = $input.first().json.results;\nreturn results.map(r => ({\n uuid: r.uuid,\n text: r.text,\n score: r.score,\n time: `${r.start_time}s - ${r.end_time}s`\n}));"
|
||||
},
|
||||
"position": [850, 300]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Manual Trigger": {
|
||||
"main": [[{"node": "Set Search Term"}]]
|
||||
},
|
||||
"Set Search Term": {
|
||||
"main": [[{"node": "Search Videos"}]]
|
||||
},
|
||||
"Search Videos": {
|
||||
"main": [[{"node": "Process Results"}]]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 n8n 列出影片 Workflow
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Get Videos",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.momentry.ddns.net/api/v1/videos",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-API-Key",
|
||||
"value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [450, 300]
|
||||
},
|
||||
{
|
||||
"name": "Extract Video List",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"parameters": {
|
||||
"jsCode": "const videos = $input.first().json.videos;\nreturn videos.map(v => ({\n json: {\n uuid: v.uuid,\n name: v.file_name,\n duration: Math.round(v.duration) + 's',\n resolution: `${v.width}x${v.height}`\n }\n}));"
|
||||
},
|
||||
"position": [650, 300]
|
||||
},
|
||||
{
|
||||
"name": "Slack Notification",
|
||||
"type": "n8n-nodes-base.slack",
|
||||
"parameters": {
|
||||
"channel": "#momentry",
|
||||
"text": "=Found {{ $json.length }} videos:\n{{ $json.map(v => `• ${v.name} (${v.duration})`).join(`\n`) }}"
|
||||
},
|
||||
"position": [850, 300]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 n8n 定時同步 Workflow
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Schedule Trigger",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [{"field": "hours", "hours": 1}]
|
||||
}
|
||||
},
|
||||
"position": [250, 300]
|
||||
},
|
||||
{
|
||||
"name": "Get Pending Videos",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.momentry.ddns.net/api/v1/videos"
|
||||
},
|
||||
"position": [450, 300]
|
||||
},
|
||||
{
|
||||
"name": "Filter Processing",
|
||||
"type": "n8n-nodes-base.filter",
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {"caseSensitive": true},
|
||||
"conditions": [
|
||||
{"id": "status", "leftValue": "{{ $json.status }}", "rightValue": "processing"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [650, 300]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. WordPress 範例
|
||||
|
||||
### 3.1 PHP 函數庫
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Momentry API Client
|
||||
*/
|
||||
|
||||
class Momentry_API {
|
||||
private const API_URL = 'https://api.momentry.ddns.net';
|
||||
private const API_KEY = 'muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69';
|
||||
|
||||
/**
|
||||
* 發送 API 請求
|
||||
*/
|
||||
private function request(string $endpoint, array $data = [], string $method = 'GET'): array {
|
||||
$url = self::API_URL . $endpoint;
|
||||
|
||||
$args = [
|
||||
'headers' => [
|
||||
'X-API-Key' => self::API_KEY,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'timeout' => 30,
|
||||
];
|
||||
|
||||
if ($method === 'POST') {
|
||||
$args['method'] = 'POST';
|
||||
$args['body'] = json_encode($data);
|
||||
}
|
||||
|
||||
$response = wp_remote_request($url, $args);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
throw new Exception($response->get_error_message());
|
||||
}
|
||||
|
||||
return json_decode(wp_remote_retrieve_body($response), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有影片
|
||||
*/
|
||||
public function list_videos(): array {
|
||||
return $this->request('/api/v1/videos');
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜尋影片內容
|
||||
*/
|
||||
public function search(string $query, int $limit = 10): array {
|
||||
return $this->request('/api/v1/search', [
|
||||
'query' => $query,
|
||||
'limit' => $limit,
|
||||
], 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得影片進度
|
||||
*/
|
||||
public function get_progress(string $uuid): array {
|
||||
return $this->request("/api/v1/progress/{$uuid}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查健康狀態
|
||||
*/
|
||||
public function health_check(): array {
|
||||
return $this->request('/health');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 短代碼 (Shortcode)
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* WordPress 短代碼範例
|
||||
*/
|
||||
|
||||
// 註冊短代碼
|
||||
add_shortcode('momentry_videos', function($atts) {
|
||||
$atts = shortcode_atts([
|
||||
'limit' => 10,
|
||||
], $atts);
|
||||
|
||||
$api = new Momentry_API();
|
||||
|
||||
try {
|
||||
$result = $api->list_videos();
|
||||
$videos = array_slice($result['videos'], 0, $atts['limit']);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="momentry-videos">
|
||||
<h3>影片列表</h3>
|
||||
<ul>
|
||||
<?php foreach ($videos as $video): ?>
|
||||
<li>
|
||||
<strong><?= esc_html($video['file_name']) ?></strong>
|
||||
<br>
|
||||
<small>
|
||||
UUID: <?= esc_html($video['uuid']) ?>
|
||||
| 時長: <?= gmdate("H:i:s", $video['duration']) ?>
|
||||
</small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
|
||||
} catch (Exception $e) {
|
||||
return '<p class="error">載入失敗: ' . esc_html($e->getMessage()) . '</p>';
|
||||
}
|
||||
});
|
||||
|
||||
// 搜尋短代碼
|
||||
add_shortcode('momentry_search', function($atts, $content = '') {
|
||||
$query = sanitize_text_field($content);
|
||||
|
||||
if (empty($query)) {
|
||||
return '<p>請提供搜尋關鍵字</p>';
|
||||
}
|
||||
|
||||
$api = new Momentry_API();
|
||||
|
||||
try {
|
||||
$result = $api->search($query);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="momentry-search-results">
|
||||
<h3>「<?= esc_html($query) ?>」搜尋結果</h3>
|
||||
<?php if (empty($result['results'])): ?>
|
||||
<p>沒有找到相關結果</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($result['results'] as $item): ?>
|
||||
<li>
|
||||
<a href="/video/<?= esc_attr($item['uuid']) ?>?t=<?= (int)$item['start_time'] ?>">
|
||||
<?= esc_html($item['text']) ?>
|
||||
</a>
|
||||
<br>
|
||||
<small>相似度: <?= round($item['score'] * 100) ?>%</small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
|
||||
} catch (Exception $e) {
|
||||
return '<p class="error">搜尋失敗: ' . esc_html($e->getMessage()) . '</p>';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3.3 使用方式
|
||||
|
||||
在 WordPress 頁面或文章中:
|
||||
|
||||
```
|
||||
[momentry_videos limit="5"]
|
||||
|
||||
[momentry_search]ExaSAN[/momentry_search]
|
||||
```
|
||||
|
||||
### 3.4 REST API 整合
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* 註冊 WordPress REST API 端點
|
||||
*/
|
||||
|
||||
add_action('rest_api_init', function() {
|
||||
register_rest_route('momentry/v1', '/search', [
|
||||
'methods' => 'GET',
|
||||
'callback' => function(WP_REST_Request $request) {
|
||||
$query = sanitize_text_field($request->get_param('q'));
|
||||
|
||||
if (empty($query)) {
|
||||
return new WP_Error('missing_query', '需要搜尋關鍵字', ['status' => 400]);
|
||||
}
|
||||
|
||||
$api = new Momentry_API();
|
||||
$result = $api->search($query);
|
||||
|
||||
return new WP_REST_Response($result, 200);
|
||||
},
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
});
|
||||
|
||||
// 使用方式: GET /wp-json/momentry/v1/search?q=ExaSAN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 疑難排解
|
||||
|
||||
### 4.1 常見錯誤
|
||||
|
||||
| 錯誤 | 原因 | 解決方案 |
|
||||
|------|------|----------|
|
||||
| `401 Unauthorized` | API Key 無效或過期 | 檢查 API Key 是否正確 |
|
||||
| `500 Internal Server Error` | 伺服器錯誤 | 檢查 `/health/detailed` 服務狀態 |
|
||||
| `Connection Timeout` | 網路問題 | 確認 `api.momentry.ddns.net` 可達 |
|
||||
|
||||
### 4.2 測試腳本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# test_api.sh - Momentry API 測試腳本
|
||||
|
||||
API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
BASE_URL="http://localhost:3002"
|
||||
|
||||
echo "=== 1. 健康檢查 ==="
|
||||
curl -s "$BASE_URL/health" | jq .
|
||||
echo ""
|
||||
|
||||
echo "=== 2. 列出影片 ==="
|
||||
curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos" | jq '.videos | length'
|
||||
echo ""
|
||||
|
||||
echo "=== 3. 搜尋測試 ==="
|
||||
curl -s -X POST -H "X-API-Key: $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "test", "limit": 3}' \
|
||||
"$BASE_URL/api/v1/search" | jq '.results | length'
|
||||
echo ""
|
||||
|
||||
echo "=== 完成 ==="
|
||||
```
|
||||
|
||||
### 4.3 驗證腳本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# verify_auth.sh - 驗證 API Key
|
||||
|
||||
API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
BASE_URL="http://localhost:3002"
|
||||
|
||||
# 測試 1: 無 API Key
|
||||
echo "測試 1: 無 API Key"
|
||||
RESULT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/v1/videos")
|
||||
[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401,實際 $RESULT"
|
||||
|
||||
# 測試 2: 有 API Key
|
||||
echo "測試 2: 有 API Key"
|
||||
RESULT=$(curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos")
|
||||
echo "$RESULT" | jq -e '.videos' > /dev/null && echo "✅ 成功取得資料" || echo "❌ 取得資料失敗"
|
||||
|
||||
# 測試 3: 無效 API Key
|
||||
echo "測試 3: 無效 API Key"
|
||||
RESULT=$(curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: invalid_key" "$BASE_URL/api/v1/videos")
|
||||
[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401,實際 $RESULT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API Key 管理
|
||||
|
||||
### 5.1 建立新 API Key
|
||||
|
||||
```bash
|
||||
# 本地建立
|
||||
./target/release/momentry api-key create "My App" --key-type user --ttl 90
|
||||
```
|
||||
|
||||
### 5.2 列出 API Keys
|
||||
|
||||
```bash
|
||||
./target/release/momentry api-key list
|
||||
```
|
||||
|
||||
### 5.3 驗證 API Key
|
||||
|
||||
```bash
|
||||
./target/release/momentry api-key validate --key "YOUR_API_KEY"
|
||||
```
|
||||
|
||||
### 5.4 撤銷 API Key
|
||||
|
||||
```bash
|
||||
./target/release/momentry api-key revoke --key "YOUR_API_KEY"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附錄
|
||||
|
||||
### A. 影片 UUID 說明
|
||||
|
||||
UUID 是基於檔案路徑的 SHA256 哈希前 16 位:
|
||||
|
||||
```
|
||||
/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4
|
||||
↓
|
||||
SHA256 Hash
|
||||
↓
|
||||
9760d0820f0cf9a7
|
||||
```
|
||||
|
||||
### B. 處理器狀態
|
||||
|
||||
| 狀態 | 說明 |
|
||||
|------|------|
|
||||
| `pending` | 等待處理 |
|
||||
| `running` | 處理中 |
|
||||
| `completed` | 已完成 |
|
||||
| `failed` | 失敗 |
|
||||
|
||||
### C. 支援的處理器
|
||||
|
||||
- **ASR**: 語音識別
|
||||
- **CUT**: 場景剪切
|
||||
- **YOLO**: 物件偵測
|
||||
|
||||
### D. 聯絡支援
|
||||
|
||||
- Email: support@momentry.ddns.net
|
||||
- 文件: https://docs.momentry.ddns.net
|
||||
- GitHub: https://github.com/anomalyco/momentry
|
||||
Reference in New Issue
Block a user