feat: Add emoji and alot of other fixes

This commit is contained in:
2026-06-05 05:40:18 +02:00
parent ca069e2f61
commit 6865147e8f
72 changed files with 3885 additions and 413 deletions

View File

@@ -11,6 +11,7 @@ import {
ReactionEntity,
BanEntity,
AttachmentEntity,
CustomEmojiEntity,
MetaEntity,
PluginDataEntity
} from '../../../entities';
@@ -27,6 +28,7 @@ export async function handleClearAllData(dataSource: DataSource): Promise<void>
await dataSource.getRepository(ReactionEntity).clear();
await dataSource.getRepository(BanEntity).clear();
await dataSource.getRepository(AttachmentEntity).clear();
await dataSource.getRepository(CustomEmojiEntity).clear();
await dataSource.getRepository(MetaEntity).clear();
await dataSource.getRepository(PluginDataEntity).clear();
}

View File

@@ -0,0 +1,7 @@
import { DataSource } from 'typeorm';
import { CustomEmojiEntity } from '../../../entities';
import { DeleteCustomEmojiCommand } from '../../types';
export async function handleDeleteCustomEmoji(command: DeleteCustomEmojiCommand, dataSource: DataSource): Promise<void> {
await dataSource.getRepository(CustomEmojiEntity).delete({ id: command.payload.emojiId });
}

View File

@@ -0,0 +1,19 @@
import { DataSource } from 'typeorm';
import { CustomEmojiEntity } from '../../../entities';
import { SaveCustomEmojiCommand } from '../../types';
export async function handleSaveCustomEmoji(command: SaveCustomEmojiCommand, dataSource: DataSource): Promise<void> {
const { emoji } = command.payload;
await dataSource.getRepository(CustomEmojiEntity).save({
id: emoji.id,
name: emoji.name,
creatorUserId: emoji.creatorUserId,
dataUrl: emoji.dataUrl,
hash: emoji.hash,
mime: emoji.mime,
size: emoji.size,
createdAt: emoji.createdAt,
updatedAt: emoji.updatedAt
});
}

View File

@@ -19,6 +19,8 @@ import {
RemoveBanCommand,
SaveAttachmentCommand,
DeleteAttachmentsForMessageCommand,
SaveCustomEmojiCommand,
DeleteCustomEmojiCommand,
SavePluginDataCommand,
DeletePluginDataCommand,
SaveMetaCommand
@@ -39,6 +41,8 @@ import { handleSaveBan } from './handlers/saveBan';
import { handleRemoveBan } from './handlers/removeBan';
import { handleSaveAttachment } from './handlers/saveAttachment';
import { handleDeleteAttachmentsForMessage } from './handlers/deleteAttachmentsForMessage';
import { handleSaveCustomEmoji } from './handlers/saveCustomEmoji';
import { handleDeleteCustomEmoji } from './handlers/deleteCustomEmoji';
import { handleSavePluginData } from './handlers/savePluginData';
import { handleDeletePluginData } from './handlers/deletePluginData';
import { handleSaveMeta } from './handlers/saveMeta';
@@ -61,6 +65,8 @@ export const buildCommandHandlers = (dataSource: DataSource): Record<CommandType
[CommandType.RemoveBan]: (cmd) => handleRemoveBan(cmd as RemoveBanCommand, dataSource),
[CommandType.SaveAttachment]: (cmd) => handleSaveAttachment(cmd as SaveAttachmentCommand, dataSource),
[CommandType.DeleteAttachmentsForMessage]: (cmd) => handleDeleteAttachmentsForMessage(cmd as DeleteAttachmentsForMessageCommand, dataSource),
[CommandType.SaveCustomEmoji]: (cmd) => handleSaveCustomEmoji(cmd as SaveCustomEmojiCommand, dataSource),
[CommandType.DeleteCustomEmoji]: (cmd) => handleDeleteCustomEmoji(cmd as DeleteCustomEmojiCommand, dataSource),
[CommandType.SavePluginData]: (cmd) => handleSavePluginData(cmd as SavePluginDataCommand, dataSource),
[CommandType.DeletePluginData]: (cmd) => handleDeletePluginData(cmd as DeletePluginDataCommand, dataSource),
[CommandType.SaveMeta]: (cmd) => handleSaveMeta(cmd as SaveMetaCommand, dataSource),

View File

@@ -9,6 +9,7 @@ import { RoomEntity } from '../entities/RoomEntity';
import { ReactionEntity } from '../entities/ReactionEntity';
import { BanEntity } from '../entities/BanEntity';
import { AttachmentEntity } from '../entities/AttachmentEntity';
import { CustomEmojiEntity } from '../entities/CustomEmojiEntity';
import { ReactionPayload } from './types';
import {
relationRecordToRoomPayload,
@@ -140,6 +141,20 @@ export function rowToAttachment(row: AttachmentEntity) {
};
}
export function rowToCustomEmoji(row: CustomEmojiEntity) {
return {
id: row.id,
name: row.name,
creatorUserId: row.creatorUserId,
dataUrl: row.dataUrl,
hash: row.hash,
mime: row.mime,
size: row.size,
createdAt: row.createdAt,
updatedAt: row.updatedAt
};
}
export function rowToBan(row: BanEntity) {
return {
oderId: row.oderId,

View File

@@ -0,0 +1,9 @@
import { DataSource } from 'typeorm';
import { CustomEmojiEntity } from '../../../entities';
import { rowToCustomEmoji } from '../../mappers';
export async function handleGetCustomEmojis(dataSource: DataSource) {
const rows = await dataSource.getRepository(CustomEmojiEntity).find({ order: { updatedAt: 'DESC' } });
return rows.map(rowToCustomEmoji);
}

View File

@@ -31,6 +31,7 @@ import { handleGetBansForRoom } from './handlers/getBansForRoom';
import { handleIsUserBanned } from './handlers/isUserBanned';
import { handleGetAttachmentsForMessage } from './handlers/getAttachmentsForMessage';
import { handleGetAllAttachments } from './handlers/getAllAttachments';
import { handleGetCustomEmojis } from './handlers/getCustomEmojis';
import { handleGetPluginData } from './handlers/getPluginData';
import { handleGetMeta } from './handlers/getMeta';
@@ -50,6 +51,7 @@ export const buildQueryHandlers = (dataSource: DataSource): Record<QueryTypeKey,
[QueryType.IsUserBanned]: (query) => handleIsUserBanned(query as IsUserBannedQuery, dataSource),
[QueryType.GetAttachmentsForMessage]: (query) => handleGetAttachmentsForMessage(query as GetAttachmentsForMessageQuery, dataSource),
[QueryType.GetAllAttachments]: () => handleGetAllAttachments(dataSource),
[QueryType.GetCustomEmojis]: () => handleGetCustomEmojis(dataSource),
[QueryType.GetPluginData]: (query) => handleGetPluginData(query as GetPluginDataQuery, dataSource),
[QueryType.GetMeta]: (query) => handleGetMeta(query as GetMetaQuery, dataSource)
});

View File

@@ -15,6 +15,8 @@ export const CommandType = {
RemoveBan: 'remove-ban',
SaveAttachment: 'save-attachment',
DeleteAttachmentsForMessage: 'delete-attachments-for-message',
SaveCustomEmoji: 'save-custom-emoji',
DeleteCustomEmoji: 'delete-custom-emoji',
SavePluginData: 'save-plugin-data',
DeletePluginData: 'delete-plugin-data',
SaveMeta: 'save-meta',
@@ -39,6 +41,7 @@ export const QueryType = {
IsUserBanned: 'is-user-banned',
GetAttachmentsForMessage: 'get-attachments-for-message',
GetAllAttachments: 'get-all-attachments',
GetCustomEmojis: 'get-custom-emojis',
GetPluginData: 'get-plugin-data',
GetMeta: 'get-meta'
} as const;
@@ -178,6 +181,18 @@ export interface AttachmentPayload {
savedPath?: string;
}
export interface CustomEmojiPayload {
id: string;
name: string;
creatorUserId: string;
dataUrl: string;
hash: string;
mime: string;
size: number;
createdAt: number;
updatedAt: number;
}
export type PluginDataScopePayload = 'local' | 'server';
export interface PluginDataPayload {
@@ -204,6 +219,8 @@ export interface SaveBanCommand { type: typeof CommandType.SaveBan; payload: { b
export interface RemoveBanCommand { type: typeof CommandType.RemoveBan; payload: { oderId: string } }
export interface SaveAttachmentCommand { type: typeof CommandType.SaveAttachment; payload: { attachment: AttachmentPayload } }
export interface DeleteAttachmentsForMessageCommand { type: typeof CommandType.DeleteAttachmentsForMessage; payload: { messageId: string } }
export interface SaveCustomEmojiCommand { type: typeof CommandType.SaveCustomEmoji; payload: { emoji: CustomEmojiPayload } }
export interface DeleteCustomEmojiCommand { type: typeof CommandType.DeleteCustomEmoji; payload: { emojiId: string } }
export interface SavePluginDataCommand { type: typeof CommandType.SavePluginData; payload: PluginDataPayload }
export interface DeletePluginDataCommand { type: typeof CommandType.DeletePluginData; payload: Omit<PluginDataPayload, 'value'> }
export interface SaveMetaCommand { type: typeof CommandType.SaveMeta; payload: { key: string; value: string | null } }
@@ -226,6 +243,8 @@ export type Command =
| RemoveBanCommand
| SaveAttachmentCommand
| DeleteAttachmentsForMessageCommand
| SaveCustomEmojiCommand
| DeleteCustomEmojiCommand
| SavePluginDataCommand
| DeletePluginDataCommand
| SaveMetaCommand
@@ -255,6 +274,7 @@ export interface GetBansForRoomQuery { type: typeof QueryType.GetBansForRoom; pa
export interface IsUserBannedQuery { type: typeof QueryType.IsUserBanned; payload: { userId: string; roomId: string } }
export interface GetAttachmentsForMessageQuery { type: typeof QueryType.GetAttachmentsForMessage; payload: { messageId: string } }
export interface GetAllAttachmentsQuery { type: typeof QueryType.GetAllAttachments; payload: Record<string, never> }
export interface GetCustomEmojisQuery { type: typeof QueryType.GetCustomEmojis; payload: Record<string, never> }
export interface GetPluginDataQuery { type: typeof QueryType.GetPluginData; payload: Omit<PluginDataPayload, 'value'> }
export interface GetMetaQuery { type: typeof QueryType.GetMeta; payload: { key: string } }
@@ -274,5 +294,6 @@ export type Query =
| IsUserBannedQuery
| GetAttachmentsForMessageQuery
| GetAllAttachmentsQuery
| GetCustomEmojisQuery
| GetPluginDataQuery
| GetMetaQuery;

View File

@@ -23,6 +23,7 @@ import {
RoomUserRoleEntity,
RoomChannelPermissionEntity,
ReactionEntity,
CustomEmojiEntity,
BanEntity,
AttachmentEntity,
MetaEntity,
@@ -50,6 +51,7 @@ export const AppDataSource = new DataSource({
RoomUserRoleEntity,
RoomChannelPermissionEntity,
ReactionEntity,
CustomEmojiEntity,
BanEntity,
AttachmentEntity,
MetaEntity,

View File

@@ -15,6 +15,7 @@ import {
RoomUserRoleEntity,
RoomChannelPermissionEntity,
ReactionEntity,
CustomEmojiEntity,
BanEntity,
AttachmentEntity,
MetaEntity,
@@ -224,6 +225,7 @@ export async function initializeDatabase(): Promise<void> {
RoomUserRoleEntity,
RoomChannelPermissionEntity,
ReactionEntity,
CustomEmojiEntity,
BanEntity,
AttachmentEntity,
MetaEntity,

View File

@@ -0,0 +1,35 @@
import {
Column,
Entity,
PrimaryColumn
} from 'typeorm';
@Entity('custom_emojis')
export class CustomEmojiEntity {
@PrimaryColumn('text')
id!: string;
@Column('text')
name!: string;
@Column('text')
creatorUserId!: string;
@Column('text')
dataUrl!: string;
@Column('text')
hash!: string;
@Column('text')
mime!: string;
@Column('integer')
size!: number;
@Column('integer')
createdAt!: number;
@Column('integer')
updatedAt!: number;
}

View File

@@ -8,6 +8,7 @@ export { RoomRoleEntity } from './RoomRoleEntity';
export { RoomUserRoleEntity } from './RoomUserRoleEntity';
export { RoomChannelPermissionEntity } from './RoomChannelPermissionEntity';
export { ReactionEntity } from './ReactionEntity';
export { CustomEmojiEntity } from './CustomEmojiEntity';
export { BanEntity } from './BanEntity';
export { AttachmentEntity } from './AttachmentEntity';
export { MetaEntity } from './MetaEntity';

View File

@@ -553,6 +553,29 @@ export function setupSystemHandlers(): void {
return true;
});
ipcMain.handle('copy-file', async (_event, sourceFilePath: string, destinationFilePath: string) => {
if (typeof sourceFilePath !== 'string' || !sourceFilePath.trim()) {
return false;
}
if (typeof destinationFilePath !== 'string' || !destinationFilePath.trim()) {
return false;
}
try {
const stats = await fsp.stat(sourceFilePath);
if (!stats.isFile()) {
return false;
}
await fsp.copyFile(sourceFilePath, destinationFilePath);
return true;
} catch {
return false;
}
});
ipcMain.handle('file-exists', async (_event, filePath: string) => {
try {
await fsp.access(filePath, fs.constants.F_OK);

View File

@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddCustomEmojis1000000000011 implements MigrationInterface {
name = 'AddCustomEmojis1000000000011';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "custom_emojis" (
"id" TEXT PRIMARY KEY NOT NULL,
"name" TEXT NOT NULL,
"creatorUserId" TEXT NOT NULL,
"dataUrl" TEXT NOT NULL,
"hash" TEXT NOT NULL,
"mime" TEXT NOT NULL,
"size" INTEGER NOT NULL,
"createdAt" INTEGER NOT NULL,
"updatedAt" INTEGER NOT NULL
)`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_custom_emojis_updated_at" ON "custom_emojis" ("updatedAt")`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_custom_emojis_creator" ON "custom_emojis" ("creatorUserId")`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX IF EXISTS "idx_custom_emojis_creator"`);
await queryRunner.query(`DROP INDEX IF EXISTS "idx_custom_emojis_updated_at"`);
await queryRunner.query(`DROP TABLE IF EXISTS "custom_emojis"`);
}
}

View File

@@ -1,4 +1,4 @@
import { contextBridge, ipcRenderer } from 'electron';
import { contextBridge, ipcRenderer, webUtils } from 'electron';
import { Command, Query } from './cqrs/types';
const LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL = 'linux-screen-share-monitor-audio-chunk';
@@ -310,6 +310,8 @@ export interface ElectronAPI {
saveFileAs: (defaultFileName: string, data: string) => Promise<{ saved: boolean; cancelled: boolean }>;
saveExistingFileAs: (sourceFilePath: string, defaultFileName: string) => Promise<{ saved: boolean; cancelled: boolean }>;
openFilePath: (filePath: string) => Promise<{ opened: boolean; reason?: string }>;
copyFile: (sourceFilePath: string, destinationFilePath: string) => Promise<boolean>;
getPathForFile: (file: File) => string;
fileExists: (filePath: string) => Promise<boolean>;
getFileUrl: (filePath: string) => Promise<string | null>;
deleteFile: (filePath: string) => Promise<boolean>;
@@ -441,6 +443,8 @@ const electronAPI: ElectronAPI = {
saveFileAs: (defaultFileName, data) => ipcRenderer.invoke('save-file-as', defaultFileName, data),
saveExistingFileAs: (sourceFilePath, defaultFileName) => ipcRenderer.invoke('save-existing-file-as', sourceFilePath, defaultFileName),
openFilePath: (filePath) => ipcRenderer.invoke('open-file-path', filePath),
copyFile: (sourceFilePath, destinationFilePath) => ipcRenderer.invoke('copy-file', sourceFilePath, destinationFilePath),
getPathForFile: (file) => webUtils.getPathForFile(file),
fileExists: (filePath) => ipcRenderer.invoke('file-exists', filePath),
getFileUrl: (filePath) => ipcRenderer.invoke('get-file-url', filePath),
deleteFile: (filePath) => ipcRenderer.invoke('delete-file', filePath),