aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-06-25 13:56:37 -0700
committerPinapelz <yukais@pinapelz.com>2025-06-25 13:56:37 -0700
commita0e06e1c121a37ff2a93868945dba4effb8aea58 (patch)
tree98c0bb9f5ab7cc909e7ff5ec2e734e814f5c951b
parent36f9b13e9a966cae780713e6c75757547434a960 (diff)
render video attachments and video urls
-rw-r--r--src/renderer/index.html2
-rw-r--r--src/renderer/src/components/Message.tsx61
2 files changed, 57 insertions, 6 deletions
diff --git a/src/renderer/index.html b/src/renderer/index.html
index edffc05..a2eebe4 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -6,7 +6,7 @@
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
- content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://discord.com https://cdn.discordapp.com https://media.discordapp.net"
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; media-src 'self' https:; connect-src 'self' https://discord.com https://cdn.discordapp.com https://media.discordapp.net"
/>
</head>
diff --git a/src/renderer/src/components/Message.tsx b/src/renderer/src/components/Message.tsx
index 0fc0c68..2dcda4e 100644
--- a/src/renderer/src/components/Message.tsx
+++ b/src/renderer/src/components/Message.tsx
@@ -13,14 +13,16 @@ const avatarBaseUrl = 'https://cdn.discordapp.com/avatars/'
interface ParsedContent {
html: string
imageUrls: string[]
+ videoUrls: string[]
}
-const parseMentionsAndEmotes = (
+const processRenderedContent = (
content: string,
mentions?: DiscordUserMention[]
): ParsedContent => {
let parsedContent = content
const imageUrls: string[] = []
+ const videoUrls: string[] = []
// Replace user mentions <@user_id> with usernames
if (mentions && mentions.length > 0) {
@@ -80,6 +82,11 @@ const parseMentionsAndEmotes = (
parsedContent = parsedContent.replace(urlRegex, (url) => {
// Check if URL ends with image extension
const imageExtensions = /\.(png|jpe?g|gif|webp)(\?[^\s]*)?$/i
+ const videoExtensions = /\.(mp4|webm|mov|avi|mkv|flv|wmv|m4v)(\?[^\s]*)?$/i
+ if (videoExtensions.test(url)) {
+ videoUrls.push(url)
+ return '' // Remove video URL from content
+ }
if (imageExtensions.test(url)) {
imageUrls.push(url)
return '' // Remove image URL from content
@@ -92,11 +99,10 @@ const parseMentionsAndEmotes = (
const placeholder = `__IMG_PLACEHOLDER_${index}__`
parsedContent = parsedContent.replace(placeholder, imgTag)
})
-
// Convert newlines to HTML line breaks
parsedContent = parsedContent.replace(/\n/g, '<br>')
- return { html: parsedContent, imageUrls }
+ return { html: parsedContent, imageUrls, videoUrls }
}
const Message: React.FC<MessageProps> = ({ message, channelNickname }) => {
@@ -126,8 +132,12 @@ const Message: React.FC<MessageProps> = ({ message, channelNickname }) => {
// Separate image and non-image attachments
const imageAttachments =
attachments?.filter((att) => att.content_type?.startsWith('image/')) || []
+ const videoAttachments =
+ attachments?.filter((att) => att.content_type?.startsWith('video/')) || []
const nonImageAttachments =
- attachments?.filter((att) => !att.content_type?.startsWith('image/')) || []
+ attachments?.filter(
+ (att) => !att.content_type?.startsWith('image/') && !att.content_type?.startsWith('video/')
+ ) || []
const renderSticker = (): React.JSX.Element | null => {
if (!sticker_id || sticker_type === undefined) return null
@@ -179,7 +189,7 @@ const Message: React.FC<MessageProps> = ({ message, channelNickname }) => {
{(() => {
if (!content) return null
- const parsed = parseMentionsAndEmotes(content, mentions)
+ const parsed = processRenderedContent(content, mentions)
return (
<>
@@ -204,6 +214,26 @@ const Message: React.FC<MessageProps> = ({ message, channelNickname }) => {
/>
</div>
))}
+
+ {/* Render extracted video URLs as React components */}
+ {parsed.videoUrls.map((videoUrl, index) => (
+ <div key={index} className="attachment-container">
+ <video
+ src={videoUrl}
+ controls
+ style={{
+ maxWidth: '400px',
+ maxHeight: '300px',
+ borderRadius: '6px',
+ marginTop: '8px',
+ display: 'block'
+ }}
+ onError={(e) => {
+ e.currentTarget.style.display = 'none'
+ }}
+ />
+ </div>
+ ))}
</>
)
})()}
@@ -231,6 +261,27 @@ const Message: React.FC<MessageProps> = ({ message, channelNickname }) => {
</div>
))}
+ {/* Render all video attachments */}
+ {videoAttachments.map((attachment, index) => (
+ <div key={`video-${attachment.id || index}`} className="attachment-container">
+ <video
+ src={attachment.proxy_url || attachment.url}
+ controls
+ style={{
+ maxWidth: '400px',
+ maxHeight: '300px',
+ borderRadius: '6px',
+ marginTop: '8px',
+ display: 'block'
+ }}
+ onError={(e) => {
+ console.error('Attachment video failed to load:', attachment.filename)
+ e.currentTarget.style.display = 'none'
+ }}
+ />
+ </div>
+ ))}
+
{/* Render non-image attachments as clickable links */}
{nonImageAttachments.map((attachment, index) => (
<div key={`file-${attachment.id || index}`} className="attachment-container">
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage