Skip to content

Commit

Permalink
Merge pull request #44 from ZickZenni/reactions
Browse files Browse the repository at this point in the history
feat: add message reactions
  • Loading branch information
ZickZenni authored Sep 19, 2024
2 parents 8a8debe + ae912dd commit 39df390
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 13 deletions.
3 changes: 3 additions & 0 deletions src/common/appconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AppConfig {
assetsPath: string;
}
12 changes: 12 additions & 0 deletions src/discord/structures/Emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Snowflake } from './Snowflake';

export interface Emoji {
animated: boolean;
available: boolean;
id: Snowflake;
managed: boolean;
name: string;
require_colons: boolean;
roles: any[];
version: number;
}
2 changes: 2 additions & 0 deletions src/discord/structures/Message.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Attachment } from './Attachment';
import { Reaction } from './Reaction';
import { Snowflake } from './Snowflake';
import { IUserData } from './user/BaseUser';

Expand All @@ -9,4 +10,5 @@ export interface Message {
id: Snowflake;
author: IUserData;
channel_id: Snowflake;
reactions?: Reaction[];
}
9 changes: 9 additions & 0 deletions src/discord/structures/Reaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Reaction {
count: number;
emoji: {
id: string | null;
name: string;
animated?: boolean;
};
me: true;
}
5 changes: 3 additions & 2 deletions src/discord/structures/guild/BaseGuild.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { IChannelData } from '../channel/BaseChannel';
import { Emoji } from '../Emoji';
import { Snowflake } from '../Snowflake';

/**
Expand Down Expand Up @@ -123,7 +124,7 @@ export interface IGuildData {
default_message_notifications: DefaultMessageNotificationLevel;
explicit_content_filter: ExplicitContentFilterLevel;
roles: any[];
emojis: any[];
emojis: Emoji[];
/**
* https://discord.com/developers/docs/resources/guild#guild-object-guild-features
*/
Expand Down Expand Up @@ -179,7 +180,7 @@ export abstract class BaseGuild {

public roles: any[];

public emojis: any[];
public emojis: Emoji[];

public features: string[];

Expand Down
28 changes: 21 additions & 7 deletions src/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { registerHandler, registerListener, sendToRenderer } from './ipc';
import { CreateMessageOptions } from '../discord/structures/channel/BaseChannel';
import { Message } from '../discord/structures/Message';
import Tenor from './utils/tenor';
import { resourcesPath } from './paths';
import { AppConfig } from '../common/appconfig';

export default class WaveCordApp {
public readonly resourcesPath: string;

public readonly isDebug: boolean =
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';

Expand Down Expand Up @@ -41,9 +41,6 @@ export default class WaveCordApp {

app.setPath('userData', path.join(app.getPath('appData'), 'WaveCord'));

this.resourcesPath = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');
this.instanceLock = app.requestSingleInstanceLock();

logger.info('Starting app...');
Expand Down Expand Up @@ -114,7 +111,7 @@ export default class WaveCordApp {
height: 763,
minWidth: 450,
minHeight: 250,
icon: path.join(this.resourcesPath, 'icon.png'),
icon: path.join(resourcesPath, 'icon.png'),
frame: false,
title: 'WaveCord',
webPreferences: {
Expand Down Expand Up @@ -165,6 +162,13 @@ export default class WaveCordApp {
}

private registerIpcs() {
registerHandler('app:config', () => {
const config: AppConfig = {
assetsPath: resourcesPath,
};
return config;
});

registerListener('window:minimize', () => {
this.window?.minimize();
});
Expand Down Expand Up @@ -204,6 +208,16 @@ export default class WaveCordApp {
return this.ready;
});

registerHandler('discord:request-emoji', (emojiId: string) => {
const guild = this.discord.guilds.cache
.values()
.find((v) => v.emojis.find((e) => e.id === emojiId) !== undefined);

if (guild === undefined) return null;

return guild.emojis.find((e) => e.id === emojiId);
});

registerHandler('discord:user', (userId: string | undefined) => {
if (userId === undefined) return this.discord.users.clientUser?.toRaw();

Expand Down Expand Up @@ -264,7 +278,7 @@ export default class WaveCordApp {

private initTray() {
logger.info('Creating new tray.');
this.tray = new Tray(path.join(this.resourcesPath, 'icon.png'));
this.tray = new Tray(path.join(resourcesPath, 'icon.png'));
const contextMenu = Menu.buildFromTemplate([
{ type: 'separator' },
{
Expand Down
2 changes: 2 additions & 0 deletions src/main/ipc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BrowserWindow, ipcMain } from 'electron';

export type IpcChannels =
| 'app:config'
| 'logger:info'
| 'logger:warn'
| 'logger:error'
Expand All @@ -23,6 +24,7 @@ export type IpcChannels =
| 'discord:gateway:message-create'
| 'discord:relationships'
| 'discord:private-channels'
| 'discord:request-emoji'
| 'tenor:fetch-gif';

export function registerHandler(
Expand Down
9 changes: 9 additions & 0 deletions src/main/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { app } from 'electron';
import path from 'path';

// eslint-disable-next-line import/prefer-default-export
export const resourcesPath = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');

export const assets = `${resourcesPath}/`;
1 change: 1 addition & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '@/styles/channel/ChannelButton.css';
import '@/styles/channel/ChannelList.css';
import '@/styles/channel/Message.css';
import '@/styles/channel/MessageAttachment.css';
import '@/styles/channel/MessageReaction.css';

import '@/styles/directmessage/DirectMessageButton.css';
import '@/styles/directmessage/DirectMessageList.css';
Expand Down
15 changes: 15 additions & 0 deletions src/renderer/components/channel/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Message as MessageData } from '@/discord/structures/Message';
import RendererUser from '@/discord/structures/user/RendererUser';
import useGif from '@/hooks/useGif';
import MessageAttachment from './MessageAttachment';
import MessageReaction from './MessageReaction';

type MessageProps = {
message: MessageData;
Expand All @@ -13,6 +14,8 @@ export default function Message({ message }: MessageProps) {
const author = new RendererUser(message.author);
const authorDecorationUrl = author.getAvatarDecorationUrl();

const reactions = message.reactions ?? [];

return (
<div className="Message">
<div className="Message--author-container">
Expand Down Expand Up @@ -49,6 +52,18 @@ export default function Message({ message }: MessageProps) {
);
})}
</div>

<div className="Message--reactions-container">
{reactions.map((reaction) => {
return (
<MessageReaction
key={`Reaction:${reaction.emoji.name}`}
messageId={message.id}
reaction={reaction}
/>
);
})}
</div>
</div>
</div>
);
Expand Down
53 changes: 53 additions & 0 deletions src/renderer/components/channel/MessageReaction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Reaction } from '@/discord/structures/Reaction';
import { Snowflake } from '@/discord/structures/Snowflake';
import { grabTheRightIcon } from '@/utils/emojiUtils';
import { useEffect, useState } from 'react';

type MessageReactionProps = {
messageId: Snowflake;
reaction: Reaction;
};

export default function MessageReaction({
messageId,
reaction,
}: MessageReactionProps) {
const [url, setUrl] = useState<string | undefined>(undefined);
const [color, setColor] = useState<string>('rgb(22,22,22)');
const [borderColor, setBorderColor] = useState<string>('rgb(22,22,22)');

useEffect(() => {
const newId =
Number(reaction.emoji.id) + Number(messageId) / (256 * 256 * 256);
const str = newId.toString();

const r = Number(str.slice(0, 3)) % 255;
const g = Number(str.slice(4, 7)) % 255;
const b = Number(str.slice(8, 11)) % 255;

setColor(`rgb(${r},${g},${b})`);
setBorderColor(`rgb(${r * 0.7},${g * 0.7},${b * 0.7})`);

if (reaction.emoji.id === null) {
setUrl(
`https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/${grabTheRightIcon(reaction.emoji.name)}.svg`,
);
return;
}

setUrl(
`https://cdn.discordapp.com/emojis/${reaction.emoji.id}.${reaction.emoji.animated ? 'gif' : 'png'}`,
);
}, [messageId, reaction]);

return (
<div
className={`MessageReaction ${reaction.me && 'MessageReaction--clicked'}`}
style={{ background: color, borderColor }}
role="presentation"
>
<img className="MessageReaction--emoji" src={url} alt="" />
<p className="MessageReaction--count">{reaction.count}</p>
</div>
);
}
2 changes: 1 addition & 1 deletion src/renderer/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
content="default-src 'self';
script-src 'self';
font-src 'self' https://fonts.gstatic.com;
img-src 'self' https://cdn.discordapp.com https://*.tenor.com;
img-src 'self' https://cdn.discordapp.com https://*.tenor.com https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
Expand Down
18 changes: 15 additions & 3 deletions src/renderer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { createRoot } from 'react-dom/client';
import App from './App';
import { AppConfig } from '../common/appconfig';

const container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);
root.render(<App />);
// eslint-disable-next-line import/prefer-default-export, import/no-mutable-exports
export let config: AppConfig | null = null;

window.electron.ipcRenderer
.invoke('app:config')
.then((conf: AppConfig) => {
config = conf;

const container = document.getElementById('root') as HTMLElement;
const root = createRoot(container);
root.render(<App />);
return true;
})
.catch((err) => console.error(err));
12 changes: 12 additions & 0 deletions src/renderer/styles/channel/Message.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
flex-direction: column;
}

.Message--content {
white-space: pre-line;
}

.Message--attachments-container {
display: flex;
}
Expand All @@ -41,3 +45,11 @@
max-width: 300px;
max-height: 300px;
}

.Message--reactions-container {
margin-top: 6px;
width: 100%;
flex-wrap: wrap;
display: flex;
gap: 6px;
}
22 changes: 22 additions & 0 deletions src/renderer/styles/channel/MessageReaction.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.MessageReaction {
display: flex;
gap: 6px;
min-width: 40px;
height: 20px;
padding: 10px 12px;
border-radius: 10px;
border: 2px solid #111;
cursor: pointer;
}

.MessageReaction--clicked {
border: 2px solid #2bff13 !important;
}

.MessageReaction:hover {
transform: scale(1.02);
}

.MessageReaction--count {
filter: drop-shadow(0px 1px 2px rgb(0, 0, 0));
}
33 changes: 33 additions & 0 deletions src/renderer/utils/emojiUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const UFE0Fg = /\uFE0F/g;

// avoid using a string literal like '\u200D' here because minifiers expand it inline
const U200D = String.fromCharCode(0x200d);

function toCodePoint(unicodeSurrogates: string, sep?: string) {
const r = [];
let c = 0;
let p = 0;
let i = 0;

while (i < unicodeSurrogates.length) {
// eslint-disable-next-line no-plusplus
c = unicodeSurrogates.charCodeAt(i++);
if (p) {
// eslint-disable-next-line no-bitwise
r.push((0x10000 + ((p - 0xd800) << 10) + (c - 0xdc00)).toString(16));
p = 0;
} else if (c >= 0xd800 && c <= 0xdbff) {
p = c;
} else {
r.push(c.toString(16));
}
}
return r.join(sep ?? '-');
}

// eslint-disable-next-line import/prefer-default-export
export function grabTheRightIcon(emoji: string) {
return toCodePoint(
emoji.indexOf(U200D) < 0 ? emoji.replace(UFE0Fg, '') : emoji,
);
}

0 comments on commit 39df390

Please sign in to comment.