1 /*
2 * Copyright (C) 2004-2020 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/IRCNetwork.h>
19
20 using std::vector;
21
22 class CAutoCycleMod : public CModule {
23 public:
MODCONSTRUCTOR(CAutoCycleMod)24 MODCONSTRUCTOR(CAutoCycleMod) {
25 AddHelpCommand();
26 AddCommand(
27 "Add", t_d("[!]<#chan>"),
28 t_d("Add an entry, use !#chan to negate and * for wildcards"),
29 [=](const CString& sLine) { OnAddCommand(sLine); });
30 AddCommand("Del", t_d("[!]<#chan>"),
31 t_d("Remove an entry, needs to be an exact match"),
32 [=](const CString& sLine) { OnDelCommand(sLine); });
33 AddCommand("List", "", t_d("List all entries"),
34 [=](const CString& sLine) { OnListCommand(sLine); });
35 m_recentlyCycled.SetTTL(15 * 1000);
36 }
37
~CAutoCycleMod()38 ~CAutoCycleMod() override {}
39
OnLoad(const CString & sArgs,CString & sMessage)40 bool OnLoad(const CString& sArgs, CString& sMessage) override {
41 VCString vsChans;
42 sArgs.Split(" ", vsChans, false);
43
44 for (const CString& sChan : vsChans) {
45 if (!Add(sChan)) {
46 PutModule(t_f("Unable to add {1}")(sChan));
47 }
48 }
49
50 // Load our saved settings, ignore errors
51 MCString::iterator it;
52 for (it = BeginNV(); it != EndNV(); ++it) {
53 Add(it->first);
54 }
55
56 // Default is auto cycle for all channels
57 if (m_vsChans.empty()) Add("*");
58
59 return true;
60 }
61
OnAddCommand(const CString & sLine)62 void OnAddCommand(const CString& sLine) {
63 CString sChan = sLine.Token(1);
64
65 if (AlreadyAdded(sChan)) {
66 PutModule(t_f("{1} is already added")(sChan));
67 } else if (Add(sChan)) {
68 PutModule(t_f("Added {1} to list")(sChan));
69 } else {
70 PutModule(t_s("Usage: Add [!]<#chan>"));
71 }
72 }
73
OnDelCommand(const CString & sLine)74 void OnDelCommand(const CString& sLine) {
75 CString sChan = sLine.Token(1);
76
77 if (Del(sChan))
78 PutModule(t_f("Removed {1} from list")(sChan));
79 else
80 PutModule(t_s("Usage: Del [!]<#chan>"));
81 }
82
OnListCommand(const CString & sLine)83 void OnListCommand(const CString& sLine) {
84 CTable Table;
85 Table.AddColumn(t_s("Channel"));
86 Table.SetStyle(CTable::ListStyle);
87
88 for (const CString& sChan : m_vsChans) {
89 Table.AddRow();
90 Table.SetCell(t_s("Channel"), sChan);
91 }
92
93 for (const CString& sChan : m_vsNegChans) {
94 Table.AddRow();
95 Table.SetCell(t_s("Channel"), "!" + sChan);
96 }
97
98 if (Table.size()) {
99 PutModule(Table);
100 } else {
101 PutModule(t_s("You have no entries."));
102 }
103 }
104
OnPart(const CNick & Nick,CChan & Channel,const CString & sMessage)105 void OnPart(const CNick& Nick, CChan& Channel,
106 const CString& sMessage) override {
107 AutoCycle(Channel);
108 }
109
OnQuit(const CNick & Nick,const CString & sMessage,const vector<CChan * > & vChans)110 void OnQuit(const CNick& Nick, const CString& sMessage,
111 const vector<CChan*>& vChans) override {
112 for (CChan* pChan : vChans) AutoCycle(*pChan);
113 }
114
OnKick(const CNick & Nick,const CString & sOpNick,CChan & Channel,const CString & sMessage)115 void OnKick(const CNick& Nick, const CString& sOpNick, CChan& Channel,
116 const CString& sMessage) override {
117 AutoCycle(Channel);
118 }
119
120 protected:
AutoCycle(CChan & Channel)121 void AutoCycle(CChan& Channel) {
122 if (!IsAutoCycle(Channel.GetName())) return;
123
124 // Did we recently annoy opers via cycling of an empty channel?
125 if (m_recentlyCycled.HasItem(Channel.GetName())) return;
126
127 // Is there only one person left in the channel?
128 if (Channel.GetNickCount() != 1) return;
129
130 // Is that person us and we don't have op?
131 const CNick& pNick = Channel.GetNicks().begin()->second;
132 if (!pNick.HasPerm(CChan::Op) &&
133 pNick.NickEquals(GetNetwork()->GetCurNick())) {
134 Channel.Cycle();
135 m_recentlyCycled.AddItem(Channel.GetName());
136 }
137 }
138
AlreadyAdded(const CString & sInput)139 bool AlreadyAdded(const CString& sInput) {
140 CString sChan = sInput;
141 if (sChan.TrimPrefix("!")) {
142 for (const CString& s : m_vsNegChans) {
143 if (s.Equals(sChan)) return true;
144 }
145 } else {
146 for (const CString& s : m_vsChans) {
147 if (s.Equals(sChan)) return true;
148 }
149 }
150 return false;
151 }
152
Add(const CString & sChan)153 bool Add(const CString& sChan) {
154 if (sChan.empty() || sChan == "!") {
155 return false;
156 }
157
158 if (sChan.Left(1) == "!") {
159 m_vsNegChans.push_back(sChan.substr(1));
160 } else {
161 m_vsChans.push_back(sChan);
162 }
163
164 // Also save it for next module load
165 SetNV(sChan, "");
166
167 return true;
168 }
169
Del(const CString & sChan)170 bool Del(const CString& sChan) {
171 vector<CString>::iterator it, end;
172
173 if (sChan.empty() || sChan == "!") return false;
174
175 if (sChan.Left(1) == "!") {
176 CString sTmp = sChan.substr(1);
177 it = m_vsNegChans.begin();
178 end = m_vsNegChans.end();
179
180 for (; it != end; ++it)
181 if (*it == sTmp) break;
182
183 if (it == end) return false;
184
185 m_vsNegChans.erase(it);
186 } else {
187 it = m_vsChans.begin();
188 end = m_vsChans.end();
189
190 for (; it != end; ++it)
191 if (*it == sChan) break;
192
193 if (it == end) return false;
194
195 m_vsChans.erase(it);
196 }
197
198 DelNV(sChan);
199
200 return true;
201 }
202
IsAutoCycle(const CString & sChan)203 bool IsAutoCycle(const CString& sChan) {
204 for (const CString& s : m_vsNegChans) {
205 if (sChan.WildCmp(s, CString::CaseInsensitive)) {
206 return false;
207 }
208 }
209
210 for (const CString& s : m_vsChans) {
211 if (sChan.WildCmp(s, CString::CaseInsensitive)) {
212 return true;
213 }
214 }
215
216 return false;
217 }
218
219 private:
220 vector<CString> m_vsChans;
221 vector<CString> m_vsNegChans;
222 TCacheMap<CString> m_recentlyCycled;
223 };
224
225 template <>
TModInfo(CModInfo & Info)226 void TModInfo<CAutoCycleMod>(CModInfo& Info) {
227 Info.SetWikiPage("autocycle");
228 Info.SetHasArgs(true);
229 Info.SetArgsHelpText(Info.t_s(
230 "List of channel masks and channel masks with ! before them."));
231 }
232
233 NETWORKMODULEDEFS(
234 CAutoCycleMod,
235 t_s("Rejoins channels to gain Op if you're the only user left"))
236