This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

View File

@@ -0,0 +1,278 @@
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
import { decodeWithPassword, encodeWithPassword } from "@/app/crypto/crypto";
import { generateRandomKey } from "@/app/utils/utils";
import { useDialogsList } from "../DialogListProvider/useDialogsList";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { DeliveredMessageState } from "./DialogProvider";
import { useSender } from "../ProtocolProvider/useSender";
import { useState } from "react";
import { PacketCreateGroup } from "../ProtocolProvider/protocol/packets/packet.create.group";
import { useProtocol } from "../ProtocolProvider/useProtocol";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { GroupStatus, PacketGroupJoin } from "../ProtocolProvider/protocol/packets/packet.group.join";
import { useGroupInviteStatus } from "./useGroupInviteStatus";
import { useNavigate } from "react-router-dom";
import { useUpdateGroupInformation } from "../InformationProvider/useUpdateGroupInformation";
import { PacketGroupLeave } from "../ProtocolProvider/protocol/packets/packet.group.leave";
import { PacketGroupBan } from "../ProtocolProvider/protocol/packets/packet.group.ban";
export function useGroups() : {
/**
* Получаем ключ шифрования из базы данных по ид группы
* @param groupId ид группы
* @returns ключ шифрования
*/
getGroupKey: (groupId: string) => Promise<string>;
/**
* Получает строку для приглашения в группу
* @param groupId ид группы
* @param title заголовок
* @param encryptKey ключ шифрования
* @param description описание
* @returns строка, которая нужна для приглашения в группу
*/
constructGroupString: (groupId: string, title: string, encryptKey: string, description?: string) => Promise<string>;
/**
* Функция, обратная constructGroupString, парсит строку приглашения в группу
* @param groupString строка приглашения в группу
* @returns объект с информацией о группе или null, если строка некорректна
*/
parseGroupString: (groupString: string) => Promise<{
groupId: string;
title: string;
encryptKey: string;
description: string;
} | null>;
/**
* Проверяет, является ли диалог группой
* @param dialog ид диалога
* @returns вернет true, если это группа и false если это пользователь
*/
hasGroup: (dialog: string) => boolean;
/**
* Возвращает подготовленный для роута groupId
* @param groupId подготавливает groupId для роута
* @returns заменяет символы которые может не обрабатывать роутер
*/
prepareForRoute: (groupId: string) => string;
/**
* Создает группу
* @param title заголовок
* @param description описание
* @returns
*/
createGroup: (title: string, description: string) => Promise<void>;
/**
* Зайдет в группу по строке приглашения
* @param groupString строка приглашение
* @returns
*/
joinGroup: (groupString: string) => Promise<void>;
/**
* Покидает группу
* @param groupId ид группы
* @returns
*/
leaveGroup: (groupId: string) => Promise<void>;
/**
*
* @param str
* @returns
*/
normalize: (str: string) => string;
banUserOnGroup: (userPublicKey: string, groupId: string) => void;
getPrefix: () => string;
loading: boolean;
} {
const {allQuery, runQuery} = useDatabase();
const privatePlain = usePrivatePlain();
const {updateDialog} = useDialogsList();
const publicKey = usePublicKey();
const [loading, setLoading] = useState<boolean>(false);
const send = useSender();
const {protocol} = useProtocol();
const {info} = useConsoleLogger('useGroups');
const {setInviteStatusByGroupId} = useGroupInviteStatus('');
const navigate = useNavigate();
const updateGroupInformation = useUpdateGroupInformation();
const constructGroupString = async (groupId: string, title: string, encryptKey: string, description?: string) => {
let groupString = `${groupId}:${title}:${encryptKey}`;
if (description && description.trim().length > 0) {
groupString += `:${description}`;
}
let encodedPayload = await encodeWithPassword('rosetta_group', groupString);
return `#group:${encodedPayload}`;
}
const hasGroup = (dialog: string) => {
return dialog.startsWith('#group:');
}
const getPrefix = () => {
return '#group:';
}
const parseGroupString = async (groupString: string) => {
try{
if (!groupString.startsWith('#group:')) {
return null;
}
let encodedPayload = groupString.substring(7);
let decodedPayload = await decodeWithPassword('rosetta_group', encodedPayload);
let parts = decodedPayload.split(':');
return {
groupId: parts[0],
title: parts[1],
encryptKey: parts[2],
description: parts[3] || ''
}
}catch(e) {
return null;
}
}
const getGroupKey = async (groupId: string) => {
const query = `SELECT key FROM groups WHERE group_id = ? AND account = ? LIMIT 1`;
const result = await allQuery(query, [normalize(groupId), publicKey]);
if(result.length > 0) {
let key = result[0].key;
return await decodeWithPassword(privatePlain, key);
}
return "";
};
const prepareForRoute = (groupId: string) => {
return `#group:${groupId}`.replace('#', '%23');
}
const normalize = (str: string) => {
return str.replace('#group:', '').trim();
}
const createGroup = async (title: string, description: string) => {
if(title.trim().length === 0){
return;
}
setLoading(true);
const packet = new PacketCreateGroup();
send(packet);
protocol.waitPacketOnce(0x11, async (packet : PacketCreateGroup) => {
const groupId = packet.getGroupId();
info(`Creating group with id ${groupId}`);
const encryptKey = generateRandomKey(64);
const secureKey = await encodeWithPassword(privatePlain, encryptKey);
let content = await encodeWithPassword(encryptKey, `$a=Group created`);
let plainMessage = await encodeWithPassword(privatePlain, `$a=Group created`);
await runQuery(`
INSERT INTO groups (account, group_id, title, description, key) VALUES (?, ?, ?, ?, ?)
`, [publicKey, groupId, title, description, secureKey]);
await runQuery(`
INSERT INTO messages
(from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [publicKey, "#group:" + groupId, content, Date.now(), 1, "", 1, plainMessage, publicKey, generateRandomKey(16),
DeliveredMessageState.DELIVERED
, '[]']);
updateDialog("#group:" + groupId);
updateGroupInformation({
groupId: groupId,
title: title,
description: description
});
setLoading(false);
navigate(`/main/chat/${prepareForRoute(groupId)}`);
});
}
const banUserOnGroup = (userPublicKey: string, groupId: string) => {
const packet = new PacketGroupBan();
packet.setGroupId(groupId);
packet.setPublicKey(userPublicKey);
send(packet);
}
const joinGroup = async (groupString: string) => {
const parsed = await parseGroupString(groupString);
if (!parsed) {
return;
}
const encryptKey = parsed.encryptKey;
const groupId = parsed.groupId;
const title = parsed.title;
const description = parsed.description;
const packet = new PacketGroupJoin();
packet.setGroupId(parsed.groupId);
send(packet);
setLoading(true);
protocol.waitPacketOnce(0x14, async (packet: PacketGroupJoin) => {
console.info(`Received group join response for group ${parsed.groupId}`);
const groupStatus = packet.getGroupStatus();
if(groupStatus != GroupStatus.JOINED){
info(`Cannot join group ${parsed.groupId}, banned`);
setInviteStatusByGroupId(parsed.groupId, groupStatus);
setLoading(false);
return;
}
const secureKey = await encodeWithPassword(privatePlain, encryptKey);
let content = await encodeWithPassword(encryptKey, `$a=Group joined`);
let plainMessage = await encodeWithPassword(privatePlain, `$a=Group joined`);
await runQuery(`
INSERT INTO groups (account, group_id, title, description, key) VALUES (?, ?, ?, ?, ?)
`, [publicKey, groupId, title, description, secureKey]);
await runQuery(`
INSERT INTO messages
(from_public_key, to_public_key, content, timestamp, read, chacha_key, from_me, plain_message, account, message_id, delivered, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [publicKey, "#group:" + groupId, content, Date.now(), 1, "", 1, plainMessage, publicKey, generateRandomKey(16),
DeliveredMessageState.DELIVERED
, '[]']);
updateDialog("#group:" + groupId);
setInviteStatusByGroupId(groupId, GroupStatus.JOINED);
setLoading(false);
updateGroupInformation({
groupId: groupId,
title: title,
description: description
});
navigate(`/main/chat/${prepareForRoute(groupId)}`);
});
}
const leaveGroup = async (groupId: string) => {
const packet = new PacketGroupLeave();
packet.setGroupId(groupId);
send(packet);
setLoading(true);
protocol.waitPacketOnce(0x15, async (packet: PacketGroupLeave) => {
if(packet.getGroupId() != groupId){
return;
}
await runQuery(`
DELETE FROM groups WHERE group_id = ? AND account = ?
`, [groupId, publicKey]);
await runQuery(`
DELETE FROM messages WHERE to_public_key = ? AND account = ?
`, ["#group:" + normalize(groupId), publicKey]);
updateDialog("#group:" + normalize(groupId));
setLoading(false);
navigate(`/main`);
});
}
return {
getGroupKey,
constructGroupString,
parseGroupString,
hasGroup,
prepareForRoute,
createGroup,
joinGroup,
leaveGroup,
getPrefix,
banUserOnGroup,
normalize,
loading
}
}