Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions slackdump2html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
35 changes: 32 additions & 3 deletions src/HtmlPrinter.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
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:
slack_data: SlackData
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 = "<!DOCTYPE html>\n"
Expand Down Expand Up @@ -71,6 +77,7 @@ def print_message(self, message: SlackMessage) -> str:
html += f' <span class="date">{self.to_time(message.date)}</span>\n'
html += " </p>\n"
html += f' <p class="message">{self.format_message(message.text)}</p>\n'
html += self.print_files(message.files)
html += self.print_reactions(message.reactions)
html += self.print_replies(message)
html += " </div>\n"
Expand All @@ -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>", '<span class="user-mention">here</span>')
text = text.replace("<!channel>", '<span class="user-mention">channel</span>')
text = re.sub(r"<(http.*?)\|(.*?)>", self.create_html_url_with_alias, text)
Expand Down Expand Up @@ -182,6 +189,27 @@ def print_replies(self, message: SlackMessage) -> str:
html += " </div>\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' <p class="embedded embedded-img"><img src="{relative_url}"></p>\n'
elif file.mimetype.startswith("video/"):
html += f' <p class="embedded embedded-video"><video controls><source src="{relative_url}" /></video></p>\n'
elif file.mimetype.startswith("audio/"):
html += f' <p class="embedded"><audio controls src="{relative_url}"></p>\n'
else:
text = escape(file.title)
html += f' <p class="embedded"><a href="{relative_url}" download>Download {text}</a></p>\n'
return html

def print_reply(self, reply: SlackThreadMessage) -> str:
html = ' <div class="reply">\n'
html += self.print_user_image(reply.user)
Expand All @@ -190,6 +218,7 @@ def print_reply(self, reply: SlackThreadMessage) -> str:
html += f' <span class="date">{self.to_datetime(reply.date)}</span>\n'
html += " </p>\n"
html += f' <p class="message">{self.format_message(reply.text)}</p>\n'
html += self.print_files(reply.files)
html += self.print_reactions(reply.reactions)
html += " </div>\n"
return html
Expand Down
2 changes: 2 additions & 0 deletions src/SlackDataCleaner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
34 changes: 28 additions & 6 deletions src/SlackDumpReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/"

Expand All @@ -17,22 +17,23 @@ 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)

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,
)
)
Expand Down Expand Up @@ -65,7 +66,7 @@ def get_message_content(self, message: dict) -> str:
if self.is_gif(message):
return f'<img src="{message["blocks"][0]["image_url"]}" alt="{message["text"]}">'
else:
return message["text"]
return message.get("text", "")

def is_gif(self, message: dict) -> bool:
return (
Expand All @@ -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"]:
Expand All @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions src/data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,6 +26,7 @@ class SlackMessage:
date: datetime
reactions: dict[str, int]
replies: list[SlackThreadMessage]
files: list[SlackFile]


class ChannelType(StrEnum):
Expand Down
14 changes: 14 additions & 0 deletions src/style/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down