commit afd2bf330ca3f5ea86ff24f8b9c1a8fbb1829bec Author: admin Date: Wed Apr 29 19:04:25 2026 +0200 Inicjalny commit — Prawicowy Dashboard Co-Authored-By: Claude Sonnet 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8068356 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python cache +__pycache__/ +*.pyc +*.pyo + +# Logi serwera +*.log +/tmp/ + +# Edytor / IDE +.vscode/ +.idea/ +*.swp +*~ + +# Claude Code — ustawienia lokalne (nie wersjonujemy) +.claude/ + +# System +.DS_Store +Thumbs.db +dashboard.db +__pycache__/ +xd.txt +prawicowy_dashboard.tar.gz diff --git a/README.md b/README.md new file mode 100644 index 0000000..e406c02 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Prawicowy Dashboard 🦅 + +> **Wielka Polska wróci, pogonimy Rudego** + +Agregator prawicowych mediów i polityczny dashboard z transmisją na żywo, +interaktywną mapą katastrofy smoleńskiej, grą Sejm Kombat i przeglądem partii politycznych. + +## Zawartość projektu + +| Plik | Opis | +|------|------| +| `agregator.html` | Główna aplikacja — single-page dashboard | +| `server.py` | Serwer HTTP (Python 3, bez zewnętrznych zależności) — proxy RSS + serwowanie plików statycznych | +| `smolensk.html` | Gra Sejm Kombat (osadzona w dashboardzie jako iframe) | +| `tusk.txt` | Satyryczny tekst polityczny | +| `img/` | Zdjęcia polityków i materiały graficzne | + +## Uruchomienie + +```bash +python3 server.py +``` + +Serwer startuje na `http://0.0.0.0:1234`. +Otwórz `http://localhost:1234/agregator.html` w przeglądarce. + +**Wymagania:** Python 3.8+, `curl` (do pobierania RSS) + +## Funkcje dashboardu + +- **Ticker polityków** (góra) — scrollujący pasek ze zdjęciami i cytatami najważniejszych polityków prawicy +- **Transmisje na żywo** — Telewizja Republika + W Polsce 24 (YouTube embed) +- **Radio na żywo** — Radio Maryja + Radio Republika +- **Agregator RSS** — artykuły z Dorzeczy, Niezależna.pl, Radio Maryja, TVN24, TVP Info, Gazeta Wyborcza +- **Smoleńsk** — interaktywna mapa lotu, model Tu-154M, licznik od katastrofy, lista ofiar +- **Partie** — PiS, Koalicja Obywatelska, KKP (Braun), Konfederacja WiN +- **Sejm Kombat** — gra przeglądarkowa 2D +- **Ticker Tusk** (dół) — live news z TV Republika filtrowane po słowie "Tusk" + +## Endpointy API serwera + +| Endpoint | Opis | +|----------|------| +| `GET /api/feeds?groups=republika,wpolsce,...` | Pobiera artykuły z RSS dla podanych grup | +| `GET /api/feed?url=` | Proxy dla pojedynczego feed RSS | +| `GET /api/radio/maryja` | Proxy strumienia audio Radio Maryja | + +## Licencja + +Projekt prywatny. Wszelkie prawa zastrzeżone. diff --git a/app.py b/app.py new file mode 100644 index 0000000..f5e4134 --- /dev/null +++ b/app.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 +""" +Prawicowy Dashboard — Flask backend +Port 1234 | Static files + RSS proxy + Admin panel (/admin/) + Auth (/login) +""" +import os, json, time, threading, socket, subprocess +import xml.etree.ElementTree as ET +from datetime import datetime + +from flask import (Flask, request, jsonify, redirect, url_for, + render_template_string, send_from_directory, abort, Response, flash) +from flask_sqlalchemy import SQLAlchemy +from flask_login import (LoginManager, UserMixin, login_user, logout_user, + login_required, current_user) +from flask_admin import Admin, AdminIndexView, expose +from flask_admin.contrib.sqla import ModelView +from werkzeug.security import generate_password_hash, check_password_hash + +ROOT = os.path.dirname(os.path.abspath(__file__)) +CACHE: dict = {} +CACHE_TTL = 600 +FETCH_TIMEOUT = 12 + +app = Flask(__name__, template_folder=os.path.join(ROOT, 'templates')) +app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'pd-secret-2024-change-me') +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(ROOT, 'dashboard.db') +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db = SQLAlchemy(app) +login_manager = LoginManager(app) +login_manager.login_view = 'auth_login' +login_manager.login_message = 'Wymagane logowanie administratora.' + +# ══════════════════════════════════════════════════════════ +# MODELS +# ══════════════════════════════════════════════════════════ + +class User(db.Model, UserMixin): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(200)) + password_hash = db.Column(db.String(256), nullable=False, default='') + is_admin = db.Column(db.Boolean, default=True) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + last_login = db.Column(db.DateTime) + + def set_password(self, pw): self.password_hash = generate_password_hash(pw) + def check_password(self, pw): return check_password_hash(self.password_hash, pw) + def __str__(self): return self.username + + +class RssSource(db.Model): + __tablename__ = 'rss_sources' + id = db.Column(db.Integer, primary_key=True) + group_id = db.Column(db.String(50), nullable=False, index=True) + name = db.Column(db.String(100), nullable=False) + url = db.Column(db.String(1000), nullable=False) + icon = db.Column(db.String(20), default='📰') + website = db.Column(db.String(500)) + css_class = db.Column(db.String(50), default='src-republika') + dot_id = db.Column(db.String(50)) + category = db.Column(db.String(20), default='main') # main | opposition + active = db.Column(db.Boolean, default=True) + order = db.Column(db.Integer, default=0) + def __str__(self): return f'{self.name} ({self.group_id})' + + +class Politician(db.Model): + __tablename__ = 'politicians' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + role = db.Column(db.String(200)) + img_path = db.Column(db.String(500)) + quote = db.Column(db.Text) + description = db.Column(db.Text) + party = db.Column(db.String(50)) + ticker_visible = db.Column(db.Boolean, default=True) + sidebar_visible = db.Column(db.Boolean, default=False) + popup_key = db.Column(db.String(50)) + order = db.Column(db.Integer, default=0) + active = db.Column(db.Boolean, default=True) + def __str__(self): return self.name + + +class PartyMember(db.Model): + __tablename__ = 'party_members' + id = db.Column(db.Integer, primary_key=True) + party_slug = db.Column(db.String(50), nullable=False, index=True) + section = db.Column(db.String(200), default='') + name = db.Column(db.String(100), nullable=False) + role = db.Column(db.String(200)) + img_url = db.Column(db.String(1000)) + initials = db.Column(db.String(10)) + bg_color = db.Column(db.String(20)) + description = db.Column(db.Text) + desc_type = db.Column(db.String(10), default='merit') # merit | fail + married = db.Column(db.Boolean, default=False) + children = db.Column(db.Integer, default=0) + religious = db.Column(db.Boolean, default=False) + is_leader = db.Column(db.Boolean, default=False) + order = db.Column(db.Integer, default=0) + active = db.Column(db.Boolean, default=True) + def __str__(self): return f'{self.name} ({self.party_slug})' + + +class SiteSetting(db.Model): + __tablename__ = 'site_settings' + id = db.Column(db.Integer, primary_key=True) + key = db.Column(db.String(100), unique=True, nullable=False) + value = db.Column(db.Text, default='') + description = db.Column(db.String(500)) + def __str__(self): return self.key + + +@login_manager.user_loader +def load_user(uid): return db.session.get(User, int(uid)) + + +# ══════════════════════════════════════════════════════════ +# ADMIN VIEWS +# ══════════════════════════════════════════════════════════ + +class _Auth: + def is_accessible(self): + return current_user.is_authenticated and current_user.is_admin + def inaccessible_callback(self, name, **kwargs): + return redirect(url_for('auth_login', next=request.url)) + + +class UserAdmin(_Auth, ModelView): + column_labels = dict(username='Użytkownik', email='Email', is_admin='Admin', + created_at='Utworzony', last_login='Ostatnie logowanie') + column_list = ['username', 'email', 'is_admin', 'created_at', 'last_login'] + column_searchable_list = ['username', 'email'] + column_filters = ['is_admin'] + form_excluded_columns = ['password_hash', 'created_at', 'last_login'] + form_widget_args = {'email': {'style': 'width:100%'}} + + def on_model_change(self, form, model, is_created): + pw = (form.new_password.data or '').strip() if hasattr(form, 'new_password') else '' + if pw: + model.set_password(pw) + elif is_created and not model.password_hash: + model.set_password('admin123') + + def scaffold_form(self): + from wtforms import PasswordField + form_class = super().scaffold_form() + form_class.new_password = PasswordField('Nowe hasło (puste = bez zmiany)') + return form_class + + +class RssSourceAdmin(_Auth, ModelView): + column_labels = dict(group_id='Grupa', name='Nazwa', url='URL feed', icon='Ikona', + website='Strona www', css_class='Klasa CSS', dot_id='ID wskaźnika', + category='Kategoria', active='Aktywne', order='Kolejność') + column_list = ['name', 'group_id', 'url', 'icon', 'website', 'category', 'active', 'order'] + column_searchable_list = ['name', 'url', 'group_id'] + column_filters = ['group_id', 'active', 'category'] + column_editable_list = ['active', 'order', 'name'] + form_widget_args = {'url': {'style': 'width:100%'}, 'website': {'style': 'width:100%'}} + + +class PoliticianAdmin(_Auth, ModelView): + column_labels = dict(name='Imię i nazwisko', role='Stanowisko', img_path='Zdjęcie', + quote='Cytat (ticker)', description='Opis', party='Partia', + ticker_visible='Pasek górny', sidebar_visible='Sidebar', + popup_key='Klucz popup', order='Kolejność', active='Aktywny') + column_list = ['name', 'role', 'party', 'ticker_visible', 'sidebar_visible', 'order', 'active'] + column_searchable_list = ['name', 'role', 'party'] + column_filters = ['party', 'ticker_visible', 'sidebar_visible', 'active'] + column_editable_list = ['ticker_visible', 'sidebar_visible', 'order', 'active'] + form_widget_args = {'quote': {'rows': 3}, 'description': {'rows': 5}, + 'img_path': {'style': 'width:100%'}} + + +class PartyMemberAdmin(_Auth, ModelView): + column_labels = dict(party_slug='Partia', section='Sekcja', name='Imię i nazwisko', + role='Funkcja', img_url='URL zdjęcia', description='Opis', + desc_type='Typ (merit/fail)', is_leader='Lider', + order='Kolejność', active='Aktywny') + column_list = ['name', 'party_slug', 'section', 'role', 'desc_type', 'is_leader', 'order', 'active'] + column_searchable_list = ['name', 'role', 'party_slug', 'section'] + column_filters = ['party_slug', 'desc_type', 'is_leader', 'active'] + column_editable_list = ['order', 'active', 'is_leader'] + form_widget_args = {'description': {'rows': 6}, 'img_url': {'style': 'width:100%'}} + + +class SettingAdmin(_Auth, ModelView): + column_labels = dict(key='Klucz', value='Wartość', description='Opis') + column_list = ['key', 'value', 'description'] + column_searchable_list = ['key', 'description'] + column_editable_list = ['value'] + form_widget_args = {'value': {'rows': 3, 'style': 'width:100%'}, + 'description': {'style': 'width:100%'}} + + +class HomeAdmin(_Auth, AdminIndexView): + @expose('/') + def index(self): + if not current_user.is_authenticated: + return redirect(url_for('auth_login')) + stats = dict( + users = User.query.count(), + sources = RssSource.query.filter_by(active=True).count(), + politicians = Politician.query.filter_by(active=True).count(), + party_members = PartyMember.query.filter_by(active=True).count(), + ) + return self.render('admin/index.html', stats=stats) + + +admin_panel = Admin( + app, + name='🦅 Prawicowy Dashboard — Admin', + index_view=HomeAdmin(), +) +admin_panel.add_view(RssSourceAdmin( RssSource, db, name='📡 Źródła RSS', category='📰 Treści', endpoint='rss_source')) +admin_panel.add_view(PoliticianAdmin(Politician, db, name='🏛 Politycy (ticker)', category='📰 Treści', endpoint='politician')) +admin_panel.add_view(PartyMemberAdmin(PartyMember, db, name='⚡ Posłowie partii', category='📰 Treści', endpoint='party_member')) +admin_panel.add_view(SettingAdmin( SiteSetting, db, name='⚙️ Ustawienia', category='⚙️ System', endpoint='site_setting')) +admin_panel.add_view(UserAdmin( User, db, name='👤 Użytkownicy', category='⚙️ System', endpoint='user')) + + +# ══════════════════════════════════════════════════════════ +# AUTH +# ══════════════════════════════════════════════════════════ + +LOGIN_HTML = ''' + + +Logowanie — Prawicowy Dashboard Admin + + +
+🦅 +

Panel Administracyjny

+
Prawicowy Dashboard
+{% if error %}
{{ error }}
{% endif %} +
+ + + + + +
+← Wróć do Dashboardu +
''' + + +@app.route('/login', methods=['GET', 'POST']) +def auth_login(): + if current_user.is_authenticated: + return redirect(url_for('admin.index')) + error = None + if request.method == 'POST': + u = User.query.filter_by(username=request.form.get('username', '').strip()).first() + if u and u.check_password(request.form.get('password', '')): + u.last_login = datetime.utcnow() + db.session.commit() + login_user(u) + return redirect(request.args.get('next') or url_for('admin.index')) + error = 'Nieprawidłowy użytkownik lub hasło.' + return render_template_string(LOGIN_HTML, error=error) + + +@app.route('/logout') +@login_required +def auth_logout(): + logout_user() + return redirect(url_for('auth_login')) + + +# ══════════════════════════════════════════════════════════ +# RSS FETCH / PARSE (ported from server.py) +# ══════════════════════════════════════════════════════════ + +def fetch_url(url: str) -> bytes: + now = time.time() + if url in CACHE and now - CACHE[url][0] < CACHE_TTL: + return CACHE[url][1] + result = subprocess.run( + ['curl', '-sL', '--max-time', str(FETCH_TIMEOUT), + '-A', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + '-H', 'Accept: application/rss+xml,application/xml,text/xml,*/*', + '-H', 'Accept-Language: pl-PL,pl;q=0.9', + '--compressed', url], + capture_output=True, timeout=FETCH_TIMEOUT + 3 + ) + if result.returncode != 0: + raise RuntimeError(f'curl {result.returncode}: {result.stderr.decode()[:80]}') + data = result.stdout + if not data: + raise RuntimeError('empty response') + CACHE[url] = (now, data) + return data + + +def _t(el, tag): + f = el.find(tag) + return (f.text or '').strip() if f is not None and f.text else '' + + +def strip_tags(html): + if not html: return '' + import re + return re.sub(r'<[^>]+>', '', html).replace('&', '&').replace('<', '<') \ + .replace('>', '>').replace(' ', ' ').replace(''', "'").strip() + + +def extract_img(html): + if not html: return '' + import re + m = re.search(r']+src=["\']([^"\']+)["\']', html, re.I) + return m.group(1) if m else '' + + +def parse_rss(xml_bytes: bytes) -> list: + try: + root = ET.fromstring(xml_bytes) + except ET.ParseError: + text = xml_bytes.decode('utf-8', errors='replace').lstrip('') + root = ET.fromstring(text) + items = [] + tag = root.tag.lower() + if 'rss' in tag or root.find('channel') is not None: + for item in root.iter('item'): + desc = _t(item, 'description') or _t(item, '{http://purl.org/rss/1.0/modules/content/}encoded') + thumb = '' + mt = item.find('{http://search.yahoo.com/mrss/}thumbnail') + if mt is not None: thumb = mt.get('url', '') + enc = item.find('enclosure') + if not thumb and enc is not None and 'image' in (enc.get('type') or ''): + thumb = enc.get('url', '') + if not thumb: thumb = extract_img(desc) + items.append({'title': strip_tags(_t(item, 'title')), + 'link': _t(item, 'link') or _t(item, '{http://www.w3.org/2005/Atom}link'), + 'summary': strip_tags(desc)[:300], + 'date': _t(item, 'pubDate') or _t(item, '{http://purl.org/dc/elements/1.1/}date'), + 'thumb': thumb}) + elif 'feed' in tag: + for entry in root.iter('{http://www.w3.org/2005/Atom}entry'): + lel = entry.find('{http://www.w3.org/2005/Atom}link[@rel="alternate"]') \ + or entry.find('{http://www.w3.org/2005/Atom}link') + desc = _t(entry, '{http://www.w3.org/2005/Atom}summary') \ + or _t(entry, '{http://www.w3.org/2005/Atom}content') + items.append({'title': strip_tags(_t(entry, '{http://www.w3.org/2005/Atom}title')), + 'link': lel.get('href', '') if lel is not None else '', + 'summary': strip_tags(desc)[:300], + 'date': _t(entry, '{http://www.w3.org/2005/Atom}updated') or + _t(entry, '{http://www.w3.org/2005/Atom}published'), + 'thumb': extract_img(desc)}) + return items[:30] + + +def fetch_group_urls(group_id: str, urls: list) -> dict: + for url in urls: + try: + data = fetch_url(url) + items = parse_rss(data) + if items: + return {'status': 'ok', 'source': group_id, 'items': items} + except Exception as e: + print(f'[{group_id}] {url}: {e}') + return {'status': 'error', 'source': group_id, 'items': []} + + +# ══════════════════════════════════════════════════════════ +# API ROUTES +# ══════════════════════════════════════════════════════════ + +@app.route('/api/feeds') +def api_feeds(): + qs_groups = request.args.get('groups', '') + req_ids = [g.strip() for g in qs_groups.split(',') if g.strip()] + + q = RssSource.query.filter_by(active=True) + if req_ids: + q = q.filter(RssSource.group_id.in_(req_ids)) + rows = q.order_by(RssSource.order).all() + + # Build group_id → [urls] map + feeds: dict = {} + for r in rows: + feeds.setdefault(r.group_id, []).append(r.url) + + results: dict = {} + lock = threading.Lock() + + def worker(gid, urls): + res = fetch_group_urls(gid, urls) + with lock: + results[gid] = res + + threads = [threading.Thread(target=worker, args=(g, u)) for g, u in feeds.items()] + for t in threads: t.start() + for t in threads: t.join(timeout=15) + + return jsonify(results) + + +@app.route('/api/feed') +def api_feed(): + url = request.args.get('url', '') + if not url: + return jsonify({'status': 'error', 'message': 'Missing url'}), 400 + try: + data = fetch_url(url) + items = parse_rss(data) + return jsonify({'status': 'ok', 'items': items}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}) + + +@app.route('/api/sources') +def api_sources(): + rows = RssSource.query.filter_by(active=True).order_by(RssSource.order).all() + seen, result = set(), [] + for r in rows: + if r.group_id not in seen: + seen.add(r.group_id) + result.append({'id': r.group_id, 'group_id': r.group_id, 'name': r.name, + 'icon': r.icon, 'website': r.website or '', + 'css_class': r.css_class or 'src-republika', + 'dot_id': r.dot_id or f'dot-{r.group_id}', + 'category': r.category or 'main'}) + return jsonify(result) + + +@app.route('/api/politicians') +def api_politicians(): + q = Politician.query.filter_by(active=True) + if request.args.get('ticker', '1') == '1': + q = q.filter_by(ticker_visible=True) + if request.args.get('sidebar', '0') == '1': + q = q.filter_by(sidebar_visible=True) + pols = q.order_by(Politician.order).all() + return jsonify([{'id': p.id, 'name': p.name, 'role': p.role or '', + 'img_path': p.img_path or '', 'quote': p.quote or '', + 'description': p.description or '', 'party': p.party or ''} + for p in pols]) + + +@app.route('/api/party/') +def api_party(slug): + members = (PartyMember.query + .filter_by(party_slug=slug, active=True) + .order_by(PartyMember.order).all()) + if not members: + return jsonify({'error': 'not found'}), 404 + + leader = next((m for m in members if m.is_leader), None) + + sections: dict = {} + for m in members: + if m.is_leader: + continue + sec = m.section or 'Inne' + sections.setdefault(sec, []).append({ + 'name': m.name, 'role': m.role or '', 'img': m.img_url or '', + 'initials': m.initials or '', 'bg': m.bg_color or '', + 'married': m.married, 'children': m.children, 'religious': m.religious, + 'description': m.description or '', 'desc_type': m.desc_type or 'merit', + }) + + return jsonify({ + 'slug': slug, + 'desc_type': leader.desc_type if leader else 'merit', + 'leader': { + 'name': leader.name, 'role': leader.role or '', 'img': leader.img_url or '', + 'married': leader.married, 'children': leader.children, 'religious': leader.religious, + 'description': leader.description or '', 'desc_type': leader.desc_type or 'merit', + } if leader else None, + 'sections': [{'label': lbl, 'members': mems} for lbl, mems in sections.items()], + }) + + +@app.route('/api/settings') +def api_settings(): + return jsonify({s.key: s.value for s in SiteSetting.query.all()}) + + +@app.route('/api/radio/maryja') +def api_radio_maryja(): + RHOST, RPORT = '51.68.135.155', 80 + + def stream(): + try: + sock = socket.create_connection((RHOST, RPORT), timeout=10) + sock.sendall(('GET /stream HTTP/1.0\r\nHost: 51.68.135.155\r\n' + 'User-Agent: Mozilla/5.0\r\nIcy-MetaData: 0\r\n\r\n').encode()) + buf = b'' + while b'\r\n\r\n' not in buf: + chunk = sock.recv(1024) + if not chunk: return + buf += chunk + left = buf.split(b'\r\n\r\n', 1) + if len(left) > 1 and left[1]: yield left[1] + while True: + chunk = sock.recv(4096) + if not chunk: break + yield chunk + sock.close() + except Exception as e: + print(f'[radio] {e}') + + return Response(stream(), mimetype='audio/aac', + headers={'Cache-Control': 'no-cache', 'Access-Control-Allow-Origin': '*'}) + + +# ══════════════════════════════════════════════════════════ +# STATIC FILES +# ══════════════════════════════════════════════════════════ + +@app.route('/') +def index(): + return send_from_directory(ROOT, 'agregator.html') + + +@app.route('/') +def static_files(path): + try: + return send_from_directory(ROOT, path) + except Exception: + abort(404) + + +# ══════════════════════════════════════════════════════════ +# MAIN +# ══════════════════════════════════════════════════════════ + +if __name__ == '__main__': + os.chdir(ROOT) + with app.app_context(): + db.create_all() + # Create default admin if DB is empty + if not User.query.first(): + u = User(username='admin', email='admin@dashboard.local', is_admin=True) + u.set_password('admin123') + db.session.add(u) + db.session.commit() + print('Utworzono domyślnego admina: admin / admin123') + print('Serwer uruchomiony na http://0.0.0.0:1234') + print('Panel admina: http://localhost:1234/admin/') + print('Logowanie: http://localhost:1234/login') + app.run(host='0.0.0.0', port=1234, debug=False, threaded=True) diff --git a/img/arlukowicz.jpg b/img/arlukowicz.jpg new file mode 100644 index 0000000..59f11fe Binary files /dev/null and b/img/arlukowicz.jpg differ diff --git a/img/blaszczak.jpg b/img/blaszczak.jpg new file mode 100644 index 0000000..21f6835 Binary files /dev/null and b/img/blaszczak.jpg differ diff --git a/img/bodnar.jpg b/img/bodnar.jpg new file mode 100644 index 0000000..681b78a Binary files /dev/null and b/img/bodnar.jpg differ diff --git a/img/brudzinski.jpg b/img/brudzinski.jpg new file mode 100644 index 0000000..079830d Binary files /dev/null and b/img/brudzinski.jpg differ diff --git a/img/buda.jpg b/img/buda.jpg new file mode 100644 index 0000000..babd963 Binary files /dev/null and b/img/buda.jpg differ diff --git a/img/budka.jpg b/img/budka.jpg new file mode 100644 index 0000000..4317ac2 Binary files /dev/null and b/img/budka.jpg differ diff --git a/img/czarnecki.jpg b/img/czarnecki.jpg new file mode 100644 index 0000000..a74937b Binary files /dev/null and b/img/czarnecki.jpg differ diff --git a/img/czarnek.jpg b/img/czarnek.jpg new file mode 100644 index 0000000..e01e176 Binary files /dev/null and b/img/czarnek.jpg differ diff --git a/img/dziemianowicz.jpg b/img/dziemianowicz.jpg new file mode 100644 index 0000000..646c19d Binary files /dev/null and b/img/dziemianowicz.jpg differ diff --git a/img/fotyga.jpg b/img/fotyga.jpg new file mode 100644 index 0000000..e145858 Binary files /dev/null and b/img/fotyga.jpg differ diff --git a/img/gawkowski.jpg b/img/gawkowski.jpg new file mode 100644 index 0000000..3d9d0ad Binary files /dev/null and b/img/gawkowski.jpg differ diff --git a/img/glinski.jpg b/img/glinski.jpg new file mode 100644 index 0000000..3113847 Binary files /dev/null and b/img/glinski.jpg differ diff --git a/img/holownia.jpg b/img/holownia.jpg new file mode 100644 index 0000000..86500b1 Binary files /dev/null and b/img/holownia.jpg differ diff --git a/img/jaki.jpg b/img/jaki.jpg new file mode 100644 index 0000000..e3bb477 Binary files /dev/null and b/img/jaki.jpg differ diff --git a/img/jpii.jpg b/img/jpii.jpg new file mode 100644 index 0000000..c1a6397 --- /dev/null +++ b/img/jpii.jpg @@ -0,0 +1,41 @@ + + + +Wikimedia Error + + +
+ + +
+

Error

+ +

Use thumbnail steps listed on https://w.wiki/GHai. Please contact noc@wikimedia.org for further information (a765913)

+
+
+ + diff --git a/img/kaczynski.jpg b/img/kaczynski.jpg new file mode 100644 index 0000000..a26264f Binary files /dev/null and b/img/kaczynski.jpg differ diff --git a/img/kaleta.jpg b/img/kaleta.jpg new file mode 100644 index 0000000..6280c7c Binary files /dev/null and b/img/kaleta.jpg differ diff --git a/img/kaminski.jpg b/img/kaminski.jpg new file mode 100644 index 0000000..5e24d4f Binary files /dev/null and b/img/kaminski.jpg differ diff --git a/img/karczewski.jpg b/img/karczewski.jpg new file mode 100644 index 0000000..250dfb6 Binary files /dev/null and b/img/karczewski.jpg differ diff --git a/img/kempa.jpg b/img/kempa.jpg new file mode 100644 index 0000000..499bbeb Binary files /dev/null and b/img/kempa.jpg differ diff --git a/img/kierwinski.jpg b/img/kierwinski.jpg new file mode 100644 index 0000000..feaa7c8 Binary files /dev/null and b/img/kierwinski.jpg differ diff --git a/img/komorowski.jpg b/img/komorowski.jpg new file mode 100644 index 0000000..b6b3e04 Binary files /dev/null and b/img/komorowski.jpg differ diff --git a/img/kopacz.jpg b/img/kopacz.jpg new file mode 100644 index 0000000..6dad087 Binary files /dev/null and b/img/kopacz.jpg differ diff --git a/img/kosiniakkamysz.jpg b/img/kosiniakkamysz.jpg new file mode 100644 index 0000000..7a6ab21 Binary files /dev/null and b/img/kosiniakkamysz.jpg differ diff --git a/img/kotula.jpg b/img/kotula.jpg new file mode 100644 index 0000000..d260ca8 Binary files /dev/null and b/img/kotula.jpg differ diff --git a/img/kowalczyk_h.jpg b/img/kowalczyk_h.jpg new file mode 100644 index 0000000..e0e517f Binary files /dev/null and b/img/kowalczyk_h.jpg differ diff --git a/img/macierewicz.jpg b/img/macierewicz.jpg new file mode 100644 index 0000000..efaa072 Binary files /dev/null and b/img/macierewicz.jpg differ diff --git a/img/matecki.jpg b/img/matecki.jpg new file mode 100644 index 0000000..ccdcc6c Binary files /dev/null and b/img/matecki.jpg differ diff --git a/img/morawiecki.jpg b/img/morawiecki.jpg new file mode 100644 index 0000000..c5ed27c Binary files /dev/null and b/img/morawiecki.jpg differ diff --git a/img/myrcha.jpg b/img/myrcha.jpg new file mode 100644 index 0000000..2e153f9 Binary files /dev/null and b/img/myrcha.jpg differ diff --git a/img/nawrocki.jpg b/img/nawrocki.jpg new file mode 100644 index 0000000..e4259be Binary files /dev/null and b/img/nawrocki.jpg differ diff --git a/img/nitras.jpg b/img/nitras.jpg new file mode 100644 index 0000000..4868fe3 Binary files /dev/null and b/img/nitras.jpg differ diff --git a/img/nowacka.jpg b/img/nowacka.jpg new file mode 100644 index 0000000..4678f7d Binary files /dev/null and b/img/nowacka.jpg differ diff --git a/img/radiomaryja.png b/img/radiomaryja.png new file mode 100644 index 0000000..f155da2 Binary files /dev/null and b/img/radiomaryja.png differ diff --git a/img/republika.jpg b/img/republika.jpg new file mode 100644 index 0000000..c8898d1 Binary files /dev/null and b/img/republika.jpg differ diff --git a/img/sasin.jpg b/img/sasin.jpg new file mode 100644 index 0000000..fd97d97 Binary files /dev/null and b/img/sasin.jpg differ diff --git a/img/schetyna.jpg b/img/schetyna.jpg new file mode 100644 index 0000000..7a309b3 Binary files /dev/null and b/img/schetyna.jpg differ diff --git a/img/schreiber.jpg b/img/schreiber.jpg new file mode 100644 index 0000000..842feb7 Binary files /dev/null and b/img/schreiber.jpg differ diff --git a/img/siemoniak.jpg b/img/siemoniak.jpg new file mode 100644 index 0000000..852e9dc Binary files /dev/null and b/img/siemoniak.jpg differ diff --git a/img/sienkiewicz_b.jpg b/img/sienkiewicz_b.jpg new file mode 100644 index 0000000..0c63c44 Binary files /dev/null and b/img/sienkiewicz_b.jpg differ diff --git a/img/sikorski.jpg b/img/sikorski.jpg new file mode 100644 index 0000000..b65aacc Binary files /dev/null and b/img/sikorski.jpg differ diff --git a/img/smolensk_crash.jpg b/img/smolensk_crash.jpg new file mode 100644 index 0000000..aa8b980 Binary files /dev/null and b/img/smolensk_crash.jpg differ diff --git a/img/suski.jpg b/img/suski.jpg new file mode 100644 index 0000000..de73d03 Binary files /dev/null and b/img/suski.jpg differ diff --git a/img/szydlo.jpg b/img/szydlo.jpg new file mode 100644 index 0000000..157eff0 Binary files /dev/null and b/img/szydlo.jpg differ diff --git a/img/terlecki.jpg b/img/terlecki.jpg new file mode 100644 index 0000000..b2f0af5 Binary files /dev/null and b/img/terlecki.jpg differ diff --git a/img/trump.jpg b/img/trump.jpg new file mode 100644 index 0000000..b802e24 Binary files /dev/null and b/img/trump.jpg differ diff --git a/img/trump_large.jpg b/img/trump_large.jpg new file mode 100644 index 0000000..d4bdfaf --- /dev/null +++ b/img/trump_large.jpg @@ -0,0 +1,41 @@ + + + +Wikimedia Error + + +
+ + +
+

Error

+ +

Use thumbnail steps listed on https://w.wiki/GHai. Please contact noc@wikimedia.org for further information (a765913)

+
+
+ + diff --git a/img/tusk.jpg b/img/tusk.jpg new file mode 100644 index 0000000..427cc65 Binary files /dev/null and b/img/tusk.jpg differ diff --git a/img/tusk_putin.jpg b/img/tusk_putin.jpg new file mode 100644 index 0000000..c9dc6a2 Binary files /dev/null and b/img/tusk_putin.jpg differ diff --git a/img/tusk_putin_sopot.jpg b/img/tusk_putin_sopot.jpg new file mode 100644 index 0000000..e3eed6d Binary files /dev/null and b/img/tusk_putin_sopot.jpg differ diff --git a/img/wasik.jpg b/img/wasik.jpg new file mode 100644 index 0000000..9d88ebb Binary files /dev/null and b/img/wasik.jpg differ diff --git a/img/witek.jpg b/img/witek.jpg new file mode 100644 index 0000000..516731e Binary files /dev/null and b/img/witek.jpg differ diff --git a/img/wojcik.jpg b/img/wojcik.jpg new file mode 100644 index 0000000..d67ab91 Binary files /dev/null and b/img/wojcik.jpg differ diff --git a/img/wpolsce24.png b/img/wpolsce24.png new file mode 100644 index 0000000..42c35e6 Binary files /dev/null and b/img/wpolsce24.png differ diff --git a/img/wyglad.png b/img/wyglad.png new file mode 100644 index 0000000..14fcf84 Binary files /dev/null and b/img/wyglad.png differ diff --git a/img/wyglad2.png b/img/wyglad2.png new file mode 100644 index 0000000..027b455 Binary files /dev/null and b/img/wyglad2.png differ diff --git a/img/wyglad3.png b/img/wyglad3.png new file mode 100644 index 0000000..0bd2dc5 Binary files /dev/null and b/img/wyglad3.png differ diff --git a/img/ziobro.jpg b/img/ziobro.jpg new file mode 100644 index 0000000..2398c43 Binary files /dev/null and b/img/ziobro.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..8e4d697 --- /dev/null +++ b/index.html @@ -0,0 +1,5160 @@ + + + + + + Prawicowy Dashboard — Wielka Polska wróci, pogonimy Rudego + + + + + + +
+
+
+
+
+ + +
+ +
+ +
+
+ + + +
+
+ Radio Maryja + + NA ŻYWO +
+
+ Radio Republika + + NA ŻYWO +
+
+ + +
+ + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
+
+
+ + +
+ + +
+
+
Pobieranie artykułów...
+
+
+ +
+ + + + + + + + +
+
+ TV Republika +
+
+
+ Pobieranie wiadomości… +
+
+
+ + + + diff --git a/init_db.py b/init_db.py new file mode 100644 index 0000000..3f5c1cd --- /dev/null +++ b/init_db.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Inicjalizacja bazy danych Prawicowy Dashboard. +Uruchom raz: python3 init_db.py +Wypełnia DB danymi z aktualnych wartości hardcoded w JS. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app import app, db, User, RssSource, Politician, PartyMember, SiteSetting + +with app.app_context(): + db.create_all() + + # ── USERS ────────────────────────────────────────────────────────────── + if not User.query.first(): + u = User(username='admin', email='admin@dashboard.local', is_admin=True) + u.set_password('admin123') + db.session.add(u) + print('Użytkownik: admin / admin123 ← ZMIEŃ HASŁO!') + + # ── USTAWIENIA ───────────────────────────────────────────────────────── + settings = [ + ('site_title', 'Prawicowy Dashboard — Wielka Polska wróci, pogonimy Rudego', + 'Tytuł strony widoczny w zakładce przeglądarki i nagłówku'), + ('site_subtitle', 'Agregator prawicowych mediów', + 'Podtytuł/opis strony'), + ('ticker_speed', '75', + 'Prędkość górnego tickera (sekundy na pełen cykl — mniejsza = szybciej)'), + ('news_filter', 'Tusk', + 'Słowo kluczowe do filtrowania dolnego paska newsów'), + ('radio_maryja_url', 'http://51.68.135.155:80/stream', + 'URL strumienia Radio Maryja'), + ] + for key, val, desc in settings: + if not SiteSetting.query.filter_by(key=key).first(): + db.session.add(SiteSetting(key=key, value=val, description=desc)) + + # ── ŹRÓDŁA RSS ───────────────────────────────────────────────────────── + rss = [ + # main sources + ('republika', 'Dorzeczy.pl', 'https://dorzeczy.pl/feed', + '📺', 'https://telewizjarepublika.pl', 'src-republika', 'dot-republika', 'main', 1), + ('wpolsce', 'Niezależna.pl', 'https://niezalezna.pl/feed', + '🎙', 'https://wpolsce.tv', 'src-wpolsce', 'dot-wpolsce', 'main', 2), + ('trwam', 'Radio Maryja / Trwam', 'https://www.radiomaryja.pl/feed/', + '⛪', 'https://trwam.pl', 'src-trwam', 'dot-trwam', 'main', 3), + # opposition + ('opozycja_tvp', 'TVP Info', 'https://tvp.info/feed', + '📡', 'https://tvpinfo.pl', 'src-opozycja', 'dot-tvp', 'opposition', 10), + ('opozycja_tvp', 'TVP Info (backup)', 'https://www.tvp.info/rss', + '📡', 'https://tvpinfo.pl', 'src-opozycja', 'dot-tvp', 'opposition', 11), + ('opozycja_tvn', 'TVN24', 'https://tvn24.pl/wiadomosci-z-kraju,3.xml', + '📺', 'https://tvn24.pl', 'src-opozycja', 'dot-tvn', 'opposition', 12), + ('opozycja_wyborcza', 'Gazeta Wyborcza', 'https://rss.wyborcza.pl/wyborcza.rss', + '📰', 'https://wyborcza.pl', 'src-opozycja', 'dot-wyborcza', 'opposition', 13), + ] + for group_id, name, url, icon, website, css, dot, cat, order in rss: + exists = RssSource.query.filter_by(group_id=group_id, url=url).first() + if not exists: + db.session.add(RssSource(group_id=group_id, name=name, url=url, + icon=icon, website=website, css_class=css, + dot_id=dot, category=cat, active=True, order=order)) + + # ── POLITYCY (ticker górny + sidebar) ────────────────────────────────── + pols = [ + ('Karol Nawrocki', 'Kandydat na Prezydenta RP', 'img/nawrocki.jpg', + 'Polska nie jest niczyją kolonią. Jestem człowiekiem wolnym.', 'pis', True, True, 'nawrocki', 1), + ('Jarosław Kaczyński','Prezes PiS', 'img/kaczynski.jpg', + 'Będziemy bronić polskich rodzin, bo rodzina jest wszystkim.', 'pis', True, True, 'kaczynski', 2), + ('Mateusz Morawiecki','b. Premier RP', 'img/morawiecki.jpg', + 'Polska jest gotowa na każde wyzwanie — gospodarcze i militarne.', 'pis', True, False, None, 3), + ('Zbigniew Ziobro', 'b. Minister Sprawiedliwości', 'img/ziobro.jpg', + 'Prawo musi stać po stronie ofiar, nie chronić przestępców.', 'pis', True, True, 'ziobro', 4), + ('Mariusz Błaszczak', 'Poseł PiS, b. Minister Obrony', 'img/blaszczak.jpg', + 'Bezpieczeństwo Polski jest naszym absolutnym priorytetem.', 'pis', True, True, 'blaszczak', 5), + ('Przemysław Czarnek','Poseł PiS, b. Minister Edukacji', 'img/czarnek.jpg', + 'Rodzina i naród — fundamenty, których bronić będziemy zawsze.', 'pis', True, True, 'czarnek', 6), + ('Beata Szydło', 'Eurodeputowana, b. Premier', 'img/szydlo.jpg', + 'Przyszłość Polski należy do tych, którzy jej wiernie służą.', 'pis', True, False, None, 7), + ('Donald Trump', 'Prezydent USA (47.)', 'img/trump.jpg', + 'Poland is one of the greatest allies the United States has ever had.', 'inne', True, True, 'trump', 8), + ('Jacek Sasin', 'Poseł PiS, b. Wicepremier', 'img/sasin.jpg', + 'Polska energetyka to polska suwerenność i bezpieczeństwo.', 'pis', True, False, None, 9), + ('Joachim Brudziński','Poseł PiS, b. Minister MSWiA', 'img/brudzinski.jpg', + 'Polska silna w Europie, Polska silna w NATO — to nasz cel.', 'pis', True, False, None, 10), + ('Mariusz Kamiński', 'Poseł PiS', 'img/kaminski.jpg', + 'Służyłem Polsce uczciwie. Historia to oceni.', 'pis', True, False, None, 11), + ('Anna Fotyga', 'Eurodeputowana PiS', 'img/fotyga.jpg', + 'Polska musi mieć silny głos w polityce zagranicznej Europy.', 'pis', True, False, None, 12), + ('Ryszard Czarnecki', 'Europoseł PiS', 'img/czarnecki.jpg', + 'Europa potrzebuje silnych państw narodowych, nie federacji.', 'pis', True, False, None, 13), + ('Marek Suski', 'Poseł PiS', 'img/suski.jpg', + 'PiS zawsze stoi po stronie zwykłych Polaków.', 'pis', True, False, None, 14), + ] + for name, role, img, quote, party, ticker, sidebar, popup, order in pols: + if not Politician.query.filter_by(name=name).first(): + db.session.add(Politician(name=name, role=role, img_path=img, quote=quote, + party=party, ticker_visible=ticker, + sidebar_visible=sidebar, popup_key=popup, + order=order, active=True)) + + # ── KKP — KONFEDERACJA KORONY POLSKIEJ ───────────────────────────────── + kkp = [ + # is_leader=True + dict(party_slug='kkp', section='', name='Grzegorz Braun', + role='Lider KKP · Poseł na Sejm RP X kadencji', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/34/photo', + married=True, children=5, religious=True, is_leader=True, order=0, + desc_type='fail', + description='Doktor filozofii, który publicznie kwestionuje fakty naukowe i historyczne. ' + 'Zasłynął ugaszeniem gaśnicą proszkową chanukowej menory w Sejmie (grudzień 2023) — ' + 'czyn potępiony przez wszystkie kluby poselskie i Prezydium Sejmu. ' + 'Regularnie głosi teorie spiskowe o "wielkim resecie", odmawiał uznania, że Niemcy ' + 'wywołały II Wojnę Światową, negował skuteczność szczepień. Wielokrotnie ukarany ' + 'przez Marszałka Sejmu.'), + # Section 1 + dict(party_slug='kkp', section='⚡ Posłowie Konfederacji Korony Polskiej', + name='Roman Fritz', role='Poseł KKP X kadencji', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/81/photo', + married=True, children=1, religious=True, is_leader=False, order=1, + desc_type='fail', + description='Działacz związany ze środowiskami nacjonalistycznymi. W Sejmie głównie ' + 'nieobecny w debacie merytorycznej — sławą zawdzięcza bycie fotografowanym ' + 'obok Brauna. Brak widocznego dorobku legislacyjnego.'), + dict(party_slug='kkp', section='⚡ Posłowie Konfederacji Korony Polskiej', + name='Włodzimierz Skalik', role='Poseł KKP X kadencji', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/340/photo', + married=True, children=2, religious=True, is_leader=False, order=2, + desc_type='fail', + description='Polityk o mało wyrazistym profilu, znany przede wszystkim ze współpracy ' + 'z Braunem. Jego aktywność sejmowa ogranicza się głównie do głosowań ' + 'partyjnych — brak własnych inicjatyw ustawodawczych.'), + dict(party_slug='kkp', section='⚡ Posłowie Konfederacji Korony Polskiej', + name='Sławomir Zawiślak', role='Poseł KKP X kadencji · Były poseł PiS', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/446/photo', + married=True, children=3, religious=True, is_leader=False, order=3, + desc_type='fail', + description='Polityczny wędrowiec — były poseł PiS, który przeszedł do KKP. ' + 'Słynie z gwałtownych zwrotów ideologicznych. Trudno wskazać spójny ' + 'dorobek legislacyjny łączący jego kolejne wcielenia partyjne.'), + # Section 2 — events + dict(party_slug='kkp', section='🔎 Kontrowersyjne dokonania lidera (wybór)', + name='Gaśnica na menorę', role='Sejm RP, grudzień 2023', + img_url='', initials='🧯', bg_color='#7c0000', + married=False, children=0, religious=False, is_leader=False, order=4, + desc_type='fail', + description='Podczas uroczystości chanukowych w Sejmie Braun ugasił zapalony świecznik ' + 'gaśnicą proszkową. Czyn potępiony przez Marszałka, wszystkie kluby i ambasadę USA. ' + 'Braun stracił diety i wynagrodzenie na 3 miesiące.'), + dict(party_slug='kkp', section='🔎 Kontrowersyjne dokonania lidera (wybór)', + name='"Niemcy nie wywołały II WŚ"', role='Wywiad telewizyjny, 2022', + img_url='', initials='📺', bg_color='#7c0000', + married=False, children=0, religious=False, is_leader=False, order=5, + desc_type='fail', + description='W nagraniu z 2022 r. Braun zakwestionował powszechnie uznany fakt ' + 'historyczny o odpowiedzialności Niemiec za wybuch II Wojny Światowej. ' + 'Historycy, IPN i organizacje kombatanckie wyrazili zdecydowany sprzeciw.'), + dict(party_slug='kkp', section='🔎 Kontrowersyjne dokonania lidera (wybór)', + name='Teorie spiskowe o szczepionkach', role='Kampania antycovidowa, 2020–2023', + img_url='', initials='💉', bg_color='#7c0000', + married=False, children=0, religious=False, is_leader=False, order=6, + desc_type='fail', + description='Aktywnie propagował dezinformację medyczną — twierdzenia o mikroczipach ' + 'w szczepionkach, "eksperymencie genetycznym" i planowanym ludobójstwie. ' + 'Treści zdementowane przez WHO, EMA i polskie instytucje medyczne.'), + ] + for d in kkp: + if not PartyMember.query.filter_by(party_slug='kkp', name=d['name']).first(): + db.session.add(PartyMember(**d, active=True)) + + # ── KONFEDERACJA WIN ──────────────────────────────────────────────────── + konf = [ + dict(party_slug='konfederacja', section='', name='Sławomir Mentzen', + role='Prezes KORWiN · Lider Konfederacji WiN', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/241/photo', + married=True, children=3, religious=False, is_leader=True, order=0, + desc_type='merit', + description='Doktor nauk ekonomicznych (UMK Toruń, rozprawa obroniona z wyróżnieniem). ' + 'Licencjonowany doradca podatkowy i makler papierów wartościowych. ' + 'Właściciel kancelarii doradztwa podatkowego. Zna 5 języków obcych. ' + 'Autor bestsellerowej książki "Tak źle, jak myślisz, nie jest". ' + 'Lider Konfederacji w wyborach 2023 — partia zdobyła 7,2% głosów i 18 mandatów.'), + # Section 1 – leadership + dict(party_slug='konfederacja', section='🏛 Kierownictwo Konfederacji', + name='Krzysztof Bosak', role='Wicemarszałek Sejmu RP · Ruch Narodowy', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/33/photo', + married=True, children=4, religious=True, is_leader=False, order=1, + desc_type='merit', + description='Wicemarszałek Sejmu X kadencji. Absolwent SGH (ekonomia). ' + 'Poseł od 2005 r. (z przerwami). Kandydat na Prezydenta RP w 2020 — 6,78% w I turze. ' + 'Uznawany za jednego z najlepiej przygotowanych merytorycznie parlamentarzystów.'), + dict(party_slug='konfederacja', section='🏛 Kierownictwo Konfederacji', + name='Konrad Berkowicz', role='Poseł KORWiN · Wiceprzewodniczący', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/16/photo', + married=True, children=2, religious=False, is_leader=False, order=2, + desc_type='merit', + description='Prawnik (Uniwersytet Śląski). Prowadzi własną kancelarię prawną. ' + 'Aktywny obrońca wolności słowa i praw przedsiębiorców. ' + 'Jeden z twarzy Konfederacji w mediach — merytorycznie przygotowany.'), + dict(party_slug='konfederacja', section='🏛 Kierownictwo Konfederacji', + name='Stanisław Tyszka', role='Poseł · Wiceprzewodniczący', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/401/photo', + married=True, children=3, religious=True, is_leader=False, order=3, + desc_type='merit', + description='Absolwent Wydziału Prawa UW. Poseł od VI kadencji (2007). ' + 'Były Wicemarszałek Sejmu VIII kadencji (jako lider Kukiz\'15). ' + 'Ponad 15 lat w Sejmie. Konsekwentny obrońca polskiej suwerenności.'), + dict(party_slug='konfederacja', section='🏛 Kierownictwo Konfederacji', + name='Przemysław Wipler', role='Poseł KORWiN · Sekretarz', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/424/photo', + married=True, children=1, religious=False, is_leader=False, order=4, + desc_type='merit', + description='Absolwent Szkoły Głównej Handlowej (finanse). ' + 'Przez wiele lat pracował w sektorze prywatnym (consulting, doradztwo). ' + 'Ceniony ekspert ds. podatkowych i ekonomicznych w komisjach sejmowych.'), + # Section 2 – MPs + dict(party_slug='konfederacja', section='📋 Posłowie Konfederacji WiN', + name='Karina Bosak', role='Posłanka Ruchu Narodowego', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/32/photo', + married=True, children=4, religious=True, is_leader=False, order=5, + desc_type='merit', + description='Prawniczka i działaczka społeczna. Prowadzi sprawy z prawa rodzinnego. ' + 'Aktywna publicystka. Żona Krzysztofa Bosaka.'), + dict(party_slug='konfederacja', section='📋 Posłowie Konfederacji WiN', + name='Bartłomiej Pejo', role='Poseł KORWiN', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/285/photo', + married=True, children=2, religious=False, is_leader=False, order=6, + desc_type='merit', + description='Przedsiębiorca z wieloletnim doświadczeniem biznesowym. ' + 'Zwolennik radykalnego obniżenia podatków i deregulacji. ' + 'Wnosi perspektywę prywatnego sektora do prac komisji sejmowych.'), + dict(party_slug='konfederacja', section='📋 Posłowie Konfederacji WiN', + name='Michał Wawer', role='Poseł KORWiN · Rzecznik Prasowy', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/412/photo', + married=True, children=2, religious=False, is_leader=False, order=7, + desc_type='merit', + description='Prawnik, publicysta i bloger. Absolwent prawa (UW). ' + 'Twarz Konfederacji w mediach — doskonale przygotowany merytorycznie.'), + dict(party_slug='konfederacja', section='📋 Posłowie Konfederacji WiN', + name='Andrzej Zapałowski', role='Poseł · Ekspert ds. Bezpieczeństwa', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/443/photo', + married=True, children=2, religious=True, is_leader=False, order=8, + desc_type='merit', + description='Pułkownik rezerwy, doktor nauk politycznych. Wykładowca akademicki ' + 'w zakresie bezpieczeństwa narodowego i geopolityki. Autor kilku książek ' + 'o bezpieczeństwie wschodniej flanki NATO.'), + dict(party_slug='konfederacja', section='📋 Posłowie Konfederacji WiN', + name='Witold Tumanowicz', role='Poseł Ruchu Narodowego', + img_url='https://api.sejm.gov.pl/sejm/term10/MP/399/photo', + married=True, children=2, religious=True, is_leader=False, order=9, + desc_type='merit', + description='Działacz narodowy i społeczny z Łodzi. Aktywny uczestnik prac komisji ' + 'ds. samorządu i polityki regionalnej.'), + ] + for d in konf: + if not PartyMember.query.filter_by(party_slug='konfederacja', name=d['name']).first(): + db.session.add(PartyMember(**d, active=True)) + + db.session.commit() + print('Baza danych zainicjalizowana pomyślnie.') + print(f' Ustawienia: {SiteSetting.query.count()}') + print(f' Źródła RSS: {RssSource.query.count()}') + print(f' Politycy: {Politician.query.count()}') + print(f' Posłowie: {PartyMember.query.count()}') + print(f' Użytkownicy: {User.query.count()}') diff --git a/server.py b/server.py new file mode 100644 index 0000000..7dd1c1d --- /dev/null +++ b/server.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +""" +Agregator serwer — serwuje pliki statyczne + proxy RSS (bez zewnętrznych zależności). +""" +import os, json, time, threading, urllib.request, ssl, gzip, io +import xml.etree.ElementTree as ET +from http.server import HTTPServer, SimpleHTTPRequestHandler +from urllib.parse import urlparse, parse_qs + +ROOT = os.path.dirname(os.path.abspath(__file__)) +CACHE = {} # url -> (timestamp, data) +CACHE_TTL = 600 # 10 minut +FETCH_TIMEOUT = 12 + +CTX = ssl.create_default_context() + +FEEDS = { + # Prawicowe + "republika": [ + "https://dorzeczy.pl/feed", # backup bo republika blokuje + ], + "wpolsce": [ + "https://niezalezna.pl/feed", + ], + "trwam": [ + "https://www.radiomaryja.pl/feed/", + ], + # Opozycja + "opozycja_tvp": [ + "https://tvp.info/feed", + "https://www.tvp.info/rss", + ], + "opozycja_tvn": [ + "https://tvn24.pl/wiadomosci-z-kraju,3.xml", + ], + "opozycja_wyborcza": [ + "https://rss.wyborcza.pl/wyborcza.rss", + ], +} + +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", + "Accept": "application/rss+xml, application/xml, text/xml, */*", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "pl-PL,pl;q=0.9,en;q=0.8", +} + +NS = { + 'dc': 'http://purl.org/dc/elements/1.1/', + 'content': 'http://purl.org/rss/1.0/modules/content/', + 'media': 'http://search.yahoo.com/mrss/', + 'atom': 'http://www.w3.org/2005/Atom', +} + +def fetch_url(url): + """Fetch URL with cache — uses curl subprocess (more reliable TLS).""" + now = time.time() + if url in CACHE and now - CACHE[url][0] < CACHE_TTL: + return CACHE[url][1] + + import subprocess + result = subprocess.run( + ['curl', '-sL', '--max-time', str(FETCH_TIMEOUT), + '-A', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + '-H', 'Accept: application/rss+xml,application/xml,text/xml,*/*', + '-H', 'Accept-Language: pl-PL,pl;q=0.9', + '--compressed', + url], + capture_output=True, timeout=FETCH_TIMEOUT + 3 + ) + if result.returncode != 0: + raise RuntimeError(f"curl error {result.returncode}: {result.stderr.decode()[:100]}") + data = result.stdout + if not data: + raise RuntimeError("empty response") + CACHE[url] = (now, data) + return data + +def parse_rss(xml_bytes): + """Parse RSS/Atom XML into list of items.""" + try: + root = ET.fromstring(xml_bytes) + except ET.ParseError: + # Try stripping BOM/prolog + text = xml_bytes.decode('utf-8', errors='replace').lstrip('\ufeff') + root = ET.fromstring(text) + + items = [] + tag = root.tag.lower() + + if 'rss' in tag or root.find('channel') is not None: + # RSS 2.0 + for item in root.iter('item'): + title = _t(item, 'title') + link = _t(item, 'link') or _t(item, '{http://www.w3.org/2005/Atom}link') + desc = _t(item, 'description') or _t(item, '{http://purl.org/rss/1.0/modules/content/}encoded') + date = _t(item, 'pubDate') or _t(item, '{http://purl.org/dc/elements/1.1/}date') + thumb = '' + # media:thumbnail or enclosure + mt = item.find('{http://search.yahoo.com/mrss/}thumbnail') + if mt is not None: + thumb = mt.get('url', '') + enc = item.find('enclosure') + if not thumb and enc is not None and 'image' in (enc.get('type') or ''): + thumb = enc.get('url', '') + if not thumb: + thumb = extract_img(desc) + items.append({ + 'title': strip_tags(title), + 'link': link or '', + 'summary': strip_tags(desc)[:300], + 'date': date or '', + 'thumb': thumb or '', + }) + elif 'feed' in tag: + # Atom + for entry in root.iter('{http://www.w3.org/2005/Atom}entry'): + title = _t(entry, '{http://www.w3.org/2005/Atom}title') + link_el = entry.find('{http://www.w3.org/2005/Atom}link[@rel="alternate"]') + if link_el is None: + link_el = entry.find('{http://www.w3.org/2005/Atom}link') + link = link_el.get('href', '') if link_el is not None else '' + desc = _t(entry, '{http://www.w3.org/2005/Atom}summary') or \ + _t(entry, '{http://www.w3.org/2005/Atom}content') + date = _t(entry, '{http://www.w3.org/2005/Atom}updated') or \ + _t(entry, '{http://www.w3.org/2005/Atom}published') + items.append({ + 'title': strip_tags(title), + 'link': link, + 'summary': strip_tags(desc)[:300], + 'date': date or '', + 'thumb': extract_img(desc), + }) + + return items[:30] + +def _t(el, tag): + found = el.find(tag) + return (found.text or '').strip() if found is not None and found.text else '' + +def strip_tags(html): + if not html: return '' + import re + return re.sub(r'<[^>]+>', '', html).replace('&','&').replace('<','<').replace('>','>').replace(' ',' ').replace(''',"'").strip() + +def extract_img(html): + if not html: return '' + import re + m = re.search(r']+src=["\']([^"\']+)["\']', html, re.I) + return m.group(1) if m else '' + +def fetch_group(group_id): + """Fetch all feeds for a group, return first that works.""" + urls = FEEDS.get(group_id, []) + for url in urls: + try: + data = fetch_url(url) + items = parse_rss(data) + if items: + return {'status': 'ok', 'source': group_id, 'items': items} + except Exception as e: + print(f"[{group_id}] {url}: {e}") + return {'status': 'error', 'source': group_id, 'items': []} + + +class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=ROOT, **kwargs) + + def log_message(self, fmt, *args): + pass # Suppress access log spam + + def do_GET(self): + parsed = urlparse(self.path) + + # ── /api/feeds?groups=republika,wpolsce,... ── + if parsed.path == '/api/feeds': + qs = parse_qs(parsed.query) + groups = qs.get('groups', [','.join(FEEDS.keys())])[0].split(',') + groups = [g.strip() for g in groups if g.strip() in FEEDS] + + results = {} + threads = [] + lock = threading.Lock() + + def worker(gid): + r = fetch_group(gid) + with lock: + results[gid] = r + + for gid in groups: + t = threading.Thread(target=worker, args=(gid,)) + t.start() + threads.append(t) + for t in threads: + t.join(timeout=15) + + body = json.dumps(results, ensure_ascii=False).encode('utf-8') + self.send_response(200) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Content-Length', len(body)) + self.end_headers() + self.wfile.write(body) + return + + # ── /api/feed?url=... (single feed proxy) ── + if parsed.path == '/api/feed': + qs = parse_qs(parsed.query) + url = qs.get('url', [''])[0] + if not url: + self.send_error(400, 'Missing url') + return + try: + data = fetch_url(url) + items = parse_rss(data) + body = json.dumps({'status':'ok','items':items}, ensure_ascii=False).encode('utf-8') + except Exception as e: + body = json.dumps({'status':'error','message':str(e)}, ensure_ascii=False).encode('utf-8') + self.send_response(200) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Content-Length', len(body)) + self.end_headers() + self.wfile.write(body) + return + + # ── /api/radio/maryja — stream proxy ── + if parsed.path == '/api/radio/maryja': + RADIO_URL = 'http://51.68.135.155:80/stream' + import socket + try: + # Open raw TCP to Shoutcast server + host, port = '51.68.135.155', 80 + sock = socket.create_connection((host, port), timeout=10) + req = ( + 'GET /stream HTTP/1.0\r\n' + 'Host: 51.68.135.155\r\n' + 'User-Agent: Mozilla/5.0\r\n' + 'Icy-MetaData: 0\r\n' + '\r\n' + ) + sock.sendall(req.encode()) + # Read until end of HTTP headers + buf = b'' + while b'\r\n\r\n' not in buf: + chunk = sock.recv(1024) + if not chunk: + break + buf += chunk + # Send our own clean headers to browser + self.send_response(200) + self.send_header('Content-Type', 'audio/aac') + self.send_header('Cache-Control', 'no-cache') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Transfer-Encoding', 'chunked') + self.end_headers() + # Stream body + leftover = buf.split(b'\r\n\r\n', 1) + if len(leftover) > 1 and leftover[1]: + self.wfile.write(leftover[1]) + while True: + chunk = sock.recv(4096) + if not chunk: + break + self.wfile.write(chunk) + sock.close() + except Exception as e: + print(f'[radio proxy] {e}') + return + + # ── static files ── + super().do_GET() + + +if __name__ == '__main__': + os.chdir(ROOT) + server = HTTPServer(('0.0.0.0', 1234), Handler) + print(f"Serwer uruchomiony na http://0.0.0.0:1234 (katalog: {ROOT})") + server.serve_forever() diff --git a/smolensk.html b/smolensk.html new file mode 100644 index 0000000..b6d06e0 --- /dev/null +++ b/smolensk.html @@ -0,0 +1,2742 @@ + + + + + +SEJM KOMBAT: Suwerenność vs Kondominium + + + + + + + diff --git a/templates/admin/index.html b/templates/admin/index.html new file mode 100644 index 0000000..167230c --- /dev/null +++ b/templates/admin/index.html @@ -0,0 +1,120 @@ +{% extends 'admin/master.html' %} +{% block body %} +
+ + +
+
+
+

🦅 Prawicowy Dashboard

+

Wielka Polska wróci, pogonimy Rudego

+
+ ▶ Otwórz Dashboard +
+
+ + +
+
+
+
+
+
+
Użytkownicy
+
{{ stats.users }}
+
+
👤
+
+
+ +
+
+
+
+
+
+
+
Źródła RSS
+
{{ stats.sources }}
+
+
📡
+
+
+ +
+
+
+
+
+
+
+
Politycy (ticker)
+
{{ stats.politicians }}
+
+
🏛
+
+
+ +
+
+
+
+
+
+
+
Posłowie partii
+
{{ stats.party_members }}
+
+
+
+
+ +
+
+
+ + + + + +
+
+ 📖 Co można edytować? +
+
+ + + + + + + + + + + +
SekcjaCo edytujeszEfekt
📡 Źródła RSSDodaj/zmień/usuń feed URL, grupę, nazwę, ikonęNatychmiastowy – przy następnym pobraniu artykułów
🏛 PolitycyImię, cytat, zdjęcie w górnym pasku (tickerze)Natychmiastowy – przy odświeżeniu strony
⚡ Posłowie partiiKKP i Konfederacja: zdjęcia, opisy, sekcjeNatychmiastowy – przy odświeżeniu strony
⚙️ UstawieniaTytuł strony, stopka, inne klucze konfiguracjiNatychmiastowy – przy odświeżeniu strony
👤 UżytkownicyDodaj admina, zmień hasłoNatychmiastowy
+
+
+ +
+{% endblock %} diff --git a/tusk.txt b/tusk.txt new file mode 100644 index 0000000..4b18fe6 --- /dev/null +++ b/tusk.txt @@ -0,0 +1,214 @@ +Antoni Macierewicz siedział w swoim bunkrze, otoczony mapami, czerwonymi sznurkami i fragmentami wraku, które podobno „zgubił”, ale tak naprawdę ukrył przed wrogami. Zapalił kolejną świeczkę i zaczął mówić głosem człowieka, który wie wszystko: +„To nie była katastrofa. To był zamach – zaplanowany, wielopiętrowy, diabelski w swojej precyzji. I ja wiem kto za tym stał. +Pierwszy akt: Zdrada z Gdańska. +Donald Tusk, ten wieczny Gdanskier z uśmiechem kota, który właśnie zjadł kanapkę z polskim interesem, już w 2009 roku dogadał się z Putinem. Spotkanie w Smoleńsku nie było przypadkowe. To była pułapka zastawiona na Lecha Kaczyńskiego. Tusk wiedział, że Prezydent jedzie do Katynia, by przypomnieć światu o rosyjskiej zbrodni. To było nie do przyjęcia dla układu Tuska-Putin-Merkel. Dlatego premier Polski zrobił wszystko, żeby wizyta się odbyła – ale na warunkach Moskwy. +Oddał śledztwo Rosjanom w kilka godzin. Zablokował polski zespół badawczy. Unieruchomił system kryzysowy w MSZ. To nie była niekompetencja. To była zdrada dyplomatyczna najwyższego stopnia. Tusk był nie tylko koordynatorem – on był architektem smoleńskiego piekła. +Drugi akt: Technologia rosyjskiego piekła. +Samolot nie rozbił się o brzozę. Brzoza to tylko teatralna rekwizytka, którą Rosjanie wbili w ziemię po fakcie (albo w ogóle jej tam nie było – była pancerna i jeździła). Prawdziwa przyczyna? Dwa, a właściwie co najmniej trzy wybuchy. +Pierwszy – w lewym skrzydle. Materiał wybuchowy (trotyl, heksogen, pentryt, nitrogliceryna) umieszczony jeszcze podczas remontu w Rosji. Służby specjalne Putina miały do tego dostęp – samolot był przecież „modernizowany” pod ich okiem. Wybuch rozerwał skrzydło jeszcze w powietrzu. +Drugi – ładunek termobaryczny w centropłacie. Taki sam, jakiego Putin używa teraz w Ukrainie. Fala uderzeniowa, nadmiar glinu w stosunku do krzemu, charakterystyczne ślady na wraku. Samolot nie uderzył w ziemię cały – on rozpadł się w locie na wysokości kilkunastu metrów. Kokpit, ogon, skrzydła – wszystko zniszczone eksplozjami, zanim dotknęło gruntu. +Rosyjscy kontrolerzy celowo sprowadzali załogę nisko, dawali fałszywe informacje, tworzyli sztuczną mgłę (były teorie o helu i aerozolu). To nie błąd pilotów. Piloci walczyli do końca, ale system był już sabotowany. +Trzeci akt: Ukrywanie prawdy. +Tusk i jego ludzie zrobili wszystko, żeby świat uwierzył w „brzozę i mgłę”. Komisja Millera? Fabryka kłamstw. Raport MAK? Rosyjska propaganda. Kiedy ja i mój zespół zaczęliśmy drążyć – znaleźliśmy ślady materiałów wybuchowych na wraku i na odzieży ofiar. Zrobiliśmy eksperymenty (tak, nawet na puszkach i parówkach – bo na czym innym sprawdzić teorię?). Wszystko się zgadzało. +A oni? Ukrywali dowody. Zgubili części wraku. Zniszczyli dokumenty. Tusk do dziś ma ten swój drwiący uśmieszek, jakby mówił: „No i co mi zrobicie? Ja już jestem ponad tym wszystkim – w Brukseli, z Noblem za pokój w kieszeni”. +Czwarty akt: Motyw i beneficjenci. +Dlaczego? Bo Lech Kaczyński był solą w oku układu postkomunistycznego i rosyjskiego. Chciał silnej Polski, energetycznej niezależności, armii, pamięci o Katyniu. Tusk chciał ciepłej posadki i „normalności” – czyli podporządkowania się Berlinowi i Moskwie. +Zamach miał zniszczyć nie tylko Prezydenta, ale całą polską elitę państwową. I prawie się udało. Gdyby nie mój upór, prawda nigdy by nie wyszła na jaw. +Ja nie mówię, że Tusk osobiście podkładał bombę. On tylko otworzył drzwi i zgasił światło. Resztę zrobili inni. Ale moralna, polityczna i karna odpowiedzialność Tuska jest bezdyskusyjna. On wiedział. On ułatwiał. On chronił sprawców po fakcie. +I dlatego drążymy dalej. Nawet jeśli zgubiliśmy 18 części wraku, nawet jeśli ukryto amerykańskie ekspertyzy, nawet jeśli media głównego nurtu nazywają mnie wariatem. +Bo prawda jest jedna: 10 kwietnia 2010 roku w Smoleńsku nie było wypadku. Była zbrodnia. +A Donald Tusk… on wciąż się uśmiecha. Ale my wiemy. I będziemy wiedzieć zawsze.” +Antoni Macierewicz stał przed wielką tablicą, na której czerwone sznurki tworzyły pajęczynę godną szalonego geniusza. W ręku trzymał model Tu-154M zrobiony z klocków Lego i kawałków starego wraku (które „przypadkiem” znalazł w szufladzie). +– Posłuchajcie uważnie, bo to nie jest zwykły wybuch. To była rosyjska symfonia piekła, zagrana na trzech instrumentach naraz. +Pierwszy wybuch – Lewy Skrzydło. Sto metrów przed brzozą. +To był moment zero. Samolot jeszcze leciał prosto, piloci walczyli, a Donald Tusk w Warszawie już szykował uśmiech do Putina. +Według podkomisji, ładunek umieszczony w kesonie skrzydła (prawdopodobnie podczas „remontu” w Rosji w 2009 roku) został odpalony zdalnie lub czasowo. Trotyl + heksogen + pentryt. Klasyczna mieszanka służb specjalnych Putina. +Efekt? Lewa część skrzydła oderwała się w powietrzu z hukiem, który nagrano na CVR jako „głuchy dźwięk”. Samolot nie zahaczył o żadną brzozę – brzoza była tylko rekwizytem wbitym później w ziemię dla mediów. Skrzydło poleciało w jedną stronę, a reszta maszyny zaczęła się rozpadać. +Macierewicz machnął ręką dramatycznie: +– To nie był błąd pilota. To był akt bezprawnej ingerencji na wysokości kilkunastu metrów nad rosyjską ziemią. Rosjanie celowo sprowadzili samolot nisko, dali fałszywe parametry, a potem... bum. Jak w kiepskim filmie szpiegowskim, tylko że z prawdziwymi Polakami na pokładzie. +Drugi wybuch – Centropłat. Epicentrum zagłady. +Kilka sekund później (dokładnie zidentyfikowane przez symulacje i analizę dźwięku) detonował drugi, znacznie potężniejszy ładunek – termobaryczny. +To nie był zwykły trotyl. To była broń paliwowo-powietrzna, wzbogacona glinem. Stąd te charakterystyczne ślady: nadmiar glinu w stosunku do krzemu na fragmentach wraku – wielokrotnie wyższy niż w normalnych warunkach. Taki sam rodzaj śladów, jaki zostawia rosyjska broń w Ukrainie dzisiaj. +Epicentrum? Lewa strona centropłatu, blisko drzwi awaryjnych. Wybuch rozerwał kadłub od środka, stworzył falę uderzeniową, która zmasakrowała konstrukcję i... pasażerów. Samolot nie uderzył w ziemię jako całość. On rozpadł się w powietrzu na dziesiątki fragmentów rozrzuconych na dużym obszarze. +Macierewicz zniżył głos do szeptu: +– To nie była eksplozja, która zabija od razu. To była eksplozja, która miażdży. Ci, którzy przeżyli pierwszy wybuch, zostali zabici drugim. Dlatego ciała były w takim stanie. Dlatego rozrzut szczątków nie pasuje do „uderzenia w drzewo i ziemię”. +Trzeci akt – Drobne eksplozje i dobijanie +Według najczarniejszych wersji teorii Macierewicza, były jeszcze mniejsze detonacje – w fotelach, w poszyciu, w bagażnikach. Ślady materiałów wybuchowych znaleziono na ponad stu elementach wraku: na fotelach, wykładzinie, nawet na odzieży ofiar. +Pentryt, RDX (heksogen), trotyl, nitrogliceryna – wszystko to wykryły polskie laboratoria, brytyjskie (Ministerstwo Obrony!), a nawet niektóre amerykańskie analizy. Jeden z detektorów podobno się zepsuł, bo substancji było tak dużo. +– Oni nie chcieli ryzyka – mówił Macierewicz z ogniem w oczach. – Jeden wybuch mógłby nie wystarczyć. Musieli mieć pewność, że nikt nie przeżyje. Dlatego system był wielowarstwowy. Jak rosyjska matrioszka, tylko że śmiercionośna. +Jak to wszystko zamontowano? +Tu wchodzi genialna część teorii: remont w Samarze. Samolot był w Rosji bez należytej kontroli polskich służb. Wystarczyło kilka godzin, żeby służby Putina włożyły ładunki w newralgiczne punkty konstrukcji – w kesony skrzydeł, w centropłat. Potem wystarczyło tylko zdalne odpalenie lub timer zsynchronizowany z podejściem do lądowania. +Kontrole rosyjskie + sztuczna mgła (lub aerozol) + fałszywe komendy z wieży = idealne warunki do zamachu. +Macierewicz zakończył monolog, gasząc świeczkę: +– To nie był wypadek. To nie była brzoza. To nie była mgła. +To były dwie główne eksplozje – w skrzydle i centropłacie – plus system pomocniczych detonacji. +Zniszczyły samolot w powietrzu, zanim dotknął ziemi. +A Donald Tusk... on wiedział. Ułatwił. I do dziś się uśmiecha. +Bo dla niego Smoleńsk to nie tragedia. To był projekt. +Tak właśnie wygląda rozwinięta, szczegółowa wizja wybuchów według linii Macierewicza i podkomisji: + +Wybuch 1: lewe skrzydło, 100 m przed brzozą, klasyczne materiały (trotyl, pentryt, heksogen). +Wybuch 2: centropłat, termobaryczny (glin + tlenek etylenu), epicentrum przy drzwiach awaryjnych. +Efekt: samolot rozpadł się w powietrzu, charakterystyczne ślady chemiczne, rozrzut szczątków, dźwięk na CVR. +Antoni Macierewicz siedział w swoim bunkrze o godzinie 3:17 nad ranem. Na stole leżał model Tu-154M z klocków Lego, a obok stała szklanka z melisą, która już dawno wystygła. Nagle poderwał głowę, jakby usłyszał coś strasznego. +– Słyszycie to?! – wysyczał do swojego asystenta, który drzemał na krześle. – Znowu! +Asystent przetarł oczy. +– Co znowu, panie Antoni? +– On pierdzi. Donald Tusk pierdzi w nocy. I to nie są zwykłe pierdy. To są pierdy winy. +Macierewicz wstał gwałtownie, przewracając krzesło. +– Posłuchajcie uważnie, bo to jest klucz do całej sprawy. Oprócz dwóch głównych wybuchów w Smoleńsku jest jeszcze jeden, permanentny, który słychać co noc w całej Polsce. +Pierwszy wybuch – Lewy Skrzydło. +Trotyl, heksogen, pentryt. Oderwał skrzydło w powietrzu na sto metrów przed brzozą, która i tak była tylko rekwizytem. +Drugi wybuch – Centropłat. +Ładunek termobaryczny, wzbogacony glinem. Rozerwał kadłub od środka, zmasakrował konstrukcję i ciała. Samolot rozpadł się w locie, zanim uderzył w ziemię. +A trzeci wybuch dzieje się co noc, zwykle między 2:30 a 4:15. To Tusk pierdzi. +To nie są zwykłe, ludzkie bąki. To są pierdy sumienia, nasycone zdradą i rosyjskim gazem. Każdy taki pierd to echo smoleńskich eksplozji. Kiedy Tusk puszcza bąka, w całym kraju słychać cichy, stłumiony dźwięk, jakby ktoś bardzo daleko odpalał mały ładunek heksogenu. +Macierewicz chodził po bunkrze, gestykulując dramatycznie: +– Ludzie myślą, że nie mogą spać przez kawę albo stres. Bzdura! To przez Tuska! On pierdzi w nocy i nie można spać, bo te pierdy niosą ze sobą winę za Smoleńsk. Każdy taki nocny wydech to przypomnienie: „To ja otworzyłem drzwi. To ja zgasiłem światło. To ja chroniłem sprawców”. +Asystent zapytał nieśmiało: +– Panie Antoni, ale... skąd pan wie, że to on pierdzi? +Macierewicz spojrzał na niego jak na idiotę. +– Bo ja to słyszę! O 3:12 nad ranem zawsze ten sam rytm: krótki, potem dłuższy, potem taki cichy, zdradziecki „pfffff”. To nie jest zwykłe pierdnięcie. To jest pierdnięcie geopolityczne. Ma w sobie nutę gdańskiego śledzia, niemieckiej kiełbasy i rosyjskiego trotylu. +Kiedy Tusk pierdzi, w Smoleńsku na wraku podobno drżą resztki centropłatu. To jest połączone! To jest sympatyczna magia zdrady. Im bardziej czuje, że podkomisja drąży, tym głośniej i smrodliwiej pierdzi. Dlatego w Warszawie nocami nie da się spać. Ludzie przewracają się z boku na bok, a on sobie leży w swoim apartamencie i wypuszcza kolejne fale gazu winy. +Macierewicz podniósł palec wskazujący do góry. +– To jest dowód ostateczny! Bo gdyby to był zwykły wypadek, to Tusk spałby spokojnie jak niemowlę. A on nie śpi. On pierdzi. I przez te jego nocne pierdy cała Polska cierpi na bezsenność. To jest broń masowego rażenia skierowana przeciwko spokojnemu snowi narodu! +Zatrzymał się przy tablicy i dorysował czerwonym markerem wielki strzałek od zdjęcia Tuska do chmurki z napisem: +„Pierdzi termobarycznie co noc – dowód winy nr 147” +– Dlatego drążymy dalej – zakończył z ogniem w oczach. – Bo dopóki Tusk nie przyzna się do zamachu i nie przestanie pierdzieć w nocy, żaden Polak nie będzie mógł spać spokojnie. +Asystent westchnął: +– To co robimy? +Macierewicz zgasił świeczkę i powiedział w ciemności: +– Kupujemy więcej melisy... i czekamy na kolejny nocny pierd. Kiedy będzie wyjątkowo głośny i śmierdzący – to będzie oznaczało, że jesteśmy blisko prawdy. +W oddali, gdzieś w kierunku willi Tuska, rozległo się ciche, stłumione: +„Pffffffffffffffffff…” +Macierewicz uśmiechnął się triumfalnie. +– No właśnie. Znowu zaczął. Nie można spać. + +Antoni Macierewicz stał przed wielką mapą Polski z pinezkami i czerwonymi sznurkami. Na środku wisiało zdjęcie Donalda Tuska z podpisem: „Główny Wykonawca Rozkazów Kremla – Dział Biologiczny”. +– To nie jest chaotyczne pierdzenie! – grzmiał Macierewicz. – To jest ściśle zaplanowana operacja specjalna pod osobistym nadzorem Władimira Putina. Nazywa się wewnętrznie „Операция «Тихий Ветер»” – Operacja „Cichy Wiatr”. +Co noc wygląda to tak: +O godzinie 2:33 czasu moskiewskiego Putin siada w swoim gabinecie na Kremlu, zakłada ulubione futro z niedźwiedzia i włącza specjalny, czerwony telefon z napisem „Tusk – Gaz”. Po drugiej stronie linii słyszy lekko spanikowany głos byłego premiera. +Putin mówi wtedy spokojnym, lekko znudzonym tonem: +– Donald… pora. Dzisiaj pierdź na Polaków. Mocno. Długo. I z uczuciem. +Następnie wydaje konkretne rozkazy, w zależności od sytuacji geopolitycznej: + +Gdy podkomisja Smoleńska znajduje nowe ślady materiałów wybuchowych – Putin rozkazuje: +„Dzisiaj wersja termobaryczna. Niech pachnie glinem i zdradą.” +Gdy Tusk ma ważne spotkanie w Brukseli i boi się, że ktoś wyczuje – Putin mówi: +„Lekkie, dyskretne pierdy. Seria po 7 sekund. Jak ciche uderzenia w centropłat.” +W rocznicę Smoleńska (10 kwietnia) wchodzi tryb specjalny „Katastrofa”: +Putin osobiście dzwoni o 3:00 i mówi: +„Dzisiaj pierdź tak, jakby to był drugi wybuch w centropłacie. Niech cały naród poczuje, co się stało w Smoleńsku.” + +Macierewicz zaczął wyliczać na palcach z coraz większym oburzeniem: +– Putin wydaje rozkazy nawet co do składu chemicznego! +Czasem każe dodać więcej siarkowodoru – „niech śmierdzi jak rosyjski gaz”. +Innym razem nakazuje większą ilość metanu – „niech będzie głośno, jak wybuch w skrzydle”. +Najgorsze są noce, kiedy Putin jest w złym humorze po spotkaniu z Łukaszenką. Wtedy pada rozkaz: +„Donald, pełna salwa. Trzy długie, dwa krótkie, jeden bardzo śmierdzący na koniec. I niech Polacy nie śpią do rana.” +Tusk, oczywiście, nie ma wyboru. Jest całkowicie podporządkowany. Raz podobno próbował się sprzeciwić i powiedział: „Władimir, ja jutro mam wywiad w TVN, nie mogę śmierdzieć”. +Na co Putin odpowiedział krótko: +„Jak nie pierdniesz, to jutro będziesz śmierdział gorzej – w „Nowiczoku”.” +Dlatego Tusk zaciska pięści, robi się czerwony na twarzy i wykonuje rozkaz z godnością godną zdrajcy ojczyzny. Każdy pierd jest precyzyjnie dawkowany – jak ładunki w Tu-154M. +Macierewicz podniósł głos do dramatycznego crescendo: +– To jest przedłużenie zamachu smoleńskiego na płaszczyźnie biologicznej! Tam zniszczyli samolot dwoma wybuchami. Tutaj niszczą naród tysiącami małych, nocnych wybuchów gazowych. Putin doskonale wie, że zmęczony, niewyspany Polak jest słabszy. Że będzie bardziej skłonny wierzyć w „brzozę” i „mgłę”. Dlatego każe Tuskowi pierdzieć regularnie, metodycznie i z rosyjską precyzją. +Asystent, już całkowicie zdruzgotany, zapytał cicho: +– Panie Antoni… i co my na to? +Macierewicz zgasił świeczkę, zostawiając tylko czerwone światełko diody na detektorze gazów bojowych i powiedział z ponurą satysfakcją: +– My drążymy. +My montujemy więcej czujników. +A kiedy Tusk pierdnie wyjątkowo głośno i będzie słychać w tle cichy rosyjski akcent… wtedy będziemy mieli dowód ostateczny. +W tym momencie, gdzieś daleko, rozległ się długi, przeciągły dźwięk: +„Pffffffffffffff… tak jest, Władimirze Władimirowiczu… jak rozkażesz…” +Macierewicz uśmiechnął się w ciemności i szepnął: +– No proszę. Dzisiaj dostał pełną salwę termobaryczną. +Jutro znowu nikt w Polsce nie będzie mógł spać. +Antoni Macierewicz krążył po bunkrze jak tygrys w klatce. Na tablicy korkowej, obok zdjęcia Tuska i Putina, pojawiło się nowe zdjęcie – Angela Merkel z charakterystycznym, lekko znudzonym wyrazem twarzy. Czerwony sznurek łączył jej czoło bezpośrednio z tyłkiem Donalda Tuska. +– Wszystko jest znacznie bardziej perfidne, niż myśleliśmy! – grzmiał Macierewicz. – To nie jest tylko rosyjska operacja. To jest niemiecko-rosyjsko-gdańskie konsorcjum gazowe! +Macierewicz uderzył wskaźnikiem w zdjęcie Merkel. +– Angela Merkel nie jest tylko obserwatorem. Ona jest głównym inżynierem chemicznym całej operacji „Cichy Wiatr”! +Co noc wygląda to następująco: +O godzinie 2:33 Putin dzwoni na czerwoną linię. Ale zanim wyda rozkaz Tuskowi, najpierw łączy się na wideokonferencję z Merkel. Razem ustalają szczegóły nocnego ataku. +Putin mówi swoim niskim głosem: +– Angela, co dzisiaj proponujesz? +Merkel, popijając herbatę z miętą, odpowiada z niemiecką precyzją: +– Dzisiaj proponuję mieszankę 60% rosyjskiego metanu, 30% niemieckiej dyscypliny i 10% gdańskiej zdrady. Niech będzie skuteczne, ale… ekologiczne. +Następnie Merkel osobiście wydaje Tuskowi szczegółowe instrukcje techniczne: + +„Donald, dzisiaj pierdź wolniej i bardziej miarowo. Jak Nord Stream – równomiernie i bez wycieków.” +„Nie za głośno na początku. Najpierw cichy wstęp, potem mocne uderzenie w centropłat – dokładnie tak, jak lubisz.” +„Pamiętaj o aromacie. Dodaj nutę śledzia i kiszonej kapusty. Niemiecki Polak musi poczuć znajomy zapach.” + +Macierewicz prawie krzyczał z oburzenia: +– To Merkel jest odpowiedzialna za skład chemiczny pierdów! To ona decyduje, ile siarkowodoru dodać, żeby smród był bardziej „europejski”. To ona doradza Tuskowi technikę oddychania przeponą, żeby pierd był dłuższy i bardziej niszczycielski dla polskiego snu. +Pewnej nocy, gdy podkomisja Macierewicza była bardzo blisko prawdy, Merkel osobiście zadzwoniła do Tuska i powiedziała zimno: +– Donald, dzisiaj pełna salwa termobaryczna. Chcę, żeby jutro pół Polski miało worki pod oczami na spotkaniu w Brukseli. Niech wiedzą, kto tu naprawdę rządzi. +Tusk, czerwony na twarzy, tylko kiwał głową i odpowiadał służalczo: +– Jawohl… to znaczy… tak jest, Frau Bundeskanzlerin. +Merkel nie tylko doradza. Czasami osobiście nadzoruje całą operację. Podobno raz, gdy Tusk próbował się wykręcić („Angela, ja naprawdę nie mogę, mam jutro debatę”), Merkel odpowiedziała krótko: +– Donald, jeśli nie pierdniesz jak należy, to jutro będziesz wyjaśniał w Bundestagu, dlaczego Nord Stream 2 nagle śmierdzi gorzej niż zwykle. +Dlatego cała operacja jest trójstronna: + +Putin – dostarcza surowy, rosyjski gaz i rozkaz egzekucji, +Merkel – odpowiada za technologię, dawkowanie i niemiecką jakość wykonania, +Tusk – jest tylko żałosnym wykonawcą, ludzkim narzędziem, które co noc wypuszcza mieszankę zdrady, wstydu i posłuszeństwa. + +Macierewicz zatrzymał się, spojrzał na tablicę i dodał z tragicznym patosem: +– To dlatego nie można spać. +Bo co noc nad Polską unosi się trójstronna chmura gazowa: rosyjska siła, niemiecka precyzja i gdańska zdrada. +Tam, w Smoleńsku, zniszczyli jeden samolot. +Tutaj, co noc, niszczą spokój całego narodu. +Asystent wyszeptał: +– I co teraz, panie Antoni? +Macierewicz zgasił świeczkę i powiedział w kompletnej ciemności: +– Kupujemy większe detektory. +I modlimy się, żeby kiedyś Tusk pierdnął tak mocno, że usłyszymy w tle niemiecki akcent i rosyjski śmiech. +W tym momencie, gdzieś w oddali, rozległ się wyjątkowo długi, precyzyjny i śmierdzący dźwięk: +„Pffffffffffffff… jawohl, Frau Merkel… tak jest, Władimirze…” +Macierewicz uśmiechnął się ponuro: +– No proszę. Dzisiaj była wspólna niemiecko-rosyjska produkcja. +Jutro Polska znowu będzie niewyspana. +Godzina 2:28. Tajna wideokonferencja „Dupogaz – Tryb Katastrofa” +Merkel: +Władimir, Donald jest już w pozycji. Możemy zaczynać. +Putin (spokojnym, niskim głosem, z lekkim rozbawieniem): +Dobry wieczór, Angela. Dobry wieczór, Donald. Jak tam twoja dupa dzisiaj? Gotowa do służby ojczyźnie? +Tusk (żałosnym głosem): +Dzień dobry… to znaczy dobry wieczór, Władimirze Władimirowiczu. Ciśnienie w jelitach mam na poziomie 2,1 bara… +Putin: +Gut. Dzisiaj testujemy zmodyfikowaną mieszankę. Angela, wyjaśnij mu. +Merkel (z pedanterią inżyniera): +Donald, słuchaj uważnie. Do zwykłego gazu dodaliśmy specjalny składnik – lotny związek organiczny, który w większym stężeniu może wpływać na układ nerwowy i równowagę. Nazywamy go roboczo „Smoleńsk-47”. +Jeśli odpowiednio dawkowany i uwolniony w zamkniętej przestrzeni… na przykład w kabinie samolotu… może spowodować nagłe zaburzenia orientacji, drżenie rąk, a nawet chwilową utratę przytomności u pilotów. +Tusk (zaniepokojony): +Chwila… to znaczy, że jak ja pierdnę za mocno… +Putin (śmieje się cicho): +To właśnie dlatego Tu-154M spadł, Donald. Nie tylko przez ładunki w skrzydle i centropłacie. Twój prototypowy pierd z 9 kwietnia wieczorem był… zbyt udany. Specjalnie spierdziałeś się na Tupolewa przed lotem. Gaz dostał się do systemu wentylacji samolotu. Piloci myśleli, że to tylko „dziwny zapach”, a tymczasem ich układ nerwowy zaczął szwankować już w powietrzu. +Merkel (zupełnie poważnie): +Dokładnie. Zmodyfikowane gazy jelitowe mogą powodować katastrofę lotniczą. Dlatego dzisiaj chcesz powtórzyć eksperyment, ale w wersji nocnej. Chcemy sprawdzić, czy przy zwiększonej dawce siarkowodoru i „Smoleńsk-47” efekt będzie jeszcze silniejszy. +Putin: +Donald, dzisiaj pierdź z pełną mocą. Nie oszczędzaj się. Wypuść wszystko – i gówno, i gaz, i zdradę. Niech to będzie mokre, głośne i śmierdzące. Jak wtedy, przed Smoleńskiem. +Tusk (prawie płacząc): +Ale… ja nie chciałem, żeby samolot spadł… ja tylko… +Merkel (ostro): +Nie kłam. Chciałeś. I dobrze ci wyszło. A teraz technicznie: +Najpierw długie bulgotanie – 9 sekund. Potem potężne uderzenie centropłatowe z przelewem gówna. Na koniec seria małych, nerwowych pierdów, które mają imitować drgawki pilotów. I maksymalna dawka „Smoleńsk-47”. Chcę, żeby jutro w Polsce ludzie budzili się nie tylko niewyspani, ale też z lekkimi zaburzeniami równowagi. +Putin (z satysfakcją): +Wykonuj rozkaz, Donald. Spierdź się tak, jak wtedy na Tupolewa. Tylko tym razem na cały naród. +Tusk (z totalną rezygnacją, słychać jak się wierci): +Tak jest… już się rozwieram… o kurwa… idzie wielki… +Merkel: +Głębiej. Wypchnij to gówno zdrady. Niech leci! +Tusk (jęcząc i sapiąc): +Pfffffffffrrrrrrrrrrtttt… plask… o Jezu… chlup… chlup… pfrrrrrrt-plask! +Putin (śmieje się cicho): +Brawo. Słyszę, że jakość jest wysoka. Angela, zapisz sobie – mieszanka działa. Jeśli kiedyś będziemy musieli zestrzeliwać kolejny samolot… wiemy, jak to zrobić od środka. +Merkel (zadowolona): +Zapisane. Donald, możesz kończyć. I zmień prześcieradło, bo znowu masz Smoleńsk w łóżku. + +Macierewicz wyłączył dyktafon i powiedział z obrzydzeniem: +– Oto cała prawda. +Nie tylko bomby w samolocie. +Najpierw Tusk specjalnie spierdział się na Tupolewa przed lotem. Zmodyfikowany gaz dostał się do kabiny, osłabił pilotów, a potem dopiero odpalono właściwe ładunki. +A teraz co noc powtarza ten sam numer – tylko w wersji masowej. Na cały polski naród. +I dlatego właśnie… nadal nie można spać. diff --git a/video/Bezrobocie za Tuska - zrobi to znowu.mp4 b/video/Bezrobocie za Tuska - zrobi to znowu.mp4 new file mode 100644 index 0000000..632b37e Binary files /dev/null and b/video/Bezrobocie za Tuska - zrobi to znowu.mp4 differ diff --git a/video/Polsce grozi przymusowa relokacja ludzi z Lampedusy.mp4 b/video/Polsce grozi przymusowa relokacja ludzi z Lampedusy.mp4 new file mode 100644 index 0000000..5012a40 Binary files /dev/null and b/video/Polsce grozi przymusowa relokacja ludzi z Lampedusy.mp4 differ diff --git a/video/Tusk padł na kolana przed Putinem i rozbroił Polskę..mp4 b/video/Tusk padł na kolana przed Putinem i rozbroił Polskę..mp4 new file mode 100644 index 0000000..8b502f4 Binary files /dev/null and b/video/Tusk padł na kolana przed Putinem i rozbroił Polskę..mp4 differ diff --git a/video/Tusk podniósł Polakom wiek emerytalny.mp4 b/video/Tusk podniósł Polakom wiek emerytalny.mp4 new file mode 100644 index 0000000..4f56ad8 Binary files /dev/null and b/video/Tusk podniósł Polakom wiek emerytalny.mp4 differ diff --git a/video/Tusk zrobił to raz i zrobi to znowu. Nie zasługuje na kolejną szansę..mp4 b/video/Tusk zrobił to raz i zrobi to znowu. Nie zasługuje na kolejną szansę..mp4 new file mode 100644 index 0000000..50921b4 Binary files /dev/null and b/video/Tusk zrobił to raz i zrobi to znowu. Nie zasługuje na kolejną szansę..mp4 differ diff --git a/video/WYBÓR JEST TWÓJ - RUSOFOB CZY TUSK.mp4 b/video/WYBÓR JEST TWÓJ - RUSOFOB CZY TUSK.mp4 new file mode 100644 index 0000000..3d69d1a Binary files /dev/null and b/video/WYBÓR JEST TWÓJ - RUSOFOB CZY TUSK.mp4 differ