webtap-tool 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webtap-tool might be problematic. Click here for more details.

@@ -0,0 +1,105 @@
1
+ """Network monitoring service for request/response tracking."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from webtap.cdp import CDPSession
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class NetworkService:
13
+ """Internal service for network event queries and monitoring."""
14
+
15
+ def __init__(self):
16
+ """Initialize network service."""
17
+ self.cdp: CDPSession | None = None
18
+
19
+ @property
20
+ def request_count(self) -> int:
21
+ """Count of all network requests."""
22
+ if not self.cdp:
23
+ return 0
24
+ result = self.cdp.query(
25
+ "SELECT COUNT(*) FROM events WHERE json_extract_string(event, '$.method') = 'Network.responseReceived'"
26
+ )
27
+ return result[0][0] if result else 0
28
+
29
+ def get_recent_requests(self, limit: int = 20, filter_sql: str = "") -> list[tuple]:
30
+ """Get recent network requests with common fields extracted.
31
+
32
+ Args:
33
+ limit: Maximum results
34
+ filter_sql: Optional filter SQL to append
35
+ """
36
+ if not self.cdp:
37
+ return []
38
+
39
+ sql = """
40
+ SELECT
41
+ rowid,
42
+ json_extract_string(event, '$.params.requestId') as RequestId,
43
+ COALESCE(
44
+ json_extract_string(event, '$.params.request.method'),
45
+ json_extract_string(event, '$.params.response.request.method'),
46
+ 'GET'
47
+ ) as Method,
48
+ json_extract_string(event, '$.params.response.status') as Status,
49
+ COALESCE(
50
+ json_extract_string(event, '$.params.response.url'),
51
+ json_extract_string(event, '$.params.request.url')
52
+ ) as URL,
53
+ json_extract_string(event, '$.params.type') as Type,
54
+ json_extract_string(event, '$.params.response.encodedDataLength') as Size
55
+ FROM events
56
+ WHERE json_extract_string(event, '$.method') = 'Network.responseReceived'
57
+ """
58
+
59
+ if filter_sql:
60
+ sql += " AND " + filter_sql
61
+
62
+ sql += f" ORDER BY rowid DESC LIMIT {limit}"
63
+
64
+ return self.cdp.query(sql)
65
+
66
+ def get_failed_requests(self, limit: int = 20) -> list[tuple]:
67
+ """Get failed network requests (4xx, 5xx status codes).
68
+
69
+ Args:
70
+ limit: Maximum results
71
+ """
72
+ if not self.cdp:
73
+ return []
74
+
75
+ sql = f"""
76
+ SELECT
77
+ rowid,
78
+ json_extract_string(event, '$.params.requestId') as RequestId,
79
+ json_extract_string(event, '$.params.response.status') as Status,
80
+ json_extract_string(event, '$.params.response.url') as URL,
81
+ json_extract_string(event, '$.params.response.statusText') as StatusText
82
+ FROM events
83
+ WHERE json_extract_string(event, '$.method') = 'Network.responseReceived'
84
+ AND CAST(json_extract_string(event, '$.params.response.status') AS INTEGER) >= 400
85
+ ORDER BY rowid DESC LIMIT {limit}
86
+ """
87
+
88
+ return self.cdp.query(sql)
89
+
90
+ def get_request_by_id(self, request_id: str) -> list[dict]:
91
+ """Get all events for a specific request ID.
92
+
93
+ Args:
94
+ request_id: CDP request ID
95
+ """
96
+ if not self.cdp:
97
+ return []
98
+
99
+ results = self.cdp.query(
100
+ "SELECT event FROM events WHERE json_extract_string(event, '$.params.requestId') = ?", [request_id]
101
+ )
102
+
103
+ import json
104
+
105
+ return [json.loads(row[0]) for row in results] if results else []
@@ -0,0 +1,219 @@
1
+ """Setup service for installing WebTap components."""
2
+
3
+ import os
4
+ import json
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Dict, Any
8
+
9
+ import requests
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class SetupService:
15
+ """Service for installing WebTap components."""
16
+
17
+ # GitHub URLs
18
+ FILTERS_URL = "https://raw.githubusercontent.com/angelsen/tap-tools/main/packages/webtap/data/filters.json"
19
+ EXTENSION_BASE_URL = "https://raw.githubusercontent.com/angelsen/tap-tools/main/packages/webtap/extension"
20
+ EXTENSION_FILES = ["manifest.json", "popup.html", "popup.js"]
21
+
22
+ def install_filters(self, force: bool = False) -> Dict[str, Any]:
23
+ """Install filters to .webtap/filters.json.
24
+
25
+ Args:
26
+ force: Overwrite existing file
27
+
28
+ Returns:
29
+ Dict with success, message, path, details
30
+ """
31
+ # Same path that FilterManager uses
32
+ target_path = Path.cwd() / ".webtap" / "filters.json"
33
+
34
+ # Check if exists
35
+ if target_path.exists() and not force:
36
+ return {
37
+ "success": False,
38
+ "message": f"Filters already exist at {target_path}",
39
+ "path": str(target_path),
40
+ "details": "Use --force to overwrite",
41
+ }
42
+
43
+ # Download from GitHub
44
+ try:
45
+ logger.info(f"Downloading filters from {self.FILTERS_URL}")
46
+ response = requests.get(self.FILTERS_URL, timeout=10)
47
+ response.raise_for_status()
48
+
49
+ # Validate it's proper JSON
50
+ filters_data = json.loads(response.text)
51
+
52
+ # Quick validation - should have dict structure
53
+ if not isinstance(filters_data, dict):
54
+ return {
55
+ "success": False,
56
+ "message": "Invalid filter format - expected JSON object",
57
+ "path": None,
58
+ "details": None,
59
+ }
60
+
61
+ # Count categories for user feedback
62
+ category_count = len(filters_data)
63
+
64
+ # Create directory and save
65
+ target_path.parent.mkdir(parents=True, exist_ok=True)
66
+ target_path.write_text(response.text)
67
+
68
+ logger.info(f"Saved {category_count} filter categories to {target_path}")
69
+
70
+ return {
71
+ "success": True,
72
+ "message": f"Downloaded {category_count} filter categories",
73
+ "path": str(target_path),
74
+ "details": f"Categories: {', '.join(filters_data.keys())}",
75
+ }
76
+
77
+ except requests.RequestException as e:
78
+ logger.error(f"Network error downloading filters: {e}")
79
+ return {"success": False, "message": f"Network error: {e}", "path": None, "details": None}
80
+ except json.JSONDecodeError as e:
81
+ logger.error(f"Invalid JSON in filters: {e}")
82
+ return {"success": False, "message": f"Invalid JSON format: {e}", "path": None, "details": None}
83
+ except Exception as e:
84
+ logger.error(f"Unexpected error: {e}")
85
+ return {"success": False, "message": f"Failed to download filters: {e}", "path": None, "details": None}
86
+
87
+ def install_extension(self, force: bool = False) -> Dict[str, Any]:
88
+ """Install Chrome extension to ~/.config/webtap/extension/.
89
+
90
+ Args:
91
+ force: Overwrite existing files
92
+
93
+ Returns:
94
+ Dict with success, message, path, details
95
+ """
96
+ # XDG config directory for Linux
97
+ target_dir = Path.home() / ".config" / "webtap" / "extension"
98
+
99
+ # Check if exists (manifest.json is required file)
100
+ if (target_dir / "manifest.json").exists() and not force:
101
+ return {
102
+ "success": False,
103
+ "message": f"Extension already exists at {target_dir}",
104
+ "path": str(target_dir),
105
+ "details": "Use --force to overwrite",
106
+ }
107
+
108
+ # Create directory
109
+ target_dir.mkdir(parents=True, exist_ok=True)
110
+
111
+ # Download each file
112
+ downloaded = []
113
+ failed = []
114
+
115
+ for filename in self.EXTENSION_FILES:
116
+ url = f"{self.EXTENSION_BASE_URL}/{filename}"
117
+ target_file = target_dir / filename
118
+
119
+ try:
120
+ logger.info(f"Downloading {filename}")
121
+ response = requests.get(url, timeout=10)
122
+ response.raise_for_status()
123
+
124
+ # For manifest.json, validate it's proper JSON
125
+ if filename == "manifest.json":
126
+ json.loads(response.text)
127
+
128
+ target_file.write_text(response.text)
129
+ downloaded.append(filename)
130
+
131
+ except Exception as e:
132
+ logger.error(f"Failed to download {filename}: {e}")
133
+ failed.append(filename)
134
+
135
+ # Determine success level
136
+ if not downloaded:
137
+ return {
138
+ "success": False,
139
+ "message": "Failed to download any extension files",
140
+ "path": None,
141
+ "details": "Check network connection and try again",
142
+ }
143
+
144
+ if failed:
145
+ # Partial success - some files downloaded
146
+ return {
147
+ "success": True, # Partial is still success
148
+ "message": f"Downloaded {len(downloaded)}/{len(self.EXTENSION_FILES)} files",
149
+ "path": str(target_dir),
150
+ "details": f"Failed: {', '.join(failed)}",
151
+ }
152
+
153
+ return {
154
+ "success": True,
155
+ "message": "Downloaded Chrome extension",
156
+ "path": str(target_dir),
157
+ "details": f"Files: {', '.join(downloaded)}",
158
+ }
159
+
160
+ def install_chrome_wrapper(self, force: bool = False) -> Dict[str, Any]:
161
+ """Install Chrome wrapper script for debugging.
162
+
163
+ Args:
164
+ force: Overwrite existing script
165
+
166
+ Returns:
167
+ Dict with success, message, path, details
168
+ """
169
+ target_path = Path.home() / ".local" / "bin" / "wrappers" / "google-chrome-stable"
170
+
171
+ if target_path.exists() and not force:
172
+ return {
173
+ "success": False,
174
+ "message": "Chrome wrapper already exists",
175
+ "path": str(target_path),
176
+ "details": "Use --force to overwrite",
177
+ }
178
+
179
+ wrapper_script = """#!/bin/bash
180
+ # Chrome wrapper to always enable debugging
181
+
182
+ REAL_CONFIG="$HOME/.config/google-chrome"
183
+ DEBUG_CONFIG="/tmp/chrome-debug-profile"
184
+
185
+ if [ ! -d "$DEBUG_CONFIG" ]; then
186
+ mkdir -p "$DEBUG_CONFIG"
187
+ ln -sf "$REAL_CONFIG/Default" "$DEBUG_CONFIG/Default"
188
+ cp "$REAL_CONFIG/Local State" "$DEBUG_CONFIG/" 2>/dev/null || true
189
+ cp "$REAL_CONFIG/First Run" "$DEBUG_CONFIG/" 2>/dev/null || true
190
+ fi
191
+
192
+ exec /usr/bin/google-chrome-stable \\
193
+ --remote-debugging-port=9222 \\
194
+ --remote-allow-origins=* \\
195
+ --user-data-dir="$DEBUG_CONFIG" \\
196
+ "$@"
197
+ """
198
+
199
+ # Create directory and save
200
+ target_path.parent.mkdir(parents=True, exist_ok=True)
201
+ target_path.write_text(wrapper_script)
202
+ target_path.chmod(0o755) # Make executable
203
+
204
+ # Check PATH
205
+ path_dirs = os.environ.get("PATH", "").split(":")
206
+ wrapper_dir = str(target_path.parent)
207
+ in_path = wrapper_dir in path_dirs
208
+
209
+ logger.info(f"Installed Chrome wrapper to {target_path}")
210
+
211
+ return {
212
+ "success": True,
213
+ "message": "Installed Chrome wrapper script",
214
+ "path": str(target_path),
215
+ "details": "Already in PATH ✓" if in_path else "Add to PATH",
216
+ }
217
+
218
+
219
+ __all__ = ["SetupService"]