udzialy-bot 2.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.
- udzialy_bot-2.1.0/LICENSE +13 -0
- udzialy_bot-2.1.0/PKG-INFO +169 -0
- udzialy_bot-2.1.0/README.md +135 -0
- udzialy_bot-2.1.0/bot/__init__.py +7 -0
- udzialy_bot-2.1.0/bot/__main__.py +3 -0
- udzialy_bot-2.1.0/bot/config.py +191 -0
- udzialy_bot-2.1.0/bot/keyboards/__init__.py +27 -0
- udzialy_bot-2.1.0/bot/keyboards/inline.py +231 -0
- udzialy_bot-2.1.0/bot/keyboards/reply.py +47 -0
- udzialy_bot-2.1.0/bot/main.py +174 -0
- udzialy_bot-2.1.0/bot/middlewares/__init__.py +16 -0
- udzialy_bot-2.1.0/bot/middlewares/throttle.py +81 -0
- udzialy_bot-2.1.0/bot/routers/__init__.py +25 -0
- udzialy_bot-2.1.0/bot/routers/filters.py +347 -0
- udzialy_bot-2.1.0/bot/routers/results.py +300 -0
- udzialy_bot-2.1.0/bot/routers/saved.py +226 -0
- udzialy_bot-2.1.0/bot/routers/search.py +382 -0
- udzialy_bot-2.1.0/bot/routers/settings.py +130 -0
- udzialy_bot-2.1.0/detector/__init__.py +26 -0
- udzialy_bot-2.1.0/detector/filters.py +172 -0
- udzialy_bot-2.1.0/detector/keywords.py +165 -0
- udzialy_bot-2.1.0/detector/llm_analyzer.py +773 -0
- udzialy_bot-2.1.0/detector/scorer.py +362 -0
- udzialy_bot-2.1.0/geo/__init__.py +12 -0
- udzialy_bot-2.1.0/geo/cities.py +213 -0
- udzialy_bot-2.1.0/geo/distance.py +136 -0
- udzialy_bot-2.1.0/launcher.py +1076 -0
- udzialy_bot-2.1.0/pyproject.toml +58 -0
- udzialy_bot-2.1.0/scraper/__init__.py +34 -0
- udzialy_bot-2.1.0/scraper/base.py +117 -0
- udzialy_bot-2.1.0/scraper/manager.py +281 -0
- udzialy_bot-2.1.0/scraper/portals/__init__.py +36 -0
- udzialy_bot-2.1.0/scraper/portals/allegro.py +297 -0
- udzialy_bot-2.1.0/scraper/portals/domiporta.py +188 -0
- udzialy_bot-2.1.0/scraper/portals/gratka.py +175 -0
- udzialy_bot-2.1.0/scraper/portals/morizon.py +178 -0
- udzialy_bot-2.1.0/scraper/portals/nieruchomosci_online.py +220 -0
- udzialy_bot-2.1.0/scraper/portals/olx.py +265 -0
- udzialy_bot-2.1.0/scraper/portals/otodom.py +258 -0
- udzialy_bot-2.1.0/scraper/portals/szybko.py +217 -0
- udzialy_bot-2.1.0/scraper/portals/trojmiasto.py +38 -0
- udzialy_bot-2.1.0/scraper/stealth.py +372 -0
- udzialy_bot-2.1.0/scraper/tor_manager.py +306 -0
- udzialy_bot-2.1.0/scraper/utils/__init__.py +6 -0
- udzialy_bot-2.1.0/scraper/utils/headers.py +82 -0
- udzialy_bot-2.1.0/scraper/utils/retry.py +115 -0
- udzialy_bot-2.1.0/setup.cfg +4 -0
- udzialy_bot-2.1.0/storage/__init__.py +12 -0
- udzialy_bot-2.1.0/storage/database.py +274 -0
- udzialy_bot-2.1.0/storage/models.py +177 -0
- udzialy_bot-2.1.0/storage/queries.py +487 -0
- udzialy_bot-2.1.0/tests/test_distance.py +140 -0
- udzialy_bot-2.1.0/tests/test_llm_analyzer.py +667 -0
- udzialy_bot-2.1.0/tests/test_pipeline_live.py +121 -0
- udzialy_bot-2.1.0/tests/test_scorer.py +228 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/PKG-INFO +169 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/SOURCES.txt +60 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/dependency_links.txt +1 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/entry_points.txt +5 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/requires.txt +14 -0
- udzialy_bot-2.1.0/udzialy_bot.egg-info/top_level.txt +7 -0
- udzialy_bot-2.1.0/udzialy_cli.py +245 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Udzialy Bot - Wyszukiwarka Udzialow
|
|
2
|
+
Copyright (c) 2024-2026 QA10 sp. z o.o.
|
|
3
|
+
Wszelkie prawa zastrzezone / All rights reserved.
|
|
4
|
+
|
|
5
|
+
To oprogramowanie jest licencjonowane wylacznie do uzytku osobistego.
|
|
6
|
+
Redystrybucja, modyfikacja i wykorzystanie komercyjne bez pisemnej
|
|
7
|
+
zgody QA10 sp. z o.o. jest zabronione.
|
|
8
|
+
|
|
9
|
+
This software is licensed for personal use only.
|
|
10
|
+
Redistribution, modification and commercial use without written
|
|
11
|
+
permission from QA10 sp. z o.o. is prohibited.
|
|
12
|
+
|
|
13
|
+
Kontakt / Contact: kontakt@qa10.pl
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: udzialy-bot
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Bot do wyszukiwania ogłoszeń sprzedaży udziałów w nieruchomościach
|
|
5
|
+
Author-email: "QA10 sp. z o.o." <hello@qa10.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: telegram,bot,real-estate,scraping,poland,udzialy
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Communications :: Chat
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: aiogram>=3.10
|
|
20
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
21
|
+
Requires-Dist: httpx[http2,socks]>=0.27.0
|
|
22
|
+
Requires-Dist: selectolax>=0.3.21
|
|
23
|
+
Requires-Dist: curl_cffi>=0.7.0
|
|
24
|
+
Requires-Dist: primp>=0.6.0
|
|
25
|
+
Requires-Dist: nodriver>=0.35
|
|
26
|
+
Requires-Dist: patchright>=1.43
|
|
27
|
+
Requires-Dist: stem>=1.8.2
|
|
28
|
+
Requires-Dist: PySocks>=1.7.1
|
|
29
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
30
|
+
Requires-Dist: pydantic>=2.7.0
|
|
31
|
+
Requires-Dist: pydantic-settings>=2.3.0
|
|
32
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# 🏠 Udziały Bot
|
|
36
|
+
|
|
37
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](https://docs.aiogram.dev/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[]()
|
|
41
|
+
|
|
42
|
+
**Telegram bot do wyszukiwania ogłoszeń sprzedaży udziałów w nieruchomościach** na 9 polskich portalach ogłoszeniowych.
|
|
43
|
+
|
|
44
|
+
Bot automatycznie przeszukuje portale nieruchomości w poszukiwaniu ofert sprzedaży udziałów (ułamkowych części własności) i prezentuje wyniki bezpośrednio w Telegramie.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ✨ Funkcje
|
|
49
|
+
|
|
50
|
+
- 🔍 Przeszukiwanie 9 portali nieruchomości jednocześnie
|
|
51
|
+
- 🗺️ Filtrowanie po województwie, mieście, promieniu i cenie
|
|
52
|
+
- 📄 Paginacja wyników z klawiaturą inline
|
|
53
|
+
- 💾 Zapisywanie interesujących ogłoszeń
|
|
54
|
+
- 🛡️ Rotacja proxy przez Tor dla anonimowości
|
|
55
|
+
- ⚡ Asynchroniczne scrapowanie (httpx, curl_cffi, nodriver)
|
|
56
|
+
- 🧠 FSM do konfiguracji filtrów krok po kroku
|
|
57
|
+
- 🚦 Anti-flood middleware
|
|
58
|
+
|
|
59
|
+
## 📋 Wymagania
|
|
60
|
+
|
|
61
|
+
- Python 3.11 lub nowszy
|
|
62
|
+
- Windows 10/11 (skrypty .bat)
|
|
63
|
+
- Konto Telegram + token bota z [@BotFather](https://t.me/BotFather)
|
|
64
|
+
|
|
65
|
+
## 🚀 Instalacja (Windows)
|
|
66
|
+
|
|
67
|
+
### Automatyczna (zalecana)
|
|
68
|
+
|
|
69
|
+
1. Sklonuj repozytorium:
|
|
70
|
+
```cmd
|
|
71
|
+
git clone https://github.com/your-user/udzialy-bot.git
|
|
72
|
+
cd udzialy-bot
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Uruchom instalator:
|
|
76
|
+
```cmd
|
|
77
|
+
install.bat
|
|
78
|
+
```
|
|
79
|
+
Skrypt automatycznie:
|
|
80
|
+
- Sprawdzi wersję Pythona
|
|
81
|
+
- Utworzy środowisko wirtualne (venv)
|
|
82
|
+
- Zainstaluje zależności
|
|
83
|
+
- Pobierze Tor Expert Bundle
|
|
84
|
+
- Zainstaluje przeglądarki Playwright
|
|
85
|
+
|
|
86
|
+
3. Skonfiguruj bota — edytuj `config.yaml`:
|
|
87
|
+
```yaml
|
|
88
|
+
telegram:
|
|
89
|
+
token: "YOUR_BOT_TOKEN_HERE"
|
|
90
|
+
owner_id: 123456789
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Ręczna
|
|
94
|
+
|
|
95
|
+
```cmd
|
|
96
|
+
python -m venv .venv
|
|
97
|
+
.venv\Scripts\activate
|
|
98
|
+
pip install -r requirements.txt
|
|
99
|
+
playwright install chromium
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## ▶️ Uruchomienie
|
|
103
|
+
|
|
104
|
+
```cmd
|
|
105
|
+
start_bot.bat
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Lub ręcznie:
|
|
109
|
+
```cmd
|
|
110
|
+
.venv\Scripts\activate
|
|
111
|
+
python -m bot.main
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🎮 Komendy bota
|
|
115
|
+
|
|
116
|
+
| Komenda | Opis |
|
|
117
|
+
|---------|------|
|
|
118
|
+
| `/start` | Powitanie i menu główne |
|
|
119
|
+
| `/search` | Rozpocznij nowe wyszukiwanie |
|
|
120
|
+
| `/filters` | Ustaw filtry (województwo, miasto, cena) |
|
|
121
|
+
| `/saved` | Pokaż zapisane ogłoszenia |
|
|
122
|
+
| `/settings` | Ustawienia użytkownika |
|
|
123
|
+
| `/help` | Pomoc |
|
|
124
|
+
|
|
125
|
+
## 📁 Struktura projektu
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
udzialy-bot/
|
|
129
|
+
├── bot/
|
|
130
|
+
│ ├── __init__.py
|
|
131
|
+
│ ├── main.py # Entry point, dispatcher
|
|
132
|
+
│ ├── config.py # Pydantic Settings
|
|
133
|
+
│ ├── routers/
|
|
134
|
+
│ │ ├── __init__.py # Router registration
|
|
135
|
+
│ │ ├── search.py # /search command
|
|
136
|
+
│ │ ├── results.py # Pagination
|
|
137
|
+
│ │ ├── filters.py # FSM filters
|
|
138
|
+
│ │ ├── saved.py # Saved listings
|
|
139
|
+
│ │ └── settings.py # User settings
|
|
140
|
+
│ ├── keyboards/
|
|
141
|
+
│ │ ├── __init__.py
|
|
142
|
+
│ │ ├── inline.py # Inline keyboards
|
|
143
|
+
│ │ └── reply.py # Reply keyboards
|
|
144
|
+
│ └── middlewares/
|
|
145
|
+
│ ├── __init__.py
|
|
146
|
+
│ └── throttle.py # Anti-flood
|
|
147
|
+
├── config.yaml # Configuration
|
|
148
|
+
├── requirements.txt # Dependencies
|
|
149
|
+
├── pyproject.toml # Project metadata
|
|
150
|
+
├── install.bat # Windows installer
|
|
151
|
+
├── start_bot.bat # Windows launcher
|
|
152
|
+
├── .gitignore
|
|
153
|
+
└── README.md
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## ⚙️ Konfiguracja
|
|
157
|
+
|
|
158
|
+
Plik `config.yaml` zawiera pełną konfigurację bota. Wartości można nadpisać zmiennymi środowiskowymi z prefiksem `UDZIALY_`.
|
|
159
|
+
|
|
160
|
+
## 🔒 Bezpieczeństwo
|
|
161
|
+
|
|
162
|
+
- Bot działa w trybie single-user (tylko owner_id)
|
|
163
|
+
- Tor zapewnia anonimowość przy scrapowaniu
|
|
164
|
+
- Rotacja User-Agent i fingerprint
|
|
165
|
+
- Rate limiting na portalach
|
|
166
|
+
|
|
167
|
+
## 📄 Licencja
|
|
168
|
+
|
|
169
|
+
MIT License — do użytku osobistego.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# 🏠 Udziały Bot
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://docs.aiogram.dev/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[]()
|
|
7
|
+
|
|
8
|
+
**Telegram bot do wyszukiwania ogłoszeń sprzedaży udziałów w nieruchomościach** na 9 polskich portalach ogłoszeniowych.
|
|
9
|
+
|
|
10
|
+
Bot automatycznie przeszukuje portale nieruchomości w poszukiwaniu ofert sprzedaży udziałów (ułamkowych części własności) i prezentuje wyniki bezpośrednio w Telegramie.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ✨ Funkcje
|
|
15
|
+
|
|
16
|
+
- 🔍 Przeszukiwanie 9 portali nieruchomości jednocześnie
|
|
17
|
+
- 🗺️ Filtrowanie po województwie, mieście, promieniu i cenie
|
|
18
|
+
- 📄 Paginacja wyników z klawiaturą inline
|
|
19
|
+
- 💾 Zapisywanie interesujących ogłoszeń
|
|
20
|
+
- 🛡️ Rotacja proxy przez Tor dla anonimowości
|
|
21
|
+
- ⚡ Asynchroniczne scrapowanie (httpx, curl_cffi, nodriver)
|
|
22
|
+
- 🧠 FSM do konfiguracji filtrów krok po kroku
|
|
23
|
+
- 🚦 Anti-flood middleware
|
|
24
|
+
|
|
25
|
+
## 📋 Wymagania
|
|
26
|
+
|
|
27
|
+
- Python 3.11 lub nowszy
|
|
28
|
+
- Windows 10/11 (skrypty .bat)
|
|
29
|
+
- Konto Telegram + token bota z [@BotFather](https://t.me/BotFather)
|
|
30
|
+
|
|
31
|
+
## 🚀 Instalacja (Windows)
|
|
32
|
+
|
|
33
|
+
### Automatyczna (zalecana)
|
|
34
|
+
|
|
35
|
+
1. Sklonuj repozytorium:
|
|
36
|
+
```cmd
|
|
37
|
+
git clone https://github.com/your-user/udzialy-bot.git
|
|
38
|
+
cd udzialy-bot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. Uruchom instalator:
|
|
42
|
+
```cmd
|
|
43
|
+
install.bat
|
|
44
|
+
```
|
|
45
|
+
Skrypt automatycznie:
|
|
46
|
+
- Sprawdzi wersję Pythona
|
|
47
|
+
- Utworzy środowisko wirtualne (venv)
|
|
48
|
+
- Zainstaluje zależności
|
|
49
|
+
- Pobierze Tor Expert Bundle
|
|
50
|
+
- Zainstaluje przeglądarki Playwright
|
|
51
|
+
|
|
52
|
+
3. Skonfiguruj bota — edytuj `config.yaml`:
|
|
53
|
+
```yaml
|
|
54
|
+
telegram:
|
|
55
|
+
token: "YOUR_BOT_TOKEN_HERE"
|
|
56
|
+
owner_id: 123456789
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Ręczna
|
|
60
|
+
|
|
61
|
+
```cmd
|
|
62
|
+
python -m venv .venv
|
|
63
|
+
.venv\Scripts\activate
|
|
64
|
+
pip install -r requirements.txt
|
|
65
|
+
playwright install chromium
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## ▶️ Uruchomienie
|
|
69
|
+
|
|
70
|
+
```cmd
|
|
71
|
+
start_bot.bat
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Lub ręcznie:
|
|
75
|
+
```cmd
|
|
76
|
+
.venv\Scripts\activate
|
|
77
|
+
python -m bot.main
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 🎮 Komendy bota
|
|
81
|
+
|
|
82
|
+
| Komenda | Opis |
|
|
83
|
+
|---------|------|
|
|
84
|
+
| `/start` | Powitanie i menu główne |
|
|
85
|
+
| `/search` | Rozpocznij nowe wyszukiwanie |
|
|
86
|
+
| `/filters` | Ustaw filtry (województwo, miasto, cena) |
|
|
87
|
+
| `/saved` | Pokaż zapisane ogłoszenia |
|
|
88
|
+
| `/settings` | Ustawienia użytkownika |
|
|
89
|
+
| `/help` | Pomoc |
|
|
90
|
+
|
|
91
|
+
## 📁 Struktura projektu
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
udzialy-bot/
|
|
95
|
+
├── bot/
|
|
96
|
+
│ ├── __init__.py
|
|
97
|
+
│ ├── main.py # Entry point, dispatcher
|
|
98
|
+
│ ├── config.py # Pydantic Settings
|
|
99
|
+
│ ├── routers/
|
|
100
|
+
│ │ ├── __init__.py # Router registration
|
|
101
|
+
│ │ ├── search.py # /search command
|
|
102
|
+
│ │ ├── results.py # Pagination
|
|
103
|
+
│ │ ├── filters.py # FSM filters
|
|
104
|
+
│ │ ├── saved.py # Saved listings
|
|
105
|
+
│ │ └── settings.py # User settings
|
|
106
|
+
│ ├── keyboards/
|
|
107
|
+
│ │ ├── __init__.py
|
|
108
|
+
│ │ ├── inline.py # Inline keyboards
|
|
109
|
+
│ │ └── reply.py # Reply keyboards
|
|
110
|
+
│ └── middlewares/
|
|
111
|
+
│ ├── __init__.py
|
|
112
|
+
│ └── throttle.py # Anti-flood
|
|
113
|
+
├── config.yaml # Configuration
|
|
114
|
+
├── requirements.txt # Dependencies
|
|
115
|
+
├── pyproject.toml # Project metadata
|
|
116
|
+
├── install.bat # Windows installer
|
|
117
|
+
├── start_bot.bat # Windows launcher
|
|
118
|
+
├── .gitignore
|
|
119
|
+
└── README.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## ⚙️ Konfiguracja
|
|
123
|
+
|
|
124
|
+
Plik `config.yaml` zawiera pełną konfigurację bota. Wartości można nadpisać zmiennymi środowiskowymi z prefiksem `UDZIALY_`.
|
|
125
|
+
|
|
126
|
+
## 🔒 Bezpieczeństwo
|
|
127
|
+
|
|
128
|
+
- Bot działa w trybie single-user (tylko owner_id)
|
|
129
|
+
- Tor zapewnia anonimowość przy scrapowaniu
|
|
130
|
+
- Rotacja User-Agent i fingerprint
|
|
131
|
+
- Rate limiting na portalach
|
|
132
|
+
|
|
133
|
+
## 📄 Licencja
|
|
134
|
+
|
|
135
|
+
MIT License — do użytku osobistego.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bot configuration — loads config.yaml relative to project root.
|
|
3
|
+
|
|
4
|
+
Exposes a Settings dataclass with: telegram_token, owner_id, portals dict,
|
|
5
|
+
tor config, scraping timeouts. Uses pydantic-settings with YAML source.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# --- Project root detection ---
|
|
19
|
+
|
|
20
|
+
def _find_project_root() -> Path:
|
|
21
|
+
"""Find project root by looking for config.yaml upward from this file."""
|
|
22
|
+
current = Path(__file__).resolve().parent # bot/
|
|
23
|
+
for parent in [current.parent, current.parent.parent, Path.cwd()]:
|
|
24
|
+
if (parent / "config.yaml").exists():
|
|
25
|
+
return parent
|
|
26
|
+
# Fallback: assume CWD is project root
|
|
27
|
+
return Path.cwd()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
PROJECT_ROOT = _find_project_root()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# --- Sub-models ---
|
|
34
|
+
|
|
35
|
+
class TelegramConfig(BaseModel):
|
|
36
|
+
"""Telegram bot settings."""
|
|
37
|
+
token: str = "YOUR_BOT_TOKEN_HERE"
|
|
38
|
+
owner_id: int = 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PortalEntry(BaseModel):
|
|
42
|
+
"""Single portal configuration."""
|
|
43
|
+
enabled: bool = True
|
|
44
|
+
base_url: str = ""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PortalsConfig(BaseModel):
|
|
48
|
+
"""All portal switches — dynamic dict of portal entries."""
|
|
49
|
+
model_config = {"extra": "allow"}
|
|
50
|
+
|
|
51
|
+
otodom: PortalEntry = PortalEntry()
|
|
52
|
+
olx: PortalEntry = PortalEntry()
|
|
53
|
+
gratka: PortalEntry = PortalEntry()
|
|
54
|
+
morizon: PortalEntry = PortalEntry()
|
|
55
|
+
nieruchomosci_online: PortalEntry = PortalEntry()
|
|
56
|
+
domiporta: PortalEntry = PortalEntry()
|
|
57
|
+
lento: PortalEntry = PortalEntry()
|
|
58
|
+
gethome: PortalEntry = PortalEntry()
|
|
59
|
+
ogloszenia24: PortalEntry = PortalEntry(enabled=False)
|
|
60
|
+
|
|
61
|
+
def enabled_portals(self) -> list[str]:
|
|
62
|
+
"""Return list of enabled portal names."""
|
|
63
|
+
result = []
|
|
64
|
+
for name in self.model_fields:
|
|
65
|
+
entry = getattr(self, name, None)
|
|
66
|
+
if isinstance(entry, PortalEntry) and entry.enabled:
|
|
67
|
+
result.append(name)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
def as_dict(self) -> Dict[str, Dict[str, Any]]:
|
|
71
|
+
"""Return portals as plain dict."""
|
|
72
|
+
result = {}
|
|
73
|
+
for name in self.model_fields:
|
|
74
|
+
entry = getattr(self, name, None)
|
|
75
|
+
if isinstance(entry, PortalEntry):
|
|
76
|
+
result[name] = {"enabled": entry.enabled, "base_url": entry.base_url}
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ScrapingConfig(BaseModel):
|
|
81
|
+
"""Scraping behavior settings."""
|
|
82
|
+
timeout: int = 30
|
|
83
|
+
max_concurrent: int = 3
|
|
84
|
+
retry_count: int = 2
|
|
85
|
+
retry_delay: int = 5
|
|
86
|
+
delay_between: int = 2
|
|
87
|
+
user_agent_rotate: bool = True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TorConfig(BaseModel):
|
|
91
|
+
"""Tor proxy settings."""
|
|
92
|
+
enabled: bool = True
|
|
93
|
+
socks_port: int = 9050
|
|
94
|
+
control_port: int = 9051
|
|
95
|
+
control_password: str = "udzialy2026"
|
|
96
|
+
circuit_rotate_interval: int = 300
|
|
97
|
+
binary_path: str = "tor/tor.exe"
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def proxy_url(self) -> str:
|
|
101
|
+
"""SOCKS5 proxy URL for httpx."""
|
|
102
|
+
return f"socks5://127.0.0.1:{self.socks_port}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DatabaseConfig(BaseModel):
|
|
106
|
+
"""Database settings."""
|
|
107
|
+
path: str = "data/udzialy.db"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class LLMConfig(BaseModel):
|
|
111
|
+
"""LLM analysis settings (optional)."""
|
|
112
|
+
enabled: bool = False
|
|
113
|
+
provider: str = "openai" # openai, anthropic, local
|
|
114
|
+
api_key: str = ""
|
|
115
|
+
model: str = "gpt-4o-mini"
|
|
116
|
+
base_url: str = "https://api.openai.com/v1"
|
|
117
|
+
max_concurrent: int = 5
|
|
118
|
+
timeout: int = 15
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class LoggingConfig(BaseModel):
|
|
122
|
+
"""Logging settings."""
|
|
123
|
+
level: str = "INFO"
|
|
124
|
+
file: str = "data/bot.log"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# --- Main Settings ---
|
|
128
|
+
|
|
129
|
+
class Settings(BaseSettings):
|
|
130
|
+
"""
|
|
131
|
+
Application settings loaded from config.yaml + environment variables.
|
|
132
|
+
|
|
133
|
+
Environment variables override YAML values with prefix UDZIALY_.
|
|
134
|
+
Example: UDZIALY_TELEGRAM__TOKEN=xxx overrides telegram.token.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
model_config = SettingsConfigDict(
|
|
138
|
+
env_prefix="UDZIALY_",
|
|
139
|
+
env_nested_delimiter="__",
|
|
140
|
+
case_sensitive=False,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
telegram: TelegramConfig = TelegramConfig()
|
|
144
|
+
portals: PortalsConfig = PortalsConfig()
|
|
145
|
+
scraping: ScrapingConfig = ScrapingConfig()
|
|
146
|
+
tor: TorConfig = TorConfig()
|
|
147
|
+
database: DatabaseConfig = DatabaseConfig()
|
|
148
|
+
llm: LLMConfig = LLMConfig()
|
|
149
|
+
logging: LoggingConfig = LoggingConfig()
|
|
150
|
+
|
|
151
|
+
# Convenience properties
|
|
152
|
+
@property
|
|
153
|
+
def telegram_token(self) -> str:
|
|
154
|
+
return self.telegram.token
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def owner_id(self) -> int:
|
|
158
|
+
return self.telegram.owner_id
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_yaml(cls, path: str | Path | None = None) -> "Settings":
|
|
162
|
+
"""Load settings from YAML file, then apply env var overrides."""
|
|
163
|
+
if path is None:
|
|
164
|
+
path = PROJECT_ROOT / "config.yaml"
|
|
165
|
+
config_path = Path(path)
|
|
166
|
+
yaml_data: dict[str, Any] = {}
|
|
167
|
+
|
|
168
|
+
if config_path.exists():
|
|
169
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
170
|
+
yaml_data = yaml.safe_load(f) or {}
|
|
171
|
+
|
|
172
|
+
return cls(**yaml_data)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# --- Singleton ---
|
|
176
|
+
|
|
177
|
+
_settings: Settings | None = None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get_settings(config_path: str | Path | None = None) -> Settings:
|
|
181
|
+
"""Get or create settings singleton."""
|
|
182
|
+
global _settings
|
|
183
|
+
if _settings is None:
|
|
184
|
+
_settings = Settings.from_yaml(config_path)
|
|
185
|
+
return _settings
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def reset_settings() -> None:
|
|
189
|
+
"""Reset singleton (for testing)."""
|
|
190
|
+
global _settings
|
|
191
|
+
_settings = None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Keyboards package — inline and reply keyboard builders.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from bot.keyboards.inline import (
|
|
6
|
+
build_results_keyboard,
|
|
7
|
+
build_search_progress_keyboard,
|
|
8
|
+
build_listing_detail_keyboard,
|
|
9
|
+
build_voivodeship_keyboard,
|
|
10
|
+
build_radius_keyboard,
|
|
11
|
+
build_filter_summary_keyboard,
|
|
12
|
+
build_saved_keyboard,
|
|
13
|
+
build_settings_keyboard,
|
|
14
|
+
)
|
|
15
|
+
from bot.keyboards.reply import main_menu_keyboard
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"build_results_keyboard",
|
|
19
|
+
"build_search_progress_keyboard",
|
|
20
|
+
"build_listing_detail_keyboard",
|
|
21
|
+
"build_voivodeship_keyboard",
|
|
22
|
+
"build_radius_keyboard",
|
|
23
|
+
"build_filter_summary_keyboard",
|
|
24
|
+
"build_saved_keyboard",
|
|
25
|
+
"build_settings_keyboard",
|
|
26
|
+
"main_menu_keyboard",
|
|
27
|
+
]
|