chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
447
internal/rpc/channels_invites.go
Normal file
447
internal/rpc/channels_invites.go
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/gotd/td/tgerr"
|
||||
"strings"
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
func (r *Router) onMessagesExportChatInvite(ctx context.Context, req *tg.MessagesExportChatInviteRequest) (tg.ExportedChatInviteClass, error) {
|
||||
if r.deps.Channels == nil {
|
||||
return nil, notImplementedErr()
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if peer.Type != domain.PeerTypeChannel || peer.ID == 0 {
|
||||
return nil, peerIDInvalidErr()
|
||||
}
|
||||
if req.UsageLimit < 0 || req.ExpireDate < 0 || len(req.Title) > domain.MaxChannelInviteTitleLength {
|
||||
return nil, limitInvalidErr()
|
||||
}
|
||||
res, err := r.deps.Channels.ExportInvite(ctx, userID, domain.ExportChannelInviteRequest{
|
||||
UserID: userID,
|
||||
ChannelID: peer.ID,
|
||||
Title: req.Title,
|
||||
RequestNeeded: req.RequestNeeded,
|
||||
ExpireDate: req.ExpireDate,
|
||||
UsageLimit: req.UsageLimit,
|
||||
LegacyRevokePermanent: req.LegacyRevokePermanent,
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
r.invalidateRPCProjectionForChannel(res.Channel.ID)
|
||||
return tgExportedChannelInvite(res.Invite), nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesCheckChatInvite(ctx context.Context, hash string) (tg.ChatInviteClass, error) {
|
||||
if r.deps.Channels == nil {
|
||||
return nil, notImplementedErr()
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
res, err := r.deps.Channels.CheckInvite(ctx, userID, hash, int(r.clock.Now().Unix()))
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
if res.Already {
|
||||
return &tg.ChatInviteAlready{Chat: tgChannelChat(userID, res.Channel, &res.Self)}, nil
|
||||
}
|
||||
return &tg.ChatInvite{
|
||||
Channel: true,
|
||||
Broadcast: res.Channel.Broadcast,
|
||||
Megagroup: res.Channel.Megagroup,
|
||||
Public: res.Channel.Username != "",
|
||||
RequestNeeded: res.Invite.RequestNeeded,
|
||||
Title: res.Channel.Title,
|
||||
About: res.Channel.About,
|
||||
Photo: &tg.PhotoEmpty{},
|
||||
ParticipantsCount: res.Channel.ParticipantsCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesImportChatInvite(ctx context.Context, hash string) (tg.MessagesChatInviteJoinResultClass, error) {
|
||||
if r.deps.Channels == nil {
|
||||
return nil, notImplementedErr()
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
res, err := r.deps.Channels.ImportInvite(ctx, userID, domain.ImportChannelInviteRequest{
|
||||
UserID: userID,
|
||||
Hash: hash,
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrInviteRequestSent) && res.Channel.ID != 0 {
|
||||
r.pushPendingJoinRequestsToAdmins(ctx, res.Channel)
|
||||
}
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
r.addOnlineChannelMemberships(res.Channel.ID, channelMemberUserIDs(res.Members)...)
|
||||
updates := r.channelOperationUpdates(ctx, userID, res)
|
||||
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
|
||||
return r.channelOperationUpdates(ctx, viewerUserID, res)
|
||||
})
|
||||
// Layer 227:messages.importChatInvite 返回 messages.ChatInviteJoinResult;
|
||||
// 正常加入即 chatInviteJoinResultOk 包裹本次操作的 updates。
|
||||
return &tg.MessagesChatInviteJoinResultOk{Updates: updates}, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesGetExportedChatInvites(ctx context.Context, req *tg.MessagesGetExportedChatInvitesRequest) (*tg.MessagesExportedChatInvites, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Limit < 0 || req.Limit > maxChatInviteListLimit || len(req.OffsetLink) > maxChatInviteLinkLength {
|
||||
return nil, limitInvalidErr()
|
||||
}
|
||||
adminID := userID
|
||||
if !inputUserIsEmpty(req.AdminID) {
|
||||
admins, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.AdminID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(admins) > 0 {
|
||||
adminID = admins[0]
|
||||
}
|
||||
}
|
||||
offsetHash := ""
|
||||
if req.OffsetLink != "" {
|
||||
offsetHash, err = channelInviteHashFromLink(req.OffsetLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
list, err := r.deps.Channels.ListExportedInvites(ctx, userID, domain.ChannelInviteListRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
AdminUserID: adminID,
|
||||
Revoked: req.Revoked,
|
||||
OffsetDate: req.OffsetDate,
|
||||
OffsetHash: offsetHash,
|
||||
Limit: req.Limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
userIDs := []int64{adminID}
|
||||
invites := make([]tg.ExportedChatInviteClass, 0, len(list.Invites))
|
||||
for _, invite := range list.Invites {
|
||||
invites = append(invites, tgExportedChannelInvite(invite))
|
||||
userIDs = append(userIDs, invite.AdminUserID)
|
||||
}
|
||||
return &tg.MessagesExportedChatInvites{
|
||||
Count: list.Count,
|
||||
Invites: invites,
|
||||
Users: r.tgUsersForIDs(ctx, userID, userIDs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesGetExportedChatInvite(ctx context.Context, req *tg.MessagesGetExportedChatInviteRequest) (tg.MessagesExportedChatInviteClass, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash, err := channelInviteHashFromLink(req.Link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invite, err := r.deps.Channels.GetExportedInvite(ctx, userID, domain.GetChannelInviteRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
Hash: hash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
return &tg.MessagesExportedChatInvite{
|
||||
Invite: tgExportedChannelInvite(invite),
|
||||
Users: r.tgUsersForIDs(ctx, userID, []int64{invite.AdminUserID}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesEditExportedChatInvite(ctx context.Context, req *tg.MessagesEditExportedChatInviteRequest) (tg.MessagesExportedChatInviteClass, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash, err := channelInviteHashFromLink(req.Link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.ExpireDate < 0 || req.UsageLimit < 0 || len(req.Title) > domain.MaxChannelInviteTitleLength {
|
||||
return nil, limitInvalidErr()
|
||||
}
|
||||
expireDate, hasExpireDate := req.GetExpireDate()
|
||||
usageLimit, hasUsageLimit := req.GetUsageLimit()
|
||||
requestNeeded, hasRequestNeeded := req.GetRequestNeeded()
|
||||
title, hasTitle := req.GetTitle()
|
||||
edited, err := r.deps.Channels.EditExportedInvite(ctx, userID, domain.EditChannelInviteRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
Hash: hash,
|
||||
Revoked: req.Revoked,
|
||||
HasExpireDate: hasExpireDate,
|
||||
ExpireDate: expireDate,
|
||||
HasUsageLimit: hasUsageLimit,
|
||||
UsageLimit: usageLimit,
|
||||
HasRequestNeeded: hasRequestNeeded,
|
||||
RequestNeeded: requestNeeded,
|
||||
HasTitle: hasTitle,
|
||||
Title: title,
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
r.invalidateRPCProjectionForChannel(view.Channel.ID)
|
||||
users := r.tgUsersForIDs(ctx, userID, []int64{edited.Invite.AdminUserID})
|
||||
if edited.NewInvite != nil {
|
||||
return &tg.MessagesExportedChatInviteReplaced{
|
||||
Invite: tgExportedChannelInvite(edited.Invite),
|
||||
NewInvite: tgExportedChannelInvite(*edited.NewInvite),
|
||||
Users: users,
|
||||
}, nil
|
||||
}
|
||||
return &tg.MessagesExportedChatInvite{Invite: tgExportedChannelInvite(edited.Invite), Users: users}, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesDeleteRevokedExportedChatInvites(ctx context.Context, req *tg.MessagesDeleteRevokedExportedChatInvitesRequest) (bool, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
adminID := userID
|
||||
if !inputUserIsEmpty(req.AdminID) {
|
||||
admins, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.AdminID})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(admins) > 0 {
|
||||
adminID = admins[0]
|
||||
}
|
||||
}
|
||||
if err := r.deps.Channels.DeleteRevokedExportedInvites(ctx, userID, domain.DeleteRevokedChannelInvitesRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
AdminUserID: adminID,
|
||||
Limit: domain.MaxChannelHideJoinRequests,
|
||||
}); err != nil {
|
||||
return false, channelInviteErr(err)
|
||||
}
|
||||
r.invalidateRPCProjectionForChannel(view.Channel.ID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesDeleteExportedChatInvite(ctx context.Context, req *tg.MessagesDeleteExportedChatInviteRequest) (bool, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hash, err := channelInviteHashFromLink(req.Link)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.deps.Channels.DeleteExportedInvite(ctx, userID, domain.DeleteChannelInviteRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
Hash: hash,
|
||||
}); err != nil {
|
||||
return false, channelInviteErr(err)
|
||||
}
|
||||
r.invalidateRPCProjectionForChannel(view.Channel.ID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *Router) onMessagesGetChatInviteImporters(ctx context.Context, req *tg.MessagesGetChatInviteImportersRequest) (*tg.MessagesChatInviteImporters, error) {
|
||||
userID, view, err := r.inviteManagementChannelView(ctx, req.Peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link, hasLink := req.GetLink()
|
||||
query, hasQuery := req.GetQ()
|
||||
if hasLink && hasQuery && strings.TrimSpace(link) != "" && strings.TrimSpace(query) != "" {
|
||||
return nil, tgerr400("SEARCH_WITH_LINK_NOT_SUPPORTED")
|
||||
}
|
||||
if req.Limit < 0 || req.Limit > maxChatInviteListLimit || len(link) > maxChatInviteLinkLength || len(query) > maxChatInviteSearchLength {
|
||||
return nil, limitInvalidErr()
|
||||
}
|
||||
hash := ""
|
||||
if strings.TrimSpace(link) != "" {
|
||||
hash, err = channelInviteHashFromLink(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
offsetUserID := int64(0)
|
||||
if !inputUserIsEmpty(req.OffsetUser) {
|
||||
ids, err := r.userIDsFromInputUsers(ctx, userID, []tg.InputUserClass{req.OffsetUser})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
offsetUserID = ids[0]
|
||||
}
|
||||
}
|
||||
list, err := r.deps.Channels.ListInviteImporters(ctx, userID, domain.ChannelInviteImportersRequest{
|
||||
UserID: userID,
|
||||
ChannelID: view.Channel.ID,
|
||||
Hash: hash,
|
||||
Requested: req.Requested,
|
||||
Query: query,
|
||||
OffsetDate: req.OffsetDate,
|
||||
OffsetUserID: offsetUserID,
|
||||
Limit: req.Limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, channelInviteErr(err)
|
||||
}
|
||||
importers := make([]tg.ChatInviteImporter, 0, len(list.Importers))
|
||||
userIDs := make([]int64, 0, len(list.Importers))
|
||||
for _, importer := range list.Importers {
|
||||
tgImporter := tg.ChatInviteImporter{
|
||||
UserID: importer.UserID,
|
||||
Date: importer.Date,
|
||||
}
|
||||
if importer.Requested {
|
||||
tgImporter.SetRequested(true)
|
||||
}
|
||||
if importer.ApprovedBy != 0 {
|
||||
tgImporter.SetApprovedBy(importer.ApprovedBy)
|
||||
userIDs = append(userIDs, importer.ApprovedBy)
|
||||
}
|
||||
importers = append(importers, tgImporter)
|
||||
userIDs = append(userIDs, importer.UserID)
|
||||
}
|
||||
return &tg.MessagesChatInviteImporters{
|
||||
Count: list.Count,
|
||||
Importers: importers,
|
||||
Users: r.tgUsersForIDs(ctx, userID, userIDs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createChatInviteMemberIDs(ids []int64, selfUserID int64) []int64 {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]int64, 0, len(ids))
|
||||
seen := make(map[int64]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == 0 || id == selfUserID {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tgExportedChannelInvite(invite domain.ChannelInvite) tg.ExportedChatInviteClass {
|
||||
out := &tg.ChatInviteExported{
|
||||
Revoked: invite.Revoked,
|
||||
Permanent: invite.Permanent,
|
||||
RequestNeeded: invite.RequestNeeded,
|
||||
Link: "https://telesrv.net/+" + invite.Hash,
|
||||
AdminID: invite.AdminUserID,
|
||||
Date: invite.Date,
|
||||
}
|
||||
if invite.Title != "" {
|
||||
out.SetTitle(invite.Title)
|
||||
}
|
||||
if invite.ExpireDate > 0 {
|
||||
out.SetExpireDate(invite.ExpireDate)
|
||||
}
|
||||
if invite.UsageLimit > 0 {
|
||||
out.SetUsageLimit(invite.UsageLimit)
|
||||
}
|
||||
if invite.UsageCount > 0 {
|
||||
out.SetUsage(invite.UsageCount)
|
||||
}
|
||||
if invite.RequestedCount > 0 {
|
||||
out.SetRequested(invite.RequestedCount)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func validateChatInviteLink(link string) error {
|
||||
link = strings.TrimSpace(link)
|
||||
if link == "" {
|
||||
return tgerr400("INVITE_HASH_EMPTY")
|
||||
}
|
||||
if len(link) > maxChatInviteLinkLength {
|
||||
return limitInvalidErr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func channelInviteHashFromLink(link string) (string, error) {
|
||||
if err := validateChatInviteLink(link); err != nil {
|
||||
return "", err
|
||||
}
|
||||
link = strings.TrimSpace(link)
|
||||
link = strings.TrimPrefix(link, "tg://join?invite=")
|
||||
if strings.Contains(link, "://") {
|
||||
if idx := strings.LastIndex(link, "/+"); idx >= 0 {
|
||||
link = link[idx+2:]
|
||||
} else if idx := strings.LastIndex(link, "/joinchat/"); idx >= 0 {
|
||||
link = link[idx+10:]
|
||||
} else if idx := strings.LastIndex(link, "/"); idx >= 0 {
|
||||
link = link[idx+1:]
|
||||
}
|
||||
}
|
||||
link = strings.TrimPrefix(link, "+")
|
||||
link = strings.TrimSpace(link)
|
||||
if link == "" {
|
||||
return "", tgerr400("INVITE_HASH_EMPTY")
|
||||
}
|
||||
if len(link) > maxChatInviteLinkLength {
|
||||
return "", limitInvalidErr()
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func channelInviteErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrInviteHashEmpty):
|
||||
return tgerr400("INVITE_HASH_EMPTY")
|
||||
case errors.Is(err, domain.ErrInviteHashInvalid):
|
||||
return tgerr400("INVITE_HASH_INVALID")
|
||||
case errors.Is(err, domain.ErrInviteHashExpired):
|
||||
return tgerr.New(406, "INVITE_HASH_EXPIRED")
|
||||
case errors.Is(err, domain.ErrInvitePermanent):
|
||||
return tgerr400("CHAT_INVITE_PERMANENT")
|
||||
case errors.Is(err, domain.ErrInviteRevokedMissing):
|
||||
return tgerr400("INVITE_REVOKED_MISSING")
|
||||
case errors.Is(err, domain.ErrInviteRequestSent):
|
||||
return tgerr400("INVITE_REQUEST_SENT")
|
||||
case errors.Is(err, domain.ErrHideRequesterMissing):
|
||||
return tgerr400("HIDE_REQUESTER_MISSING")
|
||||
case errors.Is(err, domain.ErrUsersTooMuch):
|
||||
return tgerr400("USERS_TOO_MUCH")
|
||||
case errors.Is(err, domain.ErrUserAlreadyParticipant):
|
||||
return tgerr400("USER_ALREADY_PARTICIPANT")
|
||||
case errors.Is(err, domain.ErrUserKicked):
|
||||
return tgerr400("USER_KICKED")
|
||||
case errors.Is(err, domain.ErrBotGroupsBlocked):
|
||||
return tgerr400("BOT_GROUPS_BLOCKED")
|
||||
default:
|
||||
return channelInvalidErr(err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue