starting with c2c #227

This commit is contained in:
otsmr 2025-11-01 01:46:02 +01:00
parent 7748ecec3c
commit 76ff64ff74
40 changed files with 3518 additions and 169 deletions

30
.metadata Normal file
View file

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: macos
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -8,14 +8,16 @@ This repository contains the complete source code of the [twonly](https://twonly
- Offer a Snapchat™ like experience
- End-to-End encryption using the [Signal Protocol](https://de.wikipedia.org/wiki/Signal-Protokoll)
- twonly is Open Source and can be downloaded directly from GitHub
- Developed by humans not by AI or Vibe Coding
- No email or phone number required to register
- Privacy friendly - Everything is stored on the device
- Open-Source
- Backend is exclusively hosted in European
## In work
## Planned
- For Android: Using [UnifiedPush](https://unifiedpush.org/) instead of FCM
- For Android: Reproducible Builds + Publishing F-Droid
- For Android: Optional support for [UnifiedPush](https://unifiedpush.org/)
- For Android: Reproducible Builds
- Implementing [Sealed Sender](https://signal.org/blog/sealed-sender/) to minimize metadata
## Security Issues

View file

@ -8,7 +8,7 @@ import 'package:twonly/src/utils/misc.dart';
part 'groups.dao.g.dart';
@DriftAccessor(tables: [Groups, GroupMembers])
@DriftAccessor(tables: [Groups, GroupMembers, GroupHistories])
class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
@ -37,11 +37,21 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
}
Future<Group?> createNewGroup(GroupsCompanion group) async {
final insertGroup = group.copyWith(
groupId: Value(uuid.v4()),
isGroupAdmin: const Value(true),
return _insertGroup(group);
}
Future<void> insertGroupMember(GroupMembersCompanion members) async {
await into(groupMembers).insert(members);
}
Future<void> insertGroupAction(GroupHistoriesCompanion action) async {
var insertAction = action;
if (!action.groupHistoryId.present) {
insertAction = action.copyWith(
groupHistoryId: Value(uuid.v4()),
);
return _insertGroup(insertGroup);
}
await into(groupHistories).insert(insertAction);
}
Future<Group?> createNewDirectChat(
@ -53,6 +63,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
groupId: Value(groupIdDirectChat),
isDirectChat: const Value(true),
isGroupAdmin: const Value(true),
joinedGroup: const Value(true),
);
final result = await _insertGroup(insertGroup);
@ -138,6 +149,14 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
return (select(groups)..where((t) => t.isDirectChat.equals(true))).get();
}
Future<List<Group>> getAllNotJoinedGroups() {
return (select(groups)
..where(
(t) => t.joinedGroup.equals(false) & t.isDirectChat.equals(false),
))
.get();
}
Future<Group?> getDirectChat(int userId) async {
final query =
((select(groups)..where((t) => t.isDirectChat.equals(true))).join([

View file

@ -7,4 +7,5 @@ mixin _$GroupsDaoMixin on DatabaseAccessor<TwonlyDB> {
$GroupsTable get groups => attachedDatabase.groups;
$ContactsTable get contacts => attachedDatabase.contacts;
$GroupMembersTable get groupMembers => attachedDatabase.groupMembers;
$GroupHistoriesTable get groupHistories => attachedDatabase.groupHistories;
}

View file

@ -1,15 +1,25 @@
import 'package:drift/drift.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
const int defaultDeleteMessagesAfterMilliseconds = 1000 * 60 * 60 * 24;
@DataClassName('Group')
class Groups extends Table {
TextColumn get groupId => text()();
BoolColumn get isGroupAdmin => boolean()();
BoolColumn get isDirectChat => boolean()();
BoolColumn get isGroupAdmin => boolean().withDefault(const Constant(false))();
BoolColumn get isDirectChat => boolean().withDefault(const Constant(false))();
BoolColumn get pinned => boolean().withDefault(const Constant(false))();
BoolColumn get archived => boolean().withDefault(const Constant(false))();
BoolColumn get joinedGroup => boolean().withDefault(const Constant(false))();
BoolColumn get leftGroup => boolean().withDefault(const Constant(false))();
IntColumn get stateVersionId => integer().withDefault(const Constant(0))();
BlobColumn get stateEncryptionKey => blob().nullable()();
BlobColumn get myGroupPrivateKey => blob().nullable()();
TextColumn get groupName => text()();
IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))();
@ -17,8 +27,8 @@ class Groups extends Table {
BoolColumn get alsoBestFriend =>
boolean().withDefault(const Constant(false))();
IntColumn get deleteMessagesAfterMilliseconds =>
integer().withDefault(const Constant(1000 * 60 * 60 * 24))();
IntColumn get deleteMessagesAfterMilliseconds => integer()
.withDefault(const Constant(defaultDeleteMessagesAfterMilliseconds))();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@ -39,17 +49,49 @@ class Groups extends Table {
Set<Column> get primaryKey => {groupId};
}
enum MemberState { invited, accepted, admin }
enum MemberState { normal, admin }
@DataClassName('GroupMember')
class GroupMembers extends Table {
TextColumn get groupId => text()();
TextColumn get groupId =>
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
IntColumn get contactId => integer().references(Contacts, #userId)();
TextColumn get memberState => textEnum<MemberState>().nullable()();
BlobColumn get groupPublicKey => blob().nullable()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {groupId, contactId};
}
enum GroupActionType {
createdGroup,
removedMember,
addMember,
leftGroup,
promoteToAdmin,
demoteToMember,
updatedGroupName,
}
@DataClassName('GroupHistory')
class GroupHistories extends Table {
TextColumn get groupHistoryId => text()();
TextColumn get groupId =>
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
IntColumn get affectedContactId =>
integer().nullable().references(Contacts, #userId)();
TextColumn get oldGroupName => text().nullable()();
TextColumn get newGroupName => text().nullable()();
TextColumn get type => textEnum<GroupActionType>()();
DateTimeColumn get actionAt => dateTime().withDefault(currentDateAndTime)();
@override
Set<Column> get primaryKey => {groupHistoryId};
}

View file

@ -44,6 +44,7 @@ part 'twonly.db.g.dart';
SignalContactPreKeys,
SignalContactSignedPreKeys,
MessageActions,
GroupHistories
],
daos: [
MessagesDao,

File diff suppressed because it is too large Load diff

View file

@ -359,5 +359,11 @@
"durationShortSecond": "Sek.",
"durationShortMinute": "Min.",
"durationShortHour": "Std",
"durationShortDays": "Tagen"
"durationShortDays": "Tagen",
"newGroup": "Neue Gruppe",
"selectMembers": "Mitglieder auswählen",
"selectGroupName": "Gruppennamen wählen",
"groupNameInput": "Gruppennamen",
"groupMembers": "Mitglieder",
"createGroup": "Gruppe erstellen"
}

View file

@ -515,5 +515,11 @@
"durationShortSecond": "Sec.",
"durationShortMinute": "Min.",
"durationShortHour": "Hrs.",
"durationShortDays": "Days"
"durationShortDays": "Days",
"newGroup": "New group",
"selectMembers": "Select members",
"selectGroupName": "Select group name",
"groupNameInput": "Group name",
"groupMembers": "Members",
"createGroup": "Create group"
}

View file

@ -2197,6 +2197,42 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Days'**
String get durationShortDays;
/// No description provided for @newGroup.
///
/// In en, this message translates to:
/// **'New group'**
String get newGroup;
/// No description provided for @selectMembers.
///
/// In en, this message translates to:
/// **'Select members'**
String get selectMembers;
/// No description provided for @selectGroupName.
///
/// In en, this message translates to:
/// **'Select group name'**
String get selectGroupName;
/// No description provided for @groupNameInput.
///
/// In en, this message translates to:
/// **'Group name'**
String get groupNameInput;
/// No description provided for @groupMembers.
///
/// In en, this message translates to:
/// **'Members'**
String get groupMembers;
/// No description provided for @createGroup.
///
/// In en, this message translates to:
/// **'Create group'**
String get createGroup;
}
class _AppLocalizationsDelegate

View file

@ -1165,4 +1165,22 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get durationShortDays => 'Tagen';
@override
String get newGroup => 'Neue Gruppe';
@override
String get selectMembers => 'Mitglieder auswählen';
@override
String get selectGroupName => 'Gruppennamen wählen';
@override
String get groupNameInput => 'Gruppennamen';
@override
String get groupMembers => 'Mitglieder';
@override
String get createGroup => 'Gruppe erstellen';
}

View file

@ -1158,4 +1158,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get durationShortDays => 'Days';
@override
String get newGroup => 'New group';
@override
String get selectMembers => 'Select members';
@override
String get selectGroupName => 'Select group name';
@override
String get groupNameInput => 'Group name';
@override
String get groupMembers => 'Members';
@override
String get createGroup => 'Create group';
}

View file

@ -158,6 +158,350 @@ class UploadRequest extends $pb.GeneratedMessage {
$core.List<TextMessage> get messagesOnSuccess => $_getList(2);
}
class UpdateGroupState_UpdateTBS extends $pb.GeneratedMessage {
factory UpdateGroupState_UpdateTBS({
$fixnum.Int64? versionId,
$core.List<$core.int>? encryptedGroupState,
$core.List<$core.int>? publicKey,
$core.List<$core.int>? removeAdmin,
$core.List<$core.int>? addAdmin,
$core.List<$core.int>? nonce,
}) {
final $result = create();
if (versionId != null) {
$result.versionId = versionId;
}
if (encryptedGroupState != null) {
$result.encryptedGroupState = encryptedGroupState;
}
if (publicKey != null) {
$result.publicKey = publicKey;
}
if (removeAdmin != null) {
$result.removeAdmin = removeAdmin;
}
if (addAdmin != null) {
$result.addAdmin = addAdmin;
}
if (nonce != null) {
$result.nonce = nonce;
}
return $result;
}
UpdateGroupState_UpdateTBS._() : super();
factory UpdateGroupState_UpdateTBS.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory UpdateGroupState_UpdateTBS.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'UpdateGroupState.UpdateTBS', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'removeAdmin', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'addAdmin', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
UpdateGroupState_UpdateTBS clone() => UpdateGroupState_UpdateTBS()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
UpdateGroupState_UpdateTBS copyWith(void Function(UpdateGroupState_UpdateTBS) updates) => super.copyWith((message) => updates(message as UpdateGroupState_UpdateTBS)) as UpdateGroupState_UpdateTBS;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UpdateGroupState_UpdateTBS create() => UpdateGroupState_UpdateTBS._();
UpdateGroupState_UpdateTBS createEmptyInstance() => create();
static $pb.PbList<UpdateGroupState_UpdateTBS> createRepeated() => $pb.PbList<UpdateGroupState_UpdateTBS>();
@$core.pragma('dart2js:noInline')
static UpdateGroupState_UpdateTBS getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateGroupState_UpdateTBS>(create);
static UpdateGroupState_UpdateTBS? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get versionId => $_getI64(0);
@$pb.TagNumber(1)
set versionId($fixnum.Int64 v) { $_setInt64(0, v); }
@$pb.TagNumber(1)
$core.bool hasVersionId() => $_has(0);
@$pb.TagNumber(1)
void clearVersionId() => clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get encryptedGroupState => $_getN(1);
@$pb.TagNumber(3)
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
@$pb.TagNumber(3)
$core.bool hasEncryptedGroupState() => $_has(1);
@$pb.TagNumber(3)
void clearEncryptedGroupState() => clearField(3);
@$pb.TagNumber(4)
$core.List<$core.int> get publicKey => $_getN(2);
@$pb.TagNumber(4)
set publicKey($core.List<$core.int> v) { $_setBytes(2, v); }
@$pb.TagNumber(4)
$core.bool hasPublicKey() => $_has(2);
@$pb.TagNumber(4)
void clearPublicKey() => clearField(4);
/// public group key
@$pb.TagNumber(5)
$core.List<$core.int> get removeAdmin => $_getN(3);
@$pb.TagNumber(5)
set removeAdmin($core.List<$core.int> v) { $_setBytes(3, v); }
@$pb.TagNumber(5)
$core.bool hasRemoveAdmin() => $_has(3);
@$pb.TagNumber(5)
void clearRemoveAdmin() => clearField(5);
@$pb.TagNumber(6)
$core.List<$core.int> get addAdmin => $_getN(4);
@$pb.TagNumber(6)
set addAdmin($core.List<$core.int> v) { $_setBytes(4, v); }
@$pb.TagNumber(6)
$core.bool hasAddAdmin() => $_has(4);
@$pb.TagNumber(6)
void clearAddAdmin() => clearField(6);
@$pb.TagNumber(7)
$core.List<$core.int> get nonce => $_getN(5);
@$pb.TagNumber(7)
set nonce($core.List<$core.int> v) { $_setBytes(5, v); }
@$pb.TagNumber(7)
$core.bool hasNonce() => $_has(5);
@$pb.TagNumber(7)
void clearNonce() => clearField(7);
}
/// plaintext message send to the server
class UpdateGroupState extends $pb.GeneratedMessage {
factory UpdateGroupState({
UpdateGroupState_UpdateTBS? update,
$core.List<$core.int>? signature,
}) {
final $result = create();
if (update != null) {
$result.update = update;
}
if (signature != null) {
$result.signature = signature;
}
return $result;
}
UpdateGroupState._() : super();
factory UpdateGroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory UpdateGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'UpdateGroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
..aOM<UpdateGroupState_UpdateTBS>(1, _omitFieldNames ? '' : 'update', subBuilder: UpdateGroupState_UpdateTBS.create)
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
UpdateGroupState clone() => UpdateGroupState()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
UpdateGroupState copyWith(void Function(UpdateGroupState) updates) => super.copyWith((message) => updates(message as UpdateGroupState)) as UpdateGroupState;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UpdateGroupState create() => UpdateGroupState._();
UpdateGroupState createEmptyInstance() => create();
static $pb.PbList<UpdateGroupState> createRepeated() => $pb.PbList<UpdateGroupState>();
@$core.pragma('dart2js:noInline')
static UpdateGroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateGroupState>(create);
static UpdateGroupState? _defaultInstance;
@$pb.TagNumber(1)
UpdateGroupState_UpdateTBS get update => $_getN(0);
@$pb.TagNumber(1)
set update(UpdateGroupState_UpdateTBS v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasUpdate() => $_has(0);
@$pb.TagNumber(1)
void clearUpdate() => clearField(1);
@$pb.TagNumber(1)
UpdateGroupState_UpdateTBS ensureUpdate() => $_ensure(0);
@$pb.TagNumber(2)
$core.List<$core.int> get signature => $_getN(1);
@$pb.TagNumber(2)
set signature($core.List<$core.int> v) { $_setBytes(1, v); }
@$pb.TagNumber(2)
$core.bool hasSignature() => $_has(1);
@$pb.TagNumber(2)
void clearSignature() => clearField(2);
}
class NewGroupState extends $pb.GeneratedMessage {
factory NewGroupState({
$core.String? groupId,
$fixnum.Int64? versionId,
$core.List<$core.int>? encryptedGroupState,
$core.List<$core.int>? publicKey,
}) {
final $result = create();
if (groupId != null) {
$result.groupId = groupId;
}
if (versionId != null) {
$result.versionId = versionId;
}
if (encryptedGroupState != null) {
$result.encryptedGroupState = encryptedGroupState;
}
if (publicKey != null) {
$result.publicKey = publicKey;
}
return $result;
}
NewGroupState._() : super();
factory NewGroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory NewGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'NewGroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'groupId', protoName: 'groupId')
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
NewGroupState clone() => NewGroupState()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
NewGroupState copyWith(void Function(NewGroupState) updates) => super.copyWith((message) => updates(message as NewGroupState)) as NewGroupState;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static NewGroupState create() => NewGroupState._();
NewGroupState createEmptyInstance() => create();
static $pb.PbList<NewGroupState> createRepeated() => $pb.PbList<NewGroupState>();
@$core.pragma('dart2js:noInline')
static NewGroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<NewGroupState>(create);
static NewGroupState? _defaultInstance;
@$pb.TagNumber(1)
$core.String get groupId => $_getSZ(0);
@$pb.TagNumber(1)
set groupId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasGroupId() => $_has(0);
@$pb.TagNumber(1)
void clearGroupId() => clearField(1);
@$pb.TagNumber(2)
$fixnum.Int64 get versionId => $_getI64(1);
@$pb.TagNumber(2)
set versionId($fixnum.Int64 v) { $_setInt64(1, v); }
@$pb.TagNumber(2)
$core.bool hasVersionId() => $_has(1);
@$pb.TagNumber(2)
void clearVersionId() => clearField(2);
@$pb.TagNumber(4)
$core.List<$core.int> get encryptedGroupState => $_getN(2);
@$pb.TagNumber(4)
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(2, v); }
@$pb.TagNumber(4)
$core.bool hasEncryptedGroupState() => $_has(2);
@$pb.TagNumber(4)
void clearEncryptedGroupState() => clearField(4);
@$pb.TagNumber(5)
$core.List<$core.int> get publicKey => $_getN(3);
@$pb.TagNumber(5)
set publicKey($core.List<$core.int> v) { $_setBytes(3, v); }
@$pb.TagNumber(5)
$core.bool hasPublicKey() => $_has(3);
@$pb.TagNumber(5)
void clearPublicKey() => clearField(5);
}
class GroupState extends $pb.GeneratedMessage {
factory GroupState({
$fixnum.Int64? versionId,
$core.List<$core.int>? encryptedGroupState,
}) {
final $result = create();
if (versionId != null) {
$result.versionId = versionId;
}
if (encryptedGroupState != null) {
$result.encryptedGroupState = encryptedGroupState;
}
return $result;
}
GroupState._() : super();
factory GroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GroupState', package: const $pb.PackageName(_omitMessageNames ? '' : 'http_requests'), createEmptyInstance: create)
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'versionId', $pb.PbFieldType.OU6, protoName: 'versionId', defaultOrMaker: $fixnum.Int64.ZERO)
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
GroupState clone() => GroupState()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
GroupState copyWith(void Function(GroupState) updates) => super.copyWith((message) => updates(message as GroupState)) as GroupState;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GroupState create() => GroupState._();
GroupState createEmptyInstance() => create();
static $pb.PbList<GroupState> createRepeated() => $pb.PbList<GroupState>();
@$core.pragma('dart2js:noInline')
static GroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GroupState>(create);
static GroupState? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get versionId => $_getI64(0);
@$pb.TagNumber(1)
set versionId($fixnum.Int64 v) { $_setInt64(0, v); }
@$pb.TagNumber(1)
$core.bool hasVersionId() => $_has(0);
@$pb.TagNumber(1)
void clearVersionId() => clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get encryptedGroupState => $_getN(1);
@$pb.TagNumber(3)
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
@$pb.TagNumber(3)
$core.bool hasEncryptedGroupState() => $_has(1);
@$pb.TagNumber(3)
void clearEncryptedGroupState() => clearField(3);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -48,3 +48,71 @@ final $typed_data.Uint8List uploadRequestDescriptor = $convert.base64Decode(
'c3VjY2VzcxgDIAMoCzIaLmh0dHBfcmVxdWVzdHMuVGV4dE1lc3NhZ2VSEW1lc3NhZ2VzT25TdW'
'NjZXNz');
@$core.Deprecated('Use updateGroupStateDescriptor instead')
const UpdateGroupState$json = {
'1': 'UpdateGroupState',
'2': [
{'1': 'update', '3': 1, '4': 1, '5': 11, '6': '.http_requests.UpdateGroupState.UpdateTBS', '10': 'update'},
{'1': 'signature', '3': 2, '4': 1, '5': 12, '10': 'signature'},
],
'3': [UpdateGroupState_UpdateTBS$json],
};
@$core.Deprecated('Use updateGroupStateDescriptor instead')
const UpdateGroupState_UpdateTBS$json = {
'1': 'UpdateTBS',
'2': [
{'1': 'version_id', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
{'1': 'public_key', '3': 4, '4': 1, '5': 12, '10': 'publicKey'},
{'1': 'remove_admin', '3': 5, '4': 1, '5': 12, '9': 0, '10': 'removeAdmin', '17': true},
{'1': 'add_admin', '3': 6, '4': 1, '5': 12, '9': 1, '10': 'addAdmin', '17': true},
{'1': 'nonce', '3': 7, '4': 1, '5': 12, '10': 'nonce'},
],
'8': [
{'1': '_remove_admin'},
{'1': '_add_admin'},
],
};
/// Descriptor for `UpdateGroupState`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List updateGroupStateDescriptor = $convert.base64Decode(
'ChBVcGRhdGVHcm91cFN0YXRlEkEKBnVwZGF0ZRgBIAEoCzIpLmh0dHBfcmVxdWVzdHMuVXBkYX'
'RlR3JvdXBTdGF0ZS5VcGRhdGVUQlNSBnVwZGF0ZRIcCglzaWduYXR1cmUYAiABKAxSCXNpZ25h'
'dHVyZRr8AQoJVXBkYXRlVEJTEh0KCnZlcnNpb25faWQYASABKARSCXZlcnNpb25JZBIyChVlbm'
'NyeXB0ZWRfZ3JvdXBfc3RhdGUYAyABKAxSE2VuY3J5cHRlZEdyb3VwU3RhdGUSHQoKcHVibGlj'
'X2tleRgEIAEoDFIJcHVibGljS2V5EiYKDHJlbW92ZV9hZG1pbhgFIAEoDEgAUgtyZW1vdmVBZG'
'1pbogBARIgCglhZGRfYWRtaW4YBiABKAxIAVIIYWRkQWRtaW6IAQESFAoFbm9uY2UYByABKAxS'
'BW5vbmNlQg8KDV9yZW1vdmVfYWRtaW5CDAoKX2FkZF9hZG1pbg==');
@$core.Deprecated('Use newGroupStateDescriptor instead')
const NewGroupState$json = {
'1': 'NewGroupState',
'2': [
{'1': 'groupId', '3': 1, '4': 1, '5': 9, '10': 'groupId'},
{'1': 'versionId', '3': 2, '4': 1, '5': 4, '10': 'versionId'},
{'1': 'encrypted_group_state', '3': 4, '4': 1, '5': 12, '10': 'encryptedGroupState'},
{'1': 'public_key', '3': 5, '4': 1, '5': 12, '10': 'publicKey'},
],
};
/// Descriptor for `NewGroupState`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List newGroupStateDescriptor = $convert.base64Decode(
'Cg1OZXdHcm91cFN0YXRlEhgKB2dyb3VwSWQYASABKAlSB2dyb3VwSWQSHAoJdmVyc2lvbklkGA'
'IgASgEUgl2ZXJzaW9uSWQSMgoVZW5jcnlwdGVkX2dyb3VwX3N0YXRlGAQgASgMUhNlbmNyeXB0'
'ZWRHcm91cFN0YXRlEh0KCnB1YmxpY19rZXkYBSABKAxSCXB1YmxpY0tleQ==');
@$core.Deprecated('Use groupStateDescriptor instead')
const GroupState$json = {
'1': 'GroupState',
'2': [
{'1': 'versionId', '3': 1, '4': 1, '5': 4, '10': 'versionId'},
{'1': 'encrypted_group_state', '3': 3, '4': 1, '5': 12, '10': 'encryptedGroupState'},
],
};
/// Descriptor for `GroupState`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List groupStateDescriptor = $convert.base64Decode(
'CgpHcm91cFN0YXRlEhwKCXZlcnNpb25JZBgBIAEoBFIJdmVyc2lvbklkEjIKFWVuY3J5cHRlZF'
'9ncm91cF9zdGF0ZRgDIAEoDFITZW5jcnlwdGVkR3JvdXBTdGF0ZQ==');

View file

@ -206,6 +206,7 @@ class Handshake_Register extends $pb.GeneratedMessage {
$fixnum.Int64? signedPrekeyId,
$fixnum.Int64? registrationId,
$core.bool? isIos,
$core.String? langCode,
}) {
final $result = create();
if (username != null) {
@ -232,6 +233,9 @@ class Handshake_Register extends $pb.GeneratedMessage {
if (isIos != null) {
$result.isIos = isIos;
}
if (langCode != null) {
$result.langCode = langCode;
}
return $result;
}
Handshake_Register._() : super();
@ -247,6 +251,7 @@ class Handshake_Register extends $pb.GeneratedMessage {
..aInt64(6, _omitFieldNames ? '' : 'signedPrekeyId')
..aInt64(7, _omitFieldNames ? '' : 'registrationId')
..aOB(8, _omitFieldNames ? '' : 'isIos')
..aOS(9, _omitFieldNames ? '' : 'langCode')
..hasRequiredFields = false
;
@ -342,6 +347,15 @@ class Handshake_Register extends $pb.GeneratedMessage {
$core.bool hasIsIos() => $_has(7);
@$pb.TagNumber(8)
void clearIsIos() => clearField(8);
@$pb.TagNumber(9)
$core.String get langCode => $_getSZ(8);
@$pb.TagNumber(9)
set langCode($core.String v) { $_setString(8, v); }
@$pb.TagNumber(9)
$core.bool hasLangCode() => $_has(8);
@$pb.TagNumber(9)
void clearLangCode() => clearField(9);
}
class Handshake_GetAuthChallenge extends $pb.GeneratedMessage {

View file

@ -77,11 +77,11 @@ const Handshake_Register$json = {
{'1': 'signed_prekey_signature', '3': 5, '4': 1, '5': 12, '10': 'signedPrekeySignature'},
{'1': 'signed_prekey_id', '3': 6, '4': 1, '5': 3, '10': 'signedPrekeyId'},
{'1': 'registration_id', '3': 7, '4': 1, '5': 3, '10': 'registrationId'},
{'1': 'is_ios', '3': 8, '4': 1, '5': 8, '9': 1, '10': 'isIos', '17': true},
{'1': 'is_ios', '3': 8, '4': 1, '5': 8, '10': 'isIos'},
{'1': 'lang_code', '3': 9, '4': 1, '5': 9, '10': 'langCode'},
],
'8': [
{'1': '_invite_code'},
{'1': '_is_ios'},
],
};
@ -121,19 +121,19 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
'ZW50X3RvX3NlcnZlci5IYW5kc2hha2UuR2V0QXV0aENoYWxsZW5nZUgAUhBnZXRhdXRoY2hhbG'
'xlbmdlEk4KDGdldGF1dGh0b2tlbhgDIAEoCzIoLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtl'
'LkdldEF1dGhUb2tlbkgAUgxnZXRhdXRodG9rZW4STgoMYXV0aGVudGljYXRlGAQgASgLMiguY2'
'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRrj'
'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRrw'
'AgoIUmVnaXN0ZXISGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGA'
'IgASgJSABSCmludml0ZUNvZGWIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVi'
'bGljSWRlbnRpdHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3'
'NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQ'
'c2lnbmVkX3ByZWtleV9pZBgGIAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2'
'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIaCgZpc19pb3MYCCABKAhIAVIFaXNJb3OIAQFCDgoM'
'X2ludml0ZV9jb2RlQgkKB19pc19pb3MaEgoQR2V0QXV0aENoYWxsZW5nZRpDCgxHZXRBdXRoVG'
'9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25z'
'ZRqsAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2'
'VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3ZlcnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogB'
'ARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aWNlSWSIAQFCDgoMX2FwcF92ZXJzaW9uQgwKCl'
'9kZXZpY2VfaWRCCwoJSGFuZHNoYWtl');
'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdf'
'Y29kZRgJIAEoCVIIbGFuZ0NvZGVCDgoMX2ludml0ZV9jb2RlGhIKEEdldEF1dGhDaGFsbGVuZ2'
'UaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIaCghyZXNwb25zZRgC'
'IAEoDFIIcmVzcG9uc2UarAEKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySW'
'QSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJzaW9uGAMgASgJSABS'
'CmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZUlkiAEBQg4KDF9hcH'
'BfdmVyc2lvbkIMCgpfZGV2aWNlX2lkQgsKCUhhbmRzaGFrZQ==');
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData$json = {

View file

@ -0,0 +1,192 @@
//
// Generated code. Do not modify.
// source: groups.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
/// Stored encrypted on the server in the members columns.
class EncryptedGroupState extends $pb.GeneratedMessage {
factory EncryptedGroupState({
$core.Iterable<$fixnum.Int64>? memberIds,
$core.Iterable<$fixnum.Int64>? adminIds,
$core.String? groupName,
$fixnum.Int64? deleteMessagesAfterMilliseconds,
$core.List<$core.int>? padding,
}) {
final $result = create();
if (memberIds != null) {
$result.memberIds.addAll(memberIds);
}
if (adminIds != null) {
$result.adminIds.addAll(adminIds);
}
if (groupName != null) {
$result.groupName = groupName;
}
if (deleteMessagesAfterMilliseconds != null) {
$result.deleteMessagesAfterMilliseconds = deleteMessagesAfterMilliseconds;
}
if (padding != null) {
$result.padding = padding;
}
return $result;
}
EncryptedGroupState._() : super();
factory EncryptedGroupState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedGroupState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedGroupState', createEmptyInstance: create)
..p<$fixnum.Int64>(1, _omitFieldNames ? '' : 'memberIds', $pb.PbFieldType.K6, protoName: 'memberIds')
..p<$fixnum.Int64>(2, _omitFieldNames ? '' : 'adminIds', $pb.PbFieldType.K6, protoName: 'adminIds')
..aOS(3, _omitFieldNames ? '' : 'groupName', protoName: 'groupName')
..aInt64(4, _omitFieldNames ? '' : 'deleteMessagesAfterMilliseconds', protoName: 'deleteMessagesAfterMilliseconds')
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'padding', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedGroupState clone() => EncryptedGroupState()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedGroupState copyWith(void Function(EncryptedGroupState) updates) => super.copyWith((message) => updates(message as EncryptedGroupState)) as EncryptedGroupState;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedGroupState create() => EncryptedGroupState._();
EncryptedGroupState createEmptyInstance() => create();
static $pb.PbList<EncryptedGroupState> createRepeated() => $pb.PbList<EncryptedGroupState>();
@$core.pragma('dart2js:noInline')
static EncryptedGroupState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedGroupState>(create);
static EncryptedGroupState? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$fixnum.Int64> get memberIds => $_getList(0);
@$pb.TagNumber(2)
$core.List<$fixnum.Int64> get adminIds => $_getList(1);
@$pb.TagNumber(3)
$core.String get groupName => $_getSZ(2);
@$pb.TagNumber(3)
set groupName($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasGroupName() => $_has(2);
@$pb.TagNumber(3)
void clearGroupName() => clearField(3);
@$pb.TagNumber(4)
$fixnum.Int64 get deleteMessagesAfterMilliseconds => $_getI64(3);
@$pb.TagNumber(4)
set deleteMessagesAfterMilliseconds($fixnum.Int64 v) { $_setInt64(3, v); }
@$pb.TagNumber(4)
$core.bool hasDeleteMessagesAfterMilliseconds() => $_has(3);
@$pb.TagNumber(4)
void clearDeleteMessagesAfterMilliseconds() => clearField(4);
@$pb.TagNumber(5)
$core.List<$core.int> get padding => $_getN(4);
@$pb.TagNumber(5)
set padding($core.List<$core.int> v) { $_setBytes(4, v); }
@$pb.TagNumber(5)
$core.bool hasPadding() => $_has(4);
@$pb.TagNumber(5)
void clearPadding() => clearField(5);
}
class EncryptedGroupStateEnvelop extends $pb.GeneratedMessage {
factory EncryptedGroupStateEnvelop({
$core.List<$core.int>? nonce,
$core.List<$core.int>? encryptedGroupState,
$core.List<$core.int>? mac,
}) {
final $result = create();
if (nonce != null) {
$result.nonce = nonce;
}
if (encryptedGroupState != null) {
$result.encryptedGroupState = encryptedGroupState;
}
if (mac != null) {
$result.mac = mac;
}
return $result;
}
EncryptedGroupStateEnvelop._() : super();
factory EncryptedGroupStateEnvelop.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedGroupStateEnvelop.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedGroupStateEnvelop', createEmptyInstance: create)
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'encryptedGroupState', $pb.PbFieldType.OY, protoName: 'encryptedGroupState')
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'mac', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedGroupStateEnvelop clone() => EncryptedGroupStateEnvelop()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedGroupStateEnvelop copyWith(void Function(EncryptedGroupStateEnvelop) updates) => super.copyWith((message) => updates(message as EncryptedGroupStateEnvelop)) as EncryptedGroupStateEnvelop;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedGroupStateEnvelop create() => EncryptedGroupStateEnvelop._();
EncryptedGroupStateEnvelop createEmptyInstance() => create();
static $pb.PbList<EncryptedGroupStateEnvelop> createRepeated() => $pb.PbList<EncryptedGroupStateEnvelop>();
@$core.pragma('dart2js:noInline')
static EncryptedGroupStateEnvelop getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedGroupStateEnvelop>(create);
static EncryptedGroupStateEnvelop? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get nonce => $_getN(0);
@$pb.TagNumber(1)
set nonce($core.List<$core.int> v) { $_setBytes(0, v); }
@$pb.TagNumber(1)
$core.bool hasNonce() => $_has(0);
@$pb.TagNumber(1)
void clearNonce() => clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get encryptedGroupState => $_getN(1);
@$pb.TagNumber(2)
set encryptedGroupState($core.List<$core.int> v) { $_setBytes(1, v); }
@$pb.TagNumber(2)
$core.bool hasEncryptedGroupState() => $_has(1);
@$pb.TagNumber(2)
void clearEncryptedGroupState() => clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get mac => $_getN(2);
@$pb.TagNumber(3)
set mac($core.List<$core.int> v) { $_setBytes(2, v); }
@$pb.TagNumber(3)
$core.bool hasMac() => $_has(2);
@$pb.TagNumber(3)
void clearMac() => clearField(3);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: groups.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,54 @@
//
// Generated code. Do not modify.
// source: groups.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use encryptedGroupStateDescriptor instead')
const EncryptedGroupState$json = {
'1': 'EncryptedGroupState',
'2': [
{'1': 'memberIds', '3': 1, '4': 3, '5': 3, '10': 'memberIds'},
{'1': 'adminIds', '3': 2, '4': 3, '5': 3, '10': 'adminIds'},
{'1': 'groupName', '3': 3, '4': 1, '5': 9, '10': 'groupName'},
{'1': 'deleteMessagesAfterMilliseconds', '3': 4, '4': 1, '5': 3, '9': 0, '10': 'deleteMessagesAfterMilliseconds', '17': true},
{'1': 'padding', '3': 5, '4': 1, '5': 12, '10': 'padding'},
],
'8': [
{'1': '_deleteMessagesAfterMilliseconds'},
],
};
/// Descriptor for `EncryptedGroupState`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedGroupStateDescriptor = $convert.base64Decode(
'ChNFbmNyeXB0ZWRHcm91cFN0YXRlEhwKCW1lbWJlcklkcxgBIAMoA1IJbWVtYmVySWRzEhoKCG'
'FkbWluSWRzGAIgAygDUghhZG1pbklkcxIcCglncm91cE5hbWUYAyABKAlSCWdyb3VwTmFtZRJN'
'Ch9kZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGAQgASgDSABSH2RlbGV0ZU1lc3NhZ2'
'VzQWZ0ZXJNaWxsaXNlY29uZHOIAQESGAoHcGFkZGluZxgFIAEoDFIHcGFkZGluZ0IiCiBfZGVs'
'ZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcw==');
@$core.Deprecated('Use encryptedGroupStateEnvelopDescriptor instead')
const EncryptedGroupStateEnvelop$json = {
'1': 'EncryptedGroupStateEnvelop',
'2': [
{'1': 'nonce', '3': 1, '4': 1, '5': 12, '10': 'nonce'},
{'1': 'encryptedGroupState', '3': 2, '4': 1, '5': 12, '10': 'encryptedGroupState'},
{'1': 'mac', '3': 3, '4': 1, '5': 12, '10': 'mac'},
],
};
/// Descriptor for `EncryptedGroupStateEnvelop`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedGroupStateEnvelopDescriptor = $convert.base64Decode(
'ChpFbmNyeXB0ZWRHcm91cFN0YXRlRW52ZWxvcBIUCgVub25jZRgBIAEoDFIFbm9uY2USMAoTZW'
'5jcnlwdGVkR3JvdXBTdGF0ZRgCIAEoDFITZW5jcnlwdGVkR3JvdXBTdGF0ZRIQCgNtYWMYAyAB'
'KAxSA21hYw==');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: groups.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'groups.pb.dart';

View file

@ -214,6 +214,200 @@ class PlaintextContent extends $pb.GeneratedMessage {
PlaintextContent_DecryptionErrorMessage ensureDecryptionErrorMessage() => $_ensure(0);
}
class EncryptedContent_GroupCreate extends $pb.GeneratedMessage {
factory EncryptedContent_GroupCreate({
$core.List<$core.int>? stateKey,
$core.List<$core.int>? groupPublicKey,
}) {
final $result = create();
if (stateKey != null) {
$result.stateKey = stateKey;
}
if (groupPublicKey != null) {
$result.groupPublicKey = groupPublicKey;
}
return $result;
}
EncryptedContent_GroupCreate._() : super();
factory EncryptedContent_GroupCreate.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedContent_GroupCreate.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.GroupCreate', createEmptyInstance: create)
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'stateKey', $pb.PbFieldType.OY, protoName: 'stateKey')
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY, protoName: 'groupPublicKey')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedContent_GroupCreate clone() => EncryptedContent_GroupCreate()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedContent_GroupCreate copyWith(void Function(EncryptedContent_GroupCreate) updates) => super.copyWith((message) => updates(message as EncryptedContent_GroupCreate)) as EncryptedContent_GroupCreate;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupCreate create() => EncryptedContent_GroupCreate._();
EncryptedContent_GroupCreate createEmptyInstance() => create();
static $pb.PbList<EncryptedContent_GroupCreate> createRepeated() => $pb.PbList<EncryptedContent_GroupCreate>();
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupCreate getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedContent_GroupCreate>(create);
static EncryptedContent_GroupCreate? _defaultInstance;
/// key for the state stored on the server
@$pb.TagNumber(3)
$core.List<$core.int> get stateKey => $_getN(0);
@$pb.TagNumber(3)
set stateKey($core.List<$core.int> v) { $_setBytes(0, v); }
@$pb.TagNumber(3)
$core.bool hasStateKey() => $_has(0);
@$pb.TagNumber(3)
void clearStateKey() => clearField(3);
@$pb.TagNumber(4)
$core.List<$core.int> get groupPublicKey => $_getN(1);
@$pb.TagNumber(4)
set groupPublicKey($core.List<$core.int> v) { $_setBytes(1, v); }
@$pb.TagNumber(4)
$core.bool hasGroupPublicKey() => $_has(1);
@$pb.TagNumber(4)
void clearGroupPublicKey() => clearField(4);
}
class EncryptedContent_GroupJoin extends $pb.GeneratedMessage {
factory EncryptedContent_GroupJoin({
$core.List<$core.int>? groupPublicKey,
}) {
final $result = create();
if (groupPublicKey != null) {
$result.groupPublicKey = groupPublicKey;
}
return $result;
}
EncryptedContent_GroupJoin._() : super();
factory EncryptedContent_GroupJoin.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedContent_GroupJoin.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.GroupJoin', createEmptyInstance: create)
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY, protoName: 'groupPublicKey')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedContent_GroupJoin clone() => EncryptedContent_GroupJoin()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedContent_GroupJoin copyWith(void Function(EncryptedContent_GroupJoin) updates) => super.copyWith((message) => updates(message as EncryptedContent_GroupJoin)) as EncryptedContent_GroupJoin;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupJoin create() => EncryptedContent_GroupJoin._();
EncryptedContent_GroupJoin createEmptyInstance() => create();
static $pb.PbList<EncryptedContent_GroupJoin> createRepeated() => $pb.PbList<EncryptedContent_GroupJoin>();
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupJoin getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedContent_GroupJoin>(create);
static EncryptedContent_GroupJoin? _defaultInstance;
/// key for the state stored on the server
@$pb.TagNumber(4)
$core.List<$core.int> get groupPublicKey => $_getN(0);
@$pb.TagNumber(4)
set groupPublicKey($core.List<$core.int> v) { $_setBytes(0, v); }
@$pb.TagNumber(4)
$core.bool hasGroupPublicKey() => $_has(0);
@$pb.TagNumber(4)
void clearGroupPublicKey() => clearField(4);
}
class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage {
factory EncryptedContent_GroupUpdate({
$core.String? groupActionType,
$fixnum.Int64? affectedContactId,
$core.String? newGroupName,
}) {
final $result = create();
if (groupActionType != null) {
$result.groupActionType = groupActionType;
}
if (affectedContactId != null) {
$result.affectedContactId = affectedContactId;
}
if (newGroupName != null) {
$result.newGroupName = newGroupName;
}
return $result;
}
EncryptedContent_GroupUpdate._() : super();
factory EncryptedContent_GroupUpdate.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedContent_GroupUpdate.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.GroupUpdate', createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'groupActionType', protoName: 'groupActionType')
..aInt64(2, _omitFieldNames ? '' : 'affectedContactId', protoName: 'affectedContactId')
..aOS(3, _omitFieldNames ? '' : 'newGroupName', protoName: 'newGroupName')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedContent_GroupUpdate clone() => EncryptedContent_GroupUpdate()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedContent_GroupUpdate copyWith(void Function(EncryptedContent_GroupUpdate) updates) => super.copyWith((message) => updates(message as EncryptedContent_GroupUpdate)) as EncryptedContent_GroupUpdate;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupUpdate create() => EncryptedContent_GroupUpdate._();
EncryptedContent_GroupUpdate createEmptyInstance() => create();
static $pb.PbList<EncryptedContent_GroupUpdate> createRepeated() => $pb.PbList<EncryptedContent_GroupUpdate>();
@$core.pragma('dart2js:noInline')
static EncryptedContent_GroupUpdate getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedContent_GroupUpdate>(create);
static EncryptedContent_GroupUpdate? _defaultInstance;
@$pb.TagNumber(1)
$core.String get groupActionType => $_getSZ(0);
@$pb.TagNumber(1)
set groupActionType($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasGroupActionType() => $_has(0);
@$pb.TagNumber(1)
void clearGroupActionType() => clearField(1);
@$pb.TagNumber(2)
$fixnum.Int64 get affectedContactId => $_getI64(1);
@$pb.TagNumber(2)
set affectedContactId($fixnum.Int64 v) { $_setInt64(1, v); }
@$pb.TagNumber(2)
$core.bool hasAffectedContactId() => $_has(1);
@$pb.TagNumber(2)
void clearAffectedContactId() => clearField(2);
@$pb.TagNumber(3)
$core.String get newGroupName => $_getSZ(2);
@$pb.TagNumber(3)
set newGroupName($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasNewGroupName() => $_has(2);
@$pb.TagNumber(3)
void clearNewGroupName() => clearField(3);
}
class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
factory EncryptedContent_TextMessage({
$core.String? senderMessageId,
@ -1036,6 +1230,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_PushKeys? pushKeys,
EncryptedContent_Reaction? reaction,
EncryptedContent_TextMessage? textMessage,
EncryptedContent_GroupCreate? groupCreate,
EncryptedContent_GroupJoin? groupJoin,
EncryptedContent_GroupUpdate? groupUpdate,
}) {
final $result = create();
if (groupId != null) {
@ -1074,6 +1271,15 @@ class EncryptedContent extends $pb.GeneratedMessage {
if (textMessage != null) {
$result.textMessage = textMessage;
}
if (groupCreate != null) {
$result.groupCreate = groupCreate;
}
if (groupJoin != null) {
$result.groupJoin = groupJoin;
}
if (groupUpdate != null) {
$result.groupUpdate = groupUpdate;
}
return $result;
}
EncryptedContent._() : super();
@ -1093,6 +1299,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
..aOM<EncryptedContent_PushKeys>(11, _omitFieldNames ? '' : 'pushKeys', protoName: 'pushKeys', subBuilder: EncryptedContent_PushKeys.create)
..aOM<EncryptedContent_Reaction>(12, _omitFieldNames ? '' : 'reaction', subBuilder: EncryptedContent_Reaction.create)
..aOM<EncryptedContent_TextMessage>(13, _omitFieldNames ? '' : 'textMessage', protoName: 'textMessage', subBuilder: EncryptedContent_TextMessage.create)
..aOM<EncryptedContent_GroupCreate>(14, _omitFieldNames ? '' : 'groupCreate', protoName: 'groupCreate', subBuilder: EncryptedContent_GroupCreate.create)
..aOM<EncryptedContent_GroupJoin>(15, _omitFieldNames ? '' : 'groupJoin', protoName: 'groupJoin', subBuilder: EncryptedContent_GroupJoin.create)
..aOM<EncryptedContent_GroupUpdate>(16, _omitFieldNames ? '' : 'groupUpdate', protoName: 'groupUpdate', subBuilder: EncryptedContent_GroupUpdate.create)
..hasRequiredFields = false
;
@ -1243,6 +1452,39 @@ class EncryptedContent extends $pb.GeneratedMessage {
void clearTextMessage() => clearField(13);
@$pb.TagNumber(13)
EncryptedContent_TextMessage ensureTextMessage() => $_ensure(11);
@$pb.TagNumber(14)
EncryptedContent_GroupCreate get groupCreate => $_getN(12);
@$pb.TagNumber(14)
set groupCreate(EncryptedContent_GroupCreate v) { setField(14, v); }
@$pb.TagNumber(14)
$core.bool hasGroupCreate() => $_has(12);
@$pb.TagNumber(14)
void clearGroupCreate() => clearField(14);
@$pb.TagNumber(14)
EncryptedContent_GroupCreate ensureGroupCreate() => $_ensure(12);
@$pb.TagNumber(15)
EncryptedContent_GroupJoin get groupJoin => $_getN(13);
@$pb.TagNumber(15)
set groupJoin(EncryptedContent_GroupJoin v) { setField(15, v); }
@$pb.TagNumber(15)
$core.bool hasGroupJoin() => $_has(13);
@$pb.TagNumber(15)
void clearGroupJoin() => clearField(15);
@$pb.TagNumber(15)
EncryptedContent_GroupJoin ensureGroupJoin() => $_ensure(13);
@$pb.TagNumber(16)
EncryptedContent_GroupUpdate get groupUpdate => $_getN(14);
@$pb.TagNumber(16)
set groupUpdate(EncryptedContent_GroupUpdate v) { setField(16, v); }
@$pb.TagNumber(16)
$core.bool hasGroupUpdate() => $_has(14);
@$pb.TagNumber(16)
void clearGroupUpdate() => clearField(16);
@$pb.TagNumber(16)
EncryptedContent_GroupUpdate ensureGroupUpdate() => $_ensure(14);
}

View file

@ -106,8 +106,11 @@ const EncryptedContent$json = {
{'1': 'pushKeys', '3': 11, '4': 1, '5': 11, '6': '.EncryptedContent.PushKeys', '9': 9, '10': 'pushKeys', '17': true},
{'1': 'reaction', '3': 12, '4': 1, '5': 11, '6': '.EncryptedContent.Reaction', '9': 10, '10': 'reaction', '17': true},
{'1': 'textMessage', '3': 13, '4': 1, '5': 11, '6': '.EncryptedContent.TextMessage', '9': 11, '10': 'textMessage', '17': true},
{'1': 'groupCreate', '3': 14, '4': 1, '5': 11, '6': '.EncryptedContent.GroupCreate', '9': 12, '10': 'groupCreate', '17': true},
{'1': 'groupJoin', '3': 15, '4': 1, '5': 11, '6': '.EncryptedContent.GroupJoin', '9': 13, '10': 'groupJoin', '17': true},
{'1': 'groupUpdate', '3': 16, '4': 1, '5': 11, '6': '.EncryptedContent.GroupUpdate', '9': 14, '10': 'groupUpdate', '17': true},
],
'3': [EncryptedContent_TextMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, EncryptedContent_PushKeys$json, EncryptedContent_FlameSync$json],
'3': [EncryptedContent_GroupCreate$json, EncryptedContent_GroupJoin$json, EncryptedContent_GroupUpdate$json, EncryptedContent_TextMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, EncryptedContent_PushKeys$json, EncryptedContent_FlameSync$json],
'8': [
{'1': '_groupId'},
{'1': '_isDirectChat'},
@ -121,6 +124,40 @@ const EncryptedContent$json = {
{'1': '_pushKeys'},
{'1': '_reaction'},
{'1': '_textMessage'},
{'1': '_groupCreate'},
{'1': '_groupJoin'},
{'1': '_groupUpdate'},
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_GroupCreate$json = {
'1': 'GroupCreate',
'2': [
{'1': 'stateKey', '3': 3, '4': 1, '5': 12, '10': 'stateKey'},
{'1': 'groupPublicKey', '3': 4, '4': 1, '5': 12, '10': 'groupPublicKey'},
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_GroupJoin$json = {
'1': 'GroupJoin',
'2': [
{'1': 'groupPublicKey', '3': 4, '4': 1, '5': 12, '10': 'groupPublicKey'},
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_GroupUpdate$json = {
'1': 'GroupUpdate',
'2': [
{'1': 'groupActionType', '3': 1, '4': 1, '5': 9, '10': 'groupActionType'},
{'1': 'affectedContactId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'affectedContactId', '17': true},
{'1': 'newGroupName', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'newGroupName', '17': true},
],
'8': [
{'1': '_affectedContactId'},
{'1': '_newGroupName'},
],
};
@ -326,47 +363,57 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'bWVTeW5jiAEBEjsKCHB1c2hLZXlzGAsgASgLMhouRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5c0'
'gJUghwdXNoS2V5c4gBARI7CghyZWFjdGlvbhgMIAEoCzIaLkVuY3J5cHRlZENvbnRlbnQuUmVh'
'Y3Rpb25IClIIcmVhY3Rpb26IAQESRAoLdGV4dE1lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb2'
'50ZW50LlRleHRNZXNzYWdlSAtSC3RleHRNZXNzYWdliAEBGqkBCgtUZXh0TWVzc2FnZRIoCg9z'
'ZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZX'
'h0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJ'
'SABSDnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBpiCghSZWFjdGlvbhIoCg'
'90YXJnZXRNZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIF'
'ZW1vamkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZR'
'gBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3Nl'
'bmRlck1lc3NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYX'
'JnZXRNZXNzYWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgE'
'IAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCg'
'oGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJ'
'ZEIHCgVfdGV4dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZX'
'NzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlw'
'ZRJDChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk'
'1pbGxpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJl'
'c0F1dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTW'
'Vzc2FnZUlkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByAB'
'KAxIAlINZG93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cH'
'Rpb25LZXmIAQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0K'
'D2VuY3J5cHRpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCg'
'hSRVVQTE9BRBAAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxp'
'bWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQh'
'AKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2Ua'
'pwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVX'
'BkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdl'
'SWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1'
'IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5D'
'b250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVB'
'ABEgoKBkFDQ0VQVBACGvABCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0'
'ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI1ChNhdmF0YXJTdmdDb21wcmVzc2'
'VkGAIgASgMSABSE2F2YXRhclN2Z0NvbXByZXNzZWSIAQESJQoLZGlzcGxheU5hbWUYAyABKAlI'
'AVILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCFgoUX2'
'F2YXRhclN2Z0NvbXByZXNzZWRCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBl'
'GAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGA'
'IgASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQg'
'ASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICg'
'Zfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GocBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3Vu'
'dGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1'
'IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5k'
'QgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdENoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3VudGVyQh'
'AKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRpYUIOCgxfbWVkaWFVcGRhdGVCEAoOX2NvbnRhY3RV'
'cGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0QgwKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZXlzQgsKCV'
'9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2U=');
'50ZW50LlRleHRNZXNzYWdlSAtSC3RleHRNZXNzYWdliAEBEkQKC2dyb3VwQ3JlYXRlGA4gASgL'
'Mh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cENyZWF0ZUgMUgtncm91cENyZWF0ZYgBARI+Cglncm'
'91cEpvaW4YDyABKAsyGy5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgNUglncm91cEpvaW6I'
'AQESRAoLZ3JvdXBVcGRhdGUYECABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA'
'5SC2dyb3VwVXBkYXRliAEBGlEKC0dyb3VwQ3JlYXRlEhoKCHN0YXRlS2V5GAMgASgMUghzdGF0'
'ZUtleRImCg5ncm91cFB1YmxpY0tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkaMwoJR3JvdXBKb2'
'luEiYKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRq6AQoLR3JvdXBVcGRh'
'dGUSKAoPZ3JvdXBBY3Rpb25UeXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMQoRYWZmZWN0ZW'
'RDb250YWN0SWQYAiABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESJwoMbmV3R3JvdXBOYW1l'
'GAMgASgJSAFSDG5ld0dyb3VwTmFtZYgBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0'
'dyb3VwTmFtZRqpAQoLVGV4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5k'
'ZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbW'
'VzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9f'
'cXVvdGVNZXNzYWdlSWQaYgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YX'
'JnZXRNZXNzYWdlSWQSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVt'
'b3ZlGrcCCg1NZXNzYWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk'
'1lc3NhZ2VVcGRhdGUuVHlwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2Vu'
'ZGVyTWVzc2FnZUlkiAEBEjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdG'
'lwbGVUYXJnZXRNZXNzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3Rh'
'bXAYBSABKANSCXRpbWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEg'
'oKBk9QRU5FRBACQhIKEF9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQajAUKBU1lZGlhEigKD3Nl'
'bmRlck1lc3NhZ2VJZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5Fbm'
'NyeXB0ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNl'
'Y29uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZX'
'NBdXRoZW50aWNhdGlvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3Rh'
'bXAYBSABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3'
'NhZ2VJZIgBARIpCg1kb3dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoN'
'ZW5jcnlwdGlvbktleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYW'
'MYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIP'
'ZW5jcnlwdGlvbk5vbmNliAEBIjMKBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBV'
'ZJREVPEAISBwoDR0lGEANCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD19xdW90'
'ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5fZW5jcn'
'lwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlGqcBCgtNZWRpYVVwZGF0ZRI2CgR0eXBlGAEg'
'ASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigKD3RhcmdldE'
'1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUEVORUQQABIK'
'CgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVlc3QSOQoEdH'
'lwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZVIEdHlwZSIr'
'CgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhrwAQoNQ29udGFjdF'
'VwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYXRlLlR5'
'cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJTdmdDb21wcm'
'Vzc2VkiAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUS'
'CwoHUkVRVUVTVBAAEgoKBlVQREFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQg4KDF9kaX'
'NwbGF5TmFtZRrVAQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQu'
'UHVzaEtleXMuVHlwZVIEdHlwZRIZCgVrZXlJZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAy'
'ABKAxIAVIDa2V5iAEBEiEKCWNyZWF0ZWRBdBgEIAEoA0gCUgljcmVhdGVkQXSIAQEiHwoEVHlw'
'ZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoGX2tleUlkQgYKBF9rZXlCDAoKX2NyZWF0ZW'
'RBdBqHAQoJRmxhbWVTeW5jEiIKDGZsYW1lQ291bnRlchgBIAEoA1IMZmxhbWVDb3VudGVyEjYK'
'Fmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2USHg'
'oKYmVzdEZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZEIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RD'
'aGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaW'
'FCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIM'
'CgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg'
'4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZQ==');

View file

@ -1,10 +1,16 @@
syntax = "proto3";
// Stored encrypted on the server in the members columns.
message GroupState {
message EncryptedGroupState {
repeated int64 memberIds = 1;
repeated int64 adminIds = 2;
string groupName = 3;
optional int64 deleteMessagesAfterMilliseconds = 4;
bytes _padding = 5;
bytes padding = 5;
}
message EncryptedGroupStateEnvelop {
bytes nonce = 1;
bytes encryptedGroupState = 2;
bytes mac = 3;
}

View file

@ -44,31 +44,26 @@ message EncryptedContent {
optional PushKeys pushKeys = 11;
optional Reaction reaction = 12;
optional TextMessage textMessage = 13;
optional NewGroup newGroup = 14;
optional JoinGroup joinGroup = 15;
optional GroupCreate groupCreate = 14;
optional GroupJoin groupJoin = 15;
optional GroupUpdate groupUpdate = 16;
message NewGroup {
message GroupCreate {
// key for the state stored on the server
string groupId = 1;
string stateId = 2;
bytes stateKey = 3;
bytes groupPublicKey = 4;
}
message JoinGroup {
message GroupJoin {
// key for the state stored on the server
string groupId = 1;
bytes groupPublicKey = 4;
}
message GroupUpdate {
optional int64 removedAdmin = 1;
optional int64 removedUser = 2;
optional int64 groupName = 3;
optional int64 deleteMessagesAfterMilliseconds = 4;
bytes stateKey = 5;
string groupActionType = 1; // GroupActionType.name
optional int64 affectedContactId = 2;
optional string newGroupName = 3;
}
message TextMessage {

View file

@ -5,6 +5,7 @@ import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:drift/drift.dart';
@ -283,6 +284,10 @@ class ApiService {
request.v0.seq = seq;
final requestBytes = request.writeToBuffer();
Log.info(
'Sending ${requestBytes.length} bytes to the server via WebSocket.',
);
if (ensureRetransmission) {
await addToRetransmissionBuffer(seq, requestBytes);
}
@ -467,6 +472,7 @@ class ApiService {
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
..signedPrekeySignature = signedPreKey.signature
..signedPrekeyId = Int64(signedPreKey.id)
..langCode = ui.PlatformDispatcher.instance.locale.languageCode
..isIos = Platform.isIOS;
if (inviteCode != null && inviteCode != '') {

View file

@ -0,0 +1,78 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/group.services.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> handleGroupCreate(
int fromUserId,
String groupId,
EncryptedContent_GroupCreate newGroup,
) async {
// 1. Store the new group -> e.g. store the stateKey and groupPublicKey
// 2. Call function that should fetch all jobs
// 1. This function is also called in the main function, in case the state stored on the server could not be loaded
// 2. This function will also send the GroupJoin to all members -> so they get there public key
// 3. Finished
final myGroupKey = generateIdentityKeyPair();
// Group state is joinedGroup -> As the current state has not yet been downloaded.
final group = await twonlyDB.groupsDao.createNewGroup(
GroupsCompanion(
groupId: Value(groupId),
stateVersionId: const Value(1),
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
),
);
if (group == null) {
Log.error(
'Could not create new group. Probably because the group already existed.',
);
return;
}
final user = await twonlyDB.contactsDao
.getContactByUserId(fromUserId)
.getSingleOrNull();
if (user == null) {
// Only contacts can invite other contacts, so this can (via the UI) not happen.
Log.error(
'User is not a contact. Aborting.',
);
return;
}
await twonlyDB.groupsDao.insertGroupMember(
GroupMembersCompanion(
groupId: Value(groupId),
contactId: Value(fromUserId),
memberState: const Value(
MemberState.admin, // is the group creator, so must be admin...
),
groupPublicKey: Value(Uint8List.fromList(newGroup.groupPublicKey)),
),
);
// can be done in the background -> websocket message can be ACK
unawaited(fetchGroupStatesForUnjoinedGroups());
}
Future<void> handleGroupUpdate(
int fromUserId,
String groupId,
EncryptedContent_GroupUpdate update,
) async {}
Future<void> handleGroupJoin(
int fromUserId,
String groupId,
EncryptedContent_GroupJoin join,
) async {}

View file

@ -11,14 +11,15 @@ import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
as server;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/client2client/groups.c2c.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/api/server_messages/contact.server_messages.dart';
import 'package:twonly/src/services/api/server_messages/media.server_messages.dart';
import 'package:twonly/src/services/api/server_messages/messages.server_messages.dart';
import 'package:twonly/src/services/api/server_messages/prekeys.server_messages.dart';
import 'package:twonly/src/services/api/server_messages/pushkeys.server_messages.dart';
import 'package:twonly/src/services/api/server_messages/reaction.server_message.dart';
import 'package:twonly/src/services/api/server_messages/text_message.server_messages.dart';
import 'package:twonly/src/services/api/client2client/contact.c2c.dart';
import 'package:twonly/src/services/api/client2client/media.c2c.dart';
import 'package:twonly/src/services/api/client2client/messages.c2c.dart';
import 'package:twonly/src/services/api/client2client/prekeys.c2c.dart';
import 'package:twonly/src/services/api/client2client/pushkeys.c2c.dart';
import 'package:twonly/src/services/api/client2client/reaction.c2c.dart';
import 'package:twonly/src/services/api/client2client/text_message.c2c.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -36,7 +37,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
} else if (msg.v0.hasNewMessage()) {
final body = Uint8List.fromList(msg.v0.newMessage.body);
final fromUserId = msg.v0.newMessage.fromUserId.toInt();
await handleNewMessage(fromUserId, body);
await handleClient2ClientMessage(fromUserId, body);
} else {
Log.error('Unknown server message: $msg');
}
@ -55,7 +56,7 @@ DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
Mutex protectReceiptCheck = Mutex();
Future<void> handleNewMessage(int fromUserId, Uint8List body) async {
Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
final message = Message.fromBuffer(body);
final receiptId = message.receiptId;
@ -205,6 +206,33 @@ Future<PlaintextContent?> handleEncryptedMessage(
return null;
}
if (content.hasGroupUpdate()) {
await handleGroupUpdate(
fromUserId,
content.groupId,
content.groupUpdate,
);
return null;
}
if (content.hasGroupCreate()) {
await handleGroupCreate(
fromUserId,
content.groupId,
content.groupCreate,
);
return null;
}
if (content.hasGroupJoin()) {
await handleGroupJoin(
fromUserId,
content.groupId,
content.groupJoin,
);
return null;
}
/// Verify that the user is (still) in that group...
if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) {
if (getUUIDforDirectChat(gUser.userId, fromUserId) == content.groupId) {

View file

@ -0,0 +1,179 @@
import 'dart:math';
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:drift/drift.dart' show Value;
import 'package:fixnum/fixnum.dart';
import 'package:hashlib/random.dart';
import 'package:http/http.dart' as http;
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/groups.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
String getGroupStateUrl() {
return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/state';
}
Future<bool> createNewGroup(String groupName, List<Contact> members) async {
// First: Upload new State to the server.....
// if (groupName) return;
final groupId = uuid.v4();
final memberIds = members.map((x) => Int64(x.userId)).toList();
final groupState = EncryptedGroupState(
memberIds: memberIds,
adminIds: [Int64(gUser.userId)],
groupName: groupName,
deleteMessagesAfterMilliseconds:
Int64(defaultDeleteMessagesAfterMilliseconds),
padding: List<int>.generate(Random().nextInt(80), (_) => 0),
);
final stateEncryptionKey = getRandomUint8List(32);
final chacha20 = FlutterChacha20.poly1305Aead();
final encryptionNonce = chacha20.newNonce();
final secretBox = await chacha20.encrypt(
groupState.writeToBuffer(),
secretKey: SecretKey(stateEncryptionKey),
nonce: encryptionNonce,
);
final encryptedGroupState = EncryptedGroupStateEnvelop(
nonce: encryptionNonce,
encryptedGroupState: secretBox.cipherText,
mac: secretBox.mac.bytes,
);
final myGroupKey = generateIdentityKeyPair();
{
// Upload the group state, if this fails, the group can not be created.
final newGroupState = NewGroupState(
groupId: groupId,
versionId: Int64(1),
encryptedGroupState: encryptedGroupState.writeToBuffer(),
publicKey: myGroupKey.getPublicKey().serialize(),
);
final response = await http
.post(
Uri.parse(getGroupStateUrl()),
body: newGroupState.writeToBuffer(),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode != 200) {
Log.error(
'Could not upload group state. Got status code ${response.statusCode} from server.',
);
return false;
}
}
final group = await twonlyDB.groupsDao.createNewGroup(
GroupsCompanion(
groupId: Value(groupId),
groupName: Value(groupName),
isGroupAdmin: const Value(true),
stateEncryptionKey: Value(stateEncryptionKey),
stateVersionId: const Value(1),
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
),
);
if (group == null) {
Log.error('Could not insert group into database.');
return false;
}
Log.info('Created new group: ${group.groupId}');
for (final member in members) {
await twonlyDB.groupsDao.insertGroupMember(
GroupMembersCompanion(
groupId: Value(group.groupId),
contactId: Value(member.userId),
memberState: const Value(MemberState.normal),
),
);
}
await twonlyDB.groupsDao.insertGroupAction(
GroupHistoriesCompanion(
groupId: Value(groupId),
type: const Value(GroupActionType.createdGroup),
),
);
// Notify members about the new group :)
await sendCipherTextToGroup(
group.groupId,
EncryptedContent(
groupCreate: EncryptedContent_GroupCreate(
stateKey: stateEncryptionKey,
groupPublicKey: myGroupKey.getPublicKey().serialize(),
),
),
null,
);
return true;
}
Future<void> fetchGroupStatesForUnjoinedGroups() async {
final groups = await twonlyDB.groupsDao.getAllNotJoinedGroups();
for (final group in groups) {}
}
Future<GroupState?> fetchGroupState(Group group) async {
try {
final response = await http
.get(
Uri.parse('${getGroupStateUrl()}/${group.groupId}'),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode != 200) {
Log.error(
'Could not load group state. Got status code ${response.statusCode} from server.',
);
return null;
}
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
final envelope = EncryptedGroupStateEnvelop.fromBuffer(
groupStateServer.encryptedGroupState);
final chacha20 = FlutterChacha20.poly1305Aead();
final secretBox = SecretBox(envelope.encryptedGroupState,
nonce: envelope.nonce, mac: Mac(envelope.mac));
final encryptedGroupStateRaw = await chacha20.decrypt(secretBox,
secretKey: SecretKey(group.stateEncryptionKey!));
final encryptedGroupState =
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
encryptedGroupState.adminIds;
encryptedGroupState.memberIds;
encryptedGroupState.groupName;
encryptedGroupState.deleteMessagesAfterMilliseconds;
encryptedGroupState.deleteMessagesAfterMilliseconds;
groupStateServer.versionId;
} catch (e) {
Log.error(e);
return null;
}
}

View file

@ -277,9 +277,8 @@ class ContactsListView extends StatelessWidget {
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = contacts[index];
final displayName = getContactDisplayName(contact);
return ListTile(
title: Text(displayName),
title: Text(substringBy(contact.username, 25)),
leading: AvatarIcon(contact: contact),
trailing: Row(
mainAxisSize: MainAxisSize.min,

View file

@ -11,6 +11,7 @@ import 'package:twonly/src/views/chats/chat_messages.view.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/user_context_menu.component.dart';
import 'package:twonly/src/views/groups/group_create_select_members.view.dart';
class StartNewChatView extends StatefulWidget {
const StartNewChatView({super.key});
@ -116,11 +117,10 @@ class UserList extends StatelessWidget {
Widget build(BuildContext context) {
return ListView.builder(
restorationId: 'new_message_users_list',
itemCount: users.length + 2,
itemCount: users.length + 3,
itemBuilder: (BuildContext context, int i) {
if (i == 0) {
if (i == 1) {
return ListTile(
key: const Key('add_new_contact'),
title: Text(context.lang.startNewChatNewContact),
leading: const CircleAvatar(
child: FaIcon(
@ -138,10 +138,29 @@ class UserList extends StatelessWidget {
},
);
}
if (i == 1) {
if (i == 0) {
return ListTile(
title: Text(context.lang.newGroup),
leading: const CircleAvatar(
child: FaIcon(
FontAwesomeIcons.userGroup,
size: 13,
),
),
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const GroupCreateSelectMembersView(),
),
);
},
);
}
if (i == 2) {
return const Divider();
}
final user = users[i - 2];
final user = users[i - 3];
return UserContextMenu(
key: Key(user.userId.toString()),
contact: user,

View file

@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/group.services.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/user_context_menu.component.dart';
class GroupCreateSelectGroupNameView extends StatefulWidget {
const GroupCreateSelectGroupNameView({
required this.selectedUsers,
super.key,
});
final List<Contact> selectedUsers;
@override
State<GroupCreateSelectGroupNameView> createState() =>
_GroupCreateSelectGroupNameViewState();
}
class _GroupCreateSelectGroupNameViewState
extends State<GroupCreateSelectGroupNameView> {
final TextEditingController textFieldGroupName = TextEditingController();
bool _isLoading = false;
Future<void> _createNewGroup() async {
setState(() {
_isLoading = true;
});
final wasSuccess =
await createNewGroup(textFieldGroupName.text, widget.selectedUsers);
if (wasSuccess) {
// POP
}
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(context.lang.selectGroupName),
),
floatingActionButton: FilledButton.icon(
onPressed: (textFieldGroupName.text.isEmpty || _isLoading)
? null
: _createNewGroup,
label: Text(context.lang.createGroup),
icon: _isLoading
? const SizedBox(
width: 15,
height: 15,
child: CircularProgressIndicator(
strokeWidth: 1,
),
)
: const FaIcon(FontAwesomeIcons.penToSquare),
),
body: SafeArea(
child: Padding(
padding:
const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField(
onChanged: (_) async {
setState(() {});
},
autofocus: true,
controller: textFieldGroupName,
decoration: getInputDecoration(
context,
context.lang.groupNameInput,
),
),
),
const SizedBox(height: 10),
ListTile(
title: Text(context.lang.groupMembers),
),
Expanded(
child: ListView.builder(
restorationId: 'new_message_users_list',
itemCount: widget.selectedUsers.length,
itemBuilder: (BuildContext context, int i) {
final user = widget.selectedUsers[i];
return UserContextMenu(
key: GlobalKey(),
contact: user,
child: ListTile(
title: Row(
children: [
Text(getContactDisplayName(user)),
FlameCounterWidget(
contactId: user.userId,
prefix: true,
),
],
),
leading: AvatarIcon(
contact: user,
fontSize: 13,
),
),
);
},
),
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,250 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/user_context_menu.component.dart';
import 'package:twonly/src/views/groups/group_create_select_group_name.view.dart';
class GroupCreateSelectMembersView extends StatefulWidget {
const GroupCreateSelectMembersView({super.key});
@override
State<GroupCreateSelectMembersView> createState() => _StartNewChatView();
}
class _StartNewChatView extends State<GroupCreateSelectMembersView> {
List<Contact> contacts = [];
List<Contact> allContacts = [];
final TextEditingController searchUserName = TextEditingController();
late StreamSubscription<List<Contact>> contactSub;
final HashSet<int> selectedUsers = HashSet();
@override
void initState() {
super.initState();
final stream = twonlyDB.contactsDao.watchAllAcceptedContacts();
contactSub = stream.listen((update) async {
update.sort(
(a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)),
);
setState(() {
allContacts = update;
});
await filterUsers();
});
}
@override
void dispose() {
unawaited(contactSub.cancel());
super.dispose();
}
Future<void> filterUsers() async {
if (searchUserName.value.text.isEmpty) {
setState(() {
contacts = allContacts;
});
return;
}
final usersFiltered = allContacts
.where(
(user) => getContactDisplayName(user)
.toLowerCase()
.contains(searchUserName.value.text.toLowerCase()),
)
.toList();
setState(() {
contacts = usersFiltered;
});
}
void toggleSelectedUser(int userId) {
if (!selectedUsers.contains(userId)) {
selectedUsers.add(userId);
} else {
selectedUsers.remove(userId);
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(context.lang.selectMembers),
),
floatingActionButton: FilledButton.icon(
onPressed: selectedUsers.isEmpty
? null
: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GroupCreateSelectGroupNameView(
selectedUsers: allContacts
.where((t) => selectedUsers.contains(t.userId))
.toList(),
),
),
);
},
label: Text(context.lang.next),
icon: const FaIcon(FontAwesomeIcons.penToSquare),
),
body: SafeArea(
child: Padding(
padding:
const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField(
onChanged: (_) async {
await filterUsers();
},
controller: searchUserName,
decoration: getInputDecoration(
context,
context.lang.shareImageSearchAllContacts,
),
),
),
const SizedBox(height: 10),
Expanded(
child: ListView.builder(
restorationId: 'new_message_users_list',
itemCount:
contacts.length + (selectedUsers.isEmpty ? 0 : 2),
itemBuilder: (BuildContext context, int i) {
if (selectedUsers.isNotEmpty) {
final selected = selectedUsers.toList();
if (i == 0) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 18),
constraints: const BoxConstraints(
maxHeight: 150,
),
child: SingleChildScrollView(
child: LayoutBuilder(
builder: (context, constraints) {
// Wrap will use the available width from constraints.maxWidth
return Wrap(
spacing: 8,
children: selected.map((w) {
return _Chip(
contact: allContacts
.firstWhere((t) => t.userId == w),
onTap: toggleSelectedUser,
);
}).toList(),
);
}),
),
);
}
if (i == 1) {
return const Divider();
}
i -= 2;
}
final user = contacts[i];
return UserContextMenu(
key: GlobalKey(),
contact: user,
child: ListTile(
title: Row(
children: [
Text(getContactDisplayName(user)),
FlameCounterWidget(
contactId: user.userId,
prefix: true,
),
],
),
leading: AvatarIcon(
contact: user,
fontSize: 13,
),
trailing: Checkbox(
value: selectedUsers.contains(user.userId),
side: WidgetStateBorderSide.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return const BorderSide(width: 0);
}
return BorderSide(
color: Theme.of(context).colorScheme.outline,
);
},
),
onChanged: (bool? value) {
toggleSelectedUser(user.userId);
},
),
onTap: () {
toggleSelectedUser(user.userId);
},
),
);
},
),
),
],
),
),
),
),
);
}
}
class _Chip extends StatelessWidget {
const _Chip({
required this.contact,
required this.onTap,
});
final Contact contact;
final void Function(int) onTap;
@override
Widget build(BuildContext context) {
return Chip(
key: GlobalKey(),
avatar: AvatarIcon(
contact: contact,
fontSize: 10,
),
label: GestureDetector(
onTap: () => onTap(contact.userId),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
getContactDisplayName(contact),
style: const TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 15),
const FaIcon(
FontAwesomeIcons.xmark,
color: Colors.grey,
size: 12,
)
],
),
),
);
}
}

View file

@ -13,20 +13,19 @@ CLIENT_DIR="./lib/src/model/protobuf/client/"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "backup.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "messages.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "groups.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto"
protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto"
exit
# Definitions for the Server API
SRC_DIR="../twonly-server/twonly/src/"
SRC_DIR="../twonly-server/twonly-api/src/"
DST_DIR="$(pwd)/lib/src/model/protobuf/"
mkdir $DST_DIR
mkdir $DST_DIR &>/dev/null
ORIGINAL_DIR=$(pwd)
@ -37,9 +36,7 @@ cd "$SRC_DIR" || {
for proto_file in "api/"**/*.proto; do
if [[ -f "$proto_file" ]]; then
# Run the protoc command
protoc --proto_path="." --dart_out="$DST_DIR" "$proto_file"
echo "Processed: $proto_file"
else
echo "No .proto files found in $SRC_DIR"
fi
@ -50,4 +47,4 @@ cd "$ORIGINAL_DIR" || {
exit 1
}
echo "Finished processing .proto files."
echo "Finished processing .proto files :)"