hurriyet-mcp 0.1.0__tar.gz
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.
- hurriyet_mcp-0.1.0/.github/workflows/publish.yml +20 -0
- hurriyet_mcp-0.1.0/.github/workflows/registry.yml +61 -0
- hurriyet_mcp-0.1.0/PKG-INFO +83 -0
- hurriyet_mcp-0.1.0/README-fa.md +39 -0
- hurriyet_mcp-0.1.0/README-tr.md +41 -0
- hurriyet_mcp-0.1.0/README.md +71 -0
- hurriyet_mcp-0.1.0/pyproject.toml +23 -0
- hurriyet_mcp-0.1.0/server.json +1 -0
- hurriyet_mcp-0.1.0/src/hurriyet_mcp/__init__.py +2 -0
- hurriyet_mcp-0.1.0/src/hurriyet_mcp/http.py +98 -0
- hurriyet_mcp-0.1.0/src/hurriyet_mcp/news.py +153 -0
- hurriyet_mcp-0.1.0/src/hurriyet_mcp/server.py +128 -0
- hurriyet_mcp-0.1.0/tests/__init__.py +0 -0
- hurriyet_mcp-0.1.0/tests/fixtures/article.html +7 -0
- hurriyet_mcp-0.1.0/tests/fixtures/gundem.html +35 -0
- hurriyet_mcp-0.1.0/tests/fixtures/search.html +47 -0
- hurriyet_mcp-0.1.0/tests/test_live.py +58 -0
- hurriyet_mcp-0.1.0/tests/test_news.py +71 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # required for PyPI trusted publishing (OIDC)
|
|
12
|
+
contents: read
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: pip install build
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish to MCP Registry
|
|
2
|
+
|
|
3
|
+
# Official MCP Registry (registry.modelcontextprotocol.io) via GitHub OIDC.
|
|
4
|
+
# No secret, no interactive login: the runner's OIDC token proves ownership
|
|
5
|
+
# of the io.github.mohammad-abbasi-sh/* namespace.
|
|
6
|
+
# Refs: https://github.com/modelcontextprotocol/registry
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write # required for OIDC -> MCP Registry
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Sync server.json version to tag
|
|
23
|
+
# Only sync when running on a version tag (v*). On workflow_dispatch
|
|
24
|
+
# (ref = refs/heads/main) keep the version committed in server.json.
|
|
25
|
+
run: |
|
|
26
|
+
VER=""
|
|
27
|
+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
28
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
29
|
+
VER="${TAG#v}"
|
|
30
|
+
fi
|
|
31
|
+
if [ -n "$VER" ]; then
|
|
32
|
+
python3 - "$VER" << 'EOF'
|
|
33
|
+
import json, sys
|
|
34
|
+
ver = sys.argv[1]
|
|
35
|
+
d = json.load(open('server.json'))
|
|
36
|
+
d['version'] = ver
|
|
37
|
+
for pkg in d.get('packages', []):
|
|
38
|
+
pkg['version'] = ver
|
|
39
|
+
json.dump(d, open('server.json', 'w'), indent=2, ensure_ascii=False)
|
|
40
|
+
print('server.json version ->', ver)
|
|
41
|
+
EOF
|
|
42
|
+
else
|
|
43
|
+
echo "No version tag ($GITHUB_REF); keeping server.json version as-is"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
- name: Install mcp-publisher
|
|
47
|
+
run: |
|
|
48
|
+
curl -sL "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.7/mcp-publisher_linux_amd64.tar.gz" -o /tmp/mcp-publisher.tar.gz
|
|
49
|
+
tar -xzf /tmp/mcp-publisher.tar.gz -C /tmp
|
|
50
|
+
sudo mv /tmp/mcp-publisher /usr/local/bin/mcp-publisher
|
|
51
|
+
mcp-publisher --version
|
|
52
|
+
|
|
53
|
+
- name: Validate server.json
|
|
54
|
+
run: mcp-publisher validate server.json
|
|
55
|
+
|
|
56
|
+
- name: Publish to MCP Registry (OIDC)
|
|
57
|
+
run: |
|
|
58
|
+
mcp-publisher login github-oidc
|
|
59
|
+
mcp-publisher publish server.json
|
|
60
|
+
# A registry outage must never break the release; re-run from Actions if needed.
|
|
61
|
+
continue-on-error: true
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: hurriyet-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial read-only MCP server for Hürriyet news (headlines, full articles, search, latest)
|
|
5
|
+
Author: Mohammad Abbasi
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: curl-cffi>=0.7
|
|
9
|
+
Requires-Dist: mcp<2
|
|
10
|
+
Requires-Dist: requests>=2.32
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# hurriyet-mcp 🇹🇷
|
|
14
|
+
|
|
15
|
+
mcp-name: io.github.mohammad-abbasi-sh/hurriyet-mcp
|
|
16
|
+
|
|
17
|
+
Unofficial **read-only** MCP server for **Hürriyet** (hurriyet.com.tr) — Türkiye's news: section headlines, full article text, keyword search and the homepage's latest news.
|
|
18
|
+
|
|
19
|
+
> Part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with Hürriyet.
|
|
20
|
+
|
|
21
|
+
## For AI agents
|
|
22
|
+
|
|
23
|
+
Add to your MCP client config (stdio):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"hurriyet": {
|
|
29
|
+
"command": "uvx",
|
|
30
|
+
"args": ["hurriyet-mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run locally: `python -m hurriyet_mcp.server` (needs `mcp<2`, `curl-cffi`, `requests`).
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `hurriyet_get_headlines` | Latest headlines from a section — `section` (e.g. `gundem`, `ekonomi`, `dunya`, `sporarena`), `limit` (1..30) |
|
|
43
|
+
| `hurriyet_get_article` | Full article text — `url` (hurriyet.com.tr article URL or path) |
|
|
44
|
+
| `hurriyet_search_news` | Search news by keyword — `query`, `limit` (1..30) |
|
|
45
|
+
| `hurriyet_get_latest` | Latest news from the homepage, `limit` (1..30) |
|
|
46
|
+
| `hurriyet_list_categories` | News sections (slug, Turkish name, url) |
|
|
47
|
+
|
|
48
|
+
Example output (`hurriyet_get_headlines` / "gundem"):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{"section": "gundem", "count": 10,
|
|
52
|
+
"articles": [{"title": "Afyonkarahisar'da 8 kişiyi katletmişti! ...",
|
|
53
|
+
"url": "https://www.hurriyet.com.tr/gundem/...-43369434",
|
|
54
|
+
"id": "43369434", "section": "gundem", "kind": "article"}]}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Example output (`hurriyet_get_article`):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{"headline": "6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı",
|
|
61
|
+
"section": "Gündem", "date_published": "2026-11-07T09:38:51+03:00",
|
|
62
|
+
"word_count": 143, "body": "İzmir Cumhuriyet Başsavcılığı koordinesinde ..."}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
- Articles come from schema.org NewsArticle data (headline, dates, section, word count, body).
|
|
67
|
+
- Search is `/arama/<keyword>`; known keywords redirect to a topic hub (e.g. `/haberleri/futbol`) — the tool reports the `landed_url`.
|
|
68
|
+
- The `/arama/` path is slow under TLS impersonation — the server falls back to plain requests there.
|
|
69
|
+
- Data: hurriyet.com.tr public pages — no key, no login, read-only.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python3 -m venv .venv && .venv/bin/pip install "mcp<2" curl-cffi requests pytest
|
|
75
|
+
.venv/bin/python -m pytest tests/ -q
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `tests/test_news.py` — offline, parses real captured fixtures
|
|
79
|
+
- `tests/test_live.py` — 5 cautious live requests against hurriyet.com.tr
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# hurriyet-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
سرور MCP غیررسمی و **فقطخواندنی** برای **Hürriyet** (hurriyet.com.tr) — سایت خبری ترکیه: تیترهای بخشها، متن کامل خبر، جستوجوی خبری و آخرین خبرهای صفحهی اصلی.
|
|
4
|
+
|
|
5
|
+
> بخشی از «کارخانهی MCP ترکیه» (پایپلاین به سبک سپهر). هیچ وابستگی به Hürriyet ندارد.
|
|
6
|
+
|
|
7
|
+
## برای ایجنتهای هوش مصنوعی
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"hurriyet": {
|
|
13
|
+
"command": "uvx",
|
|
14
|
+
"args": ["hurriyet-mcp"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## ابزارها
|
|
21
|
+
|
|
22
|
+
| ابزار | توضیح |
|
|
23
|
+
|------|-------|
|
|
24
|
+
| `hurriyet_get_headlines` | آخرین تیترهای یک بخش — `section` (مثلاً `gundem`، `ekonomi`، `dunya`، `sporarena`)، `limit` (۱ تا ۳۰) |
|
|
25
|
+
| `hurriyet_get_article` | متن کامل خبر — `url` (URL خبر hurriyet.com.tr یا مسیر آن) |
|
|
26
|
+
| `hurriyet_search_news` | جستوجوی اخبار با کلیدواژه — `query`، `limit` (۱ تا ۳۰) |
|
|
27
|
+
| `hurriyet_get_latest` | آخرین خبرهای صفحهی اصلی، `limit` (۱ تا ۳۰) |
|
|
28
|
+
| `hurriyet_list_categories` | بخشهای خبری (slug، نام ترکی، url) |
|
|
29
|
+
|
|
30
|
+
مثال (`hurriyet_get_headlines` / «gundem»): ۳۰ تیتر؛ اولین خبر «Afyonkarahisar'da 8 kişiyi katletmişti! ...». مثال (`hurriyet_get_article`): «6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı» — ۷ نوامبر ۲۰۲۶، ۱۴۳ کلمه.
|
|
31
|
+
|
|
32
|
+
نکتهها:
|
|
33
|
+
- خبرها از دادهی schema.org NewsArticle میآیند (تیتر، تاریخها، بخش، تعداد کلمات، متن).
|
|
34
|
+
- جستوجو `/arama/<کلیدواژه>` است؛ کلیدواژههای شناختهشده به صفحهی موضوعی هدایت میشوند (مثلاً `/haberleri/futbol`) — ابزار `landed_url` را گزارش میکند.
|
|
35
|
+
- داده: صفحات عمومی hurriyet.com.tr — بدون لاگین، بدون کلید، فقطخواندنی.
|
|
36
|
+
|
|
37
|
+
## قوانین
|
|
38
|
+
|
|
39
|
+
غیررسمی و فقطخواندنی — بدون لاگین، بدون کلید، بدون نوشتن. تستها: آفلاین با فیکسچر + تست زنده (۱۲/۱۲ سبز).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# hurriyet-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
**Hürriyet** (hurriyet.com.tr) için gayriresmî, **salt-okunur** MCP sunucusu — Türkiye'nin haber sitesi: bölüm manşetleri, tam haber metni, anahtar kelime araması ve ana sayfanın son haberleri.
|
|
4
|
+
|
|
5
|
+
> "Türkiye MCP fabrikası" parçası (Sepehr tarzı pipeline). Hürriyet ile bağlantısı yoktur.
|
|
6
|
+
|
|
7
|
+
## Yapay zekâ ajanları için
|
|
8
|
+
|
|
9
|
+
MCP istemcinize ekleyin (stdio):
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"hurriyet": {
|
|
15
|
+
"command": "uvx",
|
|
16
|
+
"args": ["hurriyet-mcp"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Araçlar
|
|
23
|
+
|
|
24
|
+
| Araç | Açıklama |
|
|
25
|
+
|------|----------|
|
|
26
|
+
| `hurriyet_get_headlines` | Bir bölümün son manşetleri — `section` (örn. `gundem`, `ekonomi`, `dunya`, `sporarena`), `limit` (1..30) |
|
|
27
|
+
| `hurriyet_get_article` | Tam haber metni — `url` (hurriyet.com.tr haber URL'si veya yolu) |
|
|
28
|
+
| `hurriyet_search_news` | Anahtar kelimeyle haber ara — `query`, `limit` (1..30) |
|
|
29
|
+
| `hurriyet_get_latest` | Ana sayfanın son haberleri, `limit` (1..30) |
|
|
30
|
+
| `hurriyet_list_categories` | Haber bölümleri (slug, Türkçe ad, url) |
|
|
31
|
+
|
|
32
|
+
Örnek (`hurriyet_get_headlines` / "gundem"): 30 manşet; ilk haber "Afyonkarahisar'da 8 kişiyi katletmişti! ..." Örnek (`hurriyet_get_article`): "6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı" — 7 Kasım 2026 09:38, 143 kelime.
|
|
33
|
+
|
|
34
|
+
Notlar:
|
|
35
|
+
- Haberler schema.org NewsArticle verisinden gelir (başlık, tarihler, bölüm, kelime sayısı, metin).
|
|
36
|
+
- Arama `/arama/<kelime>`; bilinen kelimeler konu sayfasına yönlenir (örn. `/haberleri/futbol`) — araç `landed_url`'yi bildirir.
|
|
37
|
+
- Veri: hurriyet.com.tr herkese açık sayfalar — giriş yok, API anahtarı yok, salt-okunur.
|
|
38
|
+
|
|
39
|
+
## Kurallar
|
|
40
|
+
|
|
41
|
+
Gayriresmî ve salt-okunur — giriş yok, API anahtarı yok, yazma yok. Testler: çevrimdışı fixture + canlı testler (12/12 yeşil).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# hurriyet-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
mcp-name: io.github.mohammad-abbasi-sh/hurriyet-mcp
|
|
4
|
+
|
|
5
|
+
Unofficial **read-only** MCP server for **Hürriyet** (hurriyet.com.tr) — Türkiye's news: section headlines, full article text, keyword search and the homepage's latest news.
|
|
6
|
+
|
|
7
|
+
> Part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with Hürriyet.
|
|
8
|
+
|
|
9
|
+
## For AI agents
|
|
10
|
+
|
|
11
|
+
Add to your MCP client config (stdio):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"hurriyet": {
|
|
17
|
+
"command": "uvx",
|
|
18
|
+
"args": ["hurriyet-mcp"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or run locally: `python -m hurriyet_mcp.server` (needs `mcp<2`, `curl-cffi`, `requests`).
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `hurriyet_get_headlines` | Latest headlines from a section — `section` (e.g. `gundem`, `ekonomi`, `dunya`, `sporarena`), `limit` (1..30) |
|
|
31
|
+
| `hurriyet_get_article` | Full article text — `url` (hurriyet.com.tr article URL or path) |
|
|
32
|
+
| `hurriyet_search_news` | Search news by keyword — `query`, `limit` (1..30) |
|
|
33
|
+
| `hurriyet_get_latest` | Latest news from the homepage, `limit` (1..30) |
|
|
34
|
+
| `hurriyet_list_categories` | News sections (slug, Turkish name, url) |
|
|
35
|
+
|
|
36
|
+
Example output (`hurriyet_get_headlines` / "gundem"):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{"section": "gundem", "count": 10,
|
|
40
|
+
"articles": [{"title": "Afyonkarahisar'da 8 kişiyi katletmişti! ...",
|
|
41
|
+
"url": "https://www.hurriyet.com.tr/gundem/...-43369434",
|
|
42
|
+
"id": "43369434", "section": "gundem", "kind": "article"}]}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Example output (`hurriyet_get_article`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{"headline": "6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı",
|
|
49
|
+
"section": "Gündem", "date_published": "2026-11-07T09:38:51+03:00",
|
|
50
|
+
"word_count": 143, "body": "İzmir Cumhuriyet Başsavcılığı koordinesinde ..."}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
- Articles come from schema.org NewsArticle data (headline, dates, section, word count, body).
|
|
55
|
+
- Search is `/arama/<keyword>`; known keywords redirect to a topic hub (e.g. `/haberleri/futbol`) — the tool reports the `landed_url`.
|
|
56
|
+
- The `/arama/` path is slow under TLS impersonation — the server falls back to plain requests there.
|
|
57
|
+
- Data: hurriyet.com.tr public pages — no key, no login, read-only.
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 -m venv .venv && .venv/bin/pip install "mcp<2" curl-cffi requests pytest
|
|
63
|
+
.venv/bin/python -m pytest tests/ -q
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `tests/test_news.py` — offline, parses real captured fixtures
|
|
67
|
+
- `tests/test_live.py` — 5 cautious live requests against hurriyet.com.tr
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hurriyet-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial read-only MCP server for Hürriyet news (headlines, full articles, search, latest)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp<2",
|
|
14
|
+
"curl-cffi>=0.7",
|
|
15
|
+
"requests>=2.32",
|
|
16
|
+
]
|
|
17
|
+
authors = [{ name = "Mohammad Abbasi" }]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
hurriyet-mcp = "hurriyet_mcp.server:main"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/hurriyet_mcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json","description":"Unofficial read-only MCP server for Hürriyet news — headlines, full articles, search.","name":"io.github.mohammad-abbasi-sh/hurriyet-mcp","packages":[{"identifier":"hurriyet-mcp","registryBaseUrl":"https://pypi.org","registryType":"pypi","transport":{"type":"stdio"},"version":"0.1.0"}],"repository":{"source":"github","url":"https://github.com/mohammad-abbasi-sh/hurriyet-mcp"},"title":"Hürriyet News","version":"0.1.0","websiteUrl":"https://github.com/mohammad-abbasi-sh/hurriyet-mcp"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""HTTP layer for hurriyet_mcp.
|
|
2
|
+
|
|
3
|
+
hurriyet.com.tr serves its pages server-rendered with no login and
|
|
4
|
+
no API key. Search is a plain path (``/arama/<keyword>``) — known
|
|
5
|
+
keywords redirect to a topic hub (e.g. /haberleri/futbol), which the
|
|
6
|
+
parser handles the same way. Transport uses curl-cffi chrome120
|
|
7
|
+
impersonation (the proven Turkey-factory pattern) with a
|
|
8
|
+
plain-requests fallback: the /arama/ path timed out twice under
|
|
9
|
+
curl-cffi while plain requests answered in ~3s, so the fallback is
|
|
10
|
+
load-bearing here, not decorative. Requests are kept gentle (one at
|
|
11
|
+
a time, ~1s apart) and read-only.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
from urllib.parse import quote
|
|
17
|
+
|
|
18
|
+
from curl_cffi import requests as _creq
|
|
19
|
+
import requests as _plain
|
|
20
|
+
|
|
21
|
+
BASE = "https://www.hurriyet.com.tr"
|
|
22
|
+
TIMEOUT = 60.0
|
|
23
|
+
MIN_INTERVAL = 1.0 # seconds between requests — be polite
|
|
24
|
+
|
|
25
|
+
_HEADERS = {
|
|
26
|
+
"Accept-Language": "tr-TR,tr;q=0.9,en;q=0.8",
|
|
27
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_UA = (
|
|
31
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
32
|
+
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_last_fetch = 0.0
|
|
36
|
+
last_url: str = ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HurriyetError(RuntimeError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _throttle() -> None:
|
|
44
|
+
global _last_fetch
|
|
45
|
+
wait = MIN_INTERVAL - (time.monotonic() - _last_fetch)
|
|
46
|
+
if wait > 0:
|
|
47
|
+
time.sleep(wait)
|
|
48
|
+
_last_fetch = time.monotonic()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get(url: str) -> str:
|
|
52
|
+
global last_url
|
|
53
|
+
_throttle()
|
|
54
|
+
try:
|
|
55
|
+
resp = _creq.get(url, impersonate="chrome120", headers=_HEADERS, timeout=TIMEOUT)
|
|
56
|
+
status, text, final = resp.status_code, resp.text, str(resp.url)
|
|
57
|
+
except Exception:
|
|
58
|
+
# Fallback transport: plain requests with a browser UA.
|
|
59
|
+
try:
|
|
60
|
+
resp = _plain.get(
|
|
61
|
+
url, headers={**_HEADERS, "User-Agent": _UA}, timeout=TIMEOUT
|
|
62
|
+
)
|
|
63
|
+
status, text, final = resp.status_code, resp.text, resp.url
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise HurriyetError(f"network error fetching {url}: {e}") from e
|
|
66
|
+
last_url = final
|
|
67
|
+
if status == 403:
|
|
68
|
+
raise HurriyetError(
|
|
69
|
+
"hurriyet.com.tr refused the request (HTTP 403, bot wall) — try again later"
|
|
70
|
+
)
|
|
71
|
+
if status == 429:
|
|
72
|
+
raise HurriyetError(
|
|
73
|
+
"hurriyet.com.tr rate-limited the request (HTTP 429) — try again later"
|
|
74
|
+
)
|
|
75
|
+
if status == 404:
|
|
76
|
+
raise HurriyetError(f"hurriyet.com.tr has no page at {url} (HTTP 404)")
|
|
77
|
+
if status != 200:
|
|
78
|
+
raise HurriyetError(f"hurriyet.com.tr returned HTTP {status} for {url}")
|
|
79
|
+
return text
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def fetch_home() -> str:
|
|
83
|
+
return _get(f"{BASE}/")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def fetch_section(section: str) -> str:
|
|
87
|
+
return _get(f"{BASE}/{section.strip('/')}/")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def fetch_search(query: str) -> str:
|
|
91
|
+
return _get(f"{BASE}/arama/{quote(query)}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def fetch_article(path: str) -> str:
|
|
95
|
+
path = path.strip()
|
|
96
|
+
if path.startswith("http"):
|
|
97
|
+
return _get(path)
|
|
98
|
+
return _get(f"{BASE}/{path.lstrip('/')}")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Domain logic: parse Hürriyet pages into compact dicts.
|
|
2
|
+
|
|
3
|
+
Data sources (all public, no login, no key):
|
|
4
|
+
- headlines: /<section>/ (gundem, ekonomi, dunya, sporarena, ...) —
|
|
5
|
+
server-rendered article links, IDs in the URL slug
|
|
6
|
+
- article: /<section>/<slug>-<id> — schema.org NewsArticle JSON-LD
|
|
7
|
+
(headline, dates, section, word count) + articleBody
|
|
8
|
+
- search: /arama/<keyword> — keyword page; known keywords redirect
|
|
9
|
+
to a topic hub (/haberleri/<keyword>), parsed the same way
|
|
10
|
+
- latest: / homepage — the day's article links
|
|
11
|
+
Stdlib parsing only (re / json / html).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import html as _html
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
BASE = "https://www.hurriyet.com.tr"
|
|
20
|
+
|
|
21
|
+
# Curated news sections (slug -> Turkish display name). Verified present
|
|
22
|
+
# in the homepage nav on 2026-11-07.
|
|
23
|
+
SECTIONS = {
|
|
24
|
+
"gundem": "Gündem",
|
|
25
|
+
"ekonomi": "Ekonomi",
|
|
26
|
+
"dunya": "Dünya",
|
|
27
|
+
"sporarena": "Spor Arena",
|
|
28
|
+
"kelebek-magazin": "Kelebek Magazin",
|
|
29
|
+
"teknoloji": "Teknoloji",
|
|
30
|
+
"egitim": "Eğitim",
|
|
31
|
+
"seyahat": "Seyahat",
|
|
32
|
+
"yasam": "Yaşam",
|
|
33
|
+
"video": "Video",
|
|
34
|
+
"astroloji": "Astroloji",
|
|
35
|
+
"son-dakika-haberleri": "Son Dakika",
|
|
36
|
+
"avrupa": "Avrupa",
|
|
37
|
+
"hayat": "Hayat",
|
|
38
|
+
"kitap-sanat": "Kitap & Sanat",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_ARTICLE_RE = re.compile(r"^/[^\"?]+-\d{6,}/?$")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _clean(text: str | None) -> str:
|
|
45
|
+
if not text:
|
|
46
|
+
return ""
|
|
47
|
+
return re.sub(r"\s+", " ", _html.unescape(str(text))).strip()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _ld_json(html: str, want_type: str) -> dict | None:
|
|
51
|
+
for m in re.finditer(
|
|
52
|
+
r'<script type="application/ld\+json">(.*?)</script>', html, re.S
|
|
53
|
+
):
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(m.group(1))
|
|
56
|
+
except (json.JSONDecodeError, ValueError):
|
|
57
|
+
continue
|
|
58
|
+
items = data if isinstance(data, list) else [data]
|
|
59
|
+
for item in items:
|
|
60
|
+
if not isinstance(item, dict):
|
|
61
|
+
continue
|
|
62
|
+
t = item.get("@type")
|
|
63
|
+
types = t if isinstance(t, list) else [t]
|
|
64
|
+
if want_type in types:
|
|
65
|
+
return item
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_article_links(html: str) -> list[dict]:
|
|
70
|
+
"""Titled article links: href /<section>/<slug>-<id> with link text.
|
|
71
|
+
|
|
72
|
+
Skips nav/footer chrome (short/empty titles) and dedupes by URL.
|
|
73
|
+
Gallery links (/galeri-...) are kept and marked kind='gallery'.
|
|
74
|
+
"""
|
|
75
|
+
out, seen = [], set()
|
|
76
|
+
for href, inner in re.findall(
|
|
77
|
+
r'href="(/[^"]+-\d{6,}/?)"[^>]*>(.*?)</a>', html, re.S
|
|
78
|
+
):
|
|
79
|
+
if not _ARTICLE_RE.match(href):
|
|
80
|
+
continue
|
|
81
|
+
title = _clean(re.sub(r"<[^>]+>", "", inner))
|
|
82
|
+
if len(title) < 15 or href in seen:
|
|
83
|
+
continue
|
|
84
|
+
seen.add(href)
|
|
85
|
+
m = re.search(r"-(\d{6,})/?$", href)
|
|
86
|
+
section = href.strip("/").split("/", 1)[0]
|
|
87
|
+
out.append(
|
|
88
|
+
{
|
|
89
|
+
"title": title,
|
|
90
|
+
"url": f"{BASE}{href}",
|
|
91
|
+
"id": m.group(1) if m else "",
|
|
92
|
+
"section": section,
|
|
93
|
+
"kind": "gallery" if section.startswith("galeri") else "article",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
return out
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def parse_article_page(html: str) -> dict:
|
|
100
|
+
"""Parse an article page via its NewsArticle JSON-LD + articleBody."""
|
|
101
|
+
ld = _ld_json(html, "NewsArticle")
|
|
102
|
+
if ld is None:
|
|
103
|
+
raise ValueError("no NewsArticle structured data found on this page")
|
|
104
|
+
body = _clean(ld.get("articleBody"))
|
|
105
|
+
if not body:
|
|
106
|
+
# Fallback: long <p> paragraphs, skipping script/nav junk.
|
|
107
|
+
paras = []
|
|
108
|
+
for p in re.findall(r"<p[^>]*>(.*?)</p>", html, re.S):
|
|
109
|
+
t = _clean(re.sub(r"<[^>]+>", "", p))
|
|
110
|
+
if len(t) > 120 and "addEventListener" not in t:
|
|
111
|
+
paras.append(t)
|
|
112
|
+
body = "\n\n".join(paras)
|
|
113
|
+
image = ld.get("image")
|
|
114
|
+
if isinstance(image, dict):
|
|
115
|
+
image = image.get("url") or ""
|
|
116
|
+
elif isinstance(image, list):
|
|
117
|
+
image = image[0] if image else ""
|
|
118
|
+
author = ld.get("author")
|
|
119
|
+
if isinstance(author, dict):
|
|
120
|
+
author = author.get("name") or ""
|
|
121
|
+
elif isinstance(author, list) and author and isinstance(author[0], dict):
|
|
122
|
+
author = author[0].get("name") or ""
|
|
123
|
+
wc = ld.get("wordCount")
|
|
124
|
+
try:
|
|
125
|
+
wc = int(wc) if wc is not None else None
|
|
126
|
+
except (TypeError, ValueError):
|
|
127
|
+
pass
|
|
128
|
+
return {
|
|
129
|
+
"headline": _clean(ld.get("headline")),
|
|
130
|
+
"section": _clean(ld.get("articleSection")),
|
|
131
|
+
"date_published": ld.get("datePublished") or "",
|
|
132
|
+
"date_modified": ld.get("dateModified") or "",
|
|
133
|
+
"word_count": wc,
|
|
134
|
+
"keywords": _clean(ld.get("keywords")),
|
|
135
|
+
"author": _clean(author),
|
|
136
|
+
"image": image or "",
|
|
137
|
+
"body": body,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def list_categories(html: str | None = None) -> list[dict]:
|
|
142
|
+
"""News sections. If homepage HTML is given, only sections actually
|
|
143
|
+
linked from it are returned; otherwise the full curated list."""
|
|
144
|
+
if html is None:
|
|
145
|
+
wanted = set(SECTIONS)
|
|
146
|
+
else:
|
|
147
|
+
found = set(re.findall(r'href="(?:https://www\.hurriyet\.com\.tr)?/([^"/]+)/"', html))
|
|
148
|
+
wanted = set(SECTIONS) & found
|
|
149
|
+
return [
|
|
150
|
+
{"slug": slug, "name": SECTIONS[slug], "url": f"{BASE}/{slug}/"}
|
|
151
|
+
for slug in SECTIONS
|
|
152
|
+
if slug in wanted
|
|
153
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""MCP server: unofficial read-only access to Hürriyet news."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from . import http as _http
|
|
9
|
+
from . import news as _N
|
|
10
|
+
|
|
11
|
+
mcp = FastMCP("hurriyet-mcp")
|
|
12
|
+
|
|
13
|
+
INSTRUCTIONS = """\
|
|
14
|
+
Unofficial read-only MCP server for Hürriyet (hurriyet.com.tr), \
|
|
15
|
+
Türkiye's news site — section headlines, full articles, keyword \
|
|
16
|
+
search and the homepage's latest news. Articles come from the \
|
|
17
|
+
site's server-rendered pages with schema.org NewsArticle data \
|
|
18
|
+
(headline, dates, section, word count, body). Search is \
|
|
19
|
+
/arama/<keyword>; known keywords redirect to a topic hub \
|
|
20
|
+
(e.g. /haberleri/futbol) and the tool reports the final URL it \
|
|
21
|
+
landed on. No login, no API key. Read-only; transport uses Chrome \
|
|
22
|
+
TLS impersonation with a plain-requests fallback (the /arama/ path \
|
|
23
|
+
is slow under impersonation); requests are spaced ~1s apart. Built \
|
|
24
|
+
by Mohammad Abbasi (mohammad-abbasi-sh) — Turkey MCP factory.\
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _err(e: Exception) -> str:
|
|
29
|
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _validate_limit(limit: int, lo: int = 1, hi: int = 30) -> int:
|
|
33
|
+
if not lo <= limit <= hi:
|
|
34
|
+
raise ValueError(f"limit must be between {lo} and {hi}")
|
|
35
|
+
return limit
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def hurriyet_get_headlines(section: str = "gundem", limit: int = 10) -> str:
|
|
40
|
+
"""Latest headlines from a Hürriyet section.
|
|
41
|
+
section: e.g. "gundem", "ekonomi", "dunya", "sporarena",
|
|
42
|
+
"kelebek-magazin", "teknoloji" (see hurriyet_list_categories).
|
|
43
|
+
limit 1..30."""
|
|
44
|
+
try:
|
|
45
|
+
s = section.strip().strip("/").lower()
|
|
46
|
+
if s not in _N.SECTIONS:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"unknown section {section!r}; known: {', '.join(_N.SECTIONS)}"
|
|
49
|
+
)
|
|
50
|
+
_validate_limit(limit)
|
|
51
|
+
rows = _N.parse_article_links(_http.fetch_section(s))
|
|
52
|
+
except (ValueError, _http.HurriyetError) as e:
|
|
53
|
+
return _err(e)
|
|
54
|
+
return json.dumps(
|
|
55
|
+
{"section": s, "count": len(rows[:limit]), "articles": rows[:limit]},
|
|
56
|
+
ensure_ascii=False,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@mcp.tool()
|
|
61
|
+
def hurriyet_get_article(url: str) -> str:
|
|
62
|
+
"""Full article text. url: a hurriyet.com.tr article URL
|
|
63
|
+
(or its path, e.g. /gundem/<slug>-<id>)."""
|
|
64
|
+
try:
|
|
65
|
+
if not url or not url.strip():
|
|
66
|
+
raise ValueError("url must not be empty")
|
|
67
|
+
art = _N.parse_article_page(_http.fetch_article(url.strip()))
|
|
68
|
+
except (ValueError, _http.HurriyetError) as e:
|
|
69
|
+
return _err(e)
|
|
70
|
+
return json.dumps({"url": url.strip(), **art}, ensure_ascii=False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@mcp.tool()
|
|
74
|
+
def hurriyet_search_news(query: str, limit: int = 10) -> str:
|
|
75
|
+
"""Search Hürriyet news by keyword.
|
|
76
|
+
query: e.g. "deprem", "futbol", "ekonomi". limit 1..30.
|
|
77
|
+
Known keywords redirect to a topic hub — the returned
|
|
78
|
+
`landed_url` shows where the search actually landed."""
|
|
79
|
+
try:
|
|
80
|
+
if not query or not query.strip():
|
|
81
|
+
raise ValueError("query must not be empty")
|
|
82
|
+
_validate_limit(limit)
|
|
83
|
+
rows = _N.parse_article_links(_http.fetch_search(query.strip()))
|
|
84
|
+
landed = _http.last_url
|
|
85
|
+
except (ValueError, _http.HurriyetError) as e:
|
|
86
|
+
return _err(e)
|
|
87
|
+
return json.dumps(
|
|
88
|
+
{
|
|
89
|
+
"query": query.strip(),
|
|
90
|
+
"landed_url": landed,
|
|
91
|
+
"count": len(rows[:limit]),
|
|
92
|
+
"articles": rows[:limit],
|
|
93
|
+
},
|
|
94
|
+
ensure_ascii=False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@mcp.tool()
|
|
99
|
+
def hurriyet_get_latest(limit: int = 10) -> str:
|
|
100
|
+
"""Latest news from the Hürriyet homepage. limit 1..30."""
|
|
101
|
+
try:
|
|
102
|
+
_validate_limit(limit)
|
|
103
|
+
rows = _N.parse_article_links(_http.fetch_home())
|
|
104
|
+
except (ValueError, _http.HurriyetError) as e:
|
|
105
|
+
return _err(e)
|
|
106
|
+
return json.dumps(
|
|
107
|
+
{"count": len(rows[:limit]), "articles": rows[:limit]}, ensure_ascii=False
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@mcp.tool()
|
|
112
|
+
def hurriyet_list_categories() -> str:
|
|
113
|
+
"""Hürriyet news sections (slug, Turkish name, url)."""
|
|
114
|
+
try:
|
|
115
|
+
cats = _N.list_categories(_http.fetch_home())
|
|
116
|
+
except (ValueError, _http.HurriyetError) as e:
|
|
117
|
+
return _err(e)
|
|
118
|
+
return json.dumps({"count": len(cats), "categories": cats}, ensure_ascii=False)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main() -> None:
|
|
122
|
+
mcp.run()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
import sys
|
|
127
|
+
|
|
128
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<html><head><title>6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı - Son Dakika Haber</title></head><body>
|
|
2
|
+
<script type="application/ld+json">{"@context": "https://schema.org","@type": "NewsArticle","inLanguage": "tr-TR","articleSection": "Gündem","mainEntityOfPage": {"@type": "WebPage","@id": "https://www.hurriyet.com.tr/gundem/6-ilde-telegram-uzerinden-asilsiz-ihbar-ve-santaj-yapan-7-supheli-tutuklandi-43369105"},"headline": "6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı","wordCount":"143","keywords":["asılsız ihbar","şantaj","tehdit","Telegram"],"image": {"@type": "ImageObject","url": "https://image.hurimg.com/i/hurriyet/90/1200x675/6aeec764aa7d7bef19766538.jpg","width": "1200","height": "675"},"datePublished":"2026-11-07T09:38:51+03:00","dateModified":"2026-11-07T09:38:51+03:00","copyrightYear": "2026","description": "İzmir merkezli İstanbul, Antalya, Afyonkarahisar, Diyarbakır, Şanlıurfa ve Muğla'da düzenlenen operasyonda, Telegram üzerinden asılsız ihbar ve şantaj yaptıkları belirlenen 9 şüpheli yakalandı. Adliyeye sevk edilen şüphelilerden 7'si tutuklandı.","articleBody": "İzmir Cumhuriyet Başsavcılığı koordinesinde, İzmir Emniyet Müdürlüğü Organize Suçlarla Mücadele Şube Müdürlüğü ve İstihbarat Şube Müdürlüğü ekipleri çalışma yürüttü. Yapılan çalışmalarda şüphelilerin Telegram uygulamasında grup oluşturduğu tespit edildi.Bu grup üzerinden örgütlü hareket eden şüphelilerin, para karşılığında toplumda infial oluşturabilecek bomba, fuhuş ve terör saldırısı içerikli asılsız ihbarlarda bulunduğu belirlendi. Farklı illerde toplam 45 asılsız ihbar gerçekleştiren şüphelilerin, müştekilere yönelik şantaj ve tehdit eylemleriyle haksız ekonomik menfaat sağladığı tespit edildi.7 TUTUKLAMAİzmir merkezli İstanbul, Antalya, Afyonkarahisar, Diyarbakır, Şanlıurfa ve Muğla'nın Fethiye ilçesinde hava destekli müşterek operasyonlar yapıldı. Operasyonlarda 9 şüpheli yakalanarak gözaltına alındı. Emniyetteki işlemlerinin ardından adli makamlara sevk edilen şüphelilerden 7'si tutuklandı.Bülten Abonelik Formu","author": {"@type": "Person", "name": "Tayibe Önel"},"publisher": {"@type": "Organization","name":"www.hurriyet.com.tr","logo": {"@type": "ImageObject","url": "https://image.hurimg.com/i/hurriyet/100/230x60/590c24950f25442978242248.jpg","width": 230,"height": 60}}} </script>
|
|
3
|
+
<p>document.addEventListener('DOMContentLoaded', () => {const isLezizz = document.querySelector(".header-lezizz");const loginText = isLezizz ? "Giriş" : "GİRİŞ";try {if (DM) {DM.auth.oidc.init('btn-dm-signin', {'storage': 'nostorage','acr_values': 'hurriyet','onsuccess': onSuccess,'onfailure': onFailure});function loadGoogleTapOneJs(src) {let script = document.createElement('script');script.src = src;script.async = true;script.defer = true;document.head.appendChild(script);}function onSuccess(dmUser) {let full_name = `${dmUser.profile.firstName?.trim()} ${dmUser.profile.lastName?.trim()}`;let parent = DM.auth.clickElement.parentElement;if (parent) {parent.removeChild(DM.auth.clickElement);parent.appendChild(DM.auth.clickElement.cloneNode(true));}const sidebarPremiumBox = document.querySelector(".sidebar__external--signin"),loginAccount = document.querySelector('.header__external--account'),gmConnectBtn = document.querySelector(".gm-connect-btn"),gmLoginAfter = document.querySelector(".login-after"),gmLoginInfoElements = document.querySelectorAll("#gmUserName"),gmLogoutButtons = document.querySelectorAll(".gm-connect-box .gm-link"),gmLoginContainers = document.querySelectorAll(".gm-login-container"),h16Header = document.querySelector(".h16-header"),backdrop = document.querySelector('.header__backdrop');let profileLink = "https://hesabim.hurriyet.com.tr/#/profil";let loginUrl = "https://u.hurriyet.com.tr";let lastUrl = "?lasturl=https://www.hurriyet.com.tr/gundem/6-ilde-telegram-uzerinden-asilsiz-ihbar-ve-santaj-yapan-7-supheli-tutuklandi-43369105";if (sidebarPremiumBox) {sidebarPremiumBox.innerHTML = GetShortName(full_name);sidebarPremiumBox.setAttribute("href", profileLink);}if (loginAccount) {let parent = loginAccount.parentElement;parent.removeChild(loginAccount);parent.appendChild(loginAccount);const advancedHeader = document.querySelector(".category-advanced-header");let userProfile = null;if (advancedHeader) {let shortName = full_name.split(' ').map(name => name[0] + '').join('');const capitalizeFullName = full_name.toLocaleLowerCase('tr-TR').split(' ').map(word => word.charAt(0).toLocaleUpperCase('tr-TR') + word.slice(1)).join(' ');userProfile = `${shortName}${capitalizeFullName}Kullanıcı BilgilerimÇıkış Yap`;}else {userProfile = `Profili GörÇıkış Yap`;}loginAccount.classList.add('login');let shortOrFullName;if (IsMobile()) {shortOrFullName = full_name.split(' ').map(name => name[0] + '.').join('');} else {shortOrFullName = full_name;}loginAccount.innerHTML = GetShortName(shortOrFullName);loginAccount.setAttribute("href", "javascript:;");loginAccount.innerHTML += userProfile;const mobileUserTarget = document.querySelector('.mobile-user-profile-menu');if (mobileUserTarget) {mobileUserTarget.innerHTML = userProfile;}loginAccount.addEventListener("click", () => {const isOpened = loginAccount.classList.toggle("opened");if (window.innerWidth {setCookie("firstLogin", false, -1);setCookie("isPopupShow", true, 7);document.location = loginUrl +"/accounts/logout" + lastUrl;});}}if (h16Header) {h16Header.setAttribute("href", profileLink);let loginPlaces = document.querySelectorAll(".login-user");[].forEach.call(loginPlaces, element => {element.innerText = GetShortName(full_name)});}trackFirstLogin(dmUser);if (gmConnectBtn && gmLoginAfter) {gmConnectBtn.remove();gmLoginAfter.remove();[].forEach.call(gmLoginContainers, element => {element.classList.add('gm-show');});[].forEach.call(gmLoginInfoElements, element => {element.innerHTML = GetShortName(full_name);});[].forEach.call(gmLogoutButtons, element => {element.setAttribute("href", "javascript:void(0);");});}}function onFailure(error) {let loginUrl = "https://u.hurriyet.com.tr";let lastUrl = "?lasturl=https://www.hurriyet.com.tr/gundem/6-ilde-telegram-uzerinden-asilsiz-ihbar-ve-santaj-yapan-7-supheli-tutuklandi-43369105";popupShowRule();setCookie("firstLogin", false, -1);if (document.getElementById('logoutbtn') != null) {document.getElementById('logoutbtn').style.display = 'none';}document.getElementById('btn-dm-signin').innerText = loginText;document.getElementById('btn-dm-signin').href = loginUrl + "/accounts/login" + lastUrl;let loginAccount = document.querySelector('.header__external--account');let h16Header = document.querySelector(".h16-header");if (loginAccount) {loginAccount.classList.remove('login');}if (h16Header) {loginAccount.setAttribute("href", "javascript:void(0)");let loginPlaces = document.querySelectorAll(".login-user");[].forEach.call(loginPlaces, element => {element.innerText = loginText;});}window.consoleLog?.('onFailure', error);}function trackFirstLogin(dmUser) {if (getCookie("firstLogin") !== "") {return;}const actionNames = {7: "google",5: "twitter",4: "facebook",17: "apple"};let action = "manuel";try {const referrer = dmUser.profile.referrer;action = actionNames[referrer] || action;} catch (e) {window.consoleLog?.("Error referrer", e);}const script = document.createElement("script");script.innerHTML = `window.dataLayer = window.dataLayer || [];window.dataLayer.push({'event': 'login','Category': 'login','Action': '${action}','Label': 'hurriyet.com'});`;document.getElementsByTagName('head')[0].appendChild(script);setCookie("firstLogin", true, 15);};function popupShowRule() {let isReferralValid = false;let isWebSiteFirstVisit = false;let isMainPageAutoReload = false;let documentReferrer = document.referrer;if (documentReferrer.includes(".hurriyet.com.tr") || documentReferrer.includes("localhost") || documentReferrer == "") {isReferralValid = true;}if (isReferralValid) {if (getCookie("isWebSiteFirstVisit") == "") {setCookie("isWebSiteFirstVisit", true, 1);isWebSiteFirstVisit = true;}if (isWebSiteFirstVisit == false && getCookie("isPopupShow") == "") {setCookie("isPopupShow", true, 7);let googleFastLoginControlText = document.getElementById("googleFastLoginControl").value;if (googleFastLoginControlText == 1) {window.consoleLog?.("OneTap: Mobile, Web On");loadGoogleTapOneJs("https://accounts.google.com/gsi/client");}else if (googleFastLoginControlText == 2 && IsMobile()) {window.consoleLog?.("OneTap: Mobile On, Web Off");loadGoogleTapOneJs("https://accounts.google.com/gsi/client");}else if (googleFastLoginControlText == 3 && !IsMobile()) {window.consoleLog?.("OneTap: Mobile Off, Web On");loadGoogleTapOneJs("https://accounts.google.com/gsi/client");}}}}function setCookie(cname, cvalue, exdays) {const d = new Date();d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));let expires = "expires=" + d.toUTCString();document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";}function getCookie(cname) {let name = cname + "=";let decodedCookie = decodeURIComponent(document.cookie);let ca = decodedCookie.split(';');for (let i = 0; i 1) {for (var i = 1; i #credential_picker_container {z-index: 9999999 !important;}.dm-modal-container {font-family: 'Metropolis Medium';}.adserviceOop{position: fixed;top: 0px;left: 0px;width: 0px;height: 0px;}.news-inline-banner {img {@media screen and (max-width: 767px) {max-height: 165px;height: auto;}@media screen and (min-width: 768px) {max-height: 200px;height: auto;width: auto;max-width: 100%;}}}HABERLERGündem Haberleri6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandıGüncelleme Tarihi: Kasım 07, 2026 09:38</p>
|
|
4
|
+
<p>Font BoyutuYazıyı BüyütYazıyı KüçültPaylaşNSosyalTwitterFacebookLinkedinWhatsappFlipboardLinki KopyalaİHAOluşturulma Tarihi: Kasım 07, 2026 09:38İzmir merkezli İstanbul, Antalya, Afyonkarahisar, Diyarbakır, Şanlıurfa ve Muğla'da düzenlenen operasyonda, Telegram üzerinden asılsız ihbar ve şantaj yaptıkları belirlenen 9 şüpheli yakalandı. Adliyeye sevk edilen şüphelilerden 7'si tutuklandı.Haberlerimizi Google’da Takip EdinGelişmelerden anında haberdar olun.Google’da tercih edilenkaynak olarak ekleyinHaberin Devamıİzmir Cumhuriyet Başsavcılığı koordinesinde, İzmir Emniyet Müdürlüğü Organize Suçlarla Mücadele Şube Müdürlüğü ve İstihbarat Şube Müdürlüğü ekipleri çalışma yürüttü. Yapılan çalışmalarda şüphelilerin Telegram uygulamasında grup oluşturduğu tespit edildi.</p>
|
|
5
|
+
<p>Bu grup üzerinden örgütlü hareket eden şüphelilerin, para karşılığında toplumda infial oluşturabilecek bomba, fuhuş ve terör saldırısı içerikli asılsız ihbarlarda bulunduğu belirlendi. Farklı illerde toplam 45 asılsız ihbar gerçekleştiren şüphelilerin, müştekilere yönelik şantaj ve tehdit eylemleriyle haksız ekonomik menfaat sağladığı tespit edildi.</p>
|
|
6
|
+
<p>İzmir merkezli İstanbul, Antalya, Afyonkarahisar, Diyarbakır, Şanlıurfa ve Muğla'nın Fethiye ilçesinde hava destekli müşterek operasyonlar yapıldı. Operasyonlarda 9 şüpheli yakalanarak gözaltına alındı. Emniyetteki işlemlerinin ardından adli makamlara sevk edilen şüphelilerden 7'si tutuklandı.</p>
|
|
7
|
+
</body></html>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<html><head><title>Güncel Haberler - Bugün Yaşanan Son Olaylar, Son Dakika Gündem Haber</title></head><body>
|
|
2
|
+
<a href="/gundem/afyonkarahisarda-8-kisiyi-katletmisti-saldirganin-silahi-12-bin-tlye-aldigi-ortaya-cikti-43369434">Afyonkarahisar'da 8 kişiyi katletmişti! Saldırganın silahı 12 bin TL'ye aldığı ortaya çıktı</a>
|
|
3
|
+
<a href="/gundem/universitelerde-4-yillik-egitim-suresi-dusuyor-mu-yok-baskani-ozvardan-yeni-aciklama-43369429">Üniversitelerde 4 yıllık eğitim süresi düşüyor mu? YÖK Başkanı Özvar'dan yeni açıklama</a>
|
|
4
|
+
<a href="/gundem/manisada-annesini-ve-kardesini-olduren-zanli-tutuklandi-neden-tartistigimizi-ve-oldurdugumu-hatirlamiyorum-43369415">Manisa'da annesini ve kardeşini öldüren zanlı tutuklandı! 'Neden tartıştığımızı ve öldürdüğümü hatırlamıyorum'</a>
|
|
5
|
+
<a href="/gundem/aydinda-mobilya-fabrikasinda-yangin-ekipler-mudahale-ediyor-43369356">Aydın'da mobilya fabrikasında yangın: Ekipler müdahale ediyor</a>
|
|
6
|
+
<a href="/gundem/adana-9-gun-boyunca-festival-coskusu-yasayacak-43369338">Adana, 9 gün boyunca festival coşkusu yaşayacak!</a>
|
|
7
|
+
<a href="/galeri-istanbulun-yani-basinda-kar-yagisi-evler-kara-gomuldu-sabah-kalktik-her-taraf-bembeyaz-olmus-43369214">İstanbul'un yanı başında kar yağışı! Evler kara gömüldü: 'Sabah kalktık her taraf bembeyaz olmuş'</a>
|
|
8
|
+
<a href="/gundem/emine-erdogandan-81-il-81-kadin-81-hikaye-bize-ozgu-sergisi-acilis-programina-iliskin-paylasim-43369264">Emine Erdoğan'dan 81 İl, 81 Kadın, 81 Hikaye: Bize Özgü Sergisi Açılış Programı'na ilişkin paylaşım</a>
|
|
9
|
+
<a href="/gundem/zeytinburnunda-trafikte-yol-verme-kavgasi-otomobilden-inen-surucu-diger-aracin-camini-yumrukladi-43369234">Zeytinburnu'nda trafikte yol verme kavgası! Otomobilden inen sürücü, diğer aracın camını yumrukladı</a>
|
|
10
|
+
<a href="/gundem/fon-sorusturmasinda-yeni-gelisme-eski-spk-baskan-vekili-enver-usca-dahil-4-kisi-tutuklandi-43369236">Fon soruşturmasında yeni gelişme: Eski SPK Başkan Vekili Enver Usca dahil 4 kişi tutuklandı</a>
|
|
11
|
+
<a href="/gundem/kepengin-uzaktan-kumanda-sifresini-kopyalayip-kuyumcuya-girdi-caldigi-200-yuzuk-sahte-cikti-43369177">Kepengin uzaktan kumanda şifresini kopyalayıp kuyumcuya girdi! Çaldığı 200 yüzük sahte çıktı</a>
|
|
12
|
+
<a href="/galeri-asi-nehrinde-oltaya-takildi-ilk-kez-boyle-bir-turle-karsilastim-ben-yemem-43369179">Asi Nehri'nde oltaya takıldı: 'İlk kez böyle bir türle karşılaştım, ben yemem'</a>
|
|
13
|
+
<a href="/gundem/sokak-saticilarina-yonelik-uyusturucu-operasyonu-82-kisi-tutuklandi-43369147">Sokak satıcılarına yönelik uyuşturucu operasyonu: 82 kişi tutuklandı</a>
|
|
14
|
+
<a href="/gundem/super-lotoda-buyuk-ikramiye-heyecani-850-milyon-312-bin-836-tlye-ulasti-43369133">Süper Loto’da büyük ikramiye heyecanı! 850 milyon 312 bin 836 TL’ye ulaştı</a>
|
|
15
|
+
<a href="/gundem/6-ilde-telegram-uzerinden-asilsiz-ihbar-ve-santaj-yapan-7-supheli-tutuklandi-43369105">6 ilde Telegram üzerinden asılsız ihbar ve şantaj yapan 7 şüpheli tutuklandı</a>
|
|
16
|
+
<a href="/gundem/istanbullulara-sabah-surprizi-yagmur-sonrasi-gokkusagi-cikti-43369098">İstanbullulara sabah sürprizi: Yağmur sonrası gökkuşağı çıktı</a>
|
|
17
|
+
<a href="/gundem/kirmizi-bultenle-araniyordu-uyusturucu-baronu-mersinde-yakalandi-43369078">Kırmızı bültenle aranıyordu: Uyuşturucu baronu Mersin'de yakalandı</a>
|
|
18
|
+
<a href="/gundem/bursada-ayni-yone-giden-otomobiller-carpisti-kaza-ani-kamerada-43369071">Bursa’da aynı yöne giden otomobiller çarpıştı: Kaza anı kamerada</a>
|
|
19
|
+
<a href="/gundem/turkiye-sibirya-soguklarinin-etkisinde-sicakliklar-dusuyor-meteorolojiden-uyari-ustune-uyari-saganak-yagis-kar-zirai-don-ve-43369067">Türkiye Sibirya soğuklarının etkisinde: Sıcaklıklar düşüyor! Meteoroloji'den uyarı üstüne uyarı: Sağanak yağış, kar, zirai don ve fırtına alarmı</a>
|
|
20
|
+
<a href="/gundem/bursada-eglence-mekanina-tufekli-saldiri-43369065">Bursa’da eğlence mekanına tüfekli saldırı</a>
|
|
21
|
+
<a href="/gundem/tutuklama-gerekcesi-tasfiyeyi-bilerek-islem-yapti-para-ayni-gun-yatti-43369033">Tutuklama gerekçesi: Tasfiyeyi bilerek işlem yaptı para aynı gün yattı</a>
|
|
22
|
+
<a href="/gundem/erdogan-uluslararasi-sistem-iflas-bayragi-cekti-43369044">Cumhurbaşkanı Erdoğan: Uluslararası sistem iflas bayrağı çekti</a>
|
|
23
|
+
<a href="/gundem/tarihi-gecmis-ilaclari-piyasaya-suren-21-supheli-tutuklandi-43369012">Tarihi geçmiş ilaçları piyasaya süren 21 şüpheli tutuklandı</a>
|
|
24
|
+
<a href="/gundem/cinayeti-tum-detaylariyla-anlatti-annem-ve-sevgilisi-beni-zorladi-43369016">Cinayeti tüm detaylarıyla anlattı: Annem ve sevgilisi beni zorladı</a>
|
|
25
|
+
<a href="/gundem/denizolgunun-olumu-bassavcilik-ek-rapor-istedi-43369024">Denizolgun’un ölümü... Başsavcılık ek rapor istedi</a>
|
|
26
|
+
<a href="/gundem/hakan-bahcetepeye-ev-hapsiyle-tahliye-43369026">Hakan Bahçetepe’ye ev hapsiyle tahliye</a>
|
|
27
|
+
<a href="/gundem/kurtulmus-hayati-bir-mesele-43369030">Kurtulmuş: Hayati bir mesele</a>
|
|
28
|
+
<a href="/gundem/yeni-parti-yarin-sandiga-gidiyor-genel-baskanlik-icin-on-secim-43369031">YENİ Parti yarın sandığa gidiyor... Genel Başkanlık için ön seçim</a>
|
|
29
|
+
<a href="/gundem/tedmemden-yesil-egitim-onerileri-43369041">TEDMEM’DEN ‘Yeşil Eğitim’ önerileri</a>
|
|
30
|
+
<a href="/gundem/doluluk-orani-yuzde-61e-cikti-son-10-yilin-rekoru-43369050">Doluluk oranı yüzde 61’e çıktı... Son 10 yılın rekoru</a>
|
|
31
|
+
<a href="/gundem/secil-erzana-212-yil-istendi-43369010">Seçil Erzan’a 212 yıl istendi</a>
|
|
32
|
+
<a href="/galeri-ayetel-kursi-okunusu-ayetel-kursi-duasi-turkce-anlami-ve-diyanet-meali-arapca-yazilisi-faziletleri-ve-bilgiler-42300047">Ayetel Kürsi</a>
|
|
33
|
+
<a href="/galeri-guzel-sozler-2023-ozlu-sozler-ile-en-anlamli-sevgiliye-etkileyici-romantik-resimli-ve-kisa-mesajlar-40792109">Güzel Sözler</a>
|
|
34
|
+
<a href="/ramazan-ozel/namaz-sureleri/yasin-suresi-oku-dinle-yasin-suresi-arapca-turkce-okunusu-ve-meali-6-sayfa-tamami-42681788">Yasin Suresi</a>
|
|
35
|
+
</body></html>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<html><head><title>Futbol Haberleri - Son Dakika Futbol Hakkında Güncel Haber ve Bilgiler </title></head><body>
|
|
2
|
+
<a href="/sporarena/fredden-galatasaray-derbisi-aciklamasi-cilginlik-43369101">Fred'den Galatasaray derbisi açıklaması: 'Çılgınlık!'</a>
|
|
3
|
+
<a href="/sporarena/jose-mourinho-yarinki-mac-beni-korkutuyor-43368494">Jose Mourinho: 'Yarınki maç beni korkutuyor!'</a>
|
|
4
|
+
<a href="/sporarena/senol-gunesten-arda-guler-sozleri-dunyanin-en-iyi-5-oyuncusu-arasinda-gosterilebilecek-bir-yildiz-43368178">Şenol Güneş'ten Arda Güler sözleri: 'Dünyanın en iyi 5 oyuncusu arasında gösterilebilecek bir yıldız!'</a>
|
|
5
|
+
<a href="/sporarena/leroy-sane-icin-olay-sozler-galatasaraya-katkisi-cok-sinirli-43366904">Leroy Sane için olay sözler: 'Galatasaray'a katkısı çok sınırlı!'</a>
|
|
6
|
+
<a href="/sporarena/aleksey-batrakov-galatasaray-kariyerine-gorkemli-bir-baslangic-yapti-43365723">Aleksey Batrakov, Galatasaray kariyerine görkemli bir başlangıç yaptı</a>
|
|
7
|
+
<a href="/sporarena/galatasaray-2-2-stuttgart-mac-ozeti-uefa-genclik-ligi-43364740">Galatasaray 2-2 Stuttgart maç özeti (UEFA Gençlik Ligi)</a>
|
|
8
|
+
<a href="/sporarena/dort-buyuk-kulubun-futbolculari-kktcli-oyuncularla-dostluk-macina-cikacak-43364532">Dört büyük kulübün futbolcuları KKTC’li oyuncularla dostluk maçına çıkacak</a>
|
|
9
|
+
<a href="/sporarena/osimhen-formunu-yukseltiyor-raphinha-gollerini-ikiser-ikiser-siralamaya-devam-ediyor-iste-misliden-gunun-oyuncu-bahisleri-43364451">Osimhen formunu yükseltiyor, Raphinha gollerini ikişer ikişer sıralamaya devam ediyor! İşte Misli’den günün oyuncu bahisleri</a>
|
|
10
|
+
<a href="/sporarena/vitor-pereira-gecmiste-kararlarimda-bazi-hatalar-yaptim-43363600">Vitor Pereira: 'Geçmişte kararlarımda bazı hatalar yaptım'</a>
|
|
11
|
+
<a href="/sporarena/genclerbirliginde-ve-atalantada-kotu-gidis-suruyor-kayserisporun-maclari-bol-gollu-geciyor-iste-misliden-gunun-tuyolari-43363160">Gençlerbirliği’nde ve Atalanta’da Kötü Gidiş Sürüyor, Kayserispor’un Maçları Bol Gollü Geçiyor… İşte Misli’den Günün Tüyoları!</a>
|
|
12
|
+
<a href="/sporarena/fernando-musleradan-buyuk-hata-43362367">Fernando Muslera'dan büyük hata!</a>
|
|
13
|
+
<a href="/sporarena/besiktas-kasimpasaya-konuk-olacak-macin-heyecani-canli-sohbet-ile-mislide-43362231">Beşiktaş, Kasımpaşa'ya konuk olacak! Maçın heyecanı canlı sohbet ile Misli'de</a>
|
|
14
|
+
<a href="/avrupa/besiktas-avrupadaki-genclere-sahip-cikacak-43362164">Beşiktaş Avrupa’daki gençlere sahip çıkacak</a>
|
|
15
|
+
<a href="/avrupa/bundesligada-zirve-yarisi-iyice-kizisti-43362159">Bundesliga'da zirve yarışı iyice kızıştı</a>
|
|
16
|
+
<a href="/sporarena/arda-turan-yasadigimiz-zorluklarin-tamamen-farkindayim-43361407">Arda Turan: 'Yaşadığımız zorlukların tamamen farkındayım!'</a>
|
|
17
|
+
<a href="/sporarena/trabzonspor-teknik-direktoru-thomas-reis-baskiyi-seviyorum-43360334">Trabzonspor Teknik Direktörü Thomas Reis: 'Baskıyı seviyorum!'</a>
|
|
18
|
+
<a href="/sporarena/fenerbahcede-samandira-cikarmasi-kuyta-destek-icin-toplandilar-43360099">Fenerbahçe'de Samandıra çıkarması! Kuyt'a destek için toplandılar</a>
|
|
19
|
+
<a href="/sporarena/a-milli-takim-teknik-direktoru-sergen-yalcindan-iddialara-yanit-o-aciklamalari-sov-amaciyla-yapmistim-futbolcular-da-bunu-biliyor-43360031">A Milli Takım teknik direktörü Sergen Yalçın'dan iddialara yanıt: 'O açıklamaları şov amacıyla yapmıştım, futbolcular da bunu biliyor!'</a>
|
|
20
|
+
<a href="/sporarena/besiktasta-orkun-kokcu-gelismesi-43359519">Beşiktaş'ta Orkun Kökçü gelişmesi!</a>
|
|
21
|
+
<a href="/sporarena/super-ligde-10-hafta-heyecani-baslayacak-43359077">Süper Lig'de 10. hafta heyecanı başlayacak</a>
|
|
22
|
+
<a href="/sporarena/ronaldonun-milli-takima-vedasinda-jorge-jesus-etkisi-43369358">Ronaldo'nun milli takıma vedasında Jorge Jesus etkisi!</a>
|
|
23
|
+
<a href="/sporarena/gedson-fernandesten-batrakov-sozleri-cok-iyi-bir-kulube-gitti-43369229">Gedson Fernandes'ten Batrakov sözleri: 'Çok iyi bir kulübe gitti!'</a>
|
|
24
|
+
<a href="/gundem/secil-erzana-212-yil-istendi-43369010">Seçil Erzan’a 212 yıl istendi</a>
|
|
25
|
+
<a href="/sporarena/tff-tahkim-kurulundan-archie-brown-karari-43368940">TFF Tahkim Kurulu'ndan Archie Brown kararı!</a>
|
|
26
|
+
<a href="/sporarena/fenerbahceden-4-nisan-saldirisiyla-ilgili-aciklama-43368859">Fenerbahçe'den 4 Nisan saldırısıyla ilgili açıklama!</a>
|
|
27
|
+
<a href="/gundem/son-dakika-fenerbahce-otobusune-saldiriya-iddianame-5-supheli-hakkinda-ceza-istendi-43368834">Son dakika : Fenerbahçe otobüsüne saldırıya iddianame… 5 şüpheli hakkında ceza istendi</a>
|
|
28
|
+
<a href="/sporarena/ahmetcan-kaplan-kimdir-nereli-kac-yasinda-ahmetcan-kaplan-hangi-takimlarda-oynadi-43368718">Ahmetcan Kaplan kimdir, nereli, kaç yaşında? Ahmetcan Kaplan hangi takımlarda oynadı?</a>
|
|
29
|
+
<a href="/spor/videolar/super-lig/video-emre-belozoglu-fenerbahceye-imza-atti-5-sene-onceki-emre-degilim-43368679">Emre Belözoğlu, Fenerbahçe'ye imza attı: 5 sene önceki Emre değilim</a>
|
|
30
|
+
<a href="/sporarena/serhat-oztasdelen-kimdir-nereli-kac-yasinda-serhat-oztasdelen-hangi-takimlarda-oynadi-43368614">Serhat Öztaşdelen kimdir, nereli, kaç yaşında? Milli Takım Aday Kadrosuna çağrılan Serhat Öztaşdelen hangi takımlarda oynadı?</a>
|
|
31
|
+
<a href="/sporarena/emre-belozoglu-fenerbahceye-imza-atti-5-sene-onceki-emre-degilim-43368560">Emre Belözoğlu, Fenerbahçe'ye imza attı: 5 sene önceki Emre değilim</a>
|
|
32
|
+
<a href="/spor/videolar/diger/video-sergen-yalcin-a-milli-takim-aday-kadrosunu-acikladi-43368423">Sergen Yalçın, A Milli Takım aday kadrosunu açıkladı</a>
|
|
33
|
+
<a href="/sporarena/sergen-yalcin-a-milli-takim-aday-kadrosunu-acikladi-43368357">Sergen Yalçın, A Milli Takım aday kadrosunu açıkladı! İşte kadroda yer almayan isimler</a>
|
|
34
|
+
<a href="/spor/videolar/diger/video-senol-gunesten-arda-guler-sozleri-dunyanin-en-iyi-5-oyuncusu-arasinda-gosterilebilecek-bir-yildiz-43368340">Şenol Güneş'ten Arda Güler sözleri: 'Dünyanın en iyi 5 oyuncusu arasında gösterilebilecek bir yıldız!'</a>
|
|
35
|
+
<a href="/sporarena/fifa-baskani-infantinodan-portekiz-milli-takimini-birakan-ronaldoya-mesaj-43368058">FIFA Başkanı Infantino'dan Portekiz Milli Takımı'nı bırakan Ronaldo'ya mesaj</a>
|
|
36
|
+
<a href="/sporarena/galatasaray-super-ligde-amedsporu-agirlayacak-43368015">Galatasaray, Süper Lig'de Amedspor'u ağırlayacak</a>
|
|
37
|
+
<a href="/sporarena/berke-ozerden-sergen-yalcin-ve-milli-takim-aciklamasi-43367578">Berke Özer'den Sergen Yalçın ve Milli Takım açıklaması!</a>
|
|
38
|
+
<a href="/gundem/bakanliktan-giyilmis-coraplarla-bagis-paylasimina-suc-duyurusu-43367469">Bakanlıktan giyilmiş çoraplarla bağış paylaşımına suç duyurusu</a>
|
|
39
|
+
<a href="/sporarena/galeri-cristiano-ronaldo-milli-takimi-birakti-mi-ronaldodan-portekiz-milli-takim-karari-43367179">Cristiano Ronaldo Portekiz Milli Takımı'nı bıraktı mı? Ronaldo’dan milli takım kararı!</a>
|
|
40
|
+
<a href="/sporarena/sergen-yalcinin-54-yas-gunu-rivada-kutlandi-43367390">Sergen Yalçın'ın 54. yaş günü Riva'da kutlandı</a>
|
|
41
|
+
<a href="/sporarena/burak-yilmazdan-fenerbahce-maci-oncesi-iddiali-aciklama-kazanmak-icin-elimizden-gelenin-fazlasini-yapacagiz-43367209">Burak Yılmaz'dan Fenerbahçe maçı öncesi iddialı açıklama: 'Kazanmak için elimizden gelenin fazlasını yapacağız!'</a>
|
|
42
|
+
<a href="/spor/videolar/super-lig/video-burak-yilmaz-fenerbahce-macini-kazanmak-icin-sahaya-cikacagiz-43367195">Burak Yılmaz'dan Fenerbahçe maçı öncesi iddialı açıklama: 'Kazanmak için elimizden gelenin fazlasını yapacağız!'</a>
|
|
43
|
+
<a href="/sporarena/fenerbahce-paylasti-emre-belozoglu-ilk-antrenmanina-cikti-43367128">Fenerbahçe paylaştı! Emre Belözoğlu ilk antrenmanına çıktı</a>
|
|
44
|
+
<a href="/sporarena/freiburg-nerenin-takimi-hangi-ligde-oynuyor-freiburg-guncel-kadro-degeri-2026-43367119">Freiburg nerenin takımı, hangi ligde oynuyor? Freiburg güncel kadro değeri 2026!</a>
|
|
45
|
+
<a href="/sporarena/cristiano-ronaldo-milli-takim-kariyerini-noktaladi-43367037">Başbakan ve Federasyon Başkanı kararından döndüremedi: Cristiano Ronaldo milli takım kariyerini noktaladı!</a>
|
|
46
|
+
<a href="/sporarena/bayern-munih-harry-kane-ile-sozlesme-yeniledi-43366995">Bayern Münih, Harry Kane ile sözleşme yeniledi!</a>
|
|
47
|
+
</body></html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Live tests — hit the real hurriyet.com.tr. Cautious: a few requests, then stop.
|
|
2
|
+
|
|
3
|
+
Run: pytest tests/test_live.py -v
|
|
4
|
+
Skip: pytest -m "not live" (or set HURRIYET_SKIP_LIVE=1)
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import unittest
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src"))
|
|
15
|
+
|
|
16
|
+
from hurriyet_mcp import http as _http
|
|
17
|
+
from hurriyet_mcp import news as _N
|
|
18
|
+
|
|
19
|
+
pytestmark = pytest.mark.live
|
|
20
|
+
SKIP = os.environ.get("HURRIYET_SKIP_LIVE") == "1"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.skipif(SKIP, reason="HURRIYET_SKIP_LIVE=1")
|
|
24
|
+
class TestLive(unittest.TestCase):
|
|
25
|
+
def test_gundem_is_real(self):
|
|
26
|
+
rows = _N.parse_article_links(_http.fetch_section("gundem"))
|
|
27
|
+
self.assertGreaterEqual(len(rows), 10)
|
|
28
|
+
print(f"\nLIVE gundem: {len(rows)} headlines, first = {rows[0]['title'][:80]}")
|
|
29
|
+
time.sleep(1)
|
|
30
|
+
|
|
31
|
+
def test_article_is_real(self):
|
|
32
|
+
rows = _N.parse_article_links(_http.fetch_section("gundem"))
|
|
33
|
+
art = _N.parse_article_page(_http.fetch_article(rows[0]["url"]))
|
|
34
|
+
self.assertTrue(art["headline"])
|
|
35
|
+
self.assertTrue(art["date_published"])
|
|
36
|
+
self.assertGreater(len(art["body"]), 100)
|
|
37
|
+
print(f"\nLIVE article: {art['headline'][:80]} ({art['date_published']}, "
|
|
38
|
+
f"{len(art['body'])} chars body)")
|
|
39
|
+
time.sleep(1)
|
|
40
|
+
|
|
41
|
+
def test_search_is_real(self):
|
|
42
|
+
rows = _N.parse_article_links(_http.fetch_search("futbol"))
|
|
43
|
+
self.assertGreaterEqual(len(rows), 5)
|
|
44
|
+
print(f"\nLIVE search 'futbol' -> {_http.last_url}: {len(rows)} articles")
|
|
45
|
+
time.sleep(1)
|
|
46
|
+
|
|
47
|
+
def test_home_and_categories_are_real(self):
|
|
48
|
+
home = _http.fetch_home()
|
|
49
|
+
rows = _N.parse_article_links(home)
|
|
50
|
+
cats = _N.list_categories(home)
|
|
51
|
+
self.assertGreaterEqual(len(rows), 20)
|
|
52
|
+
self.assertGreaterEqual(len(cats), 12)
|
|
53
|
+
print(f"\nLIVE home: {len(rows)} links, {len(cats)} categories")
|
|
54
|
+
time.sleep(1)
|
|
55
|
+
|
|
56
|
+
def test_unknown_section_404s(self):
|
|
57
|
+
with self.assertRaises(_http.HurriyetError):
|
|
58
|
+
_http.fetch_section("boyle-bir-bolum-yok-xyz123")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Offline tests — parse real captured Hürriyet fixtures, no network."""
|
|
2
|
+
import pathlib
|
|
3
|
+
import sys
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src"))
|
|
7
|
+
|
|
8
|
+
from hurriyet_mcp import news as N
|
|
9
|
+
|
|
10
|
+
FIX = pathlib.Path(__file__).resolve().parent / "fixtures"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestArticleLinks(unittest.TestCase):
|
|
14
|
+
def test_gundem(self):
|
|
15
|
+
rows = N.parse_article_links((FIX / "gundem.html").read_text(encoding="utf-8"))
|
|
16
|
+
self.assertEqual(len(rows), 30)
|
|
17
|
+
first = rows[0]
|
|
18
|
+
self.assertEqual(first["id"], "43369434")
|
|
19
|
+
self.assertEqual(first["section"], "gundem")
|
|
20
|
+
self.assertEqual(first["kind"], "article")
|
|
21
|
+
self.assertTrue(first["url"].startswith("https://www.hurriyet.com.tr/gundem/"))
|
|
22
|
+
self.assertIn("Afyonkarahisar", first["title"])
|
|
23
|
+
|
|
24
|
+
def test_search_page(self):
|
|
25
|
+
rows = N.parse_article_links((FIX / "search.html").read_text(encoding="utf-8"))
|
|
26
|
+
self.assertEqual(len(rows), 45)
|
|
27
|
+
self.assertTrue(all(r["id"] for r in rows))
|
|
28
|
+
|
|
29
|
+
def test_dedupe_and_short_titles(self):
|
|
30
|
+
html = (
|
|
31
|
+
'<a href="/gundem/x-y-12345678">Short</a>'
|
|
32
|
+
'<a href="/gundem/uzun-baslik-haber-ornegi-12345678">Uzun başlıklı haber örneği burada</a>'
|
|
33
|
+
'<a href="/gundem/uzun-baslik-haber-ornegi-12345678">Uzun başlıklı haber örneği burada</a>'
|
|
34
|
+
)
|
|
35
|
+
rows = N.parse_article_links(html)
|
|
36
|
+
self.assertEqual(len(rows), 1)
|
|
37
|
+
self.assertEqual(rows[0]["id"], "12345678")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestArticlePage(unittest.TestCase):
|
|
41
|
+
@classmethod
|
|
42
|
+
def setUpClass(cls):
|
|
43
|
+
cls.a = N.parse_article_page((FIX / "article.html").read_text(encoding="utf-8"))
|
|
44
|
+
|
|
45
|
+
def test_meta(self):
|
|
46
|
+
self.assertTrue(self.a["headline"].startswith("6 ilde Telegram"))
|
|
47
|
+
self.assertEqual(self.a["section"], "Gündem")
|
|
48
|
+
self.assertEqual(self.a["date_published"], "2026-11-07T09:38:51+03:00")
|
|
49
|
+
self.assertEqual(self.a["word_count"], 143)
|
|
50
|
+
|
|
51
|
+
def test_body(self):
|
|
52
|
+
self.assertEqual(len(self.a["body"]), 931)
|
|
53
|
+
self.assertTrue(self.a["body"].startswith("İzmir Cumhuriyet Başsavcılığı"))
|
|
54
|
+
|
|
55
|
+
def test_no_newsarticle_raises(self):
|
|
56
|
+
with self.assertRaises(ValueError):
|
|
57
|
+
N.parse_article_page("<html><body>no ld here</body></html>")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestCategories(unittest.TestCase):
|
|
61
|
+
def test_curated_list(self):
|
|
62
|
+
cats = N.list_categories()
|
|
63
|
+
self.assertEqual(len(cats), 15)
|
|
64
|
+
slugs = [c["slug"] for c in cats]
|
|
65
|
+
for want in ("gundem", "ekonomi", "dunya", "sporarena", "teknoloji"):
|
|
66
|
+
self.assertIn(want, slugs)
|
|
67
|
+
self.assertEqual(cats[0]["name"], "Gündem")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|