Skip to content

Commit

Permalink
Merge pull request #43 from ZickZenni/direct-messages
Browse files Browse the repository at this point in the history
feat: direct messages
  • Loading branch information
ZickZenni authored Sep 19, 2024
2 parents bc85408 + 8fa442b commit 8a8debe
Show file tree
Hide file tree
Showing 24 changed files with 364 additions and 11 deletions.
7 changes: 7 additions & 0 deletions src/discord/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ export class Client extends TypedEmitter<ClientEvents> {
this.users.cache.set(userData.id, new MainUser(userData));
});

data.private_channels.forEach((channelData) => {
this.channels.cache.set(
channelData.id,
new MainChannel(this, channelData),
);
});

this.relationships = data.relationships;

this.users.clientUser = new MainUser(data.user);
Expand Down
15 changes: 14 additions & 1 deletion src/discord/managers/ChannelManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client } from '../core/client';

Check warning on line 1 in src/discord/managers/ChannelManager.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Dependency cycle detected

Check warning on line 1 in src/discord/managers/ChannelManager.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Dependency cycle detected

Check warning on line 1 in src/discord/managers/ChannelManager.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Dependency cycle detected
import { debug, error } from '../core/logger';
import { IChannelData } from '../structures/channel/BaseChannel';
import { ChannelType, IChannelData } from '../structures/channel/BaseChannel';
import MainChannel from '../structures/channel/MainChannel';
import { Snowflake } from '../structures/Snowflake';
import Collection from '../util/collection';
Expand All @@ -27,6 +27,19 @@ export class ChannelManager {
return this.cache.values().filter((v) => v.guildId === guildId);
}

/**
* Returns all private channels (direct messages, groups) where the user is in
*/
public listPrivate(): MainChannel[] {
return this.cache
.values()
.filter(
(v) =>
v.type === ChannelType.DirectMessage ||
v.type === ChannelType.GroupDM,
);
}

public async fetch(
options: SingleChannelFetchOptions | GuildChannelFetchOptions,
): Promise<MainChannel | MainChannel[] | null> {
Expand Down
17 changes: 17 additions & 0 deletions src/discord/structures/channel/BaseChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export interface IChannelData {
permissions?: string;
flags?: number;
total_message_sent?: number;
recipient_ids?: Snowflake[];
is_spam?: boolean;
}

export interface CreateMessageOptions {
Expand All @@ -78,6 +80,12 @@ export default abstract class BaseChannel {

public messages: Message[];

public recipientIds: Snowflake[];

public lastMessageId: Snowflake | null;

public icon: Snowflake | null;

constructor(data: IChannelData, messages?: Message[]) {
this.id = data.id;
this.type = data.type;
Expand All @@ -86,6 +94,9 @@ export default abstract class BaseChannel {
this.name = data.name ?? '';
this.parentId = data.parent_id ?? null;
this.messages = messages ?? [];
this.recipientIds = data.recipient_ids ?? [];
this.lastMessageId = data.last_message_id ?? null;
this.icon = data.icon ?? null;

this.patch(data);
}
Expand All @@ -99,6 +110,9 @@ export default abstract class BaseChannel {
this.position = data.position ?? 0;
this.name = data.name ?? '';
this.parentId = data.parent_id ?? null;
this.recipientIds = data.recipient_ids || [];
this.lastMessageId = data.last_message_id ?? null;
this.icon = data.icon ?? null;
}
}

Expand Down Expand Up @@ -127,6 +141,9 @@ export default abstract class BaseChannel {
position: this.position,
name: this.name,
parent_id: this.parentId,
recipient_ids: this.recipientIds,
last_message_id: this.lastMessageId,
icon: this.icon,
};
}
}
9 changes: 7 additions & 2 deletions src/discord/structures/channel/MainChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import BaseChannel, {
IChannelData,
} from './BaseChannel';

const supportedTypes = [ChannelType.GuildText, ChannelType.GuildAnnouncement];
const supportedTypes = [
ChannelType.GuildText,
ChannelType.GuildAnnouncement,
ChannelType.GroupDM,
ChannelType.DirectMessage,
];

export default class MainChannel extends BaseChannel {
private readonly client: Client;
Expand Down Expand Up @@ -67,7 +72,7 @@ export default class MainChannel extends BaseChannel {
/* --------------------------- */

private async fetchMessagesApi(): Promise<Message[]> {
if (this.type !== ChannelType.GuildText || this.client === null) return [];
if (!supportedTypes.includes(this.type) || this.client === null) return [];

try {
const json = await this.client.restGet(
Expand Down
8 changes: 7 additions & 1 deletion src/discord/structures/channel/RendererChannel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Message } from '../Message';
import BaseChannel, { CreateMessageOptions } from './BaseChannel';
import BaseChannel, { ChannelType, CreateMessageOptions } from './BaseChannel';

export default class RendererChannel extends BaseChannel {
public async fetchMessages(): Promise<Message[]> {
Expand All @@ -18,4 +18,10 @@ export default class RendererChannel extends BaseChannel {
options,
);
}

public getChannelIcon(): string | null {
if (this.type !== ChannelType.GroupDM || this.icon === null) return null;

return `https://cdn.discordapp.com/channel-icons/${this.id}/${this.icon}.webp?size=32`;
}
}
6 changes: 6 additions & 0 deletions src/discord/ws/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */

import { IChannelData } from '../structures/channel/BaseChannel';
import { IGuildData } from '../structures/guild/BaseGuild';
import { Relationship } from '../structures/Relationship';
import { IUserData } from '../structures/user/BaseUser';
Expand Down Expand Up @@ -139,4 +140,9 @@ export interface GatewayReadyDispatchData {
* Your relationships with other users
*/
relationships: Relationship[];

/**
* All private channels the user is in
*/
private_channels: IChannelData[];
}
14 changes: 13 additions & 1 deletion src/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { app, BrowserWindow, Menu, shell, Tray } from 'electron';
import fs from 'fs';
import path from 'path';
import { Snowflake } from '@/discord/structures/Snowflake';
import logger, { Logger } from '../common/log/logger';
import { Client } from '../discord/core/client';
import { GatewayDispatchEvents, GatewaySocketEvent } from '../discord/ws/types';
Expand Down Expand Up @@ -206,7 +207,14 @@ export default class WaveCordApp {
registerHandler('discord:user', (userId: string | undefined) => {
if (userId === undefined) return this.discord.users.clientUser?.toRaw();

return null;
return this.discord.users.cache.get(userId) ?? null;
});

registerHandler('discord:users', (userIds: Snowflake[]) => {
return this.discord.users.cache
.values()
.filter((v) => userIds.includes(v.id))
.map((v) => v.toRaw());
});

registerHandler('discord:guilds', () => {
Expand Down Expand Up @@ -244,6 +252,10 @@ export default class WaveCordApp {
},
);

registerHandler('discord:private-channels', () => {
return this.discord.channels.listPrivate().map((v) => v.toRaw());
});

registerHandler('tenor:fetch-gif', async (url: string) => {
const result = await this.tenor.fetchGif(url);
return result;
Expand Down
2 changes: 2 additions & 0 deletions src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ export type IpcChannels =
| 'discord:fetch-messages'
| 'discord:fetch-guild'
| 'discord:user'
| 'discord:users'
| 'discord:get-last-visited-channel'
| 'discord:set-last-visited-channel'
| 'discord:create-message'
| 'discord:gateway:message-create'
| 'discord:relationships'
| 'discord:private-channels'
| 'tenor:fetch-gif';

export function registerHandler(
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import '@/styles/channel/ChannelList.css';
import '@/styles/channel/Message.css';
import '@/styles/channel/MessageAttachment.css';

import '@/styles/directmessage/DirectMessageButton.css';
import '@/styles/directmessage/DirectMessageList.css';

import '@/styles/page/GuildChannelPage.css';
import '@/styles/page/GuildPage.css';
import '@/styles/page/HomePage.css';
import '@/styles/page/PageLayout.css';
import '@/styles/page/PageSideBar.css';

Expand All @@ -33,6 +37,7 @@ import SideBar from '@/components/app/SideBar';
import HomePage from '@/pages/HomePage/HomePage';
import GuildPage from '@/pages/GuildPage/GuildPage';
import GuildChannelPage from './pages/GuildPage/GuildChannelPage';
import HomeChannelPage from './pages/HomePage/HomeChannelPage';

export default function App() {
return (
Expand All @@ -42,7 +47,9 @@ export default function App() {
<SideBar />
<UserPanel />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/" element={<HomePage />}>
<Route path="channel/:channelId" element={<HomeChannelPage />} />
</Route>
<Route path="/guild/:guildId" element={<GuildPage />}>
<Route path="channel/:channelId" element={<GuildChannelPage />} />
</Route>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/channel/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function Message({ message }: MessageProps) {

{gif !== null ? (
<img
className="Message--gif-img"
src={gif.media_formats.find((v) => v.type === 'gif')?.url}
alt="Gif"
/>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/channel/MessageAttachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function ImageAttachment({ attachment }: MessageAttachmentProps) {
<img
className="MessageAttachment--image"
src={attachment.url}
loading="lazy"
alt={`Attachment:${attachment.filename}`}
/>
</div>
Expand Down
71 changes: 71 additions & 0 deletions src/renderer/components/directmessage/DirectMessageButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ChannelType } from '@/discord/structures/channel/BaseChannel';
import RendererChannel from '@/discord/structures/channel/RendererChannel';
import useUser from '@/hooks/useUser';
import useUsers from '@/hooks/useUsers';
import { useLocation, useNavigate } from 'react-router-dom';

type DirectMessageButtonProps = {
channel: RendererChannel;
};

export default function DirectMessageButton({
channel,
}: DirectMessageButtonProps) {
const location = useLocation();
const navigate = useNavigate();

const user = useUser(undefined);
const members = useUsers(channel.recipientIds ?? []);

if (user === null) return null;

const onClick = () => {
navigate(`/channel/${channel.id}`);
};

const getName = () => {
if (channel.type === ChannelType.GroupDM) {
if (channel.name.length > 0) return channel.name;

const membs = [user, ...members];
return membs
.map((v) => (v.globalName ? v.globalName : v.username))
.join(', ');
}

if (members.length === 0) return '';

const member = members[0];

if (member.globalName === null || member.globalName.length === 0)
return member.username;

return member.globalName;
};

const getIcon = () => {
if (members.length === 0) return '';

if (channel.type === ChannelType.GroupDM)
return channel.getChannelIcon() ?? members[0].getAvatarUrl();

return members[0].getAvatarUrl();
};

const isSelected = location.pathname.includes(`/channel/${channel.id}`);

return (
<div
className={`DirectMessage ${isSelected && 'DirectMessage--selected'}`}
role="presentation"
onClick={() => onClick()}
>
<img
className="DirectMessage--icon-img"
src={getIcon()}
alt="Direct Message Icon"
/>
<p className="DirectMessage--name">{getName()}</p>
</div>
);
}
31 changes: 31 additions & 0 deletions src/renderer/components/directmessage/DirectMessageList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import usePrivateChannels from '@/hooks/usePrivateChannels';
import DirectMessageButton from './DirectMessageButton';

export default function DirectMessageList() {
const channels = usePrivateChannels();

return (
<div className="DirectMessageList hidden-scrollbar">
{
/* Sort private channels by their last message id */
channels
.sort((a, b) => {
if (a.lastMessageId === null) return -1;

if (b.lastMessageId === null) return 1;

return Number(a.lastMessageId) - Number(b.lastMessageId);
})
.reverse()
.map((channel) => {
return (
<DirectMessageButton
key={`Channel:home:${channel.id}`}
channel={channel}
/>
);
})
}
</div>
);
}
2 changes: 1 addition & 1 deletion src/renderer/hooks/useChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function useChannels(guildId: string) {
return true;
})
.catch((err) => console.error(err));
});
}, [guildId]);

return channels;
}
2 changes: 1 addition & 1 deletion src/renderer/hooks/useGif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function useGif(url: string): TenorGif | null {
try {
const uri = new URL(url);

if (allowedHosts.includes(uri.host.toLowerCase())) {
if (!allowedHosts.includes(uri.host.toLowerCase())) {
setGif(null);
return;
}
Expand Down
22 changes: 22 additions & 0 deletions src/renderer/hooks/usePrivateChannels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IChannelData } from '@/discord/structures/channel/BaseChannel';
import RendererChannel from '@/discord/structures/channel/RendererChannel';
import { useEffect, useState } from 'react';

/**
* Fetches all private channels from the user
*/
export default function usePrivateChannels() {
const [channels, setChannels] = useState<RendererChannel[]>([]);

useEffect(() => {
window.electron.ipcRenderer
.invoke('discord:private-channels')
.then((data: IChannelData[]) => {
setChannels(data.map((v) => new RendererChannel(v)));
return true;
})
.catch((err) => console.error(err));
}, []);

return channels;
}
Loading

0 comments on commit 8a8debe

Please sign in to comment.