diff --git a/telegram/utils.go b/telegram/utils.go index 352dc07..76f8abd 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -2683,6 +2683,53 @@ func (c *Client) SetChatMemberStatus(chatID, userID int64, status ChatMemberStat return err } +// SetChatTitle is a handy wrapper for the following TDLib method +func (c *Client) SetChatTitle(chatID int64, title string) error { + _, err := c.client.SetChatTitle(&client.SetChatTitleRequest{ + ChatId: chatID, + Title: title, + }) + return err +} + +// SetChatDescription is a handy wrapper for the following TDLib method +func (c *Client) SetChatDescription(chatID int64, description string) error { + _, err := c.client.SetChatDescription(&client.SetChatDescriptionRequest{ + ChatId: chatID, + Description: description, + }) + return err +} + +// SetChatPermissions is a handy wrapper for the following TDLib method +func (c *Client) SetChatPermissions(chatID int64, permissions *client.ChatPermissions) error { + _, err := c.client.SetChatPermissions(&client.SetChatPermissionsRequest{ + ChatId: chatID, + Permissions: permissions, + }) + return err +} + +// CloneChatPermissions makes a copy of ChatPermissions structure +func CloneChatPermissions(permissions *client.ChatPermissions) *client.ChatPermissions { + return &client.ChatPermissions{ + CanSendBasicMessages: permissions.CanSendBasicMessages, + CanSendAudios: permissions.CanSendAudios, + CanSendDocuments: permissions.CanSendDocuments, + CanSendPhotos: permissions.CanSendPhotos, + CanSendVideos: permissions.CanSendVideos, + CanSendVideoNotes: permissions.CanSendVideoNotes, + CanSendVoiceNotes: permissions.CanSendVoiceNotes, + CanSendPolls: permissions.CanSendPolls, + CanSendOtherMessages: permissions.CanSendOtherMessages, + CanAddWebPagePreviews: permissions.CanAddWebPagePreviews, + CanChangeInfo: permissions.CanChangeInfo, + CanInviteUsers: permissions.CanInviteUsers, + CanPinMessages: permissions.CanPinMessages, + CanManageTopics: permissions.CanManageTopics, + } +} + func (c *Client) mucOccupantRolePresence(chatID, userID int64, status ChatMemberStatus, nickname string) { args := []args.V{ gateway.SPFrom(gateway.MUCNODE(chatID)), diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index c8889b2..432b882 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -327,6 +327,7 @@ type QueryMucAdminItem struct { // QueryMucOwner is from XEP-0045 type QueryMucOwner struct { XMLName xml.Name `xml:"http://jabber.org/protocol/muc#owner query"` + Form *stanza.Form `xml:"jabber:x:data x` ResultSet *stanza.ResultSet `xml:"set,omitempty"` } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index d0c3416..fee1cf4 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -26,6 +26,8 @@ const ( TypeVCard4 ) +const MUC_DEFAULT_HISTORY_LIMIT int32 = 20 + func logPacketType(p stanza.Packet) { log.Warnf("Ignoring packet: %T\n", p) } @@ -97,6 +99,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleSetQueryMucAdmin(s, iq, queryMucAdmin) return } + queryMucOwner, ok := iq.Payload.(*extensions.QueryMucOwner) + if ok { + go handleSetQueryMucOwner(s, iq, queryMucOwner) + return + } } else if iq.Type == stanza.IQTypeResult { discoInfo, ok := iq.Payload.(*stanza.DiscoInfo) if ok { @@ -603,7 +610,7 @@ func handleMUCPresence(s xmpp.Sender, p stanza.Presence, mucExt stanza.MucPresen } else if !mucExt.History.Since.IsZero() { limit = telegram.NewMessageLimitSince(mucExt.History.Since.Unix()) } else { - limit = telegram.NewMessageLimitMessages(20) + limit = telegram.NewMessageLimitMessages(MUC_DEFAULT_HISTORY_LIMIT) } session.JoinMUC(chatId, fromResource, limit) } @@ -1181,6 +1188,12 @@ func handleGetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer return } + chat, _, err := session.GetContactByID(toID, nil) + if err != nil || !session.IsGroup(chat) { + iqAnswerSetError(answer, 405) + return + } + if len(query.Items) != 1 { iqAnswerSetError(answer, 400) return @@ -1216,8 +1229,6 @@ func handleGetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer members, err := session.GetChatMembers(toID, false, "", membersList) if err == nil { - chat, _, _ := session.GetContactByID(toID, nil) - for _, member := range members { senderId, nickname, affiliation, role := session.TgMemberToMUCOccupant(member, chat) if item.Role != "" && role != item.Role { @@ -1241,8 +1252,69 @@ func handleGetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ) { if !ok { return } - iqAnswerSetError(answer, 405) - gateway.ResumableSend(component, answer) + defer gateway.ResumableSend(component, answer) + + bare, _, fromOk := gateway.SplitJID(iq.From) + if !fromOk { + iqAnswerSetError(answer, 400) + return + } + + session, sessionOk := sessions[bare] + if !sessionOk || !session.Session.MUC { + iqAnswerSetError(answer, 403) + return + } + + toID, toOk, toIsGroup := toToID(iq.To) + if !toOk || !toIsGroup { + iqAnswerSetError(answer, 405) + return + } + + chat, _, err := session.GetContactByID(toID, nil) + if err != nil || chat == nil || !session.IsGroup(chat) { + iqAnswerSetError(answer, 405) + return + } + + payload := &extensions.QueryMucOwner{} + answer.Payload = payload + + dummyString := "" + changeSubject := "1" + if chat.Permissions != nil && !chat.Permissions.CanPinMessages { + changeSubject = "0" + } + + payload.Form = &stanza.Form{ + Type: stanza.FormTypeForm, + Title: fmt.Sprintf("Configuration for \"%v\" room", chat.Title), + Fields: []*stanza.Field{ + &stanza.Field{ + Var: "FORM_TYPE", + Type: stanza.FieldTypeHidden, + ValuesList: []string{"http://jabber.org/protocol/muc#roominfo"}, + }, + &stanza.Field{ + Var: "muc#roomconfig_roomname", + Label: "Group name", + Required: &dummyString, + ValuesList: []string{chat.Title}, + }, + &stanza.Field{ + Var: "muc#roomconfig_roomdesc", + Label: "Description (optional)", + ValuesList: []string{session.GetChatDescription(chat)}, + }, + &stanza.Field{ + Var: "muc#roomconfig_changesubject", + Label: "Allow Occupants to Change Subject?", + Type: stanza.FieldTypeBool, + ValuesList: []string{changeSubject}, + }, + }, + } } func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) { @@ -1796,6 +1868,113 @@ func handleSetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer } } +func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryMucOwner) { + component, answer, ok := iqResultStub(s, iq) + if !ok { + return + } + defer gateway.ResumableSend(component, answer) + + bare, _, fromOk := gateway.SplitJID(iq.From) + if !fromOk { + iqAnswerSetError(answer, 400) + return + } + + session, sessionOk := sessions[bare] + if !sessionOk || !session.Session.MUC { + iqAnswerSetError(answer, 403) + return + } + + toID, toOk, toIsGroup := toToID(iq.To) + if !toOk || !toIsGroup { + iqAnswerSetError(answer, 405) + return + } + + chat, _, err := session.GetContactByID(toID, nil) + if err != nil || chat == nil || !session.IsGroup(chat) { + iqAnswerSetError(answer, 405) + return + } + + if query.Form == nil { + iqAnswerSetError(answer, 400) + return + } + + switch query.Form.Type { + case stanza.FormTypeSubmit: + // okay, noop + case stanza.FormTypeCancel: + return + default: + iqAnswerSetError(answer, 400) + return + } + + err = nil + for _, field := range query.Form.Fields { + switch field.Var { + case "muc#roomconfig_roomname", "muc#roomconfig_roomdesc", "muc#roomconfig_changesubject": + if len(field.ValuesList) != 1 { + iqAnswerSetError(answer, 400) + return + } + + value := field.ValuesList[0] + + switch field.Var { + case "muc#roomconfig_roomname": + if value == "" { + iqAnswerSetError(answer, 400) + return + } + if value != chat.Title { + err = session.SetChatTitle(toID, value) + if err != nil { + break + } + } + case "muc#roomconfig_roomdesc": + if value != session.GetChatDescription(chat) { + err = session.SetChatDescription(toID, value) + if err != nil { + break + } + } + case "muc#roomconfig_changesubject": + b, ok := ToBool(value) + if !ok { + iqAnswerSetError(answer, 400) + return + } + permissions := chat.Permissions + if permissions != nil && b != permissions.CanPinMessages { + newPermissions := telegram.CloneChatPermissions(permissions) + newPermissions.CanPinMessages = b + + err = session.SetChatPermissions(toID, newPermissions) + if err != nil { + break + } + } + } + } + } + + if err != nil { + code, ok := telegram.GetErrorCode(err) + if !ok { + code = 500 + } + iqAnswerSetError(answer, int(code)) + answer.Error.Text = err.Error() + return + } +} + func iqAnswerSetError(answer *stanza.IQ, code int) { iqAnswerSetErrorInternal(answer, code, false) } @@ -2124,3 +2303,15 @@ func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *tel return nil } + +// ToBool returns bool, ok +func ToBool(b string) (bool, bool) { + switch b { + case "0", "false": + return false, true + case "1", "true": + return true, true + } + + return false, false +}