Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,7 @@ chat_exporter/config.py
*.sqlite

.idea/*
.idea\*
.idea\*

# macOS
.DS_Store
2 changes: 1 addition & 1 deletion chat_exporter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
AttachmentToLocalFileHostHandler,
AttachmentToDiscordChannelHandler)

__version__ = "2.9.0"
__version__ = "3.0.0"

__all__ = (
export,
Expand Down
118 changes: 98 additions & 20 deletions chat_exporter/construct/assets/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,28 @@ async def flow(self):
return self.attachments

async def build_attachment(self):
is_spoiler = self._is_spoiler()

if self.attachments.content_type is not None:
if "image" in self.attachments.content_type:
return await self.image()
await self.image()
if is_spoiler:
self._mark_spoiler()
return
elif "video" in self.attachments.content_type:
return await self.video()
await self.video()
if is_spoiler:
self._mark_spoiler()
return
elif "audio" in self.attachments.content_type:
return await self.audio()
await self.audio()
if is_spoiler:
self._mark_spoiler()
return

await self.file()
if is_spoiler:
self._mark_spoiler()

async def image(self):
self.attachments = await fill_out(self.guild, img_attachment, [
Expand Down Expand Up @@ -76,6 +90,14 @@ def get_file_size(file_size):
return "%s %s" % (s, size_name[i])

async def get_file_icon(self) -> str:
return self.resolve_file_icon(
name=str(getattr(self.attachments, "filename", "") or ""),
content_type=str(getattr(self.attachments, "content_type", "") or ""),
url=str(getattr(self.attachments, "proxy_url", "") or "")
)

@staticmethod
def resolve_file_icon(name: str = "", content_type: str = "", url: str = "") -> str:
acrobat_types = "pdf"
webcode_types = "html", "htm", "css", "rss", "xhtml", "xml"
code_types = "py", "cgi", "pl", "gadget", "jar", "msi", "wsf", "bat", "php", "js"
Expand All @@ -84,23 +106,79 @@ async def get_file_icon(self) -> str:
"sxi", "sxc", "sxd", "stw"
)
archive_types = (
"br", "rpm", "dcm", "epub", "zip", "tar", "rar", "gz", "bz2", "7x", "deb", "ar", "Z", "lzo", "lz", "lz4",
"arj", "pkg", "z"
"br", "rpm", "dcm", "epub", "zip", "tar", "rar", "gz", "bz2", "7x", "7z", "deb", "ar", "z", "lzo", "lz",
"lz4", "arj", "pkg"
)

for tmp in [self.attachments.proxy_url, self.attachments.filename]:
if not tmp:
continue
extension = tmp.rsplit('.', 1)[-1]
if extension in acrobat_types:
return DiscordUtils.file_attachment_acrobat
elif extension in webcode_types:
return DiscordUtils.file_attachment_webcode
elif extension in code_types:
return DiscordUtils.file_attachment_code
elif extension in document_types:
return DiscordUtils.file_attachment_document
elif extension in archive_types:
return DiscordUtils.file_attachment_archive

content_type = (content_type or "").lower()
if content_type.startswith("audio/"):
return DiscordUtils.file_attachment_audio

def _extension_from(value: str) -> str:
if not value:
return ""
cleaned = str(value).split("?", 1)[0].split("#", 1)[0]
if "." not in cleaned:
return ""
return cleaned.rsplit(".", 1)[-1].lower()

extension = ""
for candidate in (name, url):
extension = _extension_from(candidate)
if extension:
break

if not extension and content_type:
if "html" in content_type:
extension = "html"
elif "pdf" in content_type:
extension = "pdf"

if extension in acrobat_types:
return DiscordUtils.file_attachment_acrobat
elif extension in webcode_types:
return DiscordUtils.file_attachment_webcode
elif extension in code_types:
return DiscordUtils.file_attachment_code
elif extension in document_types:
return DiscordUtils.file_attachment_document
elif extension in archive_types:
return DiscordUtils.file_attachment_archive

return DiscordUtils.file_attachment_unknown

def _is_spoiler(self) -> bool:
"""Check if an attachment is marked as a spoiler."""
attachment = self.attachments
spoiler_attr = getattr(attachment, "spoiler", None)
if callable(spoiler_attr):
try:
return bool(spoiler_attr())
except Exception:
pass
if spoiler_attr is not None:
return bool(spoiler_attr)

is_spoiler_method = getattr(attachment, "is_spoiler", None)
if callable(is_spoiler_method):
try:
return bool(is_spoiler_method())
except Exception:
return False

return False

def _mark_spoiler(self):
"""Add spoiler styling class to the rendered attachment HTML."""
if not isinstance(self.attachments, str):
return

replacements = (
('<div class=chatlog__attachment>', '<div class="chatlog__attachment chatlog__attachment-spoiler">'),
('class="chatlog__attachment"', 'class="chatlog__attachment chatlog__attachment-spoiler"'),
)

for target, replacement in replacements:
if target in self.attachments:
self.attachments = self.attachments.replace(target, replacement, 1)
break
Loading
Loading