1package xmpp
2
3import (
4	"encoding/xml"
5	"errors"
6	"fmt"
7
8	"github.com/coyim/coyim/xmpp/data"
9	"github.com/coyim/coyim/xmpp/interfaces"
10	"github.com/coyim/coyim/xmpp/jid"
11)
12
13const (
14	mucSupport = "<x xmlns='http://jabber.org/protocol/muc'/>"
15	mucNS      = "http://jabber.org/protocol/muc"
16)
17
18func (c *conn) GetChatContext() interfaces.Chat {
19	return &muc{
20		conn:   c,
21		events: make(chan interface{}),
22	}
23}
24
25type muc struct {
26	*conn
27	events chan interface{}
28}
29
30func (m *muc) Events() chan interface{} {
31	return m.events
32}
33
34//See: Section "6.2 Discovering the Features Supported by a MUC Service"
35func (m *muc) CheckForSupport(entity string) bool {
36	return m.HasSupportTo(entity, mucNS)
37}
38
39//See: Section "6.3 Discovering Rooms"
40func (m *muc) QueryRooms(entity string) ([]data.DiscoveryItem, error) {
41	query, err := m.QueryServiceItems(entity)
42	if err != nil {
43		return nil, err
44	}
45
46	return query.DiscoveryItems, nil
47}
48
49//See: Section "6.4 Querying for Room Information"
50func (m *muc) QueryRoomInformation(room string) (*data.RoomInfo, error) {
51	j := jid.Parse(room)
52	if j == jid.Domain("") {
53		return nil, errors.New("invalid room")
54	}
55
56	local := string(jid.MaybeLocal(j))
57
58	//TODO: this error is useless when it says ("expected query, got error")
59	//It should give us a xmpp error
60	query, err := m.queryRoomInformation(&data.Room{
61		ID:      local,
62		Service: string(j.Host()),
63	})
64
65	if err != nil {
66		return nil, err
67	}
68
69	return parseRoomInformation(query), nil
70}
71
72func parseRoomInfoForm(forms []data.Form) data.RoomInfoForm {
73	ret := data.RoomInfoForm{}
74	parseForms(&ret, forms)
75	return ret
76}
77
78func parseRoomType(features []data.DiscoveryFeature) data.RoomType {
79	ret := data.RoomType{}
80
81	for _, f := range features {
82		switch f.Var {
83		case "muc_public":
84			ret.Public = true
85		case "muc_open":
86			ret.Open = true
87		case "muc_moderated":
88			ret.Moderated = true
89		case "muc_semianonymous":
90			ret.SemiAnonymous = true
91		case "muc_passwordprotected":
92			ret.PasswordProtected = true
93		case "muc_persistenc":
94			ret.Persistent = true
95		}
96	}
97
98	return ret
99}
100
101func parseRoomInformation(query *data.DiscoveryInfoQuery) *data.RoomInfo {
102	return &data.RoomInfo{
103		RoomInfoForm: parseRoomInfoForm(query.Forms[:]),
104		RoomType:     parseRoomType(query.Features),
105	}
106}
107
108func (m *muc) queryRoomInformation(room *data.Room) (*data.DiscoveryInfoQuery, error) {
109	return m.QueryServiceInformation(room.JID())
110}
111
112//See: Section "7.2.2 Basic MUC Protocol"
113func (m *muc) EnterRoom(occupant *data.Occupant) error {
114	//TODO: Implement section "7.2.1 Groupchat 1.0 Protocol"?
115	return m.sendPresence(&data.ClientPresence{
116		To:    occupant.JID(),
117		Extra: mucSupport,
118	})
119}
120
121//See: Section "7.14 Exiting a Room"
122func (m *muc) LeaveRoom(occupant *data.Occupant) error {
123	return m.sendPresence(&data.ClientPresence{
124		To:    occupant.JID(),
125		Type:  "unavailable",
126		Extra: mucSupport,
127	})
128}
129
130//See: Section "7.4 Sending a Message to All Occupants"
131func (m *muc) SendChatMessage(msg string, to *data.Room) error {
132	//TODO: How to disable archive for chat messages?
133	//TODO: Can we just use the same conn.Send() with a different type?
134	_, err := fmt.Fprintf(m.out, "<message "+
135		"to='%s' "+
136		"from='%s' "+
137		"type='groupchat'>"+
138		"<body>%s</body>"+
139		"</message>",
140		xmlEscape(to.JID()), xmlEscape(m.conn.jid), xmlEscape(msg))
141	return err
142}
143
144//See: Section "10.2 Subsequent Room Configuration"
145func (m *muc) RequestRoomConfigForm(room *data.Room) (*data.Form, error) {
146	reply, _, err := m.SendIQ(room.JID(), "get", &data.RoomConfigurationQuery{})
147
148	stanza, ok := <-reply
149	if !ok {
150		return nil, errors.New("xmpp: failed to receive response")
151	}
152
153	iq, ok := stanza.Value.(*data.ClientIQ)
154	if !ok {
155		return nil, errors.New("xmpp: failed to parse response")
156	}
157
158	r := &data.RoomConfigurationQuery{}
159	err = xml.Unmarshal(iq.Query, r)
160	return r.Form, err
161}
162
163func (m *muc) RoomConfigForm(room *data.Room, formCallback data.FormCallback) error {
164	form, err := m.RequestRoomConfigForm(room)
165	if err != nil {
166		return err
167	}
168
169	var datas []data.BobData
170	roomConfig, err := processForm(form, datas, formCallback)
171	if err != nil {
172		return err
173	}
174
175	return m.UpdateRoomConfig(room, roomConfig)
176}
177
178//See: Section "10.2 Subsequent Room Configuration"
179func (m *muc) UpdateRoomConfig(room *data.Room, form *data.Form) error {
180	_, _, err := m.SendIQ(room.JID(), "set", &data.RoomConfigurationQuery{
181		Form: form,
182	})
183
184	return err
185}
186