import { AlignmentFlag, CursorShape, Direction, QBoxLayout, QGridLayout, QLabel, QPixmap, QWidget, WidgetEventTypes, } from '@nodegui/nodegui'; import { Collection, GuildChannel, Message, MessageAttachment, MessageEmbedImage, MessageMentions, } from 'discord.js'; import { __ } from 'i18n'; import markdownIt from 'markdown-it'; import open from 'open'; import { extname, join } from 'path'; import { pathToFileURL } from 'url'; import { app, MAX_QSIZE, PIXMAP_EXTS } from '../..'; import { pictureWorker } from '../../utilities/PictureWorker'; import { resolveEmoji } from '../../utilities/ResolveEmoji'; import { DLabel } from '../DLabel/DLabel'; import { MessageItem } from './MessageItem'; const MD = markdownIt({ html: false, linkify: true, breaks: true, }).disable(['hr', 'blockquote', 'lheading']).enable('link'); const EMOJI_REGEX = //g; const INVITE_REGEX = /(https?:\/\/)?(www\.)?(discord\.(gg|io|me|li)|discordapp\.com\/invite)\/.+[A-z]/g; const EMOJI_PLACEHOLDER = join(__dirname, 'assets/icons/emoji-placeholder.png'); function getCorrectSize(w?: number | null, h?: number | null) { let width = w; let height = h; width = width || 0; height = height || 0; const ratio = width / height; if (width > 400) { width = 400; height = width / ratio; } if (height > 300) { height = 300; width = height * ratio; } return { width: Math.ceil(width), height: Math.ceil(height) }; } export async function processMentions(content: string, message: Message) { // const { guild } = message; let newContent = content; const userMatches = content.match(MessageMentions.USERS_PATTERN) || []; const roleMatches = content.match(MessageMentions.ROLES_PATTERN) || []; const channelMatches = content.match(MessageMentions.CHANNELS_PATTERN) || []; await Promise.all([ ...userMatches.map(async (match) => { const id = match.replace(/<@!?/g, '').replace('>', ''); /* if (guild) { let memberName: string; try { const member = await guild.members.fetch(id); memberName = member.nickname || member.user.username; } catch (e) { memberName = 'unknown-member'; } newContent = newContent.replace(match, `@${memberName}`); } else { */ let userName: string; try { const user = await app.client.users.fetch(id); userName = user.username; } catch (e) { userName = 'unknown-user'; } newContent = newContent.replace(match, `@${userName}`); // } }), ...roleMatches.map(async (match) => { const id = match.replace(/<@&/g, '').replace('>', ''); const role = await message.guild?.roles.fetch(id); if (role) newContent = newContent.replace(match, `@${role.name}`); }), ...channelMatches.map(async (match) => { const id = match.replace(/<#/g, '').replace('>', ''); const channel = app.client.channels.resolve(id) as GuildChannel; const channelName = channel?.name || 'invalid-channel'; newContent = newContent.replace(match, `#${channelName}`); }), ]); return newContent; } export function processMarkdown(content: string) { let c = content; if (!app.config.processMarkDown) return c.replace(/\n/g, '
'); c = c .replace(/<\/?p>/g, '') .split('\n') .map((line) => (line.startsWith('> ') ? line.replace('> ', '') : line)) .join('\n') .trim(); return MD.render(c); } export async function processEmojiPlaceholders(content: string): Promise { let c = content; const emoIds = c.match(EMOJI_REGEX) || []; const size = c.replace(EMOJI_REGEX, '').replace(/<\/?p>/g, '').trim() === '' ? 48 : 24; for (const emo of emoIds) { c = c.replace(emo, ``); } return c; } export async function processEmojis(content: string): Promise { const emoIds = content.match(EMOJI_REGEX) || []; const size = content.replace(EMOJI_REGEX, '').replace(/<\/?p>/g, '').trim() === '' ? 48 : 24; let cnt = content; const promises: Promise[] = emoIds.map((emo) => { const [type, name, id] = emo.replace('<', '').replace('>', '').split(':'); const format = type === 'a' ? 'gif' : 'png'; return resolveEmoji({ emoji_id: id, emoji_name: name }) .then((emojiPath) => { // @ts-ignore const uri = new URL(app.client.rest.cdn.Emoji(id, format)); uri.searchParams.append('emoji_name', name); const pix = new QPixmap(emojiPath); const larger = pix.width() > pix.height() ? 'width' : 'height'; cnt = cnt.replace(emo, ``); }).catch(() => { }); }); try { await Promise.all(promises); } catch (e) { } return cnt; } export function processEmbeds(message: Message, item: MessageItem): QWidget[] { return message.embeds.map((embed) => { const container = new QWidget(); const cLayout = new QBoxLayout(Direction.LeftToRight); const body = new QWidget(); cLayout.addWidget(body); cLayout.addStretch(1); cLayout.setContentsMargins(0, 0, 0, 0); container.setLayout(cLayout); const layout = new QBoxLayout(Direction.TopToBottom); layout.setSpacing(8); layout.setContentsMargins(16, 16, 16, 16); body.setObjectName('EmbedBody'); body.setFlexNodeSizeControlled(false); body.setMaximumSize(520, MAX_QSIZE); body.setLayout(layout); body.setInlineStyle(`border-left: 4px solid ${embed.hexColor || 'rgba(0, 0, 0, 0.3)'};`); if (embed.author) { const aulayout = new QBoxLayout(Direction.LeftToRight); aulayout.setContentsMargins(0, 0, 0, 0); aulayout.setSpacing(8); if (embed.author.proxyIconURL) { const auimage = new QLabel(body); auimage.setFixedSize(24, 24); pictureWorker.loadImage(embed.author.proxyIconURL) .then((path) => !item._destroyed && auimage.setPixmap(new QPixmap(path).scaled(24, 24))); aulayout.addWidget(auimage); } const auname = new DLabel(body); auname.setObjectName('EmbedAuthorName'); if (embed.author.url) { auname.setText(`${embed.author.name}`); } else { auname.setText(embed.author.name || ''); } aulayout.addWidget(auname); layout.addLayout(aulayout); } if (embed.title) { const title = new DLabel(body); title.setObjectName('EmbedTitle'); if (embed.url) { title.setText(`${embed.title}`); } else { title.setText(embed.title); } layout.addWidget(title); } if (embed.description) { const descr = new DLabel(body); descr.setObjectName('EmbedDescription'); const description = processMarkdown(embed.description) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .trim(); processMentions(description, message).then((pdescription) => { if (!item._destroyed) descr.setText(pdescription); }); layout.addWidget(descr); } const grid = new QGridLayout(); // TODO: QGridLayout::addLayout grid.setContentsMargins(0, 0, 0, 0); grid.setSpacing(8); for (const field of embed.fields) { const fieldWidget = new QWidget(body); const flayout = new QBoxLayout(Direction.TopToBottom); flayout.setSpacing(2); flayout.setContentsMargins(0, 0, 0, 0); const fTitle = new QLabel(body); fTitle.setObjectName('EmbedFieldTitle'); fTitle.setText(field.name); const fValue = new DLabel(body); fValue.setObjectName('EmbedFieldValue'); fValue.setText(processMarkdown(field.value)); flayout.addWidget(fTitle); flayout.addWidget(fValue); fieldWidget.setLayout(flayout); grid.addWidget(fieldWidget, grid.rowCount(), 0, 1, field.inline ? 1 : 2); } layout.addLayout(grid); if (embed.image || embed.thumbnail) { const image = embed.image || embed.thumbnail as MessageEmbedImage; const { width, height } = getCorrectSize(image.width, image.height); const qImage = new QLabel(); qImage.setCursor(CursorShape.PointingHandCursor); qImage.setFixedSize(width, height); qImage.setObjectName('EmbedImage'); qImage.setInlineStyle('background-color: #2f3136'); body.setMaximumSize(width + 32, MAX_QSIZE); // @ts-ignore container.loadImages = async function loadImages() { if (item._destroyed || !image.proxyURL) return; const path = await pictureWorker.loadImage(image.proxyURL); if (path) qImage.setPixmap(new QPixmap(path).scaled(width, height, 1, 1)); qImage.setInlineStyle('background-color: transparent'); }; layout.addWidget(qImage); } return container; }); } export async function processInvites(message: Message): Promise { const invites = message.content.match(INVITE_REGEX) || []; const widgets: QWidget[] = []; for (const inviteLink of invites) { try { // eslint-disable-next-line no-await-in-loop const invite = await app.client.fetchInvite(inviteLink); const item = new QWidget(); item.setObjectName('InviteContainer'); item.setMinimumSize(432, 0); item.setMaximumSize(432, MAX_QSIZE); const layout = new QBoxLayout(Direction.TopToBottom); item.setLayout(layout); layout.setContentsMargins(16, 16, 16, 16); layout.setSpacing(12); const helperText = new QLabel(item); helperText.setText(__('GUILD_PROFILE_JOIN_SERVER_BUTTON')); helperText.setObjectName('Helper'); const mainLayout = new QBoxLayout(Direction.LeftToRight); const avatar = new QLabel(item); avatar.setFixedSize(50, 50); avatar.setObjectName('Icon'); avatar.setText(invite.guild?.nameAcronym || ''); avatar.setInlineStyle('font-size: 18px;'); avatar.setAlignment(AlignmentFlag.AlignCenter); // @ts-ignore item.loadImages = async function loadImages() { const iconUrl = invite.guild?.iconURL({ size: 256, format: 'png' }); if (!iconUrl) return; const path = await pictureWorker.loadImage(iconUrl); avatar.setPixmap(new QPixmap(path).scaled(50, 50, 1, 1)); avatar.setObjectName(''); }; const infoLayout = new QBoxLayout(Direction.TopToBottom); const nameLabel = new QLabel(item); nameLabel.setText(invite.guild?.name || ''); nameLabel.setAlignment(AlignmentFlag.AlignVCenter); nameLabel.setObjectName('Name'); const infoLabel = new QLabel(item); infoLabel.setText(`${__('TOTAL_MEMBERS')}: ${invite.memberCount}`); infoLabel.setInlineStyle('font-size: small; color: #72767d;'); infoLayout.addStretch(1); infoLayout.addWidget(nameLabel); infoLayout.addWidget(infoLabel); infoLayout.addStretch(1); mainLayout.addWidget(avatar); mainLayout.addLayout(infoLayout, 1); layout.addWidget(helperText); layout.addLayout(mainLayout, 1); widgets.push(item); } catch (e) { } } return widgets; } export function processAttachments(attachments: Collection): QLabel[] { return attachments.map((attach) => { const { width, height } = getCorrectSize(attach.width, attach.height); const url = `${attach.proxyURL}?width=${width}&height=${height}`; const isImage = PIXMAP_EXTS.includes(extname(attach.url).slice(1).toUpperCase()); const qimage = new DLabel(); qimage.setFixedSize(width, height); qimage.setInlineStyle('background-color: #2f3136; border-radius: 3px;'); qimage.setCursor(CursorShape.PointingHandCursor); qimage.setAlignment(AlignmentFlag.AlignCenter); qimage.setProperty('toolTip', `${attach.name || attach.url}
Size: ${attach.size} bytes ${attach.width && attach.height ? `
Resolution: ${attach.width}x${attach.height}` : ''} `); qimage.addEventListener(WidgetEventTypes.MouseButtonPress, () => { open(attach.url); }); if (!isImage) { qimage.setPixmap(new QPixmap(join(__dirname, 'assets/icons/file.png'))); qimage.setFixedSize(160, 80); } else { // @ts-ignore qimage.loadImages = async function loadImages() { if (!isImage) return; const image = await pictureWorker.loadImage(url); qimage.setPixmap(new QPixmap(image)); qimage.setInlineStyle('background-color: transparent'); }; } return qimage; }); }