Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2004-2026 ZNC, see the NOTICE file for details. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <znc/Chan.h> |
18 | | #include <znc/IRCSock.h> |
19 | | #include <znc/User.h> |
20 | | #include <znc/IRCNetwork.h> |
21 | | #include <znc/Config.h> |
22 | | #include <znc/znc.h> |
23 | | #include <znc/Message.h> |
24 | | |
25 | | using std::set; |
26 | | using std::vector; |
27 | | using std::map; |
28 | | |
29 | | CChan::CChan(const CString& sName, CIRCNetwork* pNetwork, bool bInConfig, |
30 | | CConfig* pConfig) |
31 | 0 | : m_bDetached(false), |
32 | 0 | m_bIsOn(false), |
33 | 0 | m_bAutoClearChanBuffer(pNetwork->GetUser()->AutoClearChanBuffer()), |
34 | 0 | m_bInConfig(bInConfig), |
35 | 0 | m_bDisabled(false), |
36 | 0 | m_bHasBufferCountSet(false), |
37 | 0 | m_bHasAutoClearChanBufferSet(false), |
38 | 0 | m_sName(sName.Token(0)), |
39 | 0 | m_sKey(sName.Token(1)), |
40 | 0 | m_sTopic(""), |
41 | 0 | m_sTopicOwner(""), |
42 | 0 | m_ulTopicDate(0), |
43 | 0 | m_ulCreationDate(0), |
44 | 0 | m_pNetwork(pNetwork), |
45 | 0 | m_Nick(), |
46 | 0 | m_uJoinTries(0), |
47 | 0 | m_sDefaultModes(""), |
48 | 0 | m_msNicks(), |
49 | 0 | m_Buffer(), |
50 | 0 | m_bModeKnown(false), |
51 | 0 | m_mcsModes() { |
52 | 0 | if (!m_pNetwork->IsChan(m_sName)) { |
53 | 0 | m_sName = "#" + m_sName; |
54 | 0 | } |
55 | |
|
56 | 0 | m_Nick.SetNetwork(m_pNetwork); |
57 | 0 | m_Buffer.SetLineCount(m_pNetwork->GetUser()->GetChanBufferSize(), true); |
58 | |
|
59 | 0 | if (pConfig) { |
60 | 0 | CString sValue; |
61 | 0 | if (pConfig->FindStringEntry("buffer", sValue)) |
62 | 0 | SetBufferCount(sValue.ToUInt(), true); |
63 | 0 | if (pConfig->FindStringEntry("autoclearchanbuffer", sValue)) |
64 | 0 | SetAutoClearChanBuffer(sValue.ToBool()); |
65 | 0 | if (pConfig->FindStringEntry("keepbuffer", sValue)) |
66 | | // XXX Compatibility crap, added in 0.207 |
67 | 0 | SetAutoClearChanBuffer(!sValue.ToBool()); |
68 | 0 | if (pConfig->FindStringEntry("detached", sValue)) |
69 | 0 | SetDetached(sValue.ToBool()); |
70 | 0 | if (pConfig->FindStringEntry("disabled", sValue)) |
71 | 0 | if (sValue.ToBool()) Disable(); |
72 | 0 | if (pConfig->FindStringEntry("autocycle", sValue)) |
73 | 0 | if (sValue.Equals("true")) |
74 | 0 | CUtils::PrintError( |
75 | 0 | "WARNING: AutoCycle has been removed, instead try -> " |
76 | 0 | "LoadModule = autocycle " + |
77 | 0 | sName); |
78 | 0 | if (pConfig->FindStringEntry("key", sValue)) SetKey(sValue); |
79 | 0 | if (pConfig->FindStringEntry("modes", sValue)) SetDefaultModes(sValue); |
80 | 0 | } |
81 | 0 | } |
82 | | |
83 | 0 | CChan::~CChan() { ClearNicks(); } |
84 | | |
85 | 0 | void CChan::Reset() { |
86 | 0 | m_bIsOn = false; |
87 | 0 | m_bModeKnown = false; |
88 | 0 | m_mcsModes.clear(); |
89 | 0 | m_sTopic = ""; |
90 | 0 | m_sTopicOwner = ""; |
91 | 0 | m_ulTopicDate = 0; |
92 | 0 | m_ulCreationDate = 0; |
93 | 0 | m_Nick.Reset(); |
94 | 0 | ClearNicks(); |
95 | 0 | ResetJoinTries(); |
96 | 0 | } |
97 | | |
98 | 0 | CConfig CChan::ToConfig() const { |
99 | 0 | CConfig config; |
100 | |
|
101 | 0 | if (m_bHasBufferCountSet) |
102 | 0 | config.AddKeyValuePair("Buffer", CString(GetBufferCount())); |
103 | 0 | if (m_bHasAutoClearChanBufferSet) |
104 | 0 | config.AddKeyValuePair("AutoClearChanBuffer", |
105 | 0 | CString(AutoClearChanBuffer())); |
106 | 0 | if (IsDetached()) config.AddKeyValuePair("Detached", "true"); |
107 | 0 | if (IsDisabled()) config.AddKeyValuePair("Disabled", "true"); |
108 | 0 | if (!GetKey().empty()) config.AddKeyValuePair("Key", GetKey()); |
109 | 0 | if (!GetDefaultModes().empty()) |
110 | 0 | config.AddKeyValuePair("Modes", GetDefaultModes()); |
111 | |
|
112 | 0 | return config; |
113 | 0 | } |
114 | | |
115 | 0 | void CChan::Clone(CChan& chan) { |
116 | | // We assume that m_sName and m_pNetwork are equal |
117 | 0 | SetBufferCount(chan.GetBufferCount(), true); |
118 | 0 | SetAutoClearChanBuffer(chan.AutoClearChanBuffer()); |
119 | 0 | SetKey(chan.GetKey()); |
120 | 0 | SetDefaultModes(chan.GetDefaultModes()); |
121 | |
|
122 | 0 | if (IsDetached() != chan.IsDetached()) { |
123 | | // Only send something if it makes sense |
124 | | // (= Only detach if client is on the channel |
125 | | // and only attach if we are on the channel) |
126 | 0 | if (IsOn()) { |
127 | 0 | if (IsDetached()) { |
128 | 0 | AttachUser(); |
129 | 0 | } else { |
130 | 0 | DetachUser(); |
131 | 0 | } |
132 | 0 | } |
133 | 0 | SetDetached(chan.IsDetached()); |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | 0 | void CChan::Cycle() const { |
138 | 0 | m_pNetwork->PutIRC("PART " + GetName() + "\r\nJOIN " + GetName() + " " + |
139 | 0 | GetKey()); |
140 | 0 | } |
141 | | |
142 | 0 | void CChan::JoinUser(const CString& sKey) { |
143 | 0 | if (!IsOn() && !sKey.empty()) { |
144 | 0 | SetKey(sKey); |
145 | 0 | } |
146 | 0 | if (m_pNetwork->IsIRCConnected() && !IsOn()) { |
147 | 0 | m_pNetwork->PutIRC("JOIN " + GetName() + " " + GetKey()); |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | 0 | void CChan::AttachUser(CClient* pClient) { |
152 | 0 | m_pNetwork->PutUser( |
153 | 0 | ":" + m_pNetwork->GetIRCNick().GetNickMask() + " JOIN :" + GetName(), |
154 | 0 | pClient); |
155 | |
|
156 | 0 | if (!GetTopic().empty()) { |
157 | 0 | m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 332 " + |
158 | 0 | m_pNetwork->GetIRCNick().GetNick() + " " + |
159 | 0 | GetName() + " :" + GetTopic(), |
160 | 0 | pClient); |
161 | 0 | if (!GetTopicOwner().empty()) { |
162 | 0 | m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 333 " + |
163 | 0 | m_pNetwork->GetIRCNick().GetNick() + " " + |
164 | 0 | GetName() + " " + GetTopicOwner() + " " + |
165 | 0 | CString(GetTopicDate()), |
166 | 0 | pClient); |
167 | 0 | } |
168 | 0 | } |
169 | |
|
170 | 0 | CString sPre = ":" + m_pNetwork->GetIRCServer() + " 353 " + |
171 | 0 | m_pNetwork->GetIRCNick().GetNick() + " " + |
172 | 0 | GetModeForNames() + " " + GetName() + " :"; |
173 | 0 | CString sLine = sPre; |
174 | 0 | CString sPerm, sNick; |
175 | |
|
176 | 0 | const vector<CClient*>& vpClients = m_pNetwork->GetClients(); |
177 | 0 | for (CClient* pEachClient : vpClients) { |
178 | 0 | CClient* pThisClient; |
179 | 0 | if (!pClient) |
180 | 0 | pThisClient = pEachClient; |
181 | 0 | else |
182 | 0 | pThisClient = pClient; |
183 | |
|
184 | 0 | for (map<CString, CNick>::iterator a = m_msNicks.begin(); |
185 | 0 | a != m_msNicks.end(); ++a) { |
186 | 0 | if (pThisClient->HasNamesx()) { |
187 | 0 | sPerm = a->second.GetPermStr(); |
188 | 0 | } else { |
189 | 0 | char c = a->second.GetPermChar(); |
190 | 0 | sPerm = ""; |
191 | 0 | if (c != '\0') { |
192 | 0 | sPerm += c; |
193 | 0 | } |
194 | 0 | } |
195 | 0 | if (pThisClient->HasUHNames() && !a->second.GetIdent().empty() && |
196 | 0 | !a->second.GetHost().empty()) { |
197 | 0 | sNick = a->first + "!" + a->second.GetIdent() + "@" + |
198 | 0 | a->second.GetHost(); |
199 | 0 | } else { |
200 | 0 | sNick = a->first; |
201 | 0 | } |
202 | |
|
203 | 0 | sLine += sPerm + sNick; |
204 | |
|
205 | 0 | if (sLine.size() >= 490 || a == (--m_msNicks.end())) { |
206 | 0 | m_pNetwork->PutUser(sLine, pThisClient); |
207 | 0 | sLine = sPre; |
208 | 0 | } else { |
209 | 0 | sLine += " "; |
210 | 0 | } |
211 | 0 | } |
212 | |
|
213 | 0 | if (pClient) // We only want to do this for one client |
214 | 0 | break; |
215 | 0 | } |
216 | |
|
217 | 0 | m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 366 " + |
218 | 0 | m_pNetwork->GetIRCNick().GetNick() + " " + |
219 | 0 | GetName() + " :End of /NAMES list.", |
220 | 0 | pClient); |
221 | 0 | m_bDetached = false; |
222 | | |
223 | | // Send Buffer |
224 | 0 | SendBuffer(pClient); |
225 | 0 | } |
226 | | |
227 | 0 | void CChan::DetachUser() { |
228 | 0 | if (!m_bDetached) { |
229 | 0 | m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + |
230 | 0 | " PART " + GetName()); |
231 | 0 | m_bDetached = true; |
232 | 0 | } |
233 | 0 | } |
234 | | |
235 | 0 | CString CChan::GetModeString() const { |
236 | 0 | CString sModes, sArgs; |
237 | |
|
238 | 0 | for (const auto& it : m_mcsModes) { |
239 | 0 | sModes += it.first; |
240 | 0 | if (it.second.size()) { |
241 | 0 | sArgs += " " + it.second; |
242 | 0 | } |
243 | 0 | } |
244 | |
|
245 | 0 | return sModes.empty() ? sModes : CString("+" + sModes + sArgs); |
246 | 0 | } |
247 | | |
248 | 0 | CString CChan::GetModeForNames() const { |
249 | 0 | CString sMode; |
250 | |
|
251 | 0 | for (const auto& it : m_mcsModes) { |
252 | 0 | if (it.first == 's') { |
253 | 0 | sMode = "@"; |
254 | 0 | } else if ((it.first == 'p') && sMode.empty()) { |
255 | 0 | sMode = "*"; |
256 | 0 | } |
257 | 0 | } |
258 | |
|
259 | 0 | return (sMode.empty() ? "=" : sMode); |
260 | 0 | } |
261 | | |
262 | 0 | void CChan::SetModes(const CString& sModes) { |
263 | 0 | m_mcsModes.clear(); |
264 | 0 | ModeChange(sModes); |
265 | 0 | } |
266 | | |
267 | 0 | void CChan::SetModes(const CString& modes, const VCString& vsModeParams) { |
268 | 0 | m_mcsModes.clear(); |
269 | 0 | ModeChange(modes, vsModeParams); |
270 | 0 | } |
271 | | |
272 | 0 | void CChan::SetAutoClearChanBuffer(bool b) { |
273 | 0 | m_bHasAutoClearChanBufferSet = true; |
274 | 0 | m_bAutoClearChanBuffer = b; |
275 | |
|
276 | 0 | if (m_bAutoClearChanBuffer && !IsDetached() && m_pNetwork->IsUserOnline()) { |
277 | 0 | ClearBuffer(); |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | 0 | void CChan::InheritAutoClearChanBuffer(bool b) { |
282 | 0 | if (!m_bHasAutoClearChanBufferSet) { |
283 | 0 | m_bAutoClearChanBuffer = b; |
284 | |
|
285 | 0 | if (m_bAutoClearChanBuffer && !IsDetached() && |
286 | 0 | m_pNetwork->IsUserOnline()) { |
287 | 0 | ClearBuffer(); |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | 0 | void CChan::ResetAutoClearChanBuffer() { |
293 | 0 | SetAutoClearChanBuffer(m_pNetwork->GetUser()->AutoClearChanBuffer()); |
294 | 0 | m_bHasAutoClearChanBufferSet = false; |
295 | 0 | } |
296 | | |
297 | | void CChan::OnWho(const CString& sNick, const CString& sIdent, |
298 | 0 | const CString& sHost) { |
299 | 0 | CNick* pNick = FindNick(sNick); |
300 | |
|
301 | 0 | if (pNick) { |
302 | 0 | pNick->SetIdent(sIdent); |
303 | 0 | pNick->SetHost(sHost); |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | | void CChan::ModeChange(const CString& sModes, const VCString& vsModes, |
308 | 0 | const CNick* pOpNick) { |
309 | 0 | bool bAdd = true; |
310 | | |
311 | | /* Try to find a CNick* from this channel so that pOpNick->HasPerm() |
312 | | * works as expected. */ |
313 | 0 | if (pOpNick) { |
314 | 0 | CNick* OpNick = FindNick(pOpNick->GetNick()); |
315 | | /* If nothing was found, use the original pOpNick, else use the |
316 | | * CNick* from FindNick() */ |
317 | 0 | if (OpNick) pOpNick = OpNick; |
318 | 0 | } |
319 | |
|
320 | 0 | { |
321 | 0 | CString sArgs = CString(" ").Join(vsModes.begin(), vsModes.end()); |
322 | 0 | NETWORKMODULECALL(OnRawMode2(pOpNick, *this, sModes, sArgs), |
323 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, NOTHING); |
324 | 0 | } |
325 | |
|
326 | 0 | VCString::const_iterator argIter = vsModes.begin(); |
327 | 0 | const CString sEmpty; |
328 | 0 | auto nextArg = [&]() -> const CString& { |
329 | 0 | if (argIter == vsModes.end()) { |
330 | 0 | return sEmpty; |
331 | 0 | } |
332 | 0 | return *argIter++; |
333 | 0 | }; |
334 | 0 | for (unsigned int a = 0; a < sModes.size(); a++) { |
335 | 0 | const char& cMode = sModes[a]; |
336 | |
|
337 | 0 | if (cMode == '+') { |
338 | 0 | bAdd = true; |
339 | 0 | } else if (cMode == '-') { |
340 | 0 | bAdd = false; |
341 | 0 | } else if (m_pNetwork->GetIRCSock()->IsPermMode(cMode)) { |
342 | 0 | const CString& sArg = nextArg(); |
343 | 0 | CNick* pNick = FindNick(sArg); |
344 | 0 | if (pNick) { |
345 | 0 | char cPerm = m_pNetwork->GetIRCSock()->GetPermFromMode(cMode); |
346 | |
|
347 | 0 | if (cPerm) { |
348 | 0 | bool bNoChange = (pNick->HasPerm(cPerm) == bAdd); |
349 | |
|
350 | 0 | if (bAdd) { |
351 | 0 | pNick->AddPerm(cPerm); |
352 | |
|
353 | 0 | if (pNick->NickEquals(m_pNetwork->GetCurNick())) { |
354 | 0 | AddPerm(cPerm); |
355 | 0 | } |
356 | 0 | } else { |
357 | 0 | pNick->RemPerm(cPerm); |
358 | |
|
359 | 0 | if (pNick->NickEquals(m_pNetwork->GetCurNick())) { |
360 | 0 | RemPerm(cPerm); |
361 | 0 | } |
362 | 0 | } |
363 | |
|
364 | 0 | NETWORKMODULECALL(OnChanPermission3(pOpNick, *pNick, *this, |
365 | 0 | cMode, bAdd, bNoChange), |
366 | 0 | m_pNetwork->GetUser(), m_pNetwork, |
367 | 0 | nullptr, NOTHING); |
368 | |
|
369 | 0 | if (cMode == CChan::M_Op) { |
370 | 0 | if (bAdd) { |
371 | 0 | NETWORKMODULECALL( |
372 | 0 | OnOp2(pOpNick, *pNick, *this, bNoChange), |
373 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
374 | 0 | NOTHING); |
375 | 0 | } else { |
376 | 0 | NETWORKMODULECALL( |
377 | 0 | OnDeop2(pOpNick, *pNick, *this, bNoChange), |
378 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
379 | 0 | NOTHING); |
380 | 0 | } |
381 | 0 | } else if (cMode == CChan::M_Voice) { |
382 | 0 | if (bAdd) { |
383 | 0 | NETWORKMODULECALL( |
384 | 0 | OnVoice2(pOpNick, *pNick, *this, bNoChange), |
385 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
386 | 0 | NOTHING); |
387 | 0 | } else { |
388 | 0 | NETWORKMODULECALL( |
389 | 0 | OnDevoice2(pOpNick, *pNick, *this, bNoChange), |
390 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
391 | 0 | NOTHING); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | 0 | } else { |
397 | 0 | bool bList = false; |
398 | 0 | CString sArg; |
399 | |
|
400 | 0 | switch (m_pNetwork->GetIRCSock()->GetModeType(cMode)) { |
401 | 0 | case CIRCSock::ListArg: |
402 | 0 | bList = true; |
403 | 0 | sArg = nextArg(); |
404 | 0 | break; |
405 | 0 | case CIRCSock::HasArg: |
406 | 0 | sArg = nextArg(); |
407 | 0 | break; |
408 | 0 | case CIRCSock::NoArg: |
409 | 0 | break; |
410 | 0 | case CIRCSock::ArgWhenSet: |
411 | 0 | if (bAdd) { |
412 | 0 | sArg = nextArg(); |
413 | 0 | } |
414 | |
|
415 | 0 | break; |
416 | 0 | } |
417 | | |
418 | 0 | bool bNoChange; |
419 | 0 | if (bList) { |
420 | 0 | bNoChange = false; |
421 | 0 | } else if (bAdd) { |
422 | 0 | bNoChange = HasMode(cMode) && GetModeArg(cMode) == sArg; |
423 | 0 | } else { |
424 | 0 | bNoChange = !HasMode(cMode); |
425 | 0 | } |
426 | 0 | NETWORKMODULECALL( |
427 | 0 | OnMode2(pOpNick, *this, cMode, sArg, bAdd, bNoChange), |
428 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, NOTHING); |
429 | |
|
430 | 0 | if (!bList) { |
431 | 0 | (bAdd) ? AddMode(cMode, sArg) : RemMode(cMode); |
432 | 0 | } |
433 | | |
434 | | // This is called when we join (ZNC requests the channel modes |
435 | | // on join) *and* when someone changes the channel keys. |
436 | | // We ignore channel key "*" because of some broken nets. |
437 | 0 | if (cMode == M_Key && !bNoChange && bAdd && sArg != "*") { |
438 | 0 | SetKey(sArg); |
439 | 0 | } |
440 | 0 | } |
441 | 0 | } |
442 | 0 | } |
443 | | |
444 | 0 | void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) { |
445 | 0 | VCString vsModes; |
446 | 0 | CString sModeArg = sModes.Token(0); |
447 | 0 | bool colon = sModeArg.TrimPrefix(":"); |
448 | | |
449 | | // Only handle parameters if sModes doesn't start with a colon |
450 | | // because if it does, we only have the mode string with no parameters |
451 | 0 | if (!colon) { |
452 | 0 | CString sArgs = sModes.Token(1, true); |
453 | |
|
454 | 0 | while (!sArgs.empty()) { |
455 | | // Check if this parameter is a trailing parameter |
456 | | // If so, treat the rest of sArgs as one parameter |
457 | 0 | if (sArgs.TrimPrefix(":")) { |
458 | 0 | vsModes.push_back(sArgs); |
459 | 0 | sArgs.clear(); |
460 | 0 | } else { |
461 | 0 | vsModes.push_back(sArgs.Token(0)); |
462 | 0 | sArgs = sArgs.Token(1, true); |
463 | 0 | } |
464 | 0 | } |
465 | 0 | } |
466 | |
|
467 | 0 | ModeChange(sModeArg, vsModes, pOpNick); |
468 | 0 | } |
469 | | |
470 | 0 | CString CChan::GetOptions() const { |
471 | 0 | VCString vsRet; |
472 | |
|
473 | 0 | if (IsDetached()) { |
474 | 0 | vsRet.push_back("Detached"); |
475 | 0 | } |
476 | |
|
477 | 0 | if (AutoClearChanBuffer()) { |
478 | 0 | if (HasAutoClearChanBufferSet()) { |
479 | 0 | vsRet.push_back("AutoClearChanBuffer"); |
480 | 0 | } else { |
481 | 0 | vsRet.push_back("AutoClearChanBuffer (default)"); |
482 | 0 | } |
483 | 0 | } |
484 | |
|
485 | 0 | return CString(", ").Join(vsRet.begin(), vsRet.end()); |
486 | 0 | } |
487 | | |
488 | 0 | CString CChan::GetModeArg(char cMode) const { |
489 | 0 | if (cMode) { |
490 | 0 | map<char, CString>::const_iterator it = m_mcsModes.find(cMode); |
491 | |
|
492 | 0 | if (it != m_mcsModes.end()) { |
493 | 0 | return it->second; |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | 0 | return ""; |
498 | 0 | } |
499 | | |
500 | 0 | bool CChan::HasMode(char cMode) const { |
501 | 0 | return (cMode && m_mcsModes.find(cMode) != m_mcsModes.end()); |
502 | 0 | } |
503 | | |
504 | 0 | bool CChan::AddMode(char cMode, const CString& sArg) { |
505 | 0 | m_mcsModes[cMode] = sArg; |
506 | 0 | return true; |
507 | 0 | } |
508 | | |
509 | 0 | bool CChan::RemMode(char cMode) { |
510 | 0 | if (!HasMode(cMode)) { |
511 | 0 | return false; |
512 | 0 | } |
513 | | |
514 | 0 | m_mcsModes.erase(cMode); |
515 | 0 | return true; |
516 | 0 | } |
517 | | |
518 | 0 | CString CChan::GetModeArg(CString& sArgs) const { |
519 | 0 | CString sRet = sArgs.substr(0, sArgs.find(' ')); |
520 | 0 | sArgs = (sRet.size() < sArgs.size()) ? sArgs.substr(sRet.size() + 1) : ""; |
521 | 0 | return sRet; |
522 | 0 | } |
523 | | |
524 | 0 | void CChan::ClearNicks() { m_msNicks.clear(); } |
525 | | |
526 | 0 | int CChan::AddNicks(const CString& sNicks) { |
527 | 0 | int iRet = 0; |
528 | 0 | VCString vsNicks; |
529 | |
|
530 | 0 | sNicks.Split(" ", vsNicks, false); |
531 | |
|
532 | 0 | for (const CString& sNick : vsNicks) { |
533 | 0 | if (AddNick(sNick)) { |
534 | 0 | iRet++; |
535 | 0 | } |
536 | 0 | } |
537 | |
|
538 | 0 | return iRet; |
539 | 0 | } |
540 | | |
541 | 0 | bool CChan::AddNick(const CString& sNick) { |
542 | 0 | const char* p = sNick.c_str(); |
543 | 0 | CString sPrefix, sTmp, sIdent, sHost; |
544 | |
|
545 | 0 | while (m_pNetwork->GetIRCSock()->IsPermChar(*p)) { |
546 | 0 | sPrefix += *p; |
547 | |
|
548 | 0 | if (!*++p) { |
549 | 0 | return false; |
550 | 0 | } |
551 | 0 | } |
552 | | |
553 | 0 | sTmp = p; |
554 | | |
555 | | // The UHNames extension gets us nick!ident@host instead of just plain nick |
556 | 0 | sIdent = sTmp.Token(1, true, "!"); |
557 | 0 | sHost = sIdent.Token(1, true, "@"); |
558 | 0 | sIdent = sIdent.Token(0, false, "@"); |
559 | | // Get the nick |
560 | 0 | sTmp = sTmp.Token(0, false, "!"); |
561 | |
|
562 | 0 | CNick tmpNick(sTmp); |
563 | 0 | CNick* pNick = FindNick(sTmp); |
564 | 0 | if (!pNick) { |
565 | 0 | pNick = &tmpNick; |
566 | 0 | pNick->SetNetwork(m_pNetwork); |
567 | 0 | } |
568 | |
|
569 | 0 | if (!sIdent.empty()) pNick->SetIdent(sIdent); |
570 | 0 | if (!sHost.empty()) pNick->SetHost(sHost); |
571 | |
|
572 | 0 | for (CString::size_type i = 0; i < sPrefix.length(); i++) { |
573 | 0 | pNick->AddPerm(sPrefix[i]); |
574 | 0 | } |
575 | |
|
576 | 0 | if (pNick->NickEquals(m_pNetwork->GetCurNick())) { |
577 | 0 | for (CString::size_type i = 0; i < sPrefix.length(); i++) { |
578 | 0 | AddPerm(sPrefix[i]); |
579 | 0 | } |
580 | 0 | } |
581 | |
|
582 | 0 | m_msNicks[pNick->GetNick()] = *pNick; |
583 | |
|
584 | 0 | return true; |
585 | 0 | } |
586 | | |
587 | 0 | map<char, unsigned int> CChan::GetPermCounts() const { |
588 | 0 | map<char, unsigned int> mRet; |
589 | |
|
590 | 0 | for (const auto& it : m_msNicks) { |
591 | 0 | CString sPerms = it.second.GetPermStr(); |
592 | |
|
593 | 0 | for (unsigned int p = 0; p < sPerms.size(); p++) { |
594 | 0 | mRet[sPerms[p]]++; |
595 | 0 | } |
596 | 0 | } |
597 | |
|
598 | 0 | return mRet; |
599 | 0 | } |
600 | | |
601 | 0 | bool CChan::RemNick(const CString& sNick) { |
602 | 0 | map<CString, CNick>::iterator it; |
603 | |
|
604 | 0 | it = m_msNicks.find(sNick); |
605 | 0 | if (it == m_msNicks.end()) { |
606 | 0 | return false; |
607 | 0 | } |
608 | | |
609 | 0 | m_msNicks.erase(it); |
610 | |
|
611 | 0 | return true; |
612 | 0 | } |
613 | | |
614 | 0 | bool CChan::ChangeNick(const CString& sOldNick, const CString& sNewNick) { |
615 | 0 | map<CString, CNick>::iterator it = m_msNicks.find(sOldNick); |
616 | |
|
617 | 0 | if (it == m_msNicks.end()) { |
618 | 0 | return false; |
619 | 0 | } |
620 | | |
621 | | // Rename this nick |
622 | 0 | it->second.SetNick(sNewNick); |
623 | | |
624 | | // Insert a new element into the map then erase the old one, do this to |
625 | | // change the key to the new nick |
626 | 0 | m_msNicks[sNewNick] = it->second; |
627 | 0 | m_msNicks.erase(it); |
628 | |
|
629 | 0 | return true; |
630 | 0 | } |
631 | | |
632 | 0 | const CNick* CChan::FindNick(const CString& sNick) const { |
633 | 0 | map<CString, CNick>::const_iterator it = m_msNicks.find(sNick); |
634 | 0 | return (it != m_msNicks.end()) ? &it->second : nullptr; |
635 | 0 | } |
636 | | |
637 | 0 | CNick* CChan::FindNick(const CString& sNick) { |
638 | 0 | map<CString, CNick>::iterator it = m_msNicks.find(sNick); |
639 | 0 | return (it != m_msNicks.end()) ? &it->second : nullptr; |
640 | 0 | } |
641 | | |
642 | 0 | void CChan::SendBuffer(CClient* pClient) { |
643 | 0 | SendBuffer(pClient, m_Buffer); |
644 | 0 | if (AutoClearChanBuffer()) { |
645 | 0 | ClearBuffer(); |
646 | 0 | } |
647 | 0 | } |
648 | | |
649 | 0 | void CChan::SendBuffer(CClient* pClient, const CBuffer& Buffer) { |
650 | 0 | if (m_pNetwork && m_pNetwork->IsUserAttached()) { |
651 | | // in the event that pClient is nullptr, need to send this to all |
652 | | // clients for the user I'm presuming here that pClient is listed |
653 | | // inside vClients thus vClients at this point can't be empty. |
654 | | // |
655 | | // This loop has to be cycled twice to maintain the existing behavior |
656 | | // which is |
657 | | // 1. OnChanBufferStarting |
658 | | // 2. OnChanBufferPlayLine |
659 | | // 3. ClearBuffer() if not keeping the buffer |
660 | | // 4. OnChanBufferEnding |
661 | | // |
662 | | // With the exception of ClearBuffer(), this needs to happen per |
663 | | // client, and if pClient is not nullptr, the loops break after the |
664 | | // first iteration. |
665 | | // |
666 | | // Rework this if you like ... |
667 | 0 | if (!Buffer.IsEmpty()) { |
668 | 0 | const vector<CClient*>& vClients = m_pNetwork->GetClients(); |
669 | 0 | for (CClient* pEachClient : vClients) { |
670 | 0 | CClient* pUseClient = (pClient ? pClient : pEachClient); |
671 | |
|
672 | 0 | bool bWasPlaybackActive = pUseClient->IsPlaybackActive(); |
673 | 0 | pUseClient->SetPlaybackActive(true); |
674 | |
|
675 | 0 | bool bSkipStatusMsg = pUseClient->HasServerTime(); |
676 | 0 | NETWORKMODULECALL(OnChanBufferStarting(*this, *pUseClient), |
677 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
678 | 0 | &bSkipStatusMsg); |
679 | |
|
680 | 0 | if (!bSkipStatusMsg) { |
681 | 0 | m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() + |
682 | 0 | " :" + t_s("Buffer Playback..."), |
683 | 0 | pUseClient); |
684 | 0 | } |
685 | |
|
686 | 0 | bool bBatch = pUseClient->HasBatch(); |
687 | 0 | CString sBatchName = GetName().MD5(); |
688 | |
|
689 | 0 | if (bBatch) { |
690 | 0 | m_pNetwork->PutUser(":znc.in BATCH +" + sBatchName + |
691 | 0 | " znc.in/playback " + GetName(), |
692 | 0 | pUseClient); |
693 | 0 | } |
694 | |
|
695 | 0 | size_t uSize = Buffer.Size(); |
696 | 0 | for (size_t uIdx = 0; uIdx < uSize; uIdx++) { |
697 | 0 | const CBufLine& BufLine = Buffer.GetBufLine(uIdx); |
698 | 0 | CMessage Message = |
699 | 0 | BufLine.ToMessage(*pUseClient, MCString::EmptyMap); |
700 | 0 | Message.SetChan(this); |
701 | 0 | Message.SetNetwork(m_pNetwork); |
702 | 0 | Message.SetClient(pUseClient); |
703 | 0 | if (bBatch) { |
704 | 0 | Message.SetTag("batch", sBatchName); |
705 | 0 | } |
706 | 0 | bool bNotShowThisLine = false; |
707 | 0 | NETWORKMODULECALL(OnChanBufferPlayMessage(Message), |
708 | 0 | m_pNetwork->GetUser(), m_pNetwork, |
709 | 0 | nullptr, &bNotShowThisLine); |
710 | 0 | if (bNotShowThisLine) continue; |
711 | 0 | m_pNetwork->PutUser(Message, pUseClient); |
712 | 0 | } |
713 | |
|
714 | 0 | bSkipStatusMsg = pUseClient->HasServerTime(); |
715 | 0 | NETWORKMODULECALL(OnChanBufferEnding(*this, *pUseClient), |
716 | 0 | m_pNetwork->GetUser(), m_pNetwork, nullptr, |
717 | 0 | &bSkipStatusMsg); |
718 | 0 | if (!bSkipStatusMsg) { |
719 | 0 | m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() + |
720 | 0 | " :" + t_s("Playback Complete."), |
721 | 0 | pUseClient); |
722 | 0 | } |
723 | |
|
724 | 0 | if (bBatch) { |
725 | 0 | m_pNetwork->PutUser(":znc.in BATCH -" + sBatchName, |
726 | 0 | pUseClient); |
727 | 0 | } |
728 | |
|
729 | 0 | pUseClient->SetPlaybackActive(bWasPlaybackActive); |
730 | |
|
731 | 0 | if (pClient) break; |
732 | 0 | } |
733 | 0 | } |
734 | 0 | } |
735 | 0 | } |
736 | | |
737 | 0 | void CChan::Enable() { |
738 | 0 | ResetJoinTries(); |
739 | 0 | m_bDisabled = false; |
740 | 0 | } |
741 | | |
742 | 0 | void CChan::SetKey(const CString& s) { |
743 | 0 | if (m_sKey != s) { |
744 | 0 | m_sKey = s; |
745 | 0 | if (m_bInConfig) { |
746 | 0 | CZNC::Get().SetConfigState(CZNC::ECONFIG_DELAYED_WRITE); |
747 | 0 | } |
748 | 0 | } |
749 | 0 | } |
750 | | |
751 | 0 | void CChan::SetInConfig(bool b) { |
752 | 0 | if (m_bInConfig != b) { |
753 | 0 | m_bInConfig = b; |
754 | 0 | CZNC::Get().SetConfigState(CZNC::ECONFIG_DELAYED_WRITE); |
755 | 0 | } |
756 | 0 | } |
757 | | |
758 | 0 | void CChan::ResetBufferCount() { |
759 | 0 | SetBufferCount(m_pNetwork->GetUser()->GetBufferCount()); |
760 | 0 | m_bHasBufferCountSet = false; |
761 | 0 | } |