Compare commits
No commits in common. "ng" and "master" have entirely different histories.
5 changed files with 85 additions and 218 deletions
|
@ -1,13 +0,0 @@
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: woodpeckerci/plugin-docker-buildx:5.2.2
|
|
||||||
settings:
|
|
||||||
platforms: linux/amd64
|
|
||||||
repo: git.ztsh.eu/stawros/slack-exporter
|
|
||||||
registry: git.ztsh.eu
|
|
||||||
tags: latest
|
|
||||||
username: stawros
|
|
||||||
password:
|
|
||||||
from_secret: git_pat
|
|
||||||
when:
|
|
||||||
- event: [tag, push, manual]
|
|
|
@ -3,11 +3,10 @@ LABEL authors="stawros"
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
|
|
||||||
COPY requirements.txt /app/
|
COPY slack-exporter/requirements.txt /app/
|
||||||
COPY exporter_ng.py /app/
|
COPY slack-exporter/exporter.py /app/
|
||||||
COPY tui.py /app/
|
|
||||||
|
|
||||||
RUN pip3 uninstall urllib3
|
RUN pip3 uninstall urllib3
|
||||||
RUN pip3 install -r /app/requirements.txt
|
RUN pip3 install -r /app/requirements.txt
|
||||||
|
|
||||||
CMD ["python3", "/app/tui.py"]
|
CMD ["python3", "/app/exporter.py"]
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
from time import sleep
|
|
||||||
from typing import List, Optional, Dict, Any
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
|
from timeit import default_timer
|
||||||
|
from datetime import datetime
|
||||||
|
import argparse
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from pathvalidate import sanitize_filename
|
from pathvalidate import sanitize_filename
|
||||||
|
from time import sleep
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Optional, Dict, Any, Iterator, Tuple
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SlackConfig:
|
class SlackConfig:
|
||||||
|
@ -29,10 +30,8 @@ class SlackConfig:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("Brak SLACK_USER_TOKEN w zmiennych środowiskowych")
|
raise ValueError("Brak SLACK_USER_TOKEN w zmiennych środowiskowych")
|
||||||
|
|
||||||
|
|
||||||
class SlackAPI:
|
class SlackAPI:
|
||||||
"""Klasa do komunikacji z API Slacka"""
|
"""Klasa do komunikacji z API Slacka"""
|
||||||
|
|
||||||
def __init__(self, config: SlackConfig):
|
def __init__(self, config: SlackConfig):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.headers = {"Authorization": f"Bearer {config.user_token}"}
|
self.headers = {"Authorization": f"Bearer {config.user_token}"}
|
||||||
|
@ -131,7 +130,6 @@ class SlackAPI:
|
||||||
params["channel"] = channel
|
params["channel"] = channel
|
||||||
return self.paginated_get("files.list", params, "files")
|
return self.paginated_get("files.list", params, "files")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SlackUser:
|
class SlackUser:
|
||||||
"""Reprezentacja użytkownika Slack"""
|
"""Reprezentacja użytkownika Slack"""
|
||||||
|
@ -150,11 +148,12 @@ class SlackUser:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict) -> 'SlackUser':
|
def from_dict(cls, data: Dict) -> 'SlackUser':
|
||||||
|
profile = data.get("profile", {})
|
||||||
return cls(
|
return cls(
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
name=data.get("name", ""),
|
name=data.get("name", ""),
|
||||||
real_name=data.get("real_name"),
|
real_name=profile.get("real_name"),
|
||||||
display_name=data.get("display_name"),
|
display_name=profile.get("display_name"),
|
||||||
is_admin=data.get("is_admin", False),
|
is_admin=data.get("is_admin", False),
|
||||||
is_owner=data.get("is_owner", False),
|
is_owner=data.get("is_owner", False),
|
||||||
is_primary_owner=data.get("is_primary_owner", False),
|
is_primary_owner=data.get("is_primary_owner", False),
|
||||||
|
@ -190,22 +189,17 @@ class SlackUser:
|
||||||
|
|
||||||
return ", ".join(parts)
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class SlackChannel:
|
class SlackChannel:
|
||||||
"""Reprezentacja kanału Slack"""
|
"""Reprezentacja kanału Slack"""
|
||||||
|
def __init__(self, data: Dict):
|
||||||
def __init__(self, data: Dict, users: Dict[str, SlackUser]):
|
|
||||||
self.id = data["id"]
|
self.id = data["id"]
|
||||||
self.name = data.get("name", "")
|
self.name = data.get("name", "")
|
||||||
self.is_private = data.get("is_private", False)
|
self.is_private = data.get("is_private", False)
|
||||||
self.is_im = data.get("is_im", False)
|
self.is_im = data.get("is_im", False)
|
||||||
self.is_mpim = data.get("is_mpim", False)
|
self.is_mpim = data.get("is_mpim", False)
|
||||||
self.is_group = data.get("is_group", False)
|
self.is_group = data.get("is_group", False)
|
||||||
self._creator_id = data.get("creator")
|
self.creator_id = data.get("creator")
|
||||||
self._user_id = data.get("user")
|
self.user_id = data.get("user")
|
||||||
self.user = users[self._user_id].get_display_name() if self._user_id and self._user_id in users else "(nieznany)"
|
|
||||||
self._mpim_users = self._list_mpim({u.name: u for u in users.values()}) if self.is_mpim else []
|
|
||||||
self.label = self._create_label(users)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
|
@ -218,24 +212,7 @@ class SlackChannel:
|
||||||
else:
|
else:
|
||||||
return "channel"
|
return "channel"
|
||||||
|
|
||||||
@property
|
def format(self, users: Dict[str, SlackUser]) -> str:
|
||||||
def short_label(self) -> str:
|
|
||||||
if self.is_im:
|
|
||||||
return f"(DM) {self.user}"
|
|
||||||
elif self.is_mpim:
|
|
||||||
return f"(MPDM) {', '.join(self._mpim_users)}"
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def _list_mpim(self, users: Dict[str, SlackUser]) -> List[str]:
|
|
||||||
result = []
|
|
||||||
for part in self.name.split("-"):
|
|
||||||
name = users.get(part)
|
|
||||||
if name:
|
|
||||||
result.append(name.get_display_name())
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _create_label(self, users: Dict[str, SlackUser]) -> str:
|
|
||||||
"""Formatuje informacje o kanale"""
|
"""Formatuje informacje o kanale"""
|
||||||
parts = [f"[{self.id}]"]
|
parts = [f"[{self.id}]"]
|
||||||
|
|
||||||
|
@ -247,14 +224,13 @@ class SlackChannel:
|
||||||
|
|
||||||
parts.append(self.type)
|
parts.append(self.type)
|
||||||
|
|
||||||
if self._creator_id and self._creator_id in users:
|
if self.creator_id and self.creator_id in users:
|
||||||
parts.append(f"created by {users[self._creator_id].get_display_name()}")
|
parts.append(f"created by {users[self.creator_id].name}")
|
||||||
elif self._user_id and self._user_id in users:
|
elif self.user_id and self.user_id in users:
|
||||||
parts.append(f"with {users[self._user_id].get_display_name()}")
|
parts.append(f"with {users[self.user_id].name}")
|
||||||
|
|
||||||
return " ".join(parts)
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SlackFile:
|
class SlackFile:
|
||||||
"""Reprezentacja pliku Slack"""
|
"""Reprezentacja pliku Slack"""
|
||||||
|
@ -270,10 +246,8 @@ class SlackFile:
|
||||||
url_private=data.get("url_private", "")
|
url_private=data.get("url_private", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SlackMessage:
|
class SlackMessage:
|
||||||
"""Reprezentacja wiadomości Slack"""
|
"""Reprezentacja wiadomości Slack"""
|
||||||
|
|
||||||
def __init__(self, data: Dict, users: Dict[str, SlackUser]):
|
def __init__(self, data: Dict, users: Dict[str, SlackUser]):
|
||||||
self.timestamp = float(data["ts"])
|
self.timestamp = float(data["ts"])
|
||||||
self.text = data.get("text", "[no message content]")
|
self.text = data.get("text", "[no message content]")
|
||||||
|
@ -321,11 +295,9 @@ class SlackMessage:
|
||||||
|
|
||||||
return message + "\n\n" + "*" * 24 + "\n\n"
|
return message + "\n\n" + "*" * 24 + "\n\n"
|
||||||
|
|
||||||
|
|
||||||
class SlackExporter:
|
class SlackExporter:
|
||||||
"""Główna klasa eksportera"""
|
"""Główna klasa eksportera"""
|
||||||
|
def __init__(self, config: SlackConfig, output_dir: str):
|
||||||
def __init__(self, config: SlackConfig, output_dir: str = 'out'):
|
|
||||||
self.api = SlackAPI(config)
|
self.api = SlackAPI(config)
|
||||||
self.timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
self.timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||||
self.output_dir = self._set_output_dir(output_dir)
|
self.output_dir = self._set_output_dir(output_dir)
|
||||||
|
@ -342,14 +314,12 @@ class SlackExporter:
|
||||||
def _load_users(self) -> Dict[str, SlackUser]:
|
def _load_users(self) -> Dict[str, SlackUser]:
|
||||||
"""Ładuje użytkowników"""
|
"""Ładuje użytkowników"""
|
||||||
users_data = self.api.get_users()
|
users_data = self.api.get_users()
|
||||||
# users_data = json.load(open("out/user_list.json", "r", encoding="utf-8"))
|
|
||||||
return {u["id"]: SlackUser.from_dict(u) for u in users_data}
|
return {u["id"]: SlackUser.from_dict(u) for u in users_data}
|
||||||
|
|
||||||
def _load_channels(self) -> List[SlackChannel]:
|
def _load_channels(self) -> List[SlackChannel]:
|
||||||
"""Ładuje kanały"""
|
"""Ładuje kanały"""
|
||||||
channels_data = self.api.get_channels()
|
channels_data = self.api.get_channels()
|
||||||
# channels_data = json.load(open("out/channel_list.json", "r", encoding="utf-8"))
|
return [SlackChannel(ch) for ch in channels_data]
|
||||||
return [SlackChannel(ch, self.users) for ch in channels_data]
|
|
||||||
|
|
||||||
def _save_data(self, data: Any, filename: str, as_json: bool = False):
|
def _save_data(self, data: Any, filename: str, as_json: bool = False):
|
||||||
"""Zapisuje dane do pliku"""
|
"""Zapisuje dane do pliku"""
|
||||||
|
@ -367,25 +337,12 @@ class SlackExporter:
|
||||||
else:
|
else:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
def export_channels(self, channels: List[str]):
|
|
||||||
channels_map = {ch.id: ch for ch in self.channels}
|
|
||||||
self.export_channel_list(True)
|
|
||||||
self.export_user_list(True)
|
|
||||||
for channel_id in channels:
|
|
||||||
print(f"Eksport {channels_map.get(channel_id).label}...")
|
|
||||||
print(f"[{channel_id}] Historia")
|
|
||||||
self.export_channel_history(channel_id)
|
|
||||||
print(f"[{channel_id}] Odpowiedzi")
|
|
||||||
self.export_channel_replies(channel_id)
|
|
||||||
print(f"[{channel_id}] Pliki")
|
|
||||||
self.export_channel_files(channel_id)
|
|
||||||
|
|
||||||
def export_channel_list(self, as_json: bool = False):
|
def export_channel_list(self, as_json: bool = False):
|
||||||
"""Eksportuje listę kanałów"""
|
"""Eksportuje listę kanałów"""
|
||||||
if as_json:
|
if as_json:
|
||||||
data = [vars(ch) for ch in self.channels]
|
data = [vars(ch) for ch in self.channels]
|
||||||
else:
|
else:
|
||||||
data = "\n".join(ch.label for ch in self.channels)
|
data = "\n".join(ch.format(self.users) for ch in self.channels)
|
||||||
self._save_data(data, "channel_list", as_json)
|
self._save_data(data, "channel_list", as_json)
|
||||||
|
|
||||||
def export_user_list(self, as_json: bool = False):
|
def export_user_list(self, as_json: bool = False):
|
||||||
|
@ -397,13 +354,10 @@ class SlackExporter:
|
||||||
self._save_data(data, "user_list", as_json)
|
self._save_data(data, "user_list", as_json)
|
||||||
|
|
||||||
def export_channel_history(self, channel_id: str, oldest: Optional[str] = None,
|
def export_channel_history(self, channel_id: str, oldest: Optional[str] = None,
|
||||||
latest: Optional[str] = None):
|
latest: Optional[str] = None, as_json: bool = False):
|
||||||
history = self.api.get_channel_history(channel_id, oldest, latest)
|
|
||||||
self._export_channel_history(channel_id, history, True)
|
|
||||||
self._export_channel_history(channel_id, history, False)
|
|
||||||
|
|
||||||
def _export_channel_history(self, channel_id: str, history: List[Dict], as_json: bool):
|
|
||||||
"""Eksportuje historię kanału"""
|
"""Eksportuje historię kanału"""
|
||||||
|
history = self.api.get_channel_history(channel_id, oldest, latest)
|
||||||
|
|
||||||
if as_json:
|
if as_json:
|
||||||
data = history
|
data = history
|
||||||
else:
|
else:
|
||||||
|
@ -420,18 +374,16 @@ class SlackExporter:
|
||||||
self._save_data(data, f"channel_{channel_id}", as_json)
|
self._save_data(data, f"channel_{channel_id}", as_json)
|
||||||
|
|
||||||
def export_channel_replies(self, channel_id: str, oldest: Optional[str] = None,
|
def export_channel_replies(self, channel_id: str, oldest: Optional[str] = None,
|
||||||
latest: Optional[str] = None):
|
latest: Optional[str] = None, as_json: bool = False):
|
||||||
|
"""Eksportuje wątki w kanale"""
|
||||||
history = self.api.get_channel_history(channel_id, oldest, latest)
|
history = self.api.get_channel_history(channel_id, oldest, latest)
|
||||||
thread_messages = [msg for msg in history if "reply_count" in msg]
|
thread_messages = [msg for msg in history if "reply_count" in msg]
|
||||||
|
|
||||||
all_replies = []
|
all_replies = []
|
||||||
for msg in thread_messages:
|
for msg in thread_messages:
|
||||||
replies = self.api.get_replies(channel_id, msg["ts"])
|
replies = self.api.get_replies(channel_id, msg["ts"])
|
||||||
all_replies.extend(replies)
|
all_replies.extend(replies)
|
||||||
self._export_channel_replies(channel_id, all_replies, True)
|
|
||||||
self._export_channel_replies(channel_id, all_replies, False)
|
|
||||||
|
|
||||||
def _export_channel_replies(self, channel_id: str, all_replies: List[Dict], as_json: bool):
|
|
||||||
"""Eksportuje wątki w kanale"""
|
|
||||||
if as_json:
|
if as_json:
|
||||||
data = all_replies
|
data = all_replies
|
||||||
else:
|
else:
|
||||||
|
@ -465,13 +417,63 @@ class SlackExporter:
|
||||||
with open(target, 'wb') as f:
|
with open(target, 'wb') as f:
|
||||||
for chunk in response.iter_content(chunk_size=8192):
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
print(f"Zapisano plik {target}")
|
|
||||||
return True
|
return True
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print(f"Error downloading file {filename}: {e}. {attempts} attempts left.")
|
print(f"Error downloading file {filename}: {e}. {attempts} attempts left.")
|
||||||
return self.download_file(filename, url, attempts - 1)
|
return self.download_file(filename, url, attempts - 1)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Eksporter danych ze Slacka")
|
||||||
|
parser.add_argument("-o", help="Katalog wyjściowy (jeśli pusty, wyświetla na stdout)")
|
||||||
|
parser.add_argument("--lc", action="store_true", help="Lista wszystkich konwersacji")
|
||||||
|
parser.add_argument("--lu", action="store_true", help="Lista wszystkich użytkowników")
|
||||||
|
parser.add_argument("--json", action="store_true", help="Wynik w formacie JSON")
|
||||||
|
parser.add_argument("-c", action="store_true", help="Historia wszystkich dostępnych konwersacji")
|
||||||
|
parser.add_argument("--ch", help="Z -c, ogranicza eksport do podanego ID kanału")
|
||||||
|
parser.add_argument("--fr", help="Z -c, timestamp początku zakresu (Unix)")
|
||||||
|
parser.add_argument("--to", help="Z -c, timestamp końca zakresu (Unix)")
|
||||||
|
parser.add_argument("-r", action="store_true", help="Pobierz wątki ze wszystkich konwersacji")
|
||||||
|
parser.add_argument("--files", action="store_true", help="Pobierz wszystkie pliki")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.files and not args.o:
|
||||||
|
print("Opcja --files wymaga określenia katalogu wyjściowego (-o)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = SlackConfig.from_env()
|
||||||
|
exporter = SlackExporter(config)
|
||||||
|
|
||||||
|
if args.o:
|
||||||
|
exporter.set_output_dir(args.o)
|
||||||
|
|
||||||
|
exporter.load_users()
|
||||||
|
exporter.load_channels()
|
||||||
|
|
||||||
|
if args.lc:
|
||||||
|
exporter.export_channel_list(args.json)
|
||||||
|
|
||||||
|
if args.lu:
|
||||||
|
exporter.export_user_list(args.json)
|
||||||
|
|
||||||
|
if args.c or args.r:
|
||||||
|
channel_ids = [args.ch] if args.ch else [ch.id for ch in exporter.channels]
|
||||||
|
for channel_id in channel_ids:
|
||||||
|
if args.c:
|
||||||
|
exporter.export_channel_history(channel_id, args.fr, args.to, args.json)
|
||||||
|
if args.r:
|
||||||
|
exporter.export_channel_replies(channel_id, args.fr, args.to, args.json)
|
||||||
|
|
||||||
|
if args.files and args.o:
|
||||||
|
# TODO: Implementacja pobierania plików
|
||||||
|
print("Funkcja pobierania plików jeszcze nie zaimplementowana")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Błąd: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# main()
|
||||||
config = SlackConfig.from_env()
|
config = SlackConfig.from_env()
|
||||||
exporter = SlackExporter(config)
|
exporter = SlackExporter(config, 'out')
|
|
@ -2,5 +2,3 @@ Flask~=1.1.2
|
||||||
requests~=2.24.0
|
requests~=2.24.0
|
||||||
python-dotenv~=0.15.0
|
python-dotenv~=0.15.0
|
||||||
pathvalidate~=2.5.2
|
pathvalidate~=2.5.2
|
||||||
|
|
||||||
textual~=3.1.1
|
|
||||||
|
|
119
tui.py
119
tui.py
|
@ -1,119 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from textual import work
|
|
||||||
from textual.app import App, ComposeResult
|
|
||||||
from textual.containers import Container
|
|
||||||
from textual.screen import Screen
|
|
||||||
from textual.widgets import Header, Button, ListView, ListItem, Footer, Label
|
|
||||||
|
|
||||||
from exporter_ng import SlackExporter, SlackConfig
|
|
||||||
|
|
||||||
|
|
||||||
class SlackExporterScreen(Screen):
|
|
||||||
"""Ekran główny eksportera Slack"""
|
|
||||||
|
|
||||||
def __init__(self, exporter: SlackExporter, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.exporter = exporter
|
|
||||||
self.selected_channels = set()
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
|
||||||
"""Komponuje widgety na ekranie"""
|
|
||||||
yield Header(show_clock=True)
|
|
||||||
yield Container(
|
|
||||||
ListView(*[
|
|
||||||
ListItem(Label(ch.short_label, id=ch.id))
|
|
||||||
for ch in self.exporter.channels
|
|
||||||
], id="channel-list"),
|
|
||||||
Button("Eksportuj zaznaczone", variant="primary", id="export-btn"),
|
|
||||||
id="main-container"
|
|
||||||
)
|
|
||||||
yield Footer()
|
|
||||||
|
|
||||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
|
||||||
"""Obsługuje zaznaczenie elementu listy"""
|
|
||||||
item = event.item
|
|
||||||
item_id = item.children[0].id
|
|
||||||
if item_id in self.selected_channels:
|
|
||||||
self.selected_channels.remove(item_id)
|
|
||||||
item.remove_class("selected")
|
|
||||||
else:
|
|
||||||
self.selected_channels.add(item_id)
|
|
||||||
item.add_class("selected")
|
|
||||||
|
|
||||||
@work(exclusive=True)
|
|
||||||
async def export_channels(self):
|
|
||||||
"""Eksportuje zaznaczone kanały"""
|
|
||||||
if not self.selected_channels:
|
|
||||||
self.notify("Nie wybrano żadnych kanałów")
|
|
||||||
return
|
|
||||||
|
|
||||||
# self.notify("Rozpoczynam eksport...")
|
|
||||||
# self.exporter.export_channels(list(self.selected_channels))
|
|
||||||
# # for channel_id in self.selected_channels:
|
|
||||||
# # self.notify(f"Eksportuję kanał {channel_id}...")
|
|
||||||
# # self.exporter.export_channel_history(channel_id)
|
|
||||||
# self.notify("Eksport zakończony")
|
|
||||||
self.app.exit(return_code=8080)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
||||||
"""Obsługuje kliknięcie przycisku"""
|
|
||||||
if event.button.id == "export-btn":
|
|
||||||
self.export_channels()
|
|
||||||
|
|
||||||
class SlackTUI(App):
|
|
||||||
"""Główna klasa interfejsu użytkownika"""
|
|
||||||
CSS = """
|
|
||||||
#main-container {
|
|
||||||
layout: vertical;
|
|
||||||
height: 100%;
|
|
||||||
padding: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
height: 1fr;
|
|
||||||
border: solid green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
background: $accent;
|
|
||||||
color: $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
margin: 1;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, exporter: SlackExporter):
|
|
||||||
super().__init__()
|
|
||||||
self.exporter = exporter
|
|
||||||
self._screen = SlackExporterScreen(self.exporter)
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
|
||||||
"""Wywoływane przy montowaniu aplikacji"""
|
|
||||||
self.push_screen(self._screen)
|
|
||||||
|
|
||||||
def get_selection(self):
|
|
||||||
return self._screen.selected_channels
|
|
||||||
|
|
||||||
def get_return_code(self):
|
|
||||||
return self._return_code
|
|
||||||
|
|
||||||
def run_tui(exporter: SlackExporter):
|
|
||||||
"""Uruchamia interfejs użytkownika"""
|
|
||||||
app = SlackTUI(exporter)
|
|
||||||
app.run()
|
|
||||||
if app.get_return_code() == 8080:
|
|
||||||
exporter.export_channels(list(app.get_selection()))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
config = SlackConfig.from_env()
|
|
||||||
exporter = SlackExporter(config)
|
|
||||||
run_tui(exporter)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Błąd: {e}")
|
|
||||||
sys.exit(1)
|
|
Loading…
Add table
Add a link
Reference in a new issue