Coverage Report

Created: 2026-04-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/znc/src/Chan.cpp
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
}