ownapi 1.0.0__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.
- cli.py +1017 -0
- compiler.py +948 -0
- core/__init__.py +19 -0
- core/api_keys.py +523 -0
- core/auto_repair.py +682 -0
- core/base_client.py +165 -0
- core/consent.py +169 -0
- core/form_automation.py +1044 -0
- core/hitl_queue.py +886 -0
- core/onboarding.py +1457 -0
- core/token_store.py +223 -0
- healer.py +2074 -0
- master_mcp.py +193 -0
- ownapi/__init__.py +94 -0
- ownapi/cli.py +309 -0
- ownapi/sdk.py +436 -0
- ownapi-1.0.0.dist-info/METADATA +287 -0
- ownapi-1.0.0.dist-info/RECORD +62 -0
- ownapi-1.0.0.dist-info/WHEEL +5 -0
- ownapi-1.0.0.dist-info/entry_points.txt +4 -0
- ownapi-1.0.0.dist-info/top_level.txt +10 -0
- recorder.py +486 -0
- registry/coingecko/__init__.py +5 -0
- registry/coingecko/client.py +84 -0
- registry/coingecko/schema.json +23 -0
- registry/edevlet/__init__.py +5 -0
- registry/edevlet/client.py +54 -0
- registry/edevlet/schema.json +14 -0
- registry/enuygun/__init__.py +5 -0
- registry/enuygun/client.py +78 -0
- registry/enuygun/schema.json +13 -0
- registry/github_trending/__init__.py +5 -0
- registry/github_trending/client.py +90 -0
- registry/github_trending/schema.json +31 -0
- registry/hackernews/__init__.py +5 -0
- registry/hackernews/client.py +90 -0
- registry/hackernews/schema.json +38 -0
- registry/hepsiburada/__init__.py +5 -0
- registry/hepsiburada/client.py +32 -0
- registry/hepsiburada/schema.json +23 -0
- registry/linkedin_jobs/__init__.py +5 -0
- registry/linkedin_jobs/client.py +241 -0
- registry/linkedin_jobs/schema.json +46 -0
- registry/pegasus/__init__.py +5 -0
- registry/pegasus/client.py +141 -0
- registry/pegasus/schema.json +76 -0
- registry/thy/__init__.py +5 -0
- registry/thy/client.py +1796 -0
- registry/thy/schema.json +46 -0
- registry/trendyol/__init__.py +5 -0
- registry/trendyol/client.py +228 -0
- registry/trendyol/schema.json +30 -0
- registry/ucak_bileti/client.py +94 -0
- registry/ucak_bileti/schema.json +47 -0
- registry/wikipedia/__init__.py +5 -0
- registry/wikipedia/client.py +64 -0
- registry/wikipedia/schema.json +31 -0
- registry/yahoo_finance/__init__.py +5 -0
- registry/yahoo_finance/client.py +98 -0
- registry/yahoo_finance/schema.json +22 -0
- server.py +891 -0
- static/index.html +2401 -0
registry/thy/schema.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"service_name": "thy",
|
|
3
|
+
"target_url": "https://www.turkishairlines.com/tr-tr/",
|
|
4
|
+
"goal": "İstanbul'dan Ankara'ya uçuş seferlerini ve fiyatlarını sorgula",
|
|
5
|
+
"function_name": "search_flights",
|
|
6
|
+
"explanation": "Kullanıcının hedefi İstanbul'dan Ankara'ya uçuş seferlerini ve fiyatlarını sorgulamak olduğundan, uçuş müsaitlik ve fiyat bilgilerini içeren '/api/v1/availability' uç noktası en uygun seçimdir. Bu uç nokta, uçuş bilgilerini almak için gerekli olan tüm parametreleri içermektedir.",
|
|
7
|
+
"method": "POST",
|
|
8
|
+
"endpoint_url": "https://www.turkishairlines.com/api/v1/availability",
|
|
9
|
+
"dynamic_params": [
|
|
10
|
+
{
|
|
11
|
+
"name": "departure_date",
|
|
12
|
+
"original_key": "departureDate",
|
|
13
|
+
"location": "body",
|
|
14
|
+
"param_type": "str",
|
|
15
|
+
"default_value": "02-03-2027",
|
|
16
|
+
"description": "Kalkış tarihi"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "return_date",
|
|
20
|
+
"original_key": "departureDate",
|
|
21
|
+
"location": "body",
|
|
22
|
+
"param_type": "str",
|
|
23
|
+
"default_value": "09-03-2027",
|
|
24
|
+
"description": "Dönüş tarihi"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "origin_airport_code",
|
|
28
|
+
"original_key": "originAirportCode",
|
|
29
|
+
"location": "body",
|
|
30
|
+
"param_type": "str",
|
|
31
|
+
"default_value": "IST",
|
|
32
|
+
"description": "Kalkış havaalanı kodu"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "destination_airport_code",
|
|
36
|
+
"original_key": "destinationAirportCode",
|
|
37
|
+
"location": "body",
|
|
38
|
+
"param_type": "str",
|
|
39
|
+
"default_value": "ESB",
|
|
40
|
+
"description": "Varış havaalanı kodu"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"created_at": "2026-11-01T16:00:17.309297+00:00",
|
|
44
|
+
"validation_status": "BAŞARILI",
|
|
45
|
+
"validation_passed_runs": 3
|
|
46
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import pathlib
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import quote_plus, urlencode
|
|
10
|
+
|
|
11
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
12
|
+
try:
|
|
13
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
14
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
15
|
+
except Exception:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
|
|
19
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
20
|
+
|
|
21
|
+
from core.base_client import BaseOwnClient
|
|
22
|
+
from core.token_store import TokenStore
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("own_api.trendyol")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TrendyolClient(BaseOwnClient):
|
|
28
|
+
"""Trendyol e-ticaret servisi için OwnAPI User-Delegated Browser Middleware istemcisi."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
tenant_id: str = "default",
|
|
33
|
+
token_store: TokenStore | None = None,
|
|
34
|
+
mode: str = "local",
|
|
35
|
+
) -> None:
|
|
36
|
+
super().__init__(
|
|
37
|
+
service_name="trendyol",
|
|
38
|
+
base_url="https://www.trendyol.com",
|
|
39
|
+
tenant_id=tenant_id,
|
|
40
|
+
token_store=token_store,
|
|
41
|
+
mode=mode,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def search(
|
|
45
|
+
self,
|
|
46
|
+
query: str = "laptop",
|
|
47
|
+
max_results: int = 10,
|
|
48
|
+
) -> dict[str, Any]:
|
|
49
|
+
"""Trendyol üzerinde ürün araması yapar ve yapılandırılmış ürün listesini döndürür."""
|
|
50
|
+
clean_query = str(query).strip()
|
|
51
|
+
encoded_query = quote_plus(clean_query)
|
|
52
|
+
search_path = f"/sr?q={encoded_query}"
|
|
53
|
+
full_url = f"{self.base_url}{search_path}"
|
|
54
|
+
|
|
55
|
+
headers = {
|
|
56
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
57
|
+
"accept-language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
58
|
+
"referer": "https://www.trendyol.com/",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# İsteği doğrudan BaseShadowClient (In-Browser Fetch) üzerinden gönder
|
|
62
|
+
raw = self.request(
|
|
63
|
+
method="GET",
|
|
64
|
+
url=full_url,
|
|
65
|
+
headers=headers,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Yanıt HTML metnini al
|
|
69
|
+
html_text = ""
|
|
70
|
+
if isinstance(raw, dict):
|
|
71
|
+
html_text = raw.get("text", "")
|
|
72
|
+
if not html_text and "error" in raw and not raw.get("success", True):
|
|
73
|
+
return raw
|
|
74
|
+
elif isinstance(raw, str):
|
|
75
|
+
html_text = raw
|
|
76
|
+
|
|
77
|
+
parsed = self._extract_products_from_html(html_text, clean_query, max_results)
|
|
78
|
+
return parsed
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _extract_products_from_html(html_text: str, query: str, max_results: int) -> dict[str, Any]:
|
|
82
|
+
"""Trendyol SSR HTML içindeki __single-search-result__PROPS JSON bloğunu ayrıştırır."""
|
|
83
|
+
prefix = 'window["__single-search-result__PROPS"]='
|
|
84
|
+
idx = html_text.find(prefix)
|
|
85
|
+
if idx == -1:
|
|
86
|
+
prefix_alt = 'window.__SEARCH_APP_INITIAL_STATE__='
|
|
87
|
+
idx = html_text.find(prefix_alt)
|
|
88
|
+
prefix = prefix_alt
|
|
89
|
+
|
|
90
|
+
raw_products: list[dict[str, Any]] = []
|
|
91
|
+
if idx != -1:
|
|
92
|
+
sub = html_text[idx + len(prefix):]
|
|
93
|
+
end_script = sub.find("</script>")
|
|
94
|
+
if end_script != -1:
|
|
95
|
+
raw_json = sub[:end_script].rstrip(";").strip()
|
|
96
|
+
try:
|
|
97
|
+
data = json.loads(raw_json)
|
|
98
|
+
if isinstance(data, dict):
|
|
99
|
+
if "data" in data and isinstance(data["data"], dict):
|
|
100
|
+
raw_products = data["data"].get("products", [])
|
|
101
|
+
elif "products" in data:
|
|
102
|
+
raw_products = data["products"]
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.warning(f"Trendyol JSON parse hatası: {e}")
|
|
105
|
+
|
|
106
|
+
formatted_products: list[dict[str, Any]] = []
|
|
107
|
+
for p in raw_products[:max_results]:
|
|
108
|
+
if not isinstance(p, dict):
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Fiyat ayıklama
|
|
112
|
+
p_price = p.get("price")
|
|
113
|
+
discounted_val = None
|
|
114
|
+
original_val = None
|
|
115
|
+
currency = "TL"
|
|
116
|
+
|
|
117
|
+
if isinstance(p_price, dict):
|
|
118
|
+
discounted_val = p_price.get("discountedPrice") or p_price.get("current")
|
|
119
|
+
if isinstance(discounted_val, dict):
|
|
120
|
+
discounted_val = discounted_val.get("value")
|
|
121
|
+
original_val = p_price.get("originalPrice") or p_price.get("old")
|
|
122
|
+
if isinstance(original_val, dict):
|
|
123
|
+
original_val = original_val.get("value")
|
|
124
|
+
currency = p_price.get("currency") or p_price.get("currencySymbol") or "TL"
|
|
125
|
+
elif isinstance(p_price, (int, float)):
|
|
126
|
+
discounted_val = float(p_price)
|
|
127
|
+
|
|
128
|
+
# Marka ayıklama
|
|
129
|
+
brand = p.get("brand")
|
|
130
|
+
brand_name = brand.get("name", "") if isinstance(brand, dict) else str(brand or "")
|
|
131
|
+
|
|
132
|
+
# Puan ayıklama
|
|
133
|
+
rating_score = p.get("ratingScore")
|
|
134
|
+
avg_rating = None
|
|
135
|
+
total_reviews = None
|
|
136
|
+
if isinstance(rating_score, dict):
|
|
137
|
+
avg_rating = rating_score.get("averageRating")
|
|
138
|
+
total_reviews = rating_score.get("totalCount")
|
|
139
|
+
|
|
140
|
+
# URL
|
|
141
|
+
rel_url = p.get("url", "")
|
|
142
|
+
full_prod_url = f"https://www.trendyol.com{rel_url}" if rel_url.startswith("/") else rel_url
|
|
143
|
+
|
|
144
|
+
formatted_products.append({
|
|
145
|
+
"id": p.get("id"),
|
|
146
|
+
"name": p.get("name", ""),
|
|
147
|
+
"brand": brand_name,
|
|
148
|
+
"price": discounted_val,
|
|
149
|
+
"original_price": original_val,
|
|
150
|
+
"currency": currency,
|
|
151
|
+
"rating": round(avg_rating, 2) if isinstance(avg_rating, (int, float)) else None,
|
|
152
|
+
"review_count": total_reviews,
|
|
153
|
+
"url": full_prod_url,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"success": True,
|
|
158
|
+
"service": "trendyol",
|
|
159
|
+
"query": query,
|
|
160
|
+
"total_found": len(raw_products),
|
|
161
|
+
"returned_count": len(formatted_products),
|
|
162
|
+
"products": formatted_products,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def search_products(
|
|
167
|
+
query: str = "laptop",
|
|
168
|
+
max_results: int = 10,
|
|
169
|
+
tenant_id: str = "default",
|
|
170
|
+
mode: str = "local",
|
|
171
|
+
) -> dict[str, Any]:
|
|
172
|
+
"""
|
|
173
|
+
Trendyol üzerinde ürün arama ve fiyat karşılaştırması yapar.
|
|
174
|
+
query: Aranacak kelime (örn: 'laptop', 'iphone 15', 'kablosuz kulaklık')
|
|
175
|
+
max_results: Döndürülecek maksimum ürün sayısı (varsayılan: 10)
|
|
176
|
+
tenant_id: Oturum izolasyonu için kiracı ID'si (varsayılan: 'default')
|
|
177
|
+
mode: Çalışma modu ('local' yerel Chrome veya 'cloud' Docker/Remote Browser)
|
|
178
|
+
"""
|
|
179
|
+
client = TrendyolClient(tenant_id=tenant_id, mode=mode)
|
|
180
|
+
return client.search(query=query, max_results=max_results)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def print_product_summary(data: dict[str, Any], elapsed_ms: float = 0.0) -> None:
|
|
184
|
+
"""Ayrıştırılmış ürün sonuçlarını terminalde tablo gibi temiz bir formatta yazdırır."""
|
|
185
|
+
products = data.get("products", [])
|
|
186
|
+
query = data.get("query", "")
|
|
187
|
+
total = data.get("total_found", 0)
|
|
188
|
+
|
|
189
|
+
print("\n" + "=" * 80)
|
|
190
|
+
print(f"🛍️ Trendyol Arama Sonuçları: '{query}' | Bulunan: {total} | Gösterilen: {len(products)}")
|
|
191
|
+
if elapsed_ms > 0:
|
|
192
|
+
print(f"⏱️ Yanıt Süresi: {elapsed_ms:.2f} ms")
|
|
193
|
+
print("=" * 80)
|
|
194
|
+
|
|
195
|
+
if not products:
|
|
196
|
+
print("ℹ️ Eşleşen ürün bulunamadı.")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
for idx, p in enumerate(products, 1):
|
|
200
|
+
name = p.get("name", "")
|
|
201
|
+
brand = p.get("brand", "")
|
|
202
|
+
price = p.get("price")
|
|
203
|
+
orig_price = p.get("original_price")
|
|
204
|
+
curr = p.get("currency", "TL")
|
|
205
|
+
rating = p.get("rating")
|
|
206
|
+
reviews = p.get("review_count")
|
|
207
|
+
|
|
208
|
+
price_str = f"{price:,.2f} {curr}" if isinstance(price, (int, float)) else "Belirtilmemiş"
|
|
209
|
+
if isinstance(orig_price, (int, float)) and orig_price > (price or 0):
|
|
210
|
+
price_str += f" (Eski: {orig_price:,.2f} {curr})"
|
|
211
|
+
|
|
212
|
+
rating_str = f"⭐ {rating}" if rating else ""
|
|
213
|
+
if reviews:
|
|
214
|
+
rating_str += f" ({reviews} değerlendirme)"
|
|
215
|
+
|
|
216
|
+
print(f"\n{idx:2d}. [{brand}] {name[:65]}")
|
|
217
|
+
print(f" Fiyat: {price_str} {rating_str}")
|
|
218
|
+
print(f" Link : {p.get('url')}")
|
|
219
|
+
|
|
220
|
+
print("\n" + "=" * 80 + "\n")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
t0 = time.perf_counter()
|
|
225
|
+
query_arg = sys.argv[1] if len(sys.argv) > 1 else "laptop"
|
|
226
|
+
res = search_products(query=query_arg, max_results=5)
|
|
227
|
+
t_elapsed = (time.perf_counter() - t0) * 1000
|
|
228
|
+
print_product_summary(res, elapsed_ms=t_elapsed)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"service_name": "trendyol",
|
|
3
|
+
"target_url": "https://www.trendyol.com",
|
|
4
|
+
"goal": "Trendyol üzerinde ürün arama ve fiyat karşılaştırması yap",
|
|
5
|
+
"function_name": "search_products",
|
|
6
|
+
"explanation": "Trendyol arama motoru üzerinden verilen sorgu kelimesine göre ürünleri, markaları, güncel indirimli fiyatları ve ürün puanlarını listeler.",
|
|
7
|
+
"method": "GET",
|
|
8
|
+
"endpoint_url": "https://www.trendyol.com/sr",
|
|
9
|
+
"dynamic_params": [
|
|
10
|
+
{
|
|
11
|
+
"name": "query",
|
|
12
|
+
"original_key": "q",
|
|
13
|
+
"location": "query",
|
|
14
|
+
"param_type": "str",
|
|
15
|
+
"default_value": "laptop",
|
|
16
|
+
"description": "Aranacak ürün adı veya kategori"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "max_results",
|
|
20
|
+
"original_key": "max_results",
|
|
21
|
+
"location": "query",
|
|
22
|
+
"param_type": "int",
|
|
23
|
+
"default_value": 10,
|
|
24
|
+
"description": "Döndürülecek maksimum ürün adedi"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"created_at": "2026-11-03T23:00:00.000000+00:00",
|
|
28
|
+
"validation_status": "BAŞARILI",
|
|
29
|
+
"last_validated_at": "2026-11-06T19:33:43.505975+00:00"
|
|
30
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
|
|
9
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
10
|
+
|
|
11
|
+
from core.base_client import BaseOwnClient
|
|
12
|
+
from core.token_store import TokenStore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UcakBiletiClient(BaseOwnClient):
|
|
16
|
+
"""Obilet / Uçak Bileti servisi için OwnAPI User-Delegated Browser Middleware + Generic Form Automation istemcisi."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
base_url: str = "https://www.obilet.com",
|
|
21
|
+
tenant_id: str = "default",
|
|
22
|
+
token_store: TokenStore | None = None,
|
|
23
|
+
mode: str = "local",
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(
|
|
26
|
+
service_name="ucak_bileti",
|
|
27
|
+
base_url=base_url,
|
|
28
|
+
tenant_id=tenant_id,
|
|
29
|
+
token_store=token_store,
|
|
30
|
+
mode=mode,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def search_flight_offers(
|
|
34
|
+
self,
|
|
35
|
+
origin_id: str = "250_0",
|
|
36
|
+
destination_id: str = "1912_170",
|
|
37
|
+
departure_date: str = "20270707",
|
|
38
|
+
return_date: str = "20270714",
|
|
39
|
+
tenant_id: str | None = None,
|
|
40
|
+
mode: str | None = None,
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
İki şehir ve tarih arasında uçak bileti seferlerini ve fiyatlarını
|
|
44
|
+
Generic Form Automation + In-Browser Fetch katmanı üzerinden sorgular.
|
|
45
|
+
"""
|
|
46
|
+
url = f"{self.base_url}/json/ucuslar/async/{origin_id}-{destination_id}/{departure_date}-{return_date}/2a-1c-1s/economy"
|
|
47
|
+
headers = {
|
|
48
|
+
"accept": "application/json",
|
|
49
|
+
"content-type": "application/json",
|
|
50
|
+
"origin": "https://www.obilet.com",
|
|
51
|
+
"referer": f"https://www.obilet.com/ucuslar/{origin_id}-{destination_id}/{departure_date}-{return_date}/2a-1c-1s/economy/all",
|
|
52
|
+
}
|
|
53
|
+
return self.request(
|
|
54
|
+
method="GET",
|
|
55
|
+
url=url,
|
|
56
|
+
headers=headers,
|
|
57
|
+
target_url="https://www.obilet.com/ucak-bileti",
|
|
58
|
+
mode=mode or self.mode,
|
|
59
|
+
form_params={
|
|
60
|
+
"origin": origin_id,
|
|
61
|
+
"destination": destination_id,
|
|
62
|
+
"departure_date": departure_date,
|
|
63
|
+
"return_date": return_date,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def search_flight_offers(
|
|
69
|
+
origin_id: str = "250_0",
|
|
70
|
+
destination_id: str = "1912_170",
|
|
71
|
+
departure_date: str = "20270707",
|
|
72
|
+
return_date: str = "20270714",
|
|
73
|
+
tenant_id: str = "default",
|
|
74
|
+
mode: str = "local",
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
client = UcakBiletiClient(tenant_id=tenant_id, mode=mode)
|
|
77
|
+
return client.search_flight_offers(
|
|
78
|
+
origin_id=origin_id,
|
|
79
|
+
destination_id=destination_id,
|
|
80
|
+
departure_date=departure_date,
|
|
81
|
+
return_date=return_date,
|
|
82
|
+
tenant_id=tenant_id,
|
|
83
|
+
mode=mode,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
client = UcakBiletiClient()
|
|
89
|
+
start_time = time.perf_counter()
|
|
90
|
+
result = client.search_flight_offers()
|
|
91
|
+
end_time = time.perf_counter()
|
|
92
|
+
duration = (end_time - start_time) * 1000
|
|
93
|
+
print(f"Sorgu süresi: {duration:.2f} ms")
|
|
94
|
+
print("Uçuş teklifleri:", result.get("data", {}).get("result", {}) if isinstance(result, dict) else result)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"service_name": "ucak_bileti",
|
|
3
|
+
"target_url": "https://www.obilet.com/ucak-bileti",
|
|
4
|
+
"goal": "İki şehir ve tarih arasında uçak bileti seferlerini ve fiyatlarını sorgula",
|
|
5
|
+
"function_name": "search_flight_offers",
|
|
6
|
+
"explanation": "Kullanıcının hedefi iki şehir ve tarih arasında uçak bileti seferlerini ve fiyatlarını sorgulamak olduğundan, '/json/ucuslar/async/250_0-1912_170/20270707-20270714/2a-1c-1s/economy' uç noktası bu amaca en uygun olanıdır. Bu uç nokta, belirli bir rota ve tarih aralığında uçuş araması yapmaktadır.",
|
|
7
|
+
"method": "POST",
|
|
8
|
+
"endpoint_url": "https://www.obilet.com/json/ucuslar/async/250_0-1912_170/20270707-20270714/2a-1c-1s/economy",
|
|
9
|
+
"dynamic_params": [
|
|
10
|
+
{
|
|
11
|
+
"name": "origin_id",
|
|
12
|
+
"original_key": "origin-id",
|
|
13
|
+
"location": "path",
|
|
14
|
+
"param_type": "str",
|
|
15
|
+
"default_value": "250_0",
|
|
16
|
+
"description": "Kalkış şehrinin ve havaalanının ID'si."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "destination_id",
|
|
20
|
+
"original_key": "destination-id",
|
|
21
|
+
"location": "path",
|
|
22
|
+
"param_type": "str",
|
|
23
|
+
"default_value": "1912_170",
|
|
24
|
+
"description": "Varış şehrinin ve havaalanının ID'si."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "departure_date",
|
|
28
|
+
"original_key": "departure",
|
|
29
|
+
"location": "path",
|
|
30
|
+
"param_type": "str",
|
|
31
|
+
"default_value": "20270707",
|
|
32
|
+
"description": "Kalkış tarihi (YYYYMMDD formatında)."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "return_date",
|
|
36
|
+
"original_key": "return",
|
|
37
|
+
"location": "path",
|
|
38
|
+
"param_type": "str",
|
|
39
|
+
"default_value": "20270714",
|
|
40
|
+
"description": "Dönüş tarihi (YYYYMMDD formatında)."
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"created_at": "2026-10-31T22:19:17.567346+00:00",
|
|
44
|
+
"validation_status": "BAŞARISIZ",
|
|
45
|
+
"validation_passed_runs": 0,
|
|
46
|
+
"last_validated_at": "2026-11-06T19:34:38.312611+00:00"
|
|
47
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
|
|
9
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
10
|
+
|
|
11
|
+
from core.base_client import BaseOwnClient
|
|
12
|
+
from core.token_store import TokenStore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WikipediaClient(BaseOwnClient):
|
|
16
|
+
"""Wikipedia REST API için OwnAPI User-Delegated Browser Middleware istemcisi."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
tenant_id: str = "default",
|
|
21
|
+
token_store: TokenStore | None = None,
|
|
22
|
+
mode: str = "local",
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(
|
|
25
|
+
service_name="wikipedia",
|
|
26
|
+
base_url="https://en.wikipedia.org/",
|
|
27
|
+
tenant_id=tenant_id,
|
|
28
|
+
token_store=token_store,
|
|
29
|
+
mode=mode,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def search_wikipedia(self, query: str = "peter thiel", limit: int = 10) -> dict[str, Any]:
|
|
33
|
+
url = "https://en.wikipedia.org/w/rest.php/v1/search/title"
|
|
34
|
+
headers = {
|
|
35
|
+
"accept": "application/json",
|
|
36
|
+
"referer": "https://en.wikipedia.org/",
|
|
37
|
+
}
|
|
38
|
+
params = {
|
|
39
|
+
"q": query,
|
|
40
|
+
"limit": limit,
|
|
41
|
+
}
|
|
42
|
+
return self.request(method="GET", url=url, headers=headers, params=params)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def search_wikipedia(
|
|
46
|
+
query: str = "peter thiel",
|
|
47
|
+
limit: int = 10,
|
|
48
|
+
tenant_id: str = "default",
|
|
49
|
+
mode: str = "local",
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""
|
|
52
|
+
Wikipedia üzerinde kavram ve kişi araması yapar (OwnAPI User-Delegated Browser Middleware).
|
|
53
|
+
"""
|
|
54
|
+
client = WikipediaClient(tenant_id=tenant_id, mode=mode)
|
|
55
|
+
return client.search_wikipedia(query=query, limit=limit)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
start_time = time.perf_counter()
|
|
60
|
+
results = search_wikipedia()
|
|
61
|
+
elapsed_time = (time.perf_counter() - start_time) * 1000
|
|
62
|
+
pages = results.get("pages", []) if isinstance(results, dict) else []
|
|
63
|
+
print(f"[OwnAPI] Arama sonuçları: {pages[:2]}... (Toplam {len(pages)} sonuç)")
|
|
64
|
+
print(f"[OwnAPI] Sorgu süresi: {elapsed_time:.2f} ms")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"service_name": "wikipedia",
|
|
3
|
+
"target_url": "https://en.wikipedia.org",
|
|
4
|
+
"goal": "Wikipedia üzerinde kavram ve kişi araması yap",
|
|
5
|
+
"function_name": "search_wikipedia",
|
|
6
|
+
"explanation": "Kullanıcının amacı Wikipedia üzerinde kavram ve kişi araması yapmak olduğundan, '/w/rest.php/v1/search/title' uç noktası arama sorgularını gerçekleştirmek için en uygun uç noktadır. Bu uç nokta, arama sorgusu ve sonuç limiti gibi dinamik parametreleri desteklemektedir.",
|
|
7
|
+
"method": "GET",
|
|
8
|
+
"endpoint_url": "https://en.wikipedia.org/w/rest.php/v1/search/title",
|
|
9
|
+
"dynamic_params": [
|
|
10
|
+
{
|
|
11
|
+
"name": "query",
|
|
12
|
+
"original_key": "q",
|
|
13
|
+
"location": "query",
|
|
14
|
+
"param_type": "str",
|
|
15
|
+
"default_value": "peter thiel",
|
|
16
|
+
"description": "Aranacak kavram veya kişi ismi."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "limit",
|
|
20
|
+
"original_key": "limit",
|
|
21
|
+
"location": "query",
|
|
22
|
+
"param_type": "int",
|
|
23
|
+
"default_value": 10,
|
|
24
|
+
"description": "Dönen sonuçların sayısı."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"created_at": "2026-10-31T22:06:55.412344+00:00",
|
|
28
|
+
"validation_status": "BAŞARILI",
|
|
29
|
+
"validation_passed_runs": 3,
|
|
30
|
+
"last_validated_at": "2026-11-05T15:27:20.325892+00:00"
|
|
31
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pathlib
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
|
|
11
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
12
|
+
|
|
13
|
+
from core.base_client import BaseOwnClient
|
|
14
|
+
from core.token_store import TokenStore
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class YahooFinanceClient(BaseOwnClient):
|
|
18
|
+
"""Yahoo Finance real-time stock quote & chart client via OwnAPI User-Delegated Browser Middleware."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
tenant_id: str = "default",
|
|
23
|
+
token_store: TokenStore | None = None,
|
|
24
|
+
mode: str = "local",
|
|
25
|
+
) -> None:
|
|
26
|
+
super().__init__(
|
|
27
|
+
service_name="yahoo_finance",
|
|
28
|
+
base_url="https://finance.yahoo.com/",
|
|
29
|
+
tenant_id=tenant_id,
|
|
30
|
+
token_store=token_store,
|
|
31
|
+
mode=mode,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def get_stock_quote(self, symbol: str = "NVDA") -> dict[str, Any]:
|
|
35
|
+
clean_symbol = quote(str(symbol).strip().upper() or "NVDA")
|
|
36
|
+
url = f"https://query1.finance.yahoo.com/v8/finance/chart/{clean_symbol}"
|
|
37
|
+
headers = {
|
|
38
|
+
"accept": "application/json, text/plain, */*",
|
|
39
|
+
"referer": f"https://finance.yahoo.com/quote/{clean_symbol}/",
|
|
40
|
+
}
|
|
41
|
+
params = {
|
|
42
|
+
"interval": "1d",
|
|
43
|
+
"range": "5d",
|
|
44
|
+
}
|
|
45
|
+
raw = self.request(method="GET", url=url, headers=headers, params=params)
|
|
46
|
+
if not isinstance(raw, dict):
|
|
47
|
+
return {"success": False, "symbol": symbol, "raw": raw}
|
|
48
|
+
|
|
49
|
+
chart = raw.get("chart", {})
|
|
50
|
+
results = chart.get("result") or []
|
|
51
|
+
if results and isinstance(results[0], dict):
|
|
52
|
+
meta = results[0].get("meta", {})
|
|
53
|
+
price = meta.get("regularMarketPrice")
|
|
54
|
+
prev_close = meta.get("chartPreviousClose") or meta.get("previousClose")
|
|
55
|
+
change = round(price - prev_close, 4) if isinstance(price, (int, float)) and isinstance(prev_close, (int, float)) else None
|
|
56
|
+
change_pct = (
|
|
57
|
+
round(((price - prev_close) / prev_close) * 100.0, 2)
|
|
58
|
+
if change is not None and prev_close
|
|
59
|
+
else None
|
|
60
|
+
)
|
|
61
|
+
return {
|
|
62
|
+
"success": True,
|
|
63
|
+
"service": "yahoo_finance",
|
|
64
|
+
"symbol": meta.get("symbol", clean_symbol),
|
|
65
|
+
"short_name": meta.get("shortName") or meta.get("longName") or clean_symbol,
|
|
66
|
+
"currency": meta.get("currency", "USD"),
|
|
67
|
+
"exchange": meta.get("fullExchangeName") or meta.get("exchangeName"),
|
|
68
|
+
"regular_market_price": price,
|
|
69
|
+
"previous_close": prev_close,
|
|
70
|
+
"change": change,
|
|
71
|
+
"change_percent": change_pct,
|
|
72
|
+
"fifty_two_week_high": meta.get("fiftyTwoWeekHigh"),
|
|
73
|
+
"fifty_two_week_low": meta.get("fiftyTwoWeekLow"),
|
|
74
|
+
"regular_market_volume": meta.get("regularMarketVolume"),
|
|
75
|
+
"meta": meta,
|
|
76
|
+
}
|
|
77
|
+
return raw
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_stock_quote(
|
|
81
|
+
symbol: str = "NVDA",
|
|
82
|
+
tenant_id: str = "default",
|
|
83
|
+
mode: str = "local",
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
"""
|
|
86
|
+
Fetches real-time stock/index price, daily change, and 52-week range from Yahoo Finance
|
|
87
|
+
via OwnAPI User-Delegated Browser Middleware.
|
|
88
|
+
"""
|
|
89
|
+
client = YahooFinanceClient(tenant_id=tenant_id, mode=mode)
|
|
90
|
+
return client.get_stock_quote(symbol=symbol)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
t0 = time.perf_counter()
|
|
95
|
+
res = get_stock_quote()
|
|
96
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
97
|
+
print(f"[OwnAPI - Yahoo Finance] ({elapsed:.1f} ms)")
|
|
98
|
+
print(json.dumps(res, ensure_ascii=False, indent=2)[:800])
|