diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 7ff88b31a..e5147fb38 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -1497,6 +1497,25 @@ func (o *Community) TokenPermissionsByType(permissionType protobuf.CommunityToke return permissions } +func (o *Community) ChannelTokenPermissionsByType(channelID string, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission { + permissions := make([]*protobuf.CommunityTokenPermission, 0) + for _, tokenPermission := range o.TokenPermissions() { + if tokenPermission.Type == permissionType && includes(tokenPermission.ChatIds, channelID) { + permissions = append(permissions, tokenPermission) + } + } + return permissions +} + +func includes(channelIDs []string, channelID string) bool { + for _, id := range channelIDs { + if id == channelID { + return true + } + } + return false +} + func (o *Community) AddTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() diff --git a/protocol/communities/community_test.go b/protocol/communities/community_test.go index c6f4486e9..182689e81 100644 --- a/protocol/communities/community_test.go +++ b/protocol/communities/community_test.go @@ -803,6 +803,49 @@ func (s *CommunitySuite) TestChatIDs() { s.Require().Len(chatIDs, 1) } +func (s *CommunitySuite) TestChannelTokenPermissionsByType() { + org := s.buildCommunity(&s.identity.PublicKey) + + viewOnlyPermissions := []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: make([]*protobuf.TokenCriteria, 0), + ChatIds: []string{"some-chat-id"}, + }, + } + + viewAndPostPermissions := []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-other-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL, + TokenCriteria: make([]*protobuf.TokenCriteria, 0), + ChatIds: []string{"some-chat-id-2"}, + }, + } + + for _, viewOnlyPermission := range viewOnlyPermissions { + _, err := org.AddTokenPermission(viewOnlyPermission) + s.Require().NoError(err) + } + for _, viewAndPostPermission := range viewAndPostPermissions { + _, err := org.AddTokenPermission(viewAndPostPermission) + s.Require().NoError(err) + } + + result := org.ChannelTokenPermissionsByType("some-chat-id", protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) + s.Require().Len(result, 1) + s.Require().Equal(result[0].Id, viewOnlyPermissions[0].Id) + s.Require().Equal(result[0].TokenCriteria, viewOnlyPermissions[0].TokenCriteria) + s.Require().Equal(result[0].ChatIds, viewOnlyPermissions[0].ChatIds) + + result = org.ChannelTokenPermissionsByType("some-chat-id-2", protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) + s.Require().Len(result, 1) + s.Require().Equal(result[0].Id, viewAndPostPermissions[0].Id) + s.Require().Equal(result[0].TokenCriteria, viewAndPostPermissions[0].TokenCriteria) + s.Require().Equal(result[0].ChatIds, viewAndPostPermissions[0].ChatIds) +} + func (s *CommunitySuite) emptyCommunityDescription() *protobuf.CommunityDescription { return &protobuf.CommunityDescription{ Permissions: &protobuf.CommunityPermissions{}, diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 84fc8f604..3ac0be328 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -1550,12 +1550,7 @@ func (m *Manager) CheckPermissionToJoin(id []byte, addresses []gethcommon.Addres } accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs) - hasPermission, err := m.checkPermissionToJoin(permissionsToJoin, accountsAndChainIDs, false) - if err != nil { - return nil, err - } - - return hasPermission, nil + return m.checkPermissionToJoin(permissionsToJoin, accountsAndChainIDs, false) } func (m *Manager) AcceptRequestToJoin(request *requests.AcceptRequestToJoinCommunity) (*Community, error) { @@ -1882,13 +1877,15 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request return requestToJoin, nil } -type CheckPermissionToJoinResponse struct { - Satisfied bool `json:"satisfied"` - Permissions map[string]*CheckPermissionToJoinResult `json:"permissions"` - ValidCombinations []*AccountChainIDsCombination `json:"validCombinations"` +type CheckPermissionsResponse struct { + Satisfied bool `json:"satisfied"` + Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"` + ValidCombinations []*AccountChainIDsCombination `json:"validCombinations"` } -type CheckPermissionToJoinResult struct { +type CheckPermissionToJoinResponse = CheckPermissionsResponse + +type PermissionTokenCriteriaResult struct { Criteria []bool `json:"criteria"` } @@ -1936,14 +1933,18 @@ func calculateChainIDsSet(accountsAndChainIDs []*AccountChainIDsCombination, req return revealedAccountsChainIDs } -// checkPermissionToJoin will retrieve balances and check whether the user has +func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionToJoinResponse, error) { + return m.checkPermissions(permissions, accountsAndChainIDs, shortcircuit) +} + +// checkPermissions will retrieve balances and check whether the user has // permission to join the community, if shortcircuit is true, it will stop as soon // as we know the answer -func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionToJoinResponse, error) { +func (m *Manager) checkPermissions(permissions []*protobuf.CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) { - response := &CheckPermissionToJoinResponse{ + response := &CheckPermissionsResponse{ Satisfied: false, - Permissions: make(map[string]*CheckPermissionToJoinResult), + Permissions: make(map[string]*PermissionTokenCriteriaResult), ValidCombinations: make([]*AccountChainIDsCombination, 0), } @@ -2004,7 +2005,7 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe for _, tokenPermission := range permissions { permissionRequirementsMet := true - response.Permissions[tokenPermission.Id] = &CheckPermissionToJoinResult{} + response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{} // There can be multiple token requirements per permission. // If only one is not met, the entire permission is marked @@ -2252,6 +2253,87 @@ func (m *Manager) getOwnedENS(addresses []gethcommon.Address) ([]string, error) return ownedENS, nil } +func (m *Manager) CheckChannelPermissions(communityID types.HexBytes, chatID string, addresses []gethcommon.Address) (*CheckChannelPermissionsResponse, error) { + community, err := m.GetByID(communityID) + if err != nil { + return nil, err + } + + if chatID == "" { + return nil, errors.New(fmt.Sprintf("couldn't check channel permissions, invalid chat id: %s", chatID)) + } + + viewOnlyPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) + viewAndPostPermissions := community.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) + + allChainIDs, err := m.tokenManager.GetAllChainIDs() + if err != nil { + return nil, err + } + accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs) + + return m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountsAndChainIDs, false) +} + +type CheckChannelPermissionsResponse struct { + ViewOnlyPermissions *CheckChannelViewOnlyPermissionsResult `json:"viewOnlyPermissions"` + ViewAndPostPermissions *CheckChannelViewAndPostPermissionsResult `json:"viewAndPostPermissions"` +} + +type CheckChannelViewOnlyPermissionsResult struct { + Satisfied bool `json:"satisfied"` + Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"` +} + +type CheckChannelViewAndPostPermissionsResult struct { + Satisfied bool `json:"satisfied"` + Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"` +} + +func (m *Manager) checkChannelPermissions(viewOnlyPermissions []*protobuf.CommunityTokenPermission, viewAndPostPermissions []*protobuf.CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckChannelPermissionsResponse, error) { + + response := &CheckChannelPermissionsResponse{ + ViewOnlyPermissions: &CheckChannelViewOnlyPermissionsResult{ + Satisfied: false, + Permissions: make(map[string]*PermissionTokenCriteriaResult), + }, + ViewAndPostPermissions: &CheckChannelViewAndPostPermissionsResult{ + Satisfied: false, + Permissions: make(map[string]*PermissionTokenCriteriaResult), + }, + } + + viewOnlyPermissionsResponse, err := m.checkPermissions(viewOnlyPermissions, accountsAndChainIDs, shortcircuit) + if err != nil { + return nil, err + } + + viewAndPostPermissionsResponse, err := m.checkPermissions(viewAndPostPermissions, accountsAndChainIDs, shortcircuit) + if err != nil { + return nil, err + } + + hasViewOnlyPermissions := len(viewOnlyPermissions) > 0 + hasViewAndPostPermissions := len(viewAndPostPermissions) > 0 + + if (hasViewAndPostPermissions && !hasViewOnlyPermissions) || (hasViewOnlyPermissions && hasViewAndPostPermissions && viewAndPostPermissionsResponse.Satisfied) { + response.ViewOnlyPermissions.Satisfied = viewAndPostPermissionsResponse.Satisfied + } else { + response.ViewOnlyPermissions.Satisfied = viewOnlyPermissionsResponse.Satisfied + } + response.ViewOnlyPermissions.Permissions = viewOnlyPermissionsResponse.Permissions + + if (hasViewOnlyPermissions && !viewOnlyPermissionsResponse.Satisfied) || + (hasViewOnlyPermissions && !hasViewAndPostPermissions) { + response.ViewAndPostPermissions.Satisfied = false + } else { + response.ViewAndPostPermissions.Satisfied = viewAndPostPermissionsResponse.Satisfied + } + response.ViewAndPostPermissions.Permissions = viewAndPostPermissionsResponse.Permissions + + return response, nil +} + func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) { pkString := common.PubkeyToHex(&m.identity.PublicKey) diff --git a/protocol/communities/manager_test.go b/protocol/communities/manager_test.go index 97613d8e0..e94440596 100644 --- a/protocol/communities/manager_test.go +++ b/protocol/communities/manager_test.go @@ -110,7 +110,7 @@ func (m *testTokenManager) GetBalancesByChain(ctx context.Context, accounts, tok return m.response, nil } -func (s *ManagerSuite) TestRetrieveTokens() { +func (s *ManagerSuite) setupManagerForTokenPermissions() (*Manager, *testTokenManager) { db, err := appdatabase.InitializeDB(sqlite.InMemoryPath, "", sqlite.ReducedKDFIterationsNumber) s.NoError(err, "creating sqlite db instance") err = sqlite.Migrate(db) @@ -134,6 +134,12 @@ func (s *ManagerSuite) TestRetrieveTokens() { s.Require().NoError(err) s.Require().NoError(m.Start()) + return m, tm +} + +func (s *ManagerSuite) TestRetrieveTokens() { + m, tm := s.setupManagerForTokenPermissions() + var chainID uint64 = 5 contractAddresses := make(map[uint64]string) contractAddresses[chainID] = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" @@ -804,6 +810,226 @@ func (s *ManagerSuite) TestUnseedHistoryArchiveTorrent() { s.Require().Equal(ok, false) } +func (s *ManagerSuite) TestCheckChannelPermissions_NoPermissions() { + + m, tm := s.setupManagerForTokenPermissions() + + var chainID uint64 = 5 + contractAddresses := make(map[uint64]string) + contractAddresses[chainID] = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" + + accountChainIDsCombination := []*AccountChainIDsCombination{ + &AccountChainIDsCombination{ + Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), + ChainIDs: []uint64{chainID}, + }, + } + + var viewOnlyPermissions = make([]*protobuf.CommunityTokenPermission, 0) + var viewAndPostPermissions = make([]*protobuf.CommunityTokenPermission, 0) + + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) + resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + // Both viewOnly and viewAndPost permissions are expected to be satisfied + // because we call `checkChannelPermissions()` with no permissions to check + s.Require().True(resp.ViewOnlyPermissions.Satisfied) + s.Require().True(resp.ViewAndPostPermissions.Satisfied) +} + +func (s *ManagerSuite) TestCheckChannelPermissions_ViewOnlyPermissions() { + + m, tm := s.setupManagerForTokenPermissions() + + var chainID uint64 = 5 + contractAddresses := make(map[uint64]string) + contractAddresses[chainID] = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" + var decimals uint64 = 18 + + accountChainIDsCombination := []*AccountChainIDsCombination{ + &AccountChainIDsCombination{ + Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), + ChainIDs: []uint64{chainID}, + }, + } + + var tokenCriteria = []*protobuf.TokenCriteria{ + &protobuf.TokenCriteria{ + ContractAddresses: contractAddresses, + Symbol: "STT", + Type: protobuf.CommunityTokenType_ERC20, + Name: "Status Test Token", + Amount: "1.000000000000000000", + Decimals: decimals, + }, + } + + var viewOnlyPermissions = []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: tokenCriteria, + ChatIds: []string{"test-channel-id", "test-channel-id-2"}, + }, + } + + var viewAndPostPermissions = make([]*protobuf.CommunityTokenPermission, 0) + + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) + resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + s.Require().False(resp.ViewOnlyPermissions.Satisfied) + // if viewOnly permissions are not satisfied then viewAndPost + // permissions shouldn't be satisfied either + s.Require().False(resp.ViewAndPostPermissions.Satisfied) + + // Set response to exactly the right one + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) + resp, err = m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + s.Require().True(resp.ViewOnlyPermissions.Satisfied) + s.Require().False(resp.ViewAndPostPermissions.Satisfied) +} + +func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissions() { + + m, tm := s.setupManagerForTokenPermissions() + + var chainID uint64 = 5 + contractAddresses := make(map[uint64]string) + contractAddresses[chainID] = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" + var decimals uint64 = 18 + + accountChainIDsCombination := []*AccountChainIDsCombination{ + &AccountChainIDsCombination{ + Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), + ChainIDs: []uint64{chainID}, + }, + } + + var tokenCriteria = []*protobuf.TokenCriteria{ + &protobuf.TokenCriteria{ + ContractAddresses: contractAddresses, + Symbol: "STT", + Type: protobuf.CommunityTokenType_ERC20, + Name: "Status Test Token", + Amount: "1.000000000000000000", + Decimals: decimals, + }, + } + + var viewAndPostPermissions = []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: tokenCriteria, + ChatIds: []string{"test-channel-id", "test-channel-id-2"}, + }, + } + + var viewOnlyPermissions = make([]*protobuf.CommunityTokenPermission, 0) + + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), 0) + resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + s.Require().False(resp.ViewAndPostPermissions.Satisfied) + // viewOnly permissions are flagged as not satisfied because we have no viewOnly + // permissions on this channel and the viewAndPost permission is not satisfied either + s.Require().False(resp.ViewOnlyPermissions.Satisfied) + + // Set response to exactly the right one + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) + resp, err = m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + s.Require().True(resp.ViewAndPostPermissions.Satisfied) + // if viewAndPost is satisfied then viewOnly should be automatically satisfied + s.Require().True(resp.ViewOnlyPermissions.Satisfied) +} + +func (s *ManagerSuite) TestCheckChannelPermissions_ViewAndPostPermissionsCombination() { + + m, tm := s.setupManagerForTokenPermissions() + + var chainID uint64 = 5 + contractAddresses := make(map[uint64]string) + contractAddresses[chainID] = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" + var decimals uint64 = 18 + + accountChainIDsCombination := []*AccountChainIDsCombination{ + &AccountChainIDsCombination{ + Address: gethcommon.HexToAddress("0xD6b912e09E797D291E8D0eA3D3D17F8000e01c32"), + ChainIDs: []uint64{chainID}, + }, + } + + var viewOnlyTokenCriteria = []*protobuf.TokenCriteria{ + &protobuf.TokenCriteria{ + ContractAddresses: contractAddresses, + Symbol: "STT", + Type: protobuf.CommunityTokenType_ERC20, + Name: "Status Test Token", + Amount: "1.000000000000000000", + Decimals: decimals, + }, + } + + var viewOnlyPermissions = []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: viewOnlyTokenCriteria, + ChatIds: []string{"test-channel-id", "test-channel-id-2"}, + }, + } + + testContractAddresses := make(map[uint64]string) + testContractAddresses[chainID] = "0x123" + + // Set up token criteria that won't be satisfied + var viewAndPostTokenCriteria = []*protobuf.TokenCriteria{ + &protobuf.TokenCriteria{ + ContractAddresses: testContractAddresses, + Symbol: "TEST", + Type: protobuf.CommunityTokenType_ERC20, + Name: "TEST token", + Amount: "1.000000000000000000", + Decimals: decimals, + }, + } + + var viewAndPostPermissions = []*protobuf.CommunityTokenPermission{ + &protobuf.CommunityTokenPermission{ + Id: "some-id", + Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, + TokenCriteria: viewAndPostTokenCriteria, + ChatIds: []string{"test-channel-id", "test-channel-id-2"}, + }, + } + + // Set response for viewOnly permissions + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(contractAddresses[chainID]), int64(1*math.Pow(10, float64(decimals)))) + // Set resopnse for viewAndPost permissions + tm.setResponse(chainID, accountChainIDsCombination[0].Address, gethcommon.HexToAddress(testContractAddresses[chainID]), 0) + + resp, err := m.checkChannelPermissions(viewOnlyPermissions, viewAndPostPermissions, accountChainIDsCombination, false) + s.Require().NoError(err) + s.Require().NotNil(resp) + + // viewOnly permission should be satisfied, even though viewAndPost is not satisfied + s.Require().True(resp.ViewOnlyPermissions.Satisfied) + s.Require().False(resp.ViewAndPostPermissions.Satisfied) +} + func buildTorrentConfig() params.TorrentConfig { torrentConfig := params.TorrentConfig{ Enabled: true, diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index 78ae48d79..cdf0b54b7 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -3856,6 +3856,25 @@ func (m *Messenger) CheckPermissionsToJoinCommunity(request *requests.CheckPermi return m.communitiesManager.CheckPermissionToJoin(request.CommunityID, addresses) } +func (m *Messenger) CheckCommunityChannelPermissions(request *requests.CheckCommunityChannelPermissions) (*communities.CheckChannelPermissionsResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + accounts, err := m.settings.GetAccounts() + if err != nil { + return nil, err + } + + var addresses []gethcommon.Address + + for _, a := range accounts { + addresses = append(addresses, gethcommon.HexToAddress(a.Address.Hex())) + } + + return m.communitiesManager.CheckChannelPermissions(request.CommunityID, request.ChatID, addresses) +} + func chunkSlice[T comparable](slice []T, chunkSize int) [][]T { var chunks [][]T for i := 0; i < len(slice); i += chunkSize { diff --git a/protocol/requests/check_community_channel_permissions.go b/protocol/requests/check_community_channel_permissions.go new file mode 100644 index 000000000..58dde2d41 --- /dev/null +++ b/protocol/requests/check_community_channel_permissions.go @@ -0,0 +1,28 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ( + ErrCheckCommunityChannelPermissionsInvalidID = errors.New("check-community-channel-permissions: invalid id") + ErrCheckCommunityChannelPermissionsInvalidChatID = errors.New("check-community-channel-permissions: invalid chat id") +) + +type CheckCommunityChannelPermissions struct { + CommunityID types.HexBytes + ChatID string +} + +func (u *CheckCommunityChannelPermissions) Validate() error { + if len(u.CommunityID) == 0 { + return ErrCheckCommunityChannelPermissionsInvalidID + } + if len(u.ChatID) == 0 { + return ErrCheckCommunityChannelPermissionsInvalidChatID + } + + return nil +} diff --git a/services/ext/api.go b/services/ext/api.go index f187ff9f1..111b96194 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -1329,6 +1329,10 @@ func (api *PublicAPI) CheckPermissionsToJoinCommunity(request *requests.CheckPer return api.service.messenger.CheckPermissionsToJoinCommunity(request) } +func (api *PublicAPI) CheckCommunityChannelPermissions(request *requests.CheckCommunityChannelPermissions) (*communities.CheckChannelPermissionsResponse, error) { + return api.service.messenger.CheckCommunityChannelPermissions(request) +} + func (api *PublicAPI) Messenger() *protocol.Messenger { return api.service.messenger }