feature/web-search-and-cron-improvements #2
@ -131,6 +131,34 @@ class CronTool(Tool):
|
|||||||
# Build schedule - prioritize 'in_seconds' for relative time, then 'at' for absolute time
|
# Build schedule - prioritize 'in_seconds' for relative time, then 'at' for absolute time
|
||||||
delete_after = False
|
delete_after = False
|
||||||
|
|
||||||
|
# Special case: recurring job with duration limit (every_seconds + in_seconds)
|
||||||
|
if every_seconds is not None and in_seconds is not None:
|
||||||
|
# Create multiple one-time jobs for "every X seconds for Y seconds"
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
num_jobs = max(1, in_seconds // every_seconds)
|
||||||
|
results = []
|
||||||
|
for i in range(num_jobs):
|
||||||
|
job_time = datetime.now() + timedelta(seconds=i * every_seconds)
|
||||||
|
job_at = job_time.isoformat()
|
||||||
|
try:
|
||||||
|
dt = datetime.fromisoformat(job_at)
|
||||||
|
at_ms = int(dt.timestamp() * 1000)
|
||||||
|
schedule = CronSchedule(kind="at", at_ms=at_ms)
|
||||||
|
job = self._cron.add_job(
|
||||||
|
name=f"{message[:25]} ({i+1}/{num_jobs})" if num_jobs > 1 else message[:30],
|
||||||
|
schedule=schedule,
|
||||||
|
message=message,
|
||||||
|
deliver=True,
|
||||||
|
channel=channel,
|
||||||
|
to=chat_id,
|
||||||
|
delete_after_run=True,
|
||||||
|
reminder=reminder,
|
||||||
|
)
|
||||||
|
results.append(f"Created job '{job.name}' (id: {job.id})")
|
||||||
|
except Exception as e:
|
||||||
|
results.append(f"Error creating job {i+1}: {str(e)}")
|
||||||
|
return f"Created {len([r for r in results if 'Created' in r])} reminder(s):\n" + "\n".join(results)
|
||||||
|
|
||||||
# Handle relative time (in_seconds) - compute datetime automatically
|
# Handle relative time (in_seconds) - compute datetime automatically
|
||||||
if in_seconds is not None:
|
if in_seconds is not None:
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|||||||
@ -101,9 +101,9 @@ class WebSearchTool(Tool):
|
|||||||
try:
|
try:
|
||||||
n = min(max(count or self.max_results, 1), 10)
|
n = min(max(count or self.max_results, 1), 10)
|
||||||
|
|
||||||
# Try using duckduckgo_search library if available
|
# Try using ddgs library if available (renamed from duckduckgo_search)
|
||||||
try:
|
try:
|
||||||
from duckduckgo_search import DDGS
|
from ddgs import DDGS
|
||||||
with DDGS() as ddgs:
|
with DDGS() as ddgs:
|
||||||
results = []
|
results = []
|
||||||
for r in ddgs.text(query, max_results=n):
|
for r in ddgs.text(query, max_results=n):
|
||||||
@ -112,7 +112,7 @@ class WebSearchTool(Tool):
|
|||||||
"url": r.get("href", ""),
|
"url": r.get("href", ""),
|
||||||
"description": r.get("body", "")
|
"description": r.get("body", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return f"No results found for: {query}"
|
return f"No results found for: {query}"
|
||||||
|
|
||||||
@ -123,51 +123,58 @@ class WebSearchTool(Tool):
|
|||||||
lines.append(f" {item['description']}")
|
lines.append(f" {item['description']}")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Fallback: use DuckDuckGo instant answer API (simpler, but limited)
|
# ddgs package not installed, fall through to fallback
|
||||||
async with httpx.AsyncClient(
|
pass
|
||||||
follow_redirects=True,
|
except Exception as e:
|
||||||
timeout=15.0
|
# Log ddgs errors but fall through to fallback API
|
||||||
) as client:
|
import logging
|
||||||
# Use DuckDuckGo instant answer API (no key needed)
|
logging.debug(f"ddgs search error: {e}")
|
||||||
url = "https://api.duckduckgo.com/"
|
|
||||||
r = await client.get(
|
# Fallback: use DuckDuckGo instant answer API (simpler, but limited)
|
||||||
url,
|
async with httpx.AsyncClient(
|
||||||
params={"q": query, "format": "json", "no_html": "1", "skip_disambig": "1"},
|
follow_redirects=True,
|
||||||
headers={"User-Agent": USER_AGENT},
|
timeout=15.0
|
||||||
)
|
) as client:
|
||||||
r.raise_for_status()
|
# Use DuckDuckGo instant answer API (no key needed)
|
||||||
data = r.json()
|
url = "https://api.duckduckgo.com/"
|
||||||
|
r = await client.get(
|
||||||
results = []
|
url,
|
||||||
# Get RelatedTopics (search results)
|
params={"q": query, "format": "json", "no_html": "1", "skip_disambig": "1"},
|
||||||
if "RelatedTopics" in data:
|
headers={"User-Agent": USER_AGENT},
|
||||||
for topic in data["RelatedTopics"][:n]:
|
)
|
||||||
if "Text" in topic and "FirstURL" in topic:
|
r.raise_for_status()
|
||||||
results.append({
|
data = r.json()
|
||||||
"title": topic.get("Text", "").split(" - ")[0] if " - " in topic.get("Text", "") else topic.get("Text", "")[:50],
|
|
||||||
"url": topic.get("FirstURL", ""),
|
results = []
|
||||||
"description": topic.get("Text", "")
|
# Get RelatedTopics (search results)
|
||||||
})
|
if "RelatedTopics" in data:
|
||||||
|
for topic in data["RelatedTopics"][:n]:
|
||||||
# Also check AbstractText for direct answer
|
if "Text" in topic and "FirstURL" in topic:
|
||||||
if "AbstractText" in data and data["AbstractText"]:
|
results.append({
|
||||||
results.insert(0, {
|
"title": topic.get("Text", "").split(" - ")[0] if " - " in topic.get("Text", "") else topic.get("Text", "")[:50],
|
||||||
"title": data.get("Heading", query),
|
"url": topic.get("FirstURL", ""),
|
||||||
"url": data.get("AbstractURL", ""),
|
"description": topic.get("Text", "")
|
||||||
"description": data.get("AbstractText", "")
|
})
|
||||||
})
|
|
||||||
|
# Also check AbstractText for direct answer
|
||||||
if not results:
|
if "AbstractText" in data and data["AbstractText"]:
|
||||||
return f"No results found for: {query}. Try installing 'duckduckgo-search' package for better results: pip install duckduckgo-search"
|
results.insert(0, {
|
||||||
|
"title": data.get("Heading", query),
|
||||||
lines = [f"Results for: {query}\n"]
|
"url": data.get("AbstractURL", ""),
|
||||||
for i, item in enumerate(results[:n], 1):
|
"description": data.get("AbstractText", "")
|
||||||
lines.append(f"{i}. {item['title']}\n {item['url']}")
|
})
|
||||||
if item['description']:
|
|
||||||
lines.append(f" {item['description']}")
|
if not results:
|
||||||
return "\n".join(lines)
|
return f"No results found for: {query}. Try installing 'ddgs' package for better results: pip install ddgs"
|
||||||
|
|
||||||
|
lines = [f"Results for: {query}\n"]
|
||||||
|
for i, item in enumerate(results[:n], 1):
|
||||||
|
lines.append(f"{i}. {item['title']}\n {item['url']}")
|
||||||
|
if item['description']:
|
||||||
|
lines.append(f" {item['description']}")
|
||||||
|
return "\n".join(lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error searching: {e}. Try installing 'duckduckgo-search' package: pip install duckduckgo-search"
|
return f"Error searching: {e}. Try installing 'ddgs' package: pip install ddgs"
|
||||||
|
|
||||||
|
|
||||||
class WebFetchTool(Tool):
|
class WebFetchTool(Tool):
|
||||||
|
|||||||
@ -24,6 +24,7 @@ dependencies = [
|
|||||||
"websockets>=12.0",
|
"websockets>=12.0",
|
||||||
"websocket-client>=1.6.0",
|
"websocket-client>=1.6.0",
|
||||||
"httpx>=0.25.0",
|
"httpx>=0.25.0",
|
||||||
|
"ddgs>=9.0.0",
|
||||||
"oauth-cli-kit>=0.1.1",
|
"oauth-cli-kit>=0.1.1",
|
||||||
"loguru>=0.7.0",
|
"loguru>=0.7.0",
|
||||||
"readability-lxml>=0.8.0",
|
"readability-lxml>=0.8.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user