Initial commit: JustAMessenger v0.1.0

Серверная часть (Go):
- WebSocket сервер с бинарным протоколом
- XChaCha20-Poly1305 шифрование
- zstd сжатие с дедупликацией (64KB чанки)
- SQLite хранилище (WAL режим)
- Управление гильдиями, каналами, ролями
- Федерация между серверами (ed25519)
- REST API + WebSocket endpoints

Клиентская часть (Flutter):
- Material Design 3 тёмная тема (Discord-like)
- WebSocket соединение с сервером
- Экраны: сплэш, логин, домашний, гильдии, чат
- Модели: пользователи, гильдии, каналы, сообщения, роли
- Сервисы: соединение, API, криптография, тема
- Виджеты: иконки гильдий, сообщения, ввод чата
- Web сборка (PWA)

Документация:
- AGENTS.md — контекст для ИИ ассистентов
- docs/protocol.md — спецификация протокола
This commit is contained in:
SashegDev
2026-06-06 22:39:14 +00:00
commit 096c4d0a2d
40 changed files with 5054 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
class Channel {
final String id;
final String guildId;
final String categoryId;
final String name;
final String type;
final String? topic;
final int position;
final DateTime createdAt;
Channel({
required this.id,
required this.guildId,
this.categoryId = '',
required this.name,
this.type = 'text',
this.topic,
this.position = 0,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
factory Channel.fromJson(Map<String, dynamic> json) {
return Channel(
id: json['id'] as String,
guildId: json['guild_id'] as String,
categoryId: json['category_id'] as String? ?? '',
name: json['name'] as String,
type: json['type'] as String? ?? 'text',
topic: json['topic'] as String?,
position: json['position'] as int? ?? 0,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: DateTime.now(),
);
}
bool get isText => type == 'text';
bool get isVoice => type == 'voice';
bool get isStream => type == 'stream';
}
+62
View File
@@ -0,0 +1,62 @@
import 'channel.dart';
import 'role.dart';
class Guild {
final String id;
final String name;
final String ownerId;
final String? icon;
final String? description;
final DateTime createdAt;
List<Channel> channels;
List<Role> roles;
List<Category> categories;
Guild({
required this.id,
required this.name,
required this.ownerId,
this.icon,
this.description,
DateTime? createdAt,
List<Channel>? channels,
List<Role>? roles,
List<Category>? categories,
}) : createdAt = createdAt ?? DateTime.now(),
channels = channels ?? [],
roles = roles ?? [],
categories = categories ?? [];
factory Guild.fromJson(Map<String, dynamic> json) {
return Guild(
id: json['id'] as String,
name: json['name'] as String,
ownerId: json['owner_id'] as String,
icon: json['icon'] as String?,
description: json['description'] as String?,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: DateTime.now(),
);
}
}
class Category {
final String id;
final String name;
final int position;
Category({
required this.id,
required this.name,
required this.position,
});
factory Category.fromJson(Map<String, dynamic> json) {
return Category(
id: json['id'] as String,
name: json['name'] as String,
position: json['position'] as int? ?? 0,
);
}
}
+57
View File
@@ -0,0 +1,57 @@
class Message {
final String id;
final String channelId;
final String authorId;
String? authorUsername;
List<int>? content;
bool encrypted;
List<int>? nonce;
String messageType;
String? replyTo;
bool pinned;
DateTime? editedAt;
final DateTime createdAt;
bool sending;
bool failed;
List<String>? attachments;
Message({
required this.id,
required this.channelId,
required this.authorId,
this.authorUsername,
this.content,
this.encrypted = false,
this.nonce,
this.messageType = 'text',
this.replyTo,
this.pinned = false,
this.editedAt,
DateTime? createdAt,
this.sending = false,
this.failed = false,
this.attachments,
}) : createdAt = createdAt ?? DateTime.now();
factory Message.fromJson(Map<String, dynamic> json) {
return Message(
id: json['id'] as String,
channelId: json['channel_id'] as String,
authorId: json['author_id'] as String,
authorUsername: json['username'] as String?,
content: (json['content'] as List<dynamic>?)?.cast<int>(),
encrypted: json['encrypted'] as bool? ?? false,
nonce: (json['nonce'] as List<dynamic>?)?.cast<int>(),
messageType: json['message_type'] as String? ?? 'text',
replyTo: json['reply_to'] as String?,
pinned: json['pinned'] as bool? ?? false,
editedAt: json['edited_at'] != null
? DateTime.parse(json['edited_at'] as String)
: null,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: DateTime.now(),
attachments: (json['attachments'] as List<dynamic>?)?.cast<String>(),
);
}
}
+35
View File
@@ -0,0 +1,35 @@
class Role {
final String id;
final String guildId;
final String name;
final int color;
final int position;
final List<String> permissions;
final bool isDefault;
Role({
required this.id,
required this.guildId,
required this.name,
this.color = 0,
this.position = 0,
List<String>? permissions,
this.isDefault = false,
}) : permissions = permissions ?? [];
factory Role.fromJson(Map<String, dynamic> json) {
return Role(
id: json['id'] as String,
guildId: json['guild_id'] as String,
name: json['name'] as String,
color: json['color'] as int? ?? 0,
position: json['position'] as int? ?? 0,
permissions: (json['permissions'] as List<dynamic>?)
?.cast<String>() ??
[],
isDefault: json['is_default'] as bool? ?? false,
);
}
int get colorValue => color == 0 ? 0xFFB5BAC1 : color;
}
+39
View File
@@ -0,0 +1,39 @@
class User {
final String id;
final String username;
final String? avatar;
final String? bio;
final String? publicKey;
final DateTime createdAt;
User({
required this.id,
required this.username,
this.avatar,
this.bio,
this.publicKey,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as String,
username: json['username'] as String,
avatar: json['avatar'] as String?,
bio: json['bio'] as String?,
publicKey: json['public_key'] as String?,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: DateTime.now(),
);
}
Map<String, dynamic> toJson() => {
'id': id,
'username': username,
'avatar': avatar,
'bio': bio,
'public_key': publicKey,
'created_at': createdAt.toIso8601String(),
};
}