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