151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Vaultwarden API Wrapper
|
|
用于管理 bit.180356.xyz 的密码库
|
|
|
|
用法:
|
|
python3 vaultwarden_api.py list # 列出所有项目
|
|
python3 vaultwarden_api.py get <id> # 获取项目详情
|
|
python3 vaultwarden_api.py create <name> <username> <password> [notes] # 创建项目
|
|
python3 vaultwarden_api.py search <query> # 搜索项目
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import requests
|
|
import sys
|
|
import base64
|
|
|
|
# 配置
|
|
BASE_URL = "https://bit.180356.xyz"
|
|
EMAIL = "1803560007@qq.com" # 用户账号
|
|
CLIENT_ID = "user.447f249d-4b82-4ccf-83fa-df8f45e2413a"
|
|
CLIENT_SECRET = "NMdyXUfkZiTzdVUamQOYG0QcFDNzdJ"
|
|
|
|
class VaultwardenAPI:
|
|
def __init__(self):
|
|
self.token = None
|
|
self.device_id = "openclaw-" + str(hash(str(hash(str(hash(str()))))))[-8:]
|
|
|
|
def get_token(self):
|
|
"""获取访问令牌"""
|
|
url = f"{BASE_URL}/identity/connect/token"
|
|
data = {
|
|
"grant_type": "client_credentials",
|
|
"scope": "api",
|
|
"client_id": CLIENT_ID,
|
|
"client_secret": CLIENT_SECRET,
|
|
"device_identifier": self.device_id,
|
|
"device_type": "24",
|
|
"device_name": "openclaw"
|
|
}
|
|
|
|
response = requests.post(url, data=data)
|
|
if response.status_code == 200:
|
|
self.token = response.json().get("access_token")
|
|
return self.token
|
|
else:
|
|
print(f"获取令牌失败: {response.text}")
|
|
return None
|
|
|
|
def api_call(self, endpoint, method="GET", data=None):
|
|
"""API 调用"""
|
|
if not self.token:
|
|
self.get_token()
|
|
|
|
headers = {"Authorization": f"Bearer {self.token}"}
|
|
url = f"{BASE_URL}{endpoint}"
|
|
|
|
if method == "GET":
|
|
response = requests.get(url, headers=headers)
|
|
elif method == "POST":
|
|
headers["Content-Type"] = "application/json"
|
|
response = requests.post(url, headers=headers, json=data)
|
|
elif method == "DELETE":
|
|
response = requests.delete(url, headers=headers)
|
|
|
|
return response
|
|
|
|
def list_items(self):
|
|
"""列出所有项目 (ciphers)"""
|
|
response = self.api_call("/api/ciphers")
|
|
if response.status_code == 200:
|
|
return response.json().get("data", [])
|
|
return []
|
|
|
|
def get_item(self, item_id):
|
|
"""获取项目详情"""
|
|
response = self.api_call(f"/api/ciphers/{item_id}")
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
return None
|
|
|
|
def create_item(self, name, username, password, notes="", folder_id=None):
|
|
"""创建新项目"""
|
|
data = {
|
|
"name": name,
|
|
"notes": notes,
|
|
"type": 1, # Login
|
|
"favorite": False,
|
|
"login": {
|
|
"username": username,
|
|
"password": password,
|
|
"totp": None
|
|
},
|
|
"fields": [],
|
|
"collectionIds": []
|
|
}
|
|
|
|
response = self.api_call("/api/ciphers", method="POST", data=data)
|
|
return response.status_code == 200
|
|
|
|
def search_items(self, query):
|
|
"""搜索项目"""
|
|
items = self.list_items()
|
|
return [item for item in items if query.lower() in item.get("name", "").lower()]
|
|
|
|
|
|
def main():
|
|
api = VaultwardenAPI()
|
|
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
return
|
|
|
|
command = sys.argv[1]
|
|
|
|
if command == "list":
|
|
items = api.list_items()
|
|
print(f"\n📋 项目列表 ({len(items)} 个):\n")
|
|
for item in items:
|
|
name = item.get("name", "Unknown")
|
|
item_id = item.get("id", "N/A")[:8]
|
|
print(f" • {name} [{item_id}]")
|
|
|
|
elif command == "get" and len(sys.argv) > 2:
|
|
item = api.get_item(sys.argv[2])
|
|
if item:
|
|
print(f"\n📄 项目详情:\n")
|
|
print(json.dumps(item, indent=2, ensure_ascii=False))
|
|
|
|
elif command == "create" and len(sys.argv) > 4:
|
|
name = sys.argv[2]
|
|
username = sys.argv[3]
|
|
password = sys.argv[4]
|
|
notes = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
success = api.create_item(name, username, password, notes)
|
|
if success:
|
|
print(f"\n✅ 创建成功: {name}")
|
|
else:
|
|
print(f"\n❌ 创建失败")
|
|
|
|
elif command == "search" and len(sys.argv) > 2:
|
|
results = api.search_items(sys.argv[2])
|
|
print(f"\n🔍 搜索结果 ({len(results)} 个):\n")
|
|
for item in results:
|
|
print(f" • {item.get('name', 'Unknown')}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|