mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-03 01:06:36 +08:00
docs: add AI Skill integration guide
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
# curl Examples
|
||||
|
||||
Quick reference for common operations using curl.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export FLVX_BASE_URL="https://your-panel.example.com"
|
||||
export FLVX_USERNAME="admin"
|
||||
export FLVX_PASSWORD="your-password"
|
||||
|
||||
# Login and save token
|
||||
TOKEN=$(curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"${FLVX_USERNAME}\",\"password\":\"${FLVX_PASSWORD}\"}" \
|
||||
| jq -r '.data.token')
|
||||
|
||||
echo "Token: ${TOKEN:0:20}..."
|
||||
```
|
||||
|
||||
## User Operations
|
||||
|
||||
```bash
|
||||
# Get my package info
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/package" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.'
|
||||
|
||||
# List all users (admin)
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/list" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"page":1,"pageSize":20}' | jq '.'
|
||||
|
||||
# Create user (admin)
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"user": "alice",
|
||||
"pwd": "SecurePass123!",
|
||||
"name": "Alice",
|
||||
"flow": 50,
|
||||
"num": 10,
|
||||
"expTime": 1767225600000
|
||||
}' | jq '.'
|
||||
|
||||
# Reset user traffic
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/reset" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":2,"type":"user"}' | jq '.'
|
||||
|
||||
# Delete user
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/delete" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":2}' | jq '.'
|
||||
```
|
||||
|
||||
## Node Operations
|
||||
|
||||
```bash
|
||||
# List nodes with status
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/list" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.data.list[] | {name, status: (.status == 1), ip: .server_ip}'
|
||||
|
||||
# Create node
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"US-Node-1","serverIp":"203.0.113.10"}' | jq '.'
|
||||
|
||||
# Get install command
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/install" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":2}' | jq -r '.data.command'
|
||||
|
||||
# Check node status
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/check-status" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.'
|
||||
|
||||
# Upgrade node
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/upgrade" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":2,"version":"2.1.5"}' | jq '.'
|
||||
|
||||
# Delete node
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/node/delete" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":2}' | jq '.'
|
||||
```
|
||||
|
||||
## Tunnel Operations
|
||||
|
||||
```bash
|
||||
# List tunnels
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/tunnel/list" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.data.list[] | {id, name, status}'
|
||||
|
||||
# Create tunnel
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/tunnel/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "HK-US-Tunnel",
|
||||
"type": 1,
|
||||
"inNodeId": [1],
|
||||
"outNodeId": [2]
|
||||
}' | jq '.'
|
||||
|
||||
# Assign tunnel to user
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/tunnel/user/assign" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"userId":2,"tunnelId":1,"flow":30}' | jq '.'
|
||||
|
||||
# Get available tunnels (for current user)
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/tunnel/user/tunnel" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.'
|
||||
```
|
||||
|
||||
## Forward Operations
|
||||
|
||||
```bash
|
||||
# List forwards with traffic
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/list" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.data.list[] | {
|
||||
name,
|
||||
tunnel: .tunnel_name,
|
||||
port: .in_port,
|
||||
target: .remote_addr,
|
||||
status: (if .status == 1 then "running" else "paused" end),
|
||||
upload_gb: ((.in_flow / 1073741824) | floor),
|
||||
download_gb: ((.out_flow / 1073741824) | floor)
|
||||
}'
|
||||
|
||||
# Create forward
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-web-server",
|
||||
"tunnelId": 1,
|
||||
"remoteAddr": "192.168.1.100:80",
|
||||
"strategy": "fifo"
|
||||
}' | jq '.'
|
||||
|
||||
# Create forward with load balancing
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "web-cluster",
|
||||
"tunnelId": 1,
|
||||
"remoteAddr": "10.0.0.1:80,10.0.0.2:80,10.0.0.3:80",
|
||||
"strategy": "round"
|
||||
}' | jq '.'
|
||||
|
||||
# Pause forward
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/pause" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":1}' | jq '.'
|
||||
|
||||
# Resume forward
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/resume" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":1}' | jq '.'
|
||||
|
||||
# Diagnose forward
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/diagnose" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":1}' | jq '.'
|
||||
|
||||
# Delete forward
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/delete" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":1}' | jq '.'
|
||||
|
||||
# Batch pause forwards
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/forward/batch-pause" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"ids":[1,2,3]}' | jq '.'
|
||||
```
|
||||
|
||||
## Backup Operations
|
||||
|
||||
```bash
|
||||
# Export all data
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/backup/export" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' > backup-$(date +%Y%m%d).json
|
||||
|
||||
# Export specific types
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/backup/export" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"types":["users","tunnels"]}' > partial-backup.json
|
||||
|
||||
# Import backup
|
||||
curl -s -X POST "${FLVX_BASE_URL}/api/v1/backup/import" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @backup-20260226.json | jq '.'
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc or ~/.zshrc
|
||||
|
||||
flvx-login() {
|
||||
export FLVX_BASE_URL="${1:-$FLVX_BASE_URL}"
|
||||
TOKEN=$(curl -s -X POST "${FLVX_BASE_URL}/api/v1/user/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"${FLVX_USERNAME}\",\"password\":\"${FLVX_PASSWORD}\"}" \
|
||||
| jq -r '.data.token')
|
||||
export FLVX_TOKEN="$TOKEN"
|
||||
echo "Logged in. Token: ${TOKEN:0:20}..."
|
||||
}
|
||||
|
||||
flvx-api() {
|
||||
local endpoint="$1"
|
||||
local data="${2:-{}}"
|
||||
curl -s -X POST "${FLVX_BASE_URL}${endpoint}" \
|
||||
-H "Authorization: ${FLVX_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" | jq '.'
|
||||
}
|
||||
|
||||
# Usage:
|
||||
# flvx-login
|
||||
# flvx-api /api/v1/node/list
|
||||
# flvx-api /api/v1/forward/list '{"keyword":"web"}'
|
||||
```
|
||||
@@ -0,0 +1,603 @@
|
||||
# HTTP Client Examples
|
||||
|
||||
Complete, runnable examples for various languages.
|
||||
|
||||
## Bash / curl
|
||||
|
||||
### Complete Script with Auto-Login
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BASE_URL="${FLVX_BASE_URL:?FLVX_BASE_URL not set}"
|
||||
USERNAME="${FLVX_USERNAME:?FLVX_USERNAME not set}"
|
||||
PASSWORD="${FLVX_PASSWORD:?FLVX_PASSWORD not set}"
|
||||
|
||||
# Login and get token
|
||||
echo "Logging in..."
|
||||
LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/user/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}")
|
||||
|
||||
TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.token // empty')
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "Login failed: $(echo "$LOGIN_RESPONSE" | jq -r '.msg')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Logged in successfully"
|
||||
|
||||
# API call helper
|
||||
api_call() {
|
||||
local endpoint="$1"
|
||||
local data="${2:-{}}"
|
||||
|
||||
curl -s -X POST "${BASE_URL}${endpoint}" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data"
|
||||
}
|
||||
|
||||
# Examples
|
||||
echo "=== My Package Info ==="
|
||||
api_call "/api/v1/user/package" | jq '.'
|
||||
|
||||
echo -e "\n=== Node List ==="
|
||||
api_call "/api/v1/node/list" '{}' | jq '.data.list[] | {name, status: (.status == 1)}'
|
||||
|
||||
echo -e "\n=== Forward List ==="
|
||||
api_call "/api/v1/forward/list" '{}' | jq '.data.list[] | {name, tunnel: .tunnel_name, port: .in_port, target: .remote_addr}'
|
||||
```
|
||||
|
||||
### Create Forward Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BASE_URL="${FLVX_BASE_URL}"
|
||||
TOKEN="${FLVX_TOKEN}" # Pre-obtained token
|
||||
|
||||
create_forward() {
|
||||
local name="$1"
|
||||
local tunnel_id="$2"
|
||||
local remote_addr="$3"
|
||||
|
||||
curl -s -X POST "${BASE_URL}/api/v1/forward/create" \
|
||||
-H "Authorization: ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"${name}\",
|
||||
\"tunnelId\": ${tunnel_id},
|
||||
\"remoteAddr\": \"${remote_addr}\",
|
||||
\"strategy\": \"fifo\"
|
||||
}" | jq '.'
|
||||
}
|
||||
|
||||
# Usage: ./create-forward.sh "my-web" 1 "192.168.1.100:80"
|
||||
create_forward "$@"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python
|
||||
|
||||
### Complete Client Class
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""FLVX API Client"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from typing import Optional, Any, Dict, List
|
||||
|
||||
class FlvxError(Exception):
|
||||
"""FLVX API Error"""
|
||||
def __init__(self, code: int, message: str):
|
||||
self.code = code
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
class FlvxClient:
|
||||
"""FLVX API Client with auto-login"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: Optional[str] = None,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None
|
||||
):
|
||||
self.base_url = base_url or os.environ.get("FLVX_BASE_URL")
|
||||
self.username = username or os.environ.get("FLVX_USERNAME")
|
||||
self.password = password or os.environ.get("FLVX_PASSWORD")
|
||||
|
||||
if not all([self.base_url, self.username, self.password]):
|
||||
raise ValueError("Missing credentials. Set FLVX_BASE_URL, FLVX_USERNAME, FLVX_PASSWORD")
|
||||
|
||||
self.token: Optional[str] = None
|
||||
|
||||
def _login(self) -> None:
|
||||
"""Authenticate and store token"""
|
||||
resp = requests.post(
|
||||
f"{self.base_url}/api/v1/user/login",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"username": self.username, "password": self.password}
|
||||
)
|
||||
result = resp.json()
|
||||
|
||||
if result["code"] != 0:
|
||||
raise FlvxError(result["code"], result["msg"])
|
||||
|
||||
self.token = result["data"]["token"]
|
||||
|
||||
def _headers(self) -> Dict[str, str]:
|
||||
"""Get request headers with auth"""
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if self.token:
|
||||
headers["Authorization"] = self.token # NO "Bearer " prefix!
|
||||
return headers
|
||||
|
||||
def request(self, endpoint: str, data: Any = None) -> Any:
|
||||
"""Make authenticated API request"""
|
||||
if not self.token:
|
||||
self._login()
|
||||
|
||||
resp = requests.post(
|
||||
f"{self.base_url}{endpoint}",
|
||||
headers=self._headers(),
|
||||
json=data or {}
|
||||
)
|
||||
result = resp.json()
|
||||
|
||||
if result["code"] == 0:
|
||||
return result.get("data")
|
||||
|
||||
if result["code"] == 401:
|
||||
# Token expired, retry once
|
||||
self.token = None
|
||||
return self.request(endpoint, data)
|
||||
|
||||
raise FlvxError(result["code"], result["msg"])
|
||||
|
||||
# Convenience methods
|
||||
|
||||
def get_package(self) -> Dict:
|
||||
"""Get current user's package info"""
|
||||
return self.request("/api/v1/user/package", {})
|
||||
|
||||
def list_nodes(self) -> List[Dict]:
|
||||
"""List all nodes"""
|
||||
data = self.request("/api/v1/node/list", {})
|
||||
return data.get("list", [])
|
||||
|
||||
def list_forwards(self, keyword: str = "") -> List[Dict]:
|
||||
"""List forwards"""
|
||||
data = self.request("/api/v1/forward/list", {"keyword": keyword})
|
||||
return data.get("list", [])
|
||||
|
||||
def create_forward(
|
||||
self,
|
||||
name: str,
|
||||
tunnel_id: int,
|
||||
remote_addr: str,
|
||||
strategy: str = "fifo",
|
||||
speed_id: int = 0
|
||||
) -> Dict:
|
||||
"""Create a forward"""
|
||||
return self.request("/api/v1/forward/create", {
|
||||
"name": name,
|
||||
"tunnelId": tunnel_id,
|
||||
"remoteAddr": remote_addr,
|
||||
"strategy": strategy,
|
||||
"speedId": speed_id
|
||||
})
|
||||
|
||||
def pause_forward(self, forward_id: int) -> None:
|
||||
"""Pause a forward"""
|
||||
self.request("/api/v1/forward/pause", {"id": forward_id})
|
||||
|
||||
def resume_forward(self, forward_id: int) -> None:
|
||||
"""Resume a forward"""
|
||||
self.request("/api/v1/forward/resume", {"id": forward_id})
|
||||
|
||||
def delete_forward(self, forward_id: int) -> None:
|
||||
"""Delete a forward"""
|
||||
self.request("/api/v1/forward/delete", {"id": forward_id})
|
||||
|
||||
|
||||
# Usage example
|
||||
if __name__ == "__main__":
|
||||
client = FlvxClient()
|
||||
|
||||
# Get package info
|
||||
pkg = client.get_package()
|
||||
print(f"Traffic: {pkg['inFlow'] / 1e9:.2f}GB ↑ / {pkg['outFlow'] / 1e9:.2f}GB ↓")
|
||||
print(f"Quota: {pkg['flow']}GB")
|
||||
|
||||
# List forwards with traffic
|
||||
print("\nForwards:")
|
||||
for fwd in client.list_forwards():
|
||||
print(f" {fwd['name']}: {fwd['in_port']} → {fwd['remote_addr']}")
|
||||
print(f" Traffic: {fwd['in_flow'] / 1e9:.2f}GB ↑ / {fwd['out_flow'] / 1e9:.2f}GB ↓")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js / TypeScript
|
||||
|
||||
### Complete Client Class
|
||||
|
||||
```typescript
|
||||
// flvx-client.ts
|
||||
interface APIResponse<T = unknown> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: T;
|
||||
ts: number;
|
||||
}
|
||||
|
||||
class FlvxError extends Error {
|
||||
constructor(public code: number, message: string) {
|
||||
super(message);
|
||||
this.name = "FlvxError";
|
||||
}
|
||||
}
|
||||
|
||||
interface UserPackage {
|
||||
flow: number;
|
||||
inFlow: number;
|
||||
outFlow: number;
|
||||
tunnels: number;
|
||||
forwards: number;
|
||||
expTime: number;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: number;
|
||||
name: string;
|
||||
status: number;
|
||||
server_ip: string;
|
||||
}
|
||||
|
||||
interface Forward {
|
||||
id: number;
|
||||
name: string;
|
||||
tunnel_id: number;
|
||||
tunnel_name: string;
|
||||
in_port: number;
|
||||
remote_addr: string;
|
||||
status: number;
|
||||
in_flow: number;
|
||||
out_flow: number;
|
||||
}
|
||||
|
||||
class FlvxClient {
|
||||
private baseUrl: string;
|
||||
private username: string;
|
||||
private password: string;
|
||||
private token?: string;
|
||||
|
||||
constructor(options?: {
|
||||
baseUrl?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}) {
|
||||
this.baseUrl = options?.baseUrl ?? process.env.FLVX_BASE_URL ?? "";
|
||||
this.username = options?.username ?? process.env.FLVX_USERNAME ?? "";
|
||||
this.password = options?.password ?? process.env.FLVX_PASSWORD ?? "";
|
||||
|
||||
if (!this.baseUrl || !this.username || !this.password) {
|
||||
throw new Error("Missing credentials. Set FLVX_BASE_URL, FLVX_USERNAME, FLVX_PASSWORD");
|
||||
}
|
||||
}
|
||||
|
||||
private async login(): Promise<void> {
|
||||
const res = await fetch(`${this.baseUrl}/api/v1/user/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const result: APIResponse<{ token: string }> = await res.json();
|
||||
if (result.code !== 0) {
|
||||
throw new FlvxError(result.code, result.msg);
|
||||
}
|
||||
|
||||
this.token = result.data!.token;
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, data?: object): Promise<T> {
|
||||
if (!this.token) {
|
||||
await this.login();
|
||||
}
|
||||
|
||||
const res = await fetch(`${this.baseUrl}${endpoint}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: this.token!, // NO "Bearer " prefix!
|
||||
},
|
||||
body: JSON.stringify(data ?? {}),
|
||||
});
|
||||
|
||||
const result: APIResponse<T> = await res.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
return result.data!;
|
||||
}
|
||||
|
||||
if (result.code === 401) {
|
||||
// Token expired, retry once
|
||||
this.token = undefined;
|
||||
return this.request<T>(endpoint, data);
|
||||
}
|
||||
|
||||
throw new FlvxError(result.code, result.msg);
|
||||
}
|
||||
|
||||
// Convenience methods
|
||||
|
||||
async getPackage(): Promise<UserPackage> {
|
||||
return this.request("/api/v1/user/package", {});
|
||||
}
|
||||
|
||||
async listNodes(): Promise<Node[]> {
|
||||
const data = await this.request<{ list: Node[] }>("/api/v1/node/list", {});
|
||||
return data.list ?? [];
|
||||
}
|
||||
|
||||
async listForwards(keyword = ""): Promise<Forward[]> {
|
||||
const data = await this.request<{ list: Forward[] }>("/api/v1/forward/list", {
|
||||
keyword,
|
||||
});
|
||||
return data.list ?? [];
|
||||
}
|
||||
|
||||
async createForward(options: {
|
||||
name: string;
|
||||
tunnelId: number;
|
||||
remoteAddr: string;
|
||||
strategy?: "fifo" | "round";
|
||||
speedId?: number;
|
||||
}): Promise<Forward> {
|
||||
return this.request("/api/v1/forward/create", {
|
||||
name: options.name,
|
||||
tunnelId: options.tunnelId,
|
||||
remoteAddr: options.remoteAddr,
|
||||
strategy: options.strategy ?? "fifo",
|
||||
speedId: options.speedId ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
async pauseForward(id: number): Promise<void> {
|
||||
await this.request("/api/v1/forward/pause", { id });
|
||||
}
|
||||
|
||||
async resumeForward(id: number): Promise<void> {
|
||||
await this.request("/api/v1/forward/resume", { id });
|
||||
}
|
||||
|
||||
async deleteForward(id: number): Promise<void> {
|
||||
await this.request("/api/v1/forward/delete", { id });
|
||||
}
|
||||
}
|
||||
|
||||
export { FlvxClient, FlvxError };
|
||||
|
||||
// Usage
|
||||
async function main() {
|
||||
const client = new FlvxClient();
|
||||
|
||||
// Get package info
|
||||
const pkg = await client.getPackage();
|
||||
console.log(`Traffic: ${(pkg.inFlow / 1e9).toFixed(2)}GB ↑ / ${(pkg.outFlow / 1e9).toFixed(2)}GB ↓`);
|
||||
console.log(`Quota: ${pkg.flow}GB`);
|
||||
|
||||
// List nodes
|
||||
console.log("\nNodes:");
|
||||
const nodes = await client.listNodes();
|
||||
for (const node of nodes) {
|
||||
console.log(` ${node.name}: ${node.status ? "Online" : "Offline"}`);
|
||||
}
|
||||
|
||||
// List forwards
|
||||
console.log("\nForwards:");
|
||||
const forwards = await client.listForwards();
|
||||
for (const fwd of forwards) {
|
||||
console.log(` ${fwd.name}: ${fwd.in_port} → ${fwd.remote_addr}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go
|
||||
|
||||
### Complete Client Package
|
||||
|
||||
```go
|
||||
// flvx/client.go
|
||||
package flvx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
TS int64 `json:"ts"`
|
||||
}
|
||||
|
||||
type FlvxError struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *FlvxError) Error() string {
|
||||
return fmt.Sprintf("FLVX error %d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
BaseURL: os.Getenv("FLVX_BASE_URL"),
|
||||
Username: os.Getenv("FLVX_USERNAME"),
|
||||
Password: os.Getenv("FLVX_PASSWORD"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Login() error {
|
||||
payload := map[string]string{
|
||||
"username": c.Username,
|
||||
"password": c.Password,
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.request("/api/v1/user/login", payload, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return &FlvxError{Code: result.Code, Message: result.Msg}
|
||||
}
|
||||
|
||||
c.Token = result.Data.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Request(endpoint string, data interface{}, result interface{}) error {
|
||||
// Auto-login if no token
|
||||
if c.Token == "" {
|
||||
if err := c.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.request(endpoint, data, result)
|
||||
}
|
||||
|
||||
func (c *Client) request(endpoint string, data interface{}, result interface{}) error {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", c.BaseURL+endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if c.Token != "" {
|
||||
req.Header.Set("Authorization", c.Token) // NO "Bearer " prefix!
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(respBody, result)
|
||||
}
|
||||
|
||||
// Convenience methods
|
||||
|
||||
func (c *Client) ListNodes() ([]map[string]interface{}, error) {
|
||||
var result struct {
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
List []map[string]interface{} `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.Request("/api/v1/node/list", map[string]interface{}{}, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, &FlvxError{Code: result.Code, Message: "failed to list nodes"}
|
||||
}
|
||||
|
||||
return result.Data.List, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateForward(name string, tunnelID int, remoteAddr string) (map[string]interface{}, error) {
|
||||
payload := map[string]interface{}{
|
||||
"name": name,
|
||||
"tunnelId": tunnelID,
|
||||
"remoteAddr": remoteAddr,
|
||||
"strategy": "fifo",
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
if err := c.Request("/api/v1/forward/create", payload, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, &FlvxError{Code: result.Code, Message: result.Msg}
|
||||
}
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
// Usage example
|
||||
func Example() {
|
||||
client := NewClient()
|
||||
|
||||
nodes, err := client.ListNodes()
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
fmt.Printf("Node: %v (status: %v)\n", node["name"], node["status"])
|
||||
}
|
||||
|
||||
fwd, err := client.CreateForward("my-forward", 1, "192.168.1.100:80")
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Created forward: %v\n", fwd)
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user