diff --git a/.erb/configs/webpack.config.base.ts b/.erb/configs/webpack.config.base.ts index 0ef0044..bedafab 100644 --- a/.erb/configs/webpack.config.base.ts +++ b/.erb/configs/webpack.config.base.ts @@ -4,6 +4,7 @@ import webpack from 'webpack'; import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; +import path from 'path'; import webpackPaths from './webpack.paths'; import { dependencies as externals } from '../../release/app/package.json'; @@ -47,6 +48,10 @@ const configuration: webpack.Configuration = { modules: [webpackPaths.srcPath, 'node_modules'], // There is no need to add aliases here, the paths in tsconfig get mirrored plugins: [new TsconfigPathsPlugins()], + alias: { + '@': path.join(__dirname, '../../src/renderer'), + '@/discord': path.join(__dirname, '../../src/discord'), + }, }, plugins: [ diff --git a/.vscode/settings.json b/.vscode/settings.json index f10995c..4239a3b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,11 @@ "editor.formatOnPaste": true }, + "[typescriptreact]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "javascript.validate.enable": false, "javascript.format.enable": false, "typescript.format.enable": false, diff --git a/assets/app/icons/discord.svg b/assets/app/icons/discord.svg new file mode 100644 index 0000000..4e48120 --- /dev/null +++ b/assets/app/icons/discord.svg @@ -0,0 +1 @@ + diff --git a/assets/app/icons/titlebar/maximize.svg b/assets/app/icons/titlebar/maximize.svg index fc30518..d02dfe6 100644 --- a/assets/app/icons/titlebar/maximize.svg +++ b/assets/app/icons/titlebar/maximize.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/assets/app/icons/titlebar/minimize.svg b/assets/app/icons/titlebar/minimize.svg index 46d6119..d75fefd 100644 --- a/assets/app/icons/titlebar/minimize.svg +++ b/assets/app/icons/titlebar/minimize.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/assets/app/icons/titlebar/x.svg b/assets/app/icons/titlebar/x.svg index 7d5875c..08ac7cb 100644 --- a/assets/app/icons/titlebar/x.svg +++ b/assets/app/icons/titlebar/x.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/discord/structures/Attachment.ts b/src/discord/structures/Attachment.ts new file mode 100644 index 0000000..dac8dfa --- /dev/null +++ b/src/discord/structures/Attachment.ts @@ -0,0 +1,13 @@ +import { Snowflake } from './Snowflake'; + +export interface Attachment { + content_type: string; + filename: string; + height: number; + id: Snowflake; + placeholder: string; + proxy_url: string; + size: number; + url: string; + width: number; +} diff --git a/src/discord/structures/Message.ts b/src/discord/structures/Message.ts index bea1e6f..70b771a 100644 --- a/src/discord/structures/Message.ts +++ b/src/discord/structures/Message.ts @@ -1,7 +1,9 @@ +import { Attachment } from './Attachment'; import { Snowflake } from './Snowflake'; import { IUserData } from './user/BaseUser'; export interface Message { + attachments: Attachment[]; content: string; timestamp: string; id: Snowflake; diff --git a/src/discord/structures/guild/BaseGuild.ts b/src/discord/structures/guild/BaseGuild.ts index a700093..bbf8bdb 100644 --- a/src/discord/structures/guild/BaseGuild.ts +++ b/src/discord/structures/guild/BaseGuild.ts @@ -288,7 +288,11 @@ export abstract class BaseGuild { } public getIconUrl(): string { - return ''; + return `https://cdn.discordapp.com/icons/${this.id}/${this.icon}.png`; + } + + public getBannerUrl(): string { + return `https://cdn.discordapp.com/banners/${this.id}/${this.banner}.webp?size=300`; } public toRaw(): IGuildData { diff --git a/src/discord/structures/user/BaseUser.ts b/src/discord/structures/user/BaseUser.ts index 23e03ea..5b1d1b8 100644 --- a/src/discord/structures/user/BaseUser.ts +++ b/src/discord/structures/user/BaseUser.ts @@ -121,7 +121,7 @@ export default class BaseUser { data.avatar_decoration_data ?? this.avatarDecorationData; } - public getAvatarUrl(): string { + public getAvatarUrl(animated: boolean = false): string { if (this.avatar === null) { const id = BigInt(this.id); // eslint-disable-next-line no-bitwise @@ -129,9 +129,20 @@ export default class BaseUser { return `https://cdn.discordapp.com/embed/avatars/${index < 0 ? 0 : index}.png`; } + if (this.avatar.startsWith('a_') && animated) + return `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.gif`; + return `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.png`; } + public getAvatarDecorationUrl(): string | null { + if (this.avatarDecorationData === null) return null; + + return `https://cdn.discordapp.com/avatar-decoration-presets/${ + this.avatarDecorationData.asset + }.png`; + } + public toRaw(): IUserData { return { id: this.id, diff --git a/src/main/app.ts b/src/main/app.ts index b2cc039..278cf60 100644 --- a/src/main/app.ts +++ b/src/main/app.ts @@ -8,6 +8,7 @@ import { GatewayDispatchEvents, GatewaySocketEvent } from '../discord/ws/types'; import { registerHandler, registerListener, sendToRenderer } from './ipc'; import { CreateMessageOptions } from '../discord/structures/channel/BaseChannel'; import { Message } from '../discord/structures/Message'; +import Tenor from './utils/tenor'; export default class WaveCordApp { public readonly resourcesPath: string; @@ -25,6 +26,8 @@ export default class WaveCordApp { public discord: Client; + public tenor: Tenor; + public ready: boolean = false; public quitting: boolean = false; @@ -33,6 +36,7 @@ export default class WaveCordApp { public constructor() { this.discord = new Client({ debug: true }); + this.tenor = new Tenor(); app.setPath('userData', path.join(app.getPath('appData'), 'WaveCord')); @@ -59,12 +63,6 @@ export default class WaveCordApp { this.discord.on('dispatch', (event: GatewaySocketEvent) => { logger.info('Received event: ', event.event); - if (event.event === GatewayDispatchEvents.Ready) - fs.writeFileSync( - '/home/rohloff/Documents/discord_ready_output.txt', - JSON.stringify(event.data ?? {}), - ); - if (event.event === GatewayDispatchEvents.MessageCreate) { const message = event.data as Message; sendToRenderer(this.window!, 'discord:gateway:message-create', message); @@ -111,8 +109,8 @@ export default class WaveCordApp { logger.info('Creating new window.'); this.window = new BrowserWindow({ show: false, - width: 1250, - height: 697, + width: 1320, + height: 763, minWidth: 450, minHeight: 250, icon: path.join(this.resourcesPath, 'icon.png'), @@ -245,6 +243,11 @@ export default class WaveCordApp { return channel.createMessage(options); }, ); + + registerHandler('tenor:fetch-gif', async (url: string) => { + const result = await this.tenor.fetchGif(url); + return result; + }); } private initTray() { diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 9d03a26..bf38e51 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -20,7 +20,8 @@ export type IpcChannels = | 'discord:set-last-visited-channel' | 'discord:create-message' | 'discord:gateway:message-create' - | 'discord:relationships'; + | 'discord:relationships' + | 'tenor:fetch-gif'; export function registerHandler( channel: IpcChannels, diff --git a/src/main/utils/tenor.ts b/src/main/utils/tenor.ts new file mode 100644 index 0000000..7ba5192 --- /dev/null +++ b/src/main/utils/tenor.ts @@ -0,0 +1,81 @@ +export interface TenorGifFormat { + type: string; + url: string; + duration: number; + preview: string; + width: number; + height: number; + size: number; +} + +export interface TenorGif { + id: string; + title: string; + media_formats: TenorGifFormat[]; + created: number; + content_description: string; + h1_title: string; + tags: string[]; + flags: string[]; + hasaudio: boolean; +} + +export interface TenorFetchResult { + gif: TenorGif | null; +} + +export default class Tenor { + public async fetchGif(url: string): Promise { + const result: TenorFetchResult = { + gif: null, + }; + + const uri = new URL(url); + + if (uri.hostname.toLowerCase() !== 'tenor.com') return result; + if (!uri.pathname.toLowerCase().startsWith('/view/')) return result; + + const response = await fetch(url); + const text = await response.text(); + + const raw = `{"appConfig${text.split('appConfig')[1].split('')[0]}`; + const json = JSON.parse(raw); + + // eslint-disable-next-line no-restricted-syntax + for (const id of Object.keys(json.gifs.byId)) { + const data = json.gifs.byId[id]; + + if (!data.loaded) continue; + + const dataRes = data.results[0]; + + result.gif = { + id: dataRes.id, + title: dataRes.title, + created: dataRes.created, + content_description: dataRes.content_description, + flags: dataRes.flags, + h1_title: dataRes.h1_title, + hasaudio: dataRes.hasaudio, + media_formats: [], + tags: dataRes.tags, + }; + + // eslint-disable-next-line no-restricted-syntax + for (const formatType of Object.keys(dataRes.media_formats)) { + const formatData = dataRes.media_formats[formatType]; + result.gif.media_formats.push({ + type: formatType, + url: formatData.url, + duration: formatData.duration, + preview: formatData.preview, + size: formatData.size, + width: formatData.dims[0], + height: formatData.dims[1], + }); + } + } + + return result; + } +} diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index dbe5ca4..b51fbfd 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,36 +1,53 @@ +/* eslint-disable import/order */ import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; // Styles -import './styles/global.css'; -import './styles/vars.css'; +import '@/styles/app/App.css'; +import '@/styles/app/TitleBar.css'; +import '@/styles/app/SideBar.css'; +import '@/styles/app/UserBar.css'; + +import '@/styles/channel/ChannelButton.css'; +import '@/styles/channel/ChannelList.css'; +import '@/styles/channel/Message.css'; +import '@/styles/channel/MessageAttachment.css'; + +import '@/styles/page/GuildChannelPage.css'; +import '@/styles/page/GuildPage.css'; +import '@/styles/page/PageLayout.css'; +import '@/styles/page/PageSideBar.css'; + +import '@/styles/server/ServerBar.css'; +import '@/styles/server/Server.css'; +import '@/styles/server/ServerInfo.css'; + +import '@/styles/user/UserPanel.css'; // Components -import Titlebar from './components/Titlebar'; -import Serverbar from './components/Serverbar'; -import Loading from './components/Loading'; +import AppContent from '@/components/app/AppContent'; +import TitleBar from '@/components/app/TitleBar'; +import UserPanel from '@/components/user/UserPanel'; +import SideBar from '@/components/app/SideBar'; // Pages -import HomePage from './pages/Home'; -import GuildPage from './pages/Guild'; -import ChannelPage from './pages/Guild/Channel'; -import DirectMessagePage from './pages/Home/DirectMessage'; +import HomePage from '@/pages/HomePage/HomePage'; +import GuildPage from '@/pages/GuildPage/GuildPage'; +import GuildChannelPage from './pages/GuildPage/GuildChannelPage'; export default function App() { return ( - - - -
+ + + + - }> - } /> - - }> - } /> + } /> + }> + } /> -
+
); } diff --git a/src/renderer/components/Loading/Loading.css b/src/renderer/components/Loading/Loading.css deleted file mode 100644 index aa2c187..0000000 --- a/src/renderer/components/Loading/Loading.css +++ /dev/null @@ -1,50 +0,0 @@ -.loading__container { - position: relative; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - gap: 16px; - background: var(--background); - z-index: 1000; - transition: opacity 0.104s ease; -} - -.loading__title { - font-size: xxx-large; -} - -.loading__loader { - width: 48px; - height: 48px; - border-radius: 50%; - display: inline-block; - position: relative; - background: linear-gradient(0deg, rgba(255, 61, 0, 0.2) 33%, #ff3d00 100%); - box-sizing: border-box; - animation: rotation 1s linear infinite; -} - -.loading__loader::after { - content: ''; - box-sizing: border-box; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - width: 44px; - height: 44px; - border-radius: 50%; - background: #263238; -} - -@keyframes rotation { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/src/renderer/components/Loading/Messages.ts b/src/renderer/components/Loading/Messages.ts deleted file mode 100644 index 03a3486..0000000 --- a/src/renderer/components/Loading/Messages.ts +++ /dev/null @@ -1,262 +0,0 @@ -export default [ - 'Reticulating splines...', - 'Generating witty dialog...', - 'Swapping time and space...', - 'Spinning violently around the y-axis...', - 'Tokenizing real life...', - 'Bending the spoon...', - 'Filtering morale...', - "Don't think of purple hippos...", - 'We need a new fuse...', - 'Have a good day.', - 'Upgrading Windows, your PC will restart several times. Sit back and relax.', - '640K ought to be enough for anybody', - 'The architects are still drafting', - 'The bits are breeding', - "We're building the buildings as fast as we can", - 'Would you prefer chicken, steak, or tofu?', - '(Pay no attention to the man behind the curtain)', - '...and enjoy the elevator music...', - 'Please wait while the little elves draw your map', - "Don't worry - a few bits tried to escape, but we caught them", - 'Would you like fries with that?', - 'Checking the gravitational constant in your locale...', - 'Go ahead -- hold your breath!', - "...at least you're not on hold...", - 'Hum something loud while others stare', - "You're not in Kansas any more", - 'The server is powered by a lemon and two electrodes.', - 'Please wait while a larger software vendor in Seattle takes over the world', - "We're testing your patience", - 'As if you had any other choice', - 'Follow the white rabbit', - "Why don't you order a sandwich?", - 'While the satellite moves into position', - 'keep calm and npm install', - 'The bits are flowing slowly today', - "Dig on the 'X' for buried treasure... ARRR!", - "It's still faster than you could draw it", - "The last time I tried this the monkey didn't survive. Let's hope it works better this time.", - 'I should have had a V8 this morning.', - 'My other loading screen is much faster.', - "Testing on Timmy... We're going to need another Timmy.", - 'Reconfoobling energymotron...', - '(Insert quarter)', - 'Are we there yet?', - 'Have you lost weight?', - 'Just count to 10', - 'Why so serious?', - "It's not you. It's me.", - 'Counting backwards from Infinity', - "Don't panic...", - 'Embiggening Prototypes', - 'Do not run! We are your friends!', - 'Do you come here often?', - "Warning: Don't set yourself on fire.", - "We're making you a cookie.", - 'Creating time-loop inversion field', - 'Spinning the wheel of fortune...', - 'Loading the enchanted bunny...', - 'Computing chance of success', - "I'm sorry Dave, I can't do that.", - 'Looking for exact change', - 'All your web browser are belong to us', - 'All I really need is a kilobit.', - 'I feel like im supposed to be loading something. . .', - 'What do you call 8 Hobbits? A Hobbyte.', - 'Should have used a compiled language...', - 'Is this Windows?', - 'Adjusting flux capacitor...', - 'Please wait until the sloth starts moving.', - "Don't break your screen yet!", - "I swear it's almost done.", - "Let's take a mindfulness minute...", - 'Unicorns are at the end of this road, I promise.', - 'Listening for the sound of one hand clapping...', - "Keeping all the 1's and removing all the 0's...", - 'Putting the icing on the cake. The cake is not a lie...', - 'Cleaning off the cobwebs...', - "Making sure all the i's have dots...", - 'We need more dilithium crystals', - 'Where did all the internets go', - 'Connecting Neurotoxin Storage Tank...', - 'Granting wishes...', - 'Time flies when you’re having fun.', - 'Get some coffee and come back in ten minutes..', - 'Spinning the hamster…', - '99 bottles of beer on the wall..', - 'Stay awhile and listen..', - 'Be careful not to step in the git-gui', - 'You edhall not pass! yet..', - 'Load it and they will come', - 'Convincing AI not to turn evil..', - 'There is no spoon. Because we are not done loading it', - 'Your left thumb points to the right and your right thumb points to the left.', - 'How did you get here?', - 'Wait, do you smell something burning?', - 'Computing the secret to life, the universe, and everything.', - 'When nothing is going right, go left!!...', - "I love my job only when I'm on vacation...", - "i'm not lazy, I'm just relaxed!!", - 'Never steal. The government hates competition....', - 'Why are they called apartments if they are all stuck together?', - 'Life is Short – Talk Fast!!!!', - 'Optimism – is a lack of information.....', - 'Save water and shower together', - 'Whenever I find the key to success, someone changes the lock.', - 'Sometimes I think war is God’s way of teaching us geography.', - 'I’ve got problem for your solution…..', - 'Where there’s a will, there’s a relative.', - 'User: the word computer professionals use when they mean !!idiot!!', - 'Adults are just kids with money.', - 'I think I am, therefore, I am. I think.', - 'A kiss is like a fight, with mouths.', - 'You don’t pay taxes—they take taxes.', - 'Coffee, Chocolate, Men. The richer the better!', - 'I am free of all prejudices. I hate everyone equally.', - 'git happens', - 'May the forks be with you', - 'A commit a day keeps the mobs away', - "This is not a joke, it's a commit.", - 'Constructing additional pylons...', - 'Roping some seaturtles...', - 'Locating Jebediah Kerman...', - 'We are not liable for any broken screens as a result of waiting.', - 'Hello IT, have you tried turning it off and on again?', - 'If you type Google into Google you can break the internet', - 'Well, this is embarrassing.', - 'What is the airspeed velocity of an unladen swallow?', - 'Hello, IT... Have you tried forcing an unexpected reboot?', - "They just toss us away like yesterday's jam.", - "They're fairly regular, the beatings, yes. I'd say we're on a bi-weekly beating.", - 'The Elders of the Internet would never stand for it.', - 'Space is invisible mind dust, and stars are but wishes.', - "Didn't know paint dried so quickly.", - 'Everything sounds the same', - "I'm going to walk the dog", - "I didn't choose the engineering life. The engineering life chose me.", - 'Dividing by zero...', - 'Spawn more Overlord!', - 'If I’m not back in five minutes, just wait longer.', - 'Some days, you just can’t get rid of a bug!', - 'We’re going to need a bigger boat.', - 'Chuck Norris never git push. The repo pulls before.', - 'Web developers do it with