webscout 2025.10.14.1__py3-none-any.whl → 2025.10.16__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 webscout might be problematic. Click here for more details.
- webscout/Extra/YTToolkit/README.md +1 -1
- webscout/Extra/tempmail/README.md +3 -3
- webscout/Provider/OPENAI/README.md +1 -1
- webscout/Provider/TTI/bing.py +4 -4
- webscout/__init__.py +1 -1
- webscout/cli.py +0 -147
- webscout/client.py +4 -5
- webscout/litprinter/__init__.py +0 -42
- webscout/scout/README.md +59 -8
- webscout/scout/core/scout.py +62 -0
- webscout/scout/element.py +251 -45
- webscout/search/__init__.py +5 -8
- webscout/search/bing_main.py +42 -0
- webscout/search/engines/bing/__init__.py +1 -0
- webscout/search/engines/bing/base.py +33 -0
- webscout/search/engines/bing/images.py +108 -0
- webscout/search/engines/bing/news.py +91 -0
- webscout/search/engines/bing/suggestions.py +34 -0
- webscout/search/engines/bing/text.py +106 -0
- webscout/search/engines/duckduckgo/maps.py +13 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +16 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +324 -0
- webscout/search/engines/yahoo/maps.py +16 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +16 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/http_client.py +1 -1
- webscout/search/yahoo_main.py +54 -0
- webscout/{auth → server}/__init__.py +2 -23
- webscout/server/config.py +84 -0
- webscout/{auth → server}/request_processing.py +3 -28
- webscout/{auth → server}/routes.py +14 -170
- webscout/server/schemas.py +23 -0
- webscout/{auth → server}/server.py +11 -43
- webscout/server/simple_logger.py +84 -0
- webscout/version.py +1 -1
- webscout/version.py.bak +1 -1
- webscout/zeroart/README.md +17 -9
- webscout/zeroart/__init__.py +78 -6
- webscout/zeroart/effects.py +51 -1
- webscout/zeroart/fonts.py +559 -1
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/METADATA +15 -332
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/RECORD +55 -48
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/entry_points.txt +1 -1
- webscout/Bing_search.py +0 -417
- webscout/DWEBS.py +0 -529
- webscout/auth/api_key_manager.py +0 -189
- webscout/auth/auth_system.py +0 -85
- webscout/auth/config.py +0 -175
- webscout/auth/database.py +0 -755
- webscout/auth/middleware.py +0 -248
- webscout/auth/models.py +0 -185
- webscout/auth/rate_limiter.py +0 -254
- webscout/auth/schemas.py +0 -103
- webscout/auth/simple_logger.py +0 -236
- webscout/search/engines/bing.py +0 -84
- webscout/search/engines/bing_news.py +0 -52
- webscout/search/engines/yahoo.py +0 -65
- webscout/search/engines/yahoo_news.py +0 -64
- /webscout/{auth → server}/exceptions.py +0 -0
- /webscout/{auth → server}/providers.py +0 -0
- /webscout/{auth → server}/request_models.py +0 -0
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/WHEEL +0 -0
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-2025.10.14.1.dist-info → webscout-2025.10.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for the Webscout API server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import List, Dict, Optional, Any
|
|
7
|
+
from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
# Configuration constants
|
|
11
|
+
DEFAULT_PORT = 8000
|
|
12
|
+
DEFAULT_HOST = "0.0.0.0"
|
|
13
|
+
|
|
14
|
+
# Setup logger
|
|
15
|
+
logger = Logger(
|
|
16
|
+
name="webscout.api",
|
|
17
|
+
level=LogLevel.INFO,
|
|
18
|
+
handlers=[ConsoleHandler(stream=sys.stdout)],
|
|
19
|
+
fmt=LogFormat.DEFAULT
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ServerConfig:
|
|
24
|
+
"""Centralized configuration management for the API server."""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.provider_map: Dict[str, Any] = {}
|
|
28
|
+
self.default_provider: str = "ChatGPT"
|
|
29
|
+
self.base_url: Optional[str] = None
|
|
30
|
+
self.host: str = DEFAULT_HOST
|
|
31
|
+
self.port: int = DEFAULT_PORT
|
|
32
|
+
self.debug: bool = False
|
|
33
|
+
self.cors_origins: List[str] = ["*"]
|
|
34
|
+
self.max_request_size: int = 10 * 1024 * 1024 # 10MB
|
|
35
|
+
self.request_timeout: int = 300 # 5 minutes
|
|
36
|
+
self.auth_required: bool = False
|
|
37
|
+
self.rate_limit_enabled: bool = False
|
|
38
|
+
self.request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
|
|
39
|
+
|
|
40
|
+
def update(self, **kwargs) -> None:
|
|
41
|
+
"""Update configuration with provided values."""
|
|
42
|
+
for key, value in kwargs.items():
|
|
43
|
+
if hasattr(self, key) and value is not None:
|
|
44
|
+
setattr(self, key, value)
|
|
45
|
+
logger.info(f"Config updated: {key} = {value}")
|
|
46
|
+
|
|
47
|
+
def validate(self) -> None:
|
|
48
|
+
"""Validate configuration settings."""
|
|
49
|
+
if self.port < 1 or self.port > 65535:
|
|
50
|
+
raise ValueError(f"Invalid port number: {self.port}")
|
|
51
|
+
|
|
52
|
+
if self.default_provider not in self.provider_map and self.provider_map:
|
|
53
|
+
available_providers = list(set(v.__name__ for v in self.provider_map.values()))
|
|
54
|
+
logger.warning(f"Default provider '{self.default_provider}' not found. Available: {available_providers}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AppConfig:
|
|
58
|
+
"""Legacy configuration class for backward compatibility."""
|
|
59
|
+
provider_map = {}
|
|
60
|
+
tti_provider_map = {} # Add TTI provider map
|
|
61
|
+
default_provider = "ChatGPT"
|
|
62
|
+
default_tti_provider = "PollinationsAI" # Add default TTI provider
|
|
63
|
+
base_url: Optional[str] = None
|
|
64
|
+
auth_required: bool = False
|
|
65
|
+
rate_limit_enabled: bool = False
|
|
66
|
+
request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def set_config(cls, **data):
|
|
70
|
+
"""Set configuration values."""
|
|
71
|
+
# Filter out auth-related keys
|
|
72
|
+
auth_keys = {'api_key'}
|
|
73
|
+
filtered_data = {k: v for k, v in data.items() if k not in auth_keys}
|
|
74
|
+
|
|
75
|
+
for key, value in filtered_data.items():
|
|
76
|
+
setattr(cls, key, value)
|
|
77
|
+
# Sync with new config system
|
|
78
|
+
try:
|
|
79
|
+
from .server import get_config
|
|
80
|
+
config = get_config()
|
|
81
|
+
config.update(**filtered_data)
|
|
82
|
+
except ImportError:
|
|
83
|
+
# Handle case where server module is not available
|
|
84
|
+
pass
|
|
@@ -16,8 +16,7 @@ import sys
|
|
|
16
16
|
|
|
17
17
|
from .request_models import Message, ChatCompletionRequest
|
|
18
18
|
from .exceptions import APIError, clean_text
|
|
19
|
-
|
|
20
|
-
from .auth_system import get_auth_components
|
|
19
|
+
|
|
21
20
|
from .simple_logger import log_api_request, get_client_ip, generate_request_id
|
|
22
21
|
from .config import AppConfig
|
|
23
22
|
|
|
@@ -33,9 +32,9 @@ logger = Logger(
|
|
|
33
32
|
async def log_request(request_id: str, ip_address: str, model_used: str, question: str,
|
|
34
33
|
answer: str, response_time_ms: int, status_code: int = 200,
|
|
35
34
|
error_message: str = None, provider: str = None, request_obj=None):
|
|
36
|
-
"""Log API request
|
|
35
|
+
"""Log API request."""
|
|
37
36
|
try:
|
|
38
|
-
# Use simple logger
|
|
37
|
+
# Use simple logger if request logging is enabled
|
|
39
38
|
if AppConfig.request_logging_enabled:
|
|
40
39
|
user_agent = None
|
|
41
40
|
if request_obj:
|
|
@@ -52,30 +51,6 @@ async def log_request(request_id: str, ip_address: str, model_used: str, questio
|
|
|
52
51
|
error=error_message,
|
|
53
52
|
user_agent=user_agent
|
|
54
53
|
)
|
|
55
|
-
|
|
56
|
-
# Also use the existing auth system logging if available
|
|
57
|
-
auth_manager, db_manager, _ = get_auth_components()
|
|
58
|
-
|
|
59
|
-
if db_manager:
|
|
60
|
-
request_log = RequestLog(
|
|
61
|
-
id=None, # Will be auto-generated
|
|
62
|
-
request_id=request_id,
|
|
63
|
-
ip_address=ip_address,
|
|
64
|
-
model_used=model_used,
|
|
65
|
-
question=question,
|
|
66
|
-
answer=answer,
|
|
67
|
-
user_id=None, # No auth mode
|
|
68
|
-
api_key_id=None, # No auth mode
|
|
69
|
-
created_at=datetime.now(timezone.utc),
|
|
70
|
-
response_time_ms=response_time_ms,
|
|
71
|
-
status_code=status_code,
|
|
72
|
-
error_message=error_message,
|
|
73
|
-
metadata={}
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
await db_manager.create_request_log(request_log)
|
|
77
|
-
logger.debug(f"Logged request {request_id} to auth database")
|
|
78
|
-
|
|
79
54
|
except Exception as e:
|
|
80
55
|
logger.error(f"Failed to log request {request_id}: {e}")
|
|
81
56
|
# Don't raise exception to avoid breaking the main request flow
|
|
@@ -12,7 +12,6 @@ from typing import Any
|
|
|
12
12
|
from fastapi import FastAPI, Request, Body, Query
|
|
13
13
|
from fastapi.responses import JSONResponse
|
|
14
14
|
from fastapi.exceptions import RequestValidationError
|
|
15
|
-
from fastapi.security import APIKeyHeader
|
|
16
15
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
17
16
|
from starlette.status import (
|
|
18
17
|
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
@@ -28,7 +27,6 @@ from .request_models import (
|
|
|
28
27
|
ErrorResponse
|
|
29
28
|
)
|
|
30
29
|
from .schemas import (
|
|
31
|
-
APIKeyCreateRequest, APIKeyCreateResponse, APIKeyValidationResponse,
|
|
32
30
|
HealthCheckResponse
|
|
33
31
|
)
|
|
34
32
|
from .exceptions import APIError
|
|
@@ -40,11 +38,9 @@ from .request_processing import (
|
|
|
40
38
|
process_messages, prepare_provider_params,
|
|
41
39
|
handle_streaming_response, handle_non_streaming_response
|
|
42
40
|
)
|
|
43
|
-
from .auth_system import get_auth_components
|
|
44
41
|
from .simple_logger import request_logger
|
|
45
42
|
from ..search import DuckDuckGoSearch, YepSearch
|
|
46
|
-
from ..
|
|
47
|
-
from webscout.Bing_search import BingSearch
|
|
43
|
+
from ..search import BingSearch
|
|
48
44
|
|
|
49
45
|
# Setup logger
|
|
50
46
|
logger = Logger(
|
|
@@ -60,29 +56,6 @@ class Api:
|
|
|
60
56
|
|
|
61
57
|
def __init__(self, app: FastAPI) -> None:
|
|
62
58
|
self.app = app
|
|
63
|
-
self.get_api_key = APIKeyHeader(name="authorization", auto_error=False)
|
|
64
|
-
|
|
65
|
-
def register_authorization(self):
|
|
66
|
-
"""Register legacy authorization middleware."""
|
|
67
|
-
@self.app.middleware("http")
|
|
68
|
-
async def authorization(request: Request, call_next):
|
|
69
|
-
if AppConfig.api_key is not None:
|
|
70
|
-
auth_header = await self.get_api_key(request)
|
|
71
|
-
path = request.url.path
|
|
72
|
-
if path.startswith("/v1"): # Only protect /v1 routes
|
|
73
|
-
if auth_header is None:
|
|
74
|
-
return JSONResponse(
|
|
75
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
|
76
|
-
content={"error": {"message": "API key required", "type": "authentication_error"}}
|
|
77
|
-
)
|
|
78
|
-
if auth_header.startswith("Bearer "):
|
|
79
|
-
auth_header = auth_header[7:]
|
|
80
|
-
if not secrets.compare_digest(AppConfig.api_key, auth_header):
|
|
81
|
-
return JSONResponse(
|
|
82
|
-
status_code=HTTP_403_FORBIDDEN,
|
|
83
|
-
content={"error": {"message": "Invalid API key", "type": "authentication_error"}}
|
|
84
|
-
)
|
|
85
|
-
return await call_next(request)
|
|
86
59
|
|
|
87
60
|
def register_validation_exception_handler(self):
|
|
88
61
|
"""Register comprehensive exception handlers."""
|
|
@@ -163,7 +136,6 @@ class Api:
|
|
|
163
136
|
"""Register all API routes."""
|
|
164
137
|
self._register_model_routes()
|
|
165
138
|
self._register_chat_routes()
|
|
166
|
-
self._register_auth_routes()
|
|
167
139
|
self._register_websearch_routes()
|
|
168
140
|
self._register_monitoring_routes()
|
|
169
141
|
|
|
@@ -408,105 +380,7 @@ class Api:
|
|
|
408
380
|
"internal_error"
|
|
409
381
|
)
|
|
410
382
|
|
|
411
|
-
def _register_auth_routes(self):
|
|
412
|
-
"""Register authentication routes."""
|
|
413
|
-
# Only register auth endpoints if authentication is required
|
|
414
|
-
if not AppConfig.auth_required:
|
|
415
|
-
logger.info("Auth endpoints are disabled (no-auth mode)")
|
|
416
|
-
return
|
|
417
|
-
auth_components = get_auth_components()
|
|
418
|
-
api_key_manager = auth_components.get("api_key_manager")
|
|
419
383
|
|
|
420
|
-
@self.app.post(
|
|
421
|
-
"/v1/auth/generate-key",
|
|
422
|
-
response_model=APIKeyCreateResponse,
|
|
423
|
-
tags=["Authentication"],
|
|
424
|
-
description="Generate a new API key for a user."
|
|
425
|
-
)
|
|
426
|
-
async def generate_api_key(request: APIKeyCreateRequest = Body(...)):
|
|
427
|
-
"""Generate a new API key."""
|
|
428
|
-
if not api_key_manager:
|
|
429
|
-
raise APIError("Authentication system not initialized", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
430
|
-
|
|
431
|
-
try:
|
|
432
|
-
api_key, user = await api_key_manager.create_api_key(
|
|
433
|
-
username=request.username,
|
|
434
|
-
telegram_id=request.telegram_id,
|
|
435
|
-
name=request.name,
|
|
436
|
-
rate_limit=request.rate_limit or 10,
|
|
437
|
-
expires_in_days=request.expires_in_days
|
|
438
|
-
)
|
|
439
|
-
|
|
440
|
-
return APIKeyCreateResponse(
|
|
441
|
-
api_key=api_key.key,
|
|
442
|
-
key_id=api_key.id,
|
|
443
|
-
user_id=user.id,
|
|
444
|
-
name=api_key.name,
|
|
445
|
-
created_at=api_key.created_at,
|
|
446
|
-
expires_at=api_key.expires_at,
|
|
447
|
-
rate_limit=api_key.rate_limit
|
|
448
|
-
)
|
|
449
|
-
except Exception as e:
|
|
450
|
-
logger.error(f"Error generating API key: {e}")
|
|
451
|
-
raise APIError(f"Failed to generate API key: {str(e)}", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
452
|
-
|
|
453
|
-
@self.app.get(
|
|
454
|
-
"/v1/auth/validate",
|
|
455
|
-
response_model=APIKeyValidationResponse,
|
|
456
|
-
tags=["Authentication"],
|
|
457
|
-
description="Validate an API key and return its status."
|
|
458
|
-
)
|
|
459
|
-
async def validate_api_key(request: Request):
|
|
460
|
-
"""Validate an API key."""
|
|
461
|
-
if not api_key_manager:
|
|
462
|
-
raise APIError("Authentication system not initialized", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
463
|
-
|
|
464
|
-
auth_header = request.headers.get("authorization")
|
|
465
|
-
if not auth_header:
|
|
466
|
-
return APIKeyValidationResponse(valid=False, error="No authorization header provided")
|
|
467
|
-
|
|
468
|
-
# Extract API key
|
|
469
|
-
api_key = auth_header
|
|
470
|
-
if auth_header.startswith("Bearer "):
|
|
471
|
-
api_key = auth_header[7:]
|
|
472
|
-
|
|
473
|
-
try:
|
|
474
|
-
is_valid, api_key_obj, error_msg = await api_key_manager.validate_api_key(api_key)
|
|
475
|
-
|
|
476
|
-
if is_valid and api_key_obj:
|
|
477
|
-
return APIKeyValidationResponse(
|
|
478
|
-
valid=True,
|
|
479
|
-
user_id=api_key_obj.user_id,
|
|
480
|
-
key_id=api_key_obj.id,
|
|
481
|
-
rate_limit=api_key_obj.rate_limit,
|
|
482
|
-
usage_count=api_key_obj.usage_count,
|
|
483
|
-
last_used_at=api_key_obj.last_used_at
|
|
484
|
-
)
|
|
485
|
-
else:
|
|
486
|
-
return APIKeyValidationResponse(valid=False, error=error_msg)
|
|
487
|
-
except Exception as e:
|
|
488
|
-
logger.error(f"Error validating API key: {e}")
|
|
489
|
-
return APIKeyValidationResponse(valid=False, error="Internal validation error")
|
|
490
|
-
|
|
491
|
-
@self.app.get(
|
|
492
|
-
"/health",
|
|
493
|
-
response_model=HealthCheckResponse,
|
|
494
|
-
tags=["Health"],
|
|
495
|
-
description="Health check endpoint for the API and database."
|
|
496
|
-
)
|
|
497
|
-
async def health_check():
|
|
498
|
-
"""Health check endpoint."""
|
|
499
|
-
db_status = "unknown"
|
|
500
|
-
db_manager = auth_components.get("db_manager")
|
|
501
|
-
if db_manager:
|
|
502
|
-
status_info = db_manager.get_status()
|
|
503
|
-
db_status = f"{status_info['type']} - {status_info['status']}"
|
|
504
|
-
|
|
505
|
-
return HealthCheckResponse(
|
|
506
|
-
status="healthy",
|
|
507
|
-
database=db_status,
|
|
508
|
-
timestamp=datetime.now(timezone.utc)
|
|
509
|
-
)
|
|
510
384
|
|
|
511
385
|
def _register_websearch_routes(self):
|
|
512
386
|
"""Register web search endpoint."""
|
|
@@ -514,33 +388,20 @@ class Api:
|
|
|
514
388
|
@self.app.get(
|
|
515
389
|
"/search",
|
|
516
390
|
tags=["Web search"],
|
|
517
|
-
description="Unified web search endpoint supporting
|
|
391
|
+
description="Unified web search endpoint supporting Yep, DuckDuckGo, and Bing with text, news, images, and suggestions search types."
|
|
518
392
|
)
|
|
519
393
|
async def websearch(
|
|
520
394
|
q: str = Query(..., description="Search query"),
|
|
521
|
-
engine: str = Query("
|
|
395
|
+
engine: str = Query("duckduckgo", description="Search engine: yep, duckduckgo, bing"),
|
|
522
396
|
max_results: int = Query(10, description="Maximum number of results"),
|
|
523
397
|
region: str = Query("all", description="Region code (optional)"),
|
|
524
398
|
safesearch: str = Query("moderate", description="Safe search: on, moderate, off"),
|
|
525
399
|
type: str = Query("text", description="Search type: text, news, images, suggestions"),
|
|
526
400
|
):
|
|
527
401
|
"""Unified web search endpoint."""
|
|
528
|
-
github_footer = "If you believe this is a bug, please pull an issue at https://github.com/
|
|
402
|
+
github_footer = "If you believe this is a bug, please pull an issue at https://github.com/pyscout/Webscout."
|
|
529
403
|
try:
|
|
530
|
-
if engine == "
|
|
531
|
-
gs = GoogleSearch()
|
|
532
|
-
if type == "text":
|
|
533
|
-
results = gs.text(keywords=q, region=region, safesearch=safesearch, max_results=max_results)
|
|
534
|
-
return {"engine": "google", "type": "text", "results": [r.__dict__ for r in results]}
|
|
535
|
-
elif type == "news":
|
|
536
|
-
results = gs.news(keywords=q, region=region, safesearch=safesearch, max_results=max_results)
|
|
537
|
-
return {"engine": "google", "type": "news", "results": [r.__dict__ for r in results]}
|
|
538
|
-
elif type == "suggestions":
|
|
539
|
-
results = gs.suggestions(q, region=region)
|
|
540
|
-
return {"engine": "google", "type": "suggestions", "results": results}
|
|
541
|
-
else:
|
|
542
|
-
return {"error": "Google only supports text, news, and suggestions in this API.", "footer": github_footer}
|
|
543
|
-
elif engine == "yep":
|
|
404
|
+
if engine == "yep":
|
|
544
405
|
ys = YepSearch()
|
|
545
406
|
if type == "text":
|
|
546
407
|
results = ys.text(keywords=q, region=region, safesearch=safesearch, max_results=max_results)
|
|
@@ -554,12 +415,12 @@ class Api:
|
|
|
554
415
|
else:
|
|
555
416
|
return {"error": "Yep only supports text, images, and suggestions in this API.", "footer": github_footer}
|
|
556
417
|
elif engine == "duckduckgo":
|
|
557
|
-
|
|
418
|
+
ddg = DuckDuckGoSearch()
|
|
558
419
|
if type == "text":
|
|
559
|
-
results =
|
|
420
|
+
results = ddg.text(keywords=q, region=region, safesearch=safesearch, max_results=max_results)
|
|
560
421
|
return {"engine": "duckduckgo", "type": "text", "results": results}
|
|
561
422
|
elif type == "suggestions":
|
|
562
|
-
results =
|
|
423
|
+
results = ddg.suggestions(keywords=q, region=region)
|
|
563
424
|
return {"engine": "duckduckgo", "type": "suggestions", "results": results}
|
|
564
425
|
else:
|
|
565
426
|
return {"error": "DuckDuckGo only supports text and suggestions in this API.", "footer": github_footer}
|
|
@@ -580,7 +441,7 @@ class Api:
|
|
|
580
441
|
else:
|
|
581
442
|
return {"error": "Bing only supports text, news, images, and suggestions in this API.", "footer": github_footer}
|
|
582
443
|
else:
|
|
583
|
-
return {"error": "Unknown engine. Use one of:
|
|
444
|
+
return {"error": "Unknown engine. Use one of: yep, duckduckgo, bing.", "footer": github_footer}
|
|
584
445
|
except Exception as e:
|
|
585
446
|
# Special handling for rate limit errors
|
|
586
447
|
msg = str(e)
|
|
@@ -597,18 +458,15 @@ class Api:
|
|
|
597
458
|
}
|
|
598
459
|
|
|
599
460
|
def _register_monitoring_routes(self):
|
|
600
|
-
"""Register monitoring and analytics routes
|
|
461
|
+
"""Register monitoring and analytics routes."""
|
|
601
462
|
|
|
602
463
|
@self.app.get(
|
|
603
464
|
"/monitor/requests",
|
|
604
465
|
tags=["Monitoring"],
|
|
605
|
-
description="Get recent API requests
|
|
466
|
+
description="Get recent API requests"
|
|
606
467
|
)
|
|
607
468
|
async def get_recent_requests(limit: int = Query(10, description="Number of recent requests to fetch")):
|
|
608
469
|
"""Get recent API requests for monitoring."""
|
|
609
|
-
if AppConfig.auth_required:
|
|
610
|
-
return {"error": "Monitoring is only available in no-auth mode"}
|
|
611
|
-
|
|
612
470
|
try:
|
|
613
471
|
return await request_logger.get_recent_requests(limit)
|
|
614
472
|
except Exception as e:
|
|
@@ -617,13 +475,10 @@ class Api:
|
|
|
617
475
|
@self.app.get(
|
|
618
476
|
"/monitor/stats",
|
|
619
477
|
tags=["Monitoring"],
|
|
620
|
-
description="Get API usage statistics
|
|
478
|
+
description="Get API usage statistics"
|
|
621
479
|
)
|
|
622
480
|
async def get_api_stats():
|
|
623
481
|
"""Get API usage statistics."""
|
|
624
|
-
if AppConfig.auth_required:
|
|
625
|
-
return {"error": "Monitoring is only available in no-auth mode"}
|
|
626
|
-
|
|
627
482
|
try:
|
|
628
483
|
return await request_logger.get_stats()
|
|
629
484
|
except Exception as e:
|
|
@@ -632,24 +487,13 @@ class Api:
|
|
|
632
487
|
@self.app.get(
|
|
633
488
|
"/monitor/health",
|
|
634
489
|
tags=["Monitoring"],
|
|
635
|
-
description="Health check
|
|
490
|
+
description="Health check"
|
|
636
491
|
)
|
|
637
492
|
async def enhanced_health_check():
|
|
638
|
-
"""Enhanced health check
|
|
493
|
+
"""Enhanced health check."""
|
|
639
494
|
try:
|
|
640
|
-
# Check database connectivity
|
|
641
|
-
db_status = "disconnected"
|
|
642
|
-
if request_logger.supabase_client:
|
|
643
|
-
try:
|
|
644
|
-
# Try a simple query to check connectivity
|
|
645
|
-
result = request_logger.supabase_client.table("api_requests").select("id").limit(1).execute()
|
|
646
|
-
db_status = "connected"
|
|
647
|
-
except Exception as e:
|
|
648
|
-
db_status = f"error: {str(e)[:100]}"
|
|
649
|
-
|
|
650
495
|
return {
|
|
651
496
|
"status": "healthy",
|
|
652
|
-
"database": db_status,
|
|
653
497
|
"auth_required": AppConfig.auth_required,
|
|
654
498
|
"rate_limit_enabled": AppConfig.rate_limit_enabled,
|
|
655
499
|
"request_logging_enabled": AppConfig.request_logging_enabled,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# webscout/server/schemas.py
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorResponse(BaseModel):
|
|
9
|
+
"""Standard error response model."""
|
|
10
|
+
error: str = Field(..., description="Error message")
|
|
11
|
+
code: str = Field(..., description="Error code")
|
|
12
|
+
details: Optional[Dict[str, Any]] = Field(None, description="Additional error details")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthCheckResponse(BaseModel):
|
|
16
|
+
"""Health check response model."""
|
|
17
|
+
status: str = Field(..., description="Service status")
|
|
18
|
+
timestamp: datetime = Field(..., description="Check timestamp")
|
|
19
|
+
|
|
20
|
+
class Config:
|
|
21
|
+
json_encoders = {
|
|
22
|
+
datetime: lambda v: v.isoformat()
|
|
23
|
+
}
|
|
@@ -25,7 +25,7 @@ from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
|
|
|
25
25
|
from .config import ServerConfig, AppConfig
|
|
26
26
|
from .routes import Api
|
|
27
27
|
from .providers import initialize_provider_map, initialize_tti_provider_map
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
|
|
30
30
|
# Configuration constants
|
|
31
31
|
DEFAULT_PORT = 8000
|
|
@@ -138,12 +138,7 @@ def create_app():
|
|
|
138
138
|
allow_headers=["*"],
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
initialize_auth_system(
|
|
143
|
-
app,
|
|
144
|
-
auth_required=AppConfig.auth_required,
|
|
145
|
-
rate_limit_enabled=AppConfig.rate_limit_enabled
|
|
146
|
-
)
|
|
141
|
+
|
|
147
142
|
# Add startup event handler
|
|
148
143
|
@app.on_event("startup")
|
|
149
144
|
async def startup():
|
|
@@ -175,42 +170,33 @@ def create_app_debug():
|
|
|
175
170
|
def start_server(
|
|
176
171
|
port: int = DEFAULT_PORT,
|
|
177
172
|
host: str = DEFAULT_HOST,
|
|
178
|
-
api_key: str = None,
|
|
179
173
|
default_provider: str = None,
|
|
180
174
|
base_url: str = None,
|
|
181
175
|
workers: int = 1,
|
|
182
176
|
log_level: str = 'info',
|
|
183
177
|
debug: bool = False,
|
|
184
|
-
no_auth: bool = False,
|
|
185
|
-
no_rate_limit: bool = False
|
|
186
178
|
):
|
|
187
179
|
"""Start the API server with the given configuration."""
|
|
188
180
|
run_api(
|
|
189
181
|
host=host,
|
|
190
182
|
port=port,
|
|
191
|
-
api_key=api_key,
|
|
192
183
|
default_provider=default_provider,
|
|
193
184
|
base_url=base_url,
|
|
194
185
|
workers=workers,
|
|
195
186
|
log_level=log_level,
|
|
196
187
|
debug=debug,
|
|
197
|
-
no_auth=no_auth,
|
|
198
|
-
no_rate_limit=no_rate_limit,
|
|
199
188
|
)
|
|
200
189
|
|
|
201
190
|
|
|
202
191
|
def run_api(
|
|
203
192
|
host: str = '0.0.0.0',
|
|
204
193
|
port: int = None,
|
|
205
|
-
api_key: str = None,
|
|
206
194
|
default_provider: str = None,
|
|
207
195
|
base_url: str = None,
|
|
208
196
|
debug: bool = False,
|
|
209
197
|
workers: int = 1,
|
|
210
198
|
log_level: str = 'info',
|
|
211
199
|
show_available_providers: bool = True,
|
|
212
|
-
no_auth: bool = False,
|
|
213
|
-
no_rate_limit: bool = False,
|
|
214
200
|
) -> None:
|
|
215
201
|
"""Run the API server with configuration."""
|
|
216
202
|
print("Starting Webscout OpenAI API server...")
|
|
@@ -218,11 +204,11 @@ def run_api(
|
|
|
218
204
|
port = DEFAULT_PORT
|
|
219
205
|
|
|
220
206
|
AppConfig.set_config(
|
|
221
|
-
api_key=
|
|
207
|
+
api_key=None,
|
|
222
208
|
default_provider=default_provider or AppConfig.default_provider,
|
|
223
209
|
base_url=base_url,
|
|
224
|
-
auth_required=
|
|
225
|
-
rate_limit_enabled=
|
|
210
|
+
auth_required=False,
|
|
211
|
+
rate_limit_enabled=False
|
|
226
212
|
)
|
|
227
213
|
|
|
228
214
|
if show_available_providers:
|
|
@@ -243,18 +229,10 @@ def run_api(
|
|
|
243
229
|
print(f"Docs URL: {api_endpoint_base}/docs")
|
|
244
230
|
|
|
245
231
|
# Show authentication status
|
|
246
|
-
|
|
247
|
-
print(f"Authentication: 🔓 DISABLED (No-Auth Mode)")
|
|
248
|
-
elif api_key:
|
|
249
|
-
print(f"Authentication: 🔐 Legacy API Key")
|
|
250
|
-
else:
|
|
251
|
-
print(f"Authentication: 🔑 Enhanced API Keys")
|
|
232
|
+
print(f"Authentication: 🔓 DISABLED")
|
|
252
233
|
|
|
253
234
|
# Show rate limiting status
|
|
254
|
-
|
|
255
|
-
print(f"Rate Limiting: ⚡ DISABLED")
|
|
256
|
-
else:
|
|
257
|
-
print(f"Rate Limiting: 🛡️ Enabled ({AppConfig.default_rate_limit}/min)")
|
|
235
|
+
print(f"Rate Limiting: ⚡ DISABLED")
|
|
258
236
|
|
|
259
237
|
print(f"Default Provider: {AppConfig.default_provider}")
|
|
260
238
|
print(f"Workers: {workers}")
|
|
@@ -291,7 +269,7 @@ def run_api(
|
|
|
291
269
|
print("\nUse Ctrl+C to stop the server.")
|
|
292
270
|
print("=" * 40 + "\n")
|
|
293
271
|
|
|
294
|
-
uvicorn_app_str = "webscout.
|
|
272
|
+
uvicorn_app_str = "webscout.server.server:create_app_debug" if debug else "webscout.server.server:create_app"
|
|
295
273
|
|
|
296
274
|
# Configure uvicorn settings
|
|
297
275
|
uvicorn_config = {
|
|
@@ -322,24 +300,18 @@ def main():
|
|
|
322
300
|
default_host = os.getenv('WEBSCOUT_HOST', DEFAULT_HOST)
|
|
323
301
|
default_workers = int(os.getenv('WEBSCOUT_WORKERS', '1'))
|
|
324
302
|
default_log_level = os.getenv('WEBSCOUT_LOG_LEVEL', 'info')
|
|
325
|
-
default_api_key = os.getenv('WEBSCOUT_API_KEY', os.getenv('API_KEY'))
|
|
326
303
|
default_provider = os.getenv('WEBSCOUT_DEFAULT_PROVIDER', os.getenv('DEFAULT_PROVIDER'))
|
|
327
304
|
default_base_url = os.getenv('WEBSCOUT_BASE_URL', os.getenv('BASE_URL'))
|
|
328
305
|
default_debug = os.getenv('WEBSCOUT_DEBUG', os.getenv('DEBUG', 'false')).lower() == 'true'
|
|
329
|
-
default_no_auth = os.getenv('WEBSCOUT_NO_AUTH', 'false').lower() == 'true'
|
|
330
|
-
default_no_rate_limit = os.getenv('WEBSCOUT_NO_RATE_LIMIT', 'false').lower() == 'true'
|
|
331
306
|
|
|
332
307
|
parser = argparse.ArgumentParser(description='Start Webscout OpenAI-compatible API server')
|
|
333
308
|
parser.add_argument('--port', type=int, default=default_port, help=f'Port to run the server on (default: {default_port})')
|
|
334
309
|
parser.add_argument('--host', type=str, default=default_host, help=f'Host to bind the server to (default: {default_host})')
|
|
335
310
|
parser.add_argument('--workers', type=int, default=default_workers, help=f'Number of worker processes (default: {default_workers})')
|
|
336
311
|
parser.add_argument('--log-level', type=str, default=default_log_level, choices=['debug', 'info', 'warning', 'error', 'critical'], help=f'Log level (default: {default_log_level})')
|
|
337
|
-
parser.add_argument('--api-key', type=str, default=default_api_key, help='API key for authentication (optional)')
|
|
338
312
|
parser.add_argument('--default-provider', type=str, default=default_provider, help='Default provider to use (optional)')
|
|
339
313
|
parser.add_argument('--base-url', type=str, default=default_base_url, help='Base URL for the API (optional, e.g., /api/v1)')
|
|
340
314
|
parser.add_argument('--debug', action='store_true', default=default_debug, help='Run in debug mode')
|
|
341
|
-
parser.add_argument('--no-auth', action='store_true', default=default_no_auth, help='Disable authentication (no API keys required)')
|
|
342
|
-
parser.add_argument('--no-rate-limit', action='store_true', default=default_no_rate_limit, help='Disable rate limiting (unlimited requests)')
|
|
343
315
|
args = parser.parse_args()
|
|
344
316
|
|
|
345
317
|
# Print configuration summary
|
|
@@ -349,9 +321,8 @@ def main():
|
|
|
349
321
|
print(f" Workers: {args.workers}")
|
|
350
322
|
print(f" Log Level: {args.log_level}")
|
|
351
323
|
print(f" Debug Mode: {args.debug}")
|
|
352
|
-
print(f"
|
|
353
|
-
print(f"
|
|
354
|
-
print(f" API Key: {'Set' if args.api_key else 'Not set'}")
|
|
324
|
+
print(f" Authentication: 🔓 DISABLED")
|
|
325
|
+
print(f" Rate Limiting: ⚡ DISABLED")
|
|
355
326
|
print(f" Default Provider: {args.default_provider or 'Not set'}")
|
|
356
327
|
print(f" Base URL: {args.base_url or 'Not set'}")
|
|
357
328
|
print()
|
|
@@ -361,12 +332,9 @@ def main():
|
|
|
361
332
|
port=args.port,
|
|
362
333
|
workers=args.workers,
|
|
363
334
|
log_level=args.log_level,
|
|
364
|
-
api_key=args.api_key,
|
|
365
335
|
default_provider=args.default_provider,
|
|
366
336
|
base_url=args.base_url,
|
|
367
|
-
debug=args.debug
|
|
368
|
-
no_auth=args.no_auth,
|
|
369
|
-
no_rate_limit=args.no_rate_limit
|
|
337
|
+
debug=args.debug
|
|
370
338
|
)
|
|
371
339
|
|
|
372
340
|
|