diff --git a/slackdump2html.py b/slackdump2html.py index a803220..9cd487d 100644 --- a/slackdump2html.py +++ b/slackdump2html.py @@ -18,13 +18,17 @@ def get_input_file_path() -> str: input_file = get_input_file_path() data_cleaner = SlackDataCleaner() - print("Reading slack dump...", flush=True) + data_dir = None + if input_file.endswith(".json") and os.path.exists(input_file[:-5]): + data_dir = input_file[:-5] + + print(f"Reading slack dump...", flush=True) reader = SlackDumpReader(data_cleaner) - slack_data = reader.read(input_file) + slack_data = reader.read(input_file, data_dir) print("Cleaning data...", flush=True) data_cleaner.replace_names(slack_data) print("Printing output file...", flush=True) - html_printer = HtmlPrinter(slack_data) + html_printer = HtmlPrinter(slack_data, data_dir) html_printer.print() diff --git a/src/HtmlPrinter.py b/src/HtmlPrinter.py index 3ff1692..0c894b1 100644 --- a/src/HtmlPrinter.py +++ b/src/HtmlPrinter.py @@ -1,11 +1,15 @@ import os import re +import shutil +import urllib from datetime import datetime +from html import escape +from pathlib import Path import emoji from src.SlackDataCleaner import SlackDataCleaner -from src.SlackDumpReader import SlackData, SlackMessage, SlackThreadMessage +from src.SlackDumpReader import SlackData, SlackFile, SlackMessage, SlackThreadMessage class HtmlPrinter: @@ -13,9 +17,11 @@ class HtmlPrinter: standard_emojis: dict[str, str] = dict() used_custom_emojis: set[str] = set() data_cleaner = SlackDataCleaner() + data_dir: str | None - def __init__(self, slack_data: SlackData): + def __init__(self, slack_data: SlackData, data_dir: str | None): self.slack_data = slack_data + self.data_dir = data_dir def print(self): html1 = "\n" @@ -71,6 +77,7 @@ def print_message(self, message: SlackMessage) -> str: html += f' {self.to_time(message.date)}\n' html += "

\n" html += f'

{self.format_message(message.text)}

\n' + html += self.print_files(message.files) html += self.print_reactions(message.reactions) html += self.print_replies(message) html += " \n" @@ -90,7 +97,7 @@ def calc_color_num(self, user: str) -> int: letter_sum += ord(letter) return letter_sum % 15 - def format_message(self, text: str) -> str: + def format_message(self, text: str) -> str: text = text.replace("", 'here') text = text.replace("", 'channel') text = re.sub(r"<(http.*?)\|(.*?)>", self.create_html_url_with_alias, text) @@ -182,6 +189,27 @@ def print_replies(self, message: SlackMessage) -> str: html += " \n" return html + def print_files(self, files: list[SlackFile]) -> str: + html = "" + for file in files: + input_filename = f"{self.data_dir}/{file.id}-{file.name}" + output_filename = f"{self.slack_data.channel_name}/{file.id}-{file.name}" + output_file = Path("out/" + output_filename) + output_file.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(input_filename, f"out/{output_filename}") + relative_url = urllib.parse.quote(output_filename) + + if file.mimetype.startswith("image/"): + html += f'

\n' + elif file.mimetype.startswith("video/"): + html += f'

\n' + elif file.mimetype.startswith("audio/"): + html += f'

\n' + else: + text = escape(file.title) + html += f'

Download {text}

\n' + return html + def print_reply(self, reply: SlackThreadMessage) -> str: html = '
\n' html += self.print_user_image(reply.user) @@ -190,6 +218,7 @@ def print_reply(self, reply: SlackThreadMessage) -> str: html += f' {self.to_datetime(reply.date)}\n' html += "

\n" html += f'

{self.format_message(reply.text)}

\n' + html += self.print_files(reply.files) html += self.print_reactions(reply.reactions) html += "
\n" return html diff --git a/src/SlackDataCleaner.py b/src/SlackDataCleaner.py index 279129f..2ccaa2c 100644 --- a/src/SlackDataCleaner.py +++ b/src/SlackDataCleaner.py @@ -189,6 +189,8 @@ def _read_channel_file(self): for line in lines[1:]: line = line.replace("🔒 ", "🔒").strip() parts = line.split(" ") + if parts[-1] == '(archived)': + continue self.channel_map[parts[0]] = (ChannelType(parts[-1][0]), parts[-1][1:]) def replace_names(self, slack_data: SlackData): diff --git a/src/SlackDumpReader.py b/src/SlackDumpReader.py index 9f14c1e..e7dea5f 100644 --- a/src/SlackDumpReader.py +++ b/src/SlackDumpReader.py @@ -6,7 +6,7 @@ from typing import Tuple from src.SlackDataCleaner import SlackDataCleaner -from src.data_structures import SlackData, SlackMessage, SlackThreadMessage, ChannelType +from src.data_structures import SlackData, SlackFile, SlackMessage, SlackThreadMessage, ChannelType EMOJI_PATH = "data/emojis/emojis/" @@ -17,7 +17,7 @@ class SlackDumpReader: def __init__(self, data_cleaner: SlackDataCleaner): self.data_cleaner = data_cleaner - def read(self, file_path: str) -> SlackData: + def read(self, file_path: str, data_dir: str | None) -> SlackData: dump_file = open(file_path, encoding="utf-8") file_name = Path(file_path).stem dump_data = json.load(dump_file) @@ -25,14 +25,15 @@ def read(self, file_path: str) -> SlackData: messages: list[SlackMessage] = list() for message in dump_data["messages"]: - if message["type"] == "message" and "subtype" not in message and "text" in message: - replies = self.read_replies(message) + if message["type"] == "message" and "subtype" not in message: + replies = self.read_replies(message, data_dir) messages.append( SlackMessage( user=self.get_user(message), text=self.get_message_content(message), date=self.to_datetime(message["ts"]), reactions=self.read_reactions(message), + files=self.read_files(message, data_dir), replies=replies, ) ) @@ -65,7 +66,7 @@ def get_message_content(self, message: dict) -> str: if self.is_gif(message): return f'{message[' else: - return message["text"] + return message.get("text", "") def is_gif(self, message: dict) -> bool: return ( @@ -74,7 +75,7 @@ def is_gif(self, message: dict) -> bool: and str(message["blocks"][0]["image_url"]).__contains__("giphy.com") ) - def read_replies(self, message: dict) -> list[SlackThreadMessage]: + def read_replies(self, message: dict, data_dir: str | None) -> list[SlackThreadMessage]: replies: list[SlackThreadMessage] = list() if "slackdump_thread_replies" in message: for reply in message["slackdump_thread_replies"]: @@ -85,10 +86,31 @@ def read_replies(self, message: dict) -> list[SlackThreadMessage]: text=self.get_message_content(reply), date=self.to_datetime(reply["ts"]), reactions=self.read_reactions(reply), + files=self.read_files(reply, data_dir) ) ) return replies + def read_files(self, message: dict, data_dir: str | None) -> list[SlackFile]: + files: list[SlackFile] = list() + if "files" in message and data_dir: + for file in message["files"]: + if not file["id"] or not file["name"]: + continue + data_file = Path(data_dir) / ("%s-%s" % (file["id"], file["name"])) + if not data_file.exists(): + print(f"missing file {data_file}") + continue + files.append( + SlackFile( + id=file["id"], + name=file["name"], + title=file["title"], + mimetype=file["mimetype"], + ) + ) + return files + def read_reactions(self, message: dict) -> dict[str, int]: reactions: dict[str, int] = dict() if "reactions" in message: diff --git a/src/data_structures.py b/src/data_structures.py index b972194..8c7f092 100644 --- a/src/data_structures.py +++ b/src/data_structures.py @@ -3,12 +3,20 @@ from enum import StrEnum +@dataclass +class SlackFile: + id: str + name: str + title: str + mimetype: str + @dataclass class SlackThreadMessage: user: str text: str date: datetime reactions: dict[str, int] + files: list[SlackFile] @dataclass @@ -18,6 +26,7 @@ class SlackMessage: date: datetime reactions: dict[str, int] replies: list[SlackThreadMessage] + files: list[SlackFile] class ChannelType(StrEnum): diff --git a/src/style/style.css b/src/style/style.css index 40d7825..1e654a3 100644 --- a/src/style/style.css +++ b/src/style/style.css @@ -226,6 +226,20 @@ h1 { max-width: 1.1em; } +.embedded { + margin-left: 3.5em; +} + +.embedded-img > img { + max-height: 35vh; + max-width: 100vh; +} + +.embedded-video > video { + max-height: 35vh; + max-width: 100vh; +} + code { background-color: #efefef; border: 1px solid #d4d4d4;