Initial commit with translated description
This commit is contained in:
279
scripts/session_manager.py
Normal file
279
scripts/session_manager.py
Normal file
@@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser Session Manager
|
||||
Handles cookie persistence, localStorage sync, and multi-profile management
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
SESSIONS_DIR = Path.home() / ".clawdbot" / "browser-sessions"
|
||||
PROFILES_DIR = Path.home() / ".clawdbot" / "browser-profiles"
|
||||
|
||||
|
||||
def init_dirs():
|
||||
"""Initialize storage directories"""
|
||||
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
PROFILES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
class SessionManager:
|
||||
"""Manage browser sessions with cookie and localStorage persistence"""
|
||||
|
||||
def __init__(self, session_name: str):
|
||||
init_dirs()
|
||||
self.session_name = session_name
|
||||
self.session_file = SESSIONS_DIR / f"{session_name}.json"
|
||||
self.data = self._load()
|
||||
|
||||
def _load(self) -> dict:
|
||||
"""Load session data from file"""
|
||||
if self.session_file.exists():
|
||||
return json.loads(self.session_file.read_text())
|
||||
return {
|
||||
"name": self.session_name,
|
||||
"created": datetime.now().isoformat(),
|
||||
"updated": None,
|
||||
"cookies": {},
|
||||
"localStorage": {},
|
||||
"metadata": {}
|
||||
}
|
||||
|
||||
def save(self):
|
||||
"""Save session data to file"""
|
||||
self.data["updated"] = datetime.now().isoformat()
|
||||
self.session_file.write_text(json.dumps(self.data, indent=2))
|
||||
|
||||
def set_cookies(self, cookies: Dict[str, Any], domain: str = None):
|
||||
"""Store cookies, optionally grouped by domain"""
|
||||
if domain:
|
||||
if "cookies_by_domain" not in self.data:
|
||||
self.data["cookies_by_domain"] = {}
|
||||
self.data["cookies_by_domain"][domain] = cookies
|
||||
else:
|
||||
self.data["cookies"] = cookies
|
||||
self.save()
|
||||
|
||||
def get_cookies(self, domain: str = None) -> Dict[str, Any]:
|
||||
"""Get cookies, optionally for specific domain"""
|
||||
if domain and "cookies_by_domain" in self.data:
|
||||
return self.data["cookies_by_domain"].get(domain, {})
|
||||
return self.data.get("cookies", {})
|
||||
|
||||
def set_local_storage(self, ls_data: dict, origin: str = None):
|
||||
"""Store localStorage data"""
|
||||
if origin:
|
||||
if "localStorage_by_origin" not in self.data:
|
||||
self.data["localStorage_by_origin"] = {}
|
||||
self.data["localStorage_by_origin"][origin] = ls_data
|
||||
else:
|
||||
self.data["localStorage"] = ls_data
|
||||
self.save()
|
||||
|
||||
def get_local_storage(self, origin: str = None) -> dict:
|
||||
"""Get localStorage data"""
|
||||
if origin and "localStorage_by_origin" in self.data:
|
||||
return self.data["localStorage_by_origin"].get(origin, {})
|
||||
return self.data.get("localStorage", {})
|
||||
|
||||
def set_metadata(self, key: str, value: Any):
|
||||
"""Store arbitrary metadata"""
|
||||
self.data["metadata"][key] = value
|
||||
self.save()
|
||||
|
||||
def get_metadata(self, key: str, default: Any = None) -> Any:
|
||||
"""Get metadata value"""
|
||||
return self.data["metadata"].get(key, default)
|
||||
|
||||
def export_for_browser(self, browser_type: str = "drission") -> dict:
|
||||
"""Export session in format suitable for browser injection"""
|
||||
return {
|
||||
"cookies": self.data.get("cookies", {}),
|
||||
"localStorage": self.data.get("localStorage", {}),
|
||||
"format": browser_type
|
||||
}
|
||||
|
||||
def import_from_browser(self, page, browser_type: str = "drission"):
|
||||
"""Import cookies and localStorage from active browser page"""
|
||||
if browser_type == "drission":
|
||||
self.data["cookies"] = page.cookies.as_dict()
|
||||
try:
|
||||
ls = page.run_js("return JSON.stringify(localStorage);")
|
||||
self.data["localStorage"] = json.loads(ls) if ls else {}
|
||||
except:
|
||||
pass
|
||||
self.data["metadata"]["url"] = page.url
|
||||
self.data["metadata"]["title"] = page.title
|
||||
else: # selenium/undetected
|
||||
# Convert cookie list to dict
|
||||
cookies = {}
|
||||
for c in page.get_cookies():
|
||||
cookies[c["name"]] = {
|
||||
"value": c["value"],
|
||||
"domain": c.get("domain"),
|
||||
"path": c.get("path"),
|
||||
"secure": c.get("secure"),
|
||||
"httpOnly": c.get("httpOnly")
|
||||
}
|
||||
self.data["cookies"] = cookies
|
||||
try:
|
||||
ls = page.execute_script("return JSON.stringify(localStorage);")
|
||||
self.data["localStorage"] = json.loads(ls) if ls else {}
|
||||
except:
|
||||
pass
|
||||
self.data["metadata"]["url"] = page.current_url
|
||||
self.data["metadata"]["title"] = page.title
|
||||
|
||||
self.save()
|
||||
|
||||
def apply_to_browser(self, page, browser_type: str = "drission"):
|
||||
"""Apply saved session to browser page"""
|
||||
if browser_type == "drission":
|
||||
# Set cookies
|
||||
for name, cookie_data in self.data.get("cookies", {}).items():
|
||||
if isinstance(cookie_data, str):
|
||||
page.cookies.set({name: cookie_data})
|
||||
else:
|
||||
page.cookies.set({name: cookie_data.get("value", "")})
|
||||
|
||||
# Set localStorage
|
||||
ls = self.data.get("localStorage", {})
|
||||
if ls:
|
||||
for k, v in ls.items():
|
||||
v_escaped = json.dumps(v) if not isinstance(v, str) else f'"{v}"'
|
||||
page.run_js(f"localStorage.setItem('{k}', {v_escaped});")
|
||||
else: # selenium
|
||||
for name, cookie_data in self.data.get("cookies", {}).items():
|
||||
try:
|
||||
if isinstance(cookie_data, str):
|
||||
page.add_cookie({"name": name, "value": cookie_data})
|
||||
else:
|
||||
page.add_cookie({
|
||||
"name": name,
|
||||
"value": cookie_data.get("value", ""),
|
||||
"domain": cookie_data.get("domain"),
|
||||
"path": cookie_data.get("path", "/"),
|
||||
"secure": cookie_data.get("secure", False)
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
ls = self.data.get("localStorage", {})
|
||||
if ls:
|
||||
for k, v in ls.items():
|
||||
v_escaped = json.dumps(v) if not isinstance(v, str) else f'"{v}"'
|
||||
page.execute_script(f"localStorage.setItem('{k}', {v_escaped});")
|
||||
|
||||
|
||||
def list_sessions() -> List[dict]:
|
||||
"""List all saved sessions"""
|
||||
init_dirs()
|
||||
sessions = []
|
||||
for f in SESSIONS_DIR.glob("*.json"):
|
||||
try:
|
||||
data = json.loads(f.read_text())
|
||||
sessions.append({
|
||||
"name": f.stem,
|
||||
"created": data.get("created"),
|
||||
"updated": data.get("updated"),
|
||||
"url": data.get("metadata", {}).get("url"),
|
||||
"cookies_count": len(data.get("cookies", {}))
|
||||
})
|
||||
except:
|
||||
pass
|
||||
return sessions
|
||||
|
||||
|
||||
def delete_session(session_name: str) -> bool:
|
||||
"""Delete a saved session"""
|
||||
session_file = SESSIONS_DIR / f"{session_name}.json"
|
||||
if session_file.exists():
|
||||
session_file.unlink()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_profile(profile_name: str) -> Path:
|
||||
"""Create a new browser profile directory"""
|
||||
init_dirs()
|
||||
profile_path = PROFILES_DIR / profile_name
|
||||
profile_path.mkdir(exist_ok=True)
|
||||
return profile_path
|
||||
|
||||
|
||||
def get_profile_path(profile_name: str) -> Optional[Path]:
|
||||
"""Get path to existing profile or None"""
|
||||
profile_path = PROFILES_DIR / profile_name
|
||||
return profile_path if profile_path.exists() else None
|
||||
|
||||
|
||||
def list_profiles() -> List[str]:
|
||||
"""List all browser profiles"""
|
||||
init_dirs()
|
||||
return [d.name for d in PROFILES_DIR.iterdir() if d.is_dir()]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Session Manager')
|
||||
subparsers = parser.add_subparsers(dest='command')
|
||||
|
||||
# List sessions
|
||||
list_parser = subparsers.add_parser('list', help='List sessions')
|
||||
|
||||
# Show session
|
||||
show_parser = subparsers.add_parser('show', help='Show session details')
|
||||
show_parser.add_argument('name', help='Session name')
|
||||
|
||||
# Delete session
|
||||
del_parser = subparsers.add_parser('delete', help='Delete session')
|
||||
del_parser.add_argument('name', help='Session name')
|
||||
|
||||
# List profiles
|
||||
profiles_parser = subparsers.add_parser('profiles', help='List profiles')
|
||||
|
||||
# Create profile
|
||||
create_parser = subparsers.add_parser('create-profile', help='Create profile')
|
||||
create_parser.add_argument('name', help='Profile name')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'list':
|
||||
sessions = list_sessions()
|
||||
if sessions:
|
||||
print(f"{'Name':<20} {'Updated':<25} {'URL':<40} {'Cookies'}")
|
||||
print("-" * 100)
|
||||
for s in sessions:
|
||||
print(f"{s['name']:<20} {s.get('updated', 'N/A')[:25]:<25} {(s.get('url') or 'N/A')[:40]:<40} {s['cookies_count']}")
|
||||
else:
|
||||
print("No sessions found")
|
||||
|
||||
elif args.command == 'show':
|
||||
sm = SessionManager(args.name)
|
||||
print(json.dumps(sm.data, indent=2))
|
||||
|
||||
elif args.command == 'delete':
|
||||
if delete_session(args.name):
|
||||
print(f"Deleted: {args.name}")
|
||||
else:
|
||||
print(f"Session not found: {args.name}")
|
||||
|
||||
elif args.command == 'profiles':
|
||||
profiles = list_profiles()
|
||||
if profiles:
|
||||
for p in profiles:
|
||||
print(p)
|
||||
else:
|
||||
print("No profiles found")
|
||||
|
||||
elif args.command == 'create-profile':
|
||||
path = create_profile(args.name)
|
||||
print(f"Created: {path}")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
Reference in New Issue
Block a user