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/Modules.h>
19
20 using std::vector;
21
22 class CAttachMatch {
23 public:
CAttachMatch(CModule * pModule,const CString & sChannels,const CString & sSearch,const CString & sHostmasks,bool bNegated)24 CAttachMatch(CModule* pModule, const CString& sChannels,
25 const CString& sSearch, const CString& sHostmasks,
26 bool bNegated) {
27 m_pModule = pModule;
28 m_sChannelWildcard = sChannels;
29 m_sSearchWildcard = sSearch;
30 m_sHostmaskWildcard = sHostmasks;
31 m_bNegated = bNegated;
32
33 if (m_sChannelWildcard.empty()) m_sChannelWildcard = "*";
34 if (m_sSearchWildcard.empty()) m_sSearchWildcard = "*";
35 if (m_sHostmaskWildcard.empty()) m_sHostmaskWildcard = "*!*@*";
36 }
37
IsMatch(const CString & sChan,const CString & sHost,const CString & sMessage) const38 bool IsMatch(const CString& sChan, const CString& sHost,
39 const CString& sMessage) const {
40 if (!sHost.WildCmp(m_sHostmaskWildcard, CString::CaseInsensitive))
41 return false;
42 if (!sChan.WildCmp(m_sChannelWildcard, CString::CaseInsensitive))
43 return false;
44 if (!sMessage.WildCmp(m_pModule->ExpandString(m_sSearchWildcard),
45 CString::CaseInsensitive))
46 return false;
47 return true;
48 }
49
IsNegated() const50 bool IsNegated() const { return m_bNegated; }
51
GetHostMask() const52 const CString& GetHostMask() const { return m_sHostmaskWildcard; }
53
GetSearch() const54 const CString& GetSearch() const { return m_sSearchWildcard; }
55
GetChans() const56 const CString& GetChans() const { return m_sChannelWildcard; }
57
ToString()58 CString ToString() {
59 CString sRes;
60 if (m_bNegated) sRes += "!";
61 sRes += m_sChannelWildcard;
62 sRes += " ";
63 sRes += m_sSearchWildcard;
64 sRes += " ";
65 sRes += m_sHostmaskWildcard;
66 return sRes;
67 }
68
69 private:
70 bool m_bNegated;
71 CModule* m_pModule;
72 CString m_sChannelWildcard;
73 CString m_sSearchWildcard;
74 CString m_sHostmaskWildcard;
75 };
76
77 class CChanAttach : public CModule {
78 public:
79 typedef vector<CAttachMatch> VAttachMatch;
80 typedef VAttachMatch::iterator VAttachIter;
81
82 private:
HandleAdd(const CString & sLine)83 void HandleAdd(const CString& sLine) {
84 CString sMsg = sLine.Token(1, true);
85 bool bHelp = false;
86 bool bNegated = sMsg.TrimPrefix("!");
87 CString sChan = sMsg.Token(0);
88 CString sSearch = sMsg.Token(1);
89 CString sHost = sMsg.Token(2);
90
91 if (sChan.empty()) {
92 bHelp = true;
93 } else if (Add(bNegated, sChan, sSearch, sHost)) {
94 PutModule(t_s("Added to list"));
95 } else {
96 PutModule(t_f("{1} is already added")(sLine.Token(1, true)));
97 bHelp = true;
98 }
99 if (bHelp) {
100 PutModule(t_s("Usage: Add [!]<#chan> <search> <host>"));
101 PutModule(t_s("Wildcards are allowed"));
102 }
103 }
104
HandleDel(const CString & sLine)105 void HandleDel(const CString& sLine) {
106 CString sMsg = sLine.Token(1, true);
107 bool bNegated = sMsg.TrimPrefix("!");
108 CString sChan = sMsg.Token(0);
109 CString sSearch = sMsg.Token(1);
110 CString sHost = sMsg.Token(2);
111
112 if (Del(bNegated, sChan, sSearch, sHost)) {
113 PutModule(t_f("Removed {1} from list")(sChan));
114 } else {
115 PutModule(t_s("Usage: Del [!]<#chan> <search> <host>"));
116 }
117 }
118
HandleList(const CString & sLine)119 void HandleList(const CString& sLine) {
120 CTable Table;
121 Table.AddColumn(t_s("Neg"));
122 Table.AddColumn(t_s("Chan"));
123 Table.AddColumn(t_s("Search"));
124 Table.AddColumn(t_s("Host"));
125
126 VAttachIter it = m_vMatches.begin();
127 for (; it != m_vMatches.end(); ++it) {
128 Table.AddRow();
129 Table.SetCell(t_s("Neg"), it->IsNegated() ? "!" : "");
130 Table.SetCell(t_s("Chan"), it->GetChans());
131 Table.SetCell(t_s("Search"), it->GetSearch());
132 Table.SetCell(t_s("Host"), it->GetHostMask());
133 }
134
135 if (Table.size()) {
136 PutModule(Table);
137 } else {
138 PutModule(t_s("You have no entries."));
139 }
140 }
141
142 public:
MODCONSTRUCTOR(CChanAttach)143 MODCONSTRUCTOR(CChanAttach) {
144 AddHelpCommand();
145 AddCommand(
146 "Add", t_d("[!]<#chan> <search> <host>"),
147 t_d("Add an entry, use !#chan to negate and * for wildcards"),
148 [=](const CString& sLine) { HandleAdd(sLine); });
149 AddCommand("Del", t_d("[!]<#chan> <search> <host>"),
150 t_d("Remove an entry, needs to be an exact match"),
151 [=](const CString& sLine) { HandleDel(sLine); });
152 AddCommand("List", "", t_d("List all entries"),
153 [=](const CString& sLine) { HandleList(sLine); });
154 }
155
~CChanAttach()156 ~CChanAttach() override {}
157
OnLoad(const CString & sArgs,CString & sMessage)158 bool OnLoad(const CString& sArgs, CString& sMessage) override {
159 VCString vsChans;
160 sArgs.Split(" ", vsChans, false);
161
162 for (VCString::const_iterator it = vsChans.begin(); it != vsChans.end();
163 ++it) {
164 CString sAdd = *it;
165 bool bNegated = sAdd.TrimPrefix("!");
166 CString sChan = sAdd.Token(0);
167 CString sSearch = sAdd.Token(1);
168 CString sHost = sAdd.Token(2, true);
169
170 if (!Add(bNegated, sChan, sSearch, sHost)) {
171 PutModule(t_f("Unable to add [{1}]")(*it));
172 }
173 }
174
175 // Load our saved settings, ignore errors
176 MCString::iterator it;
177 for (it = BeginNV(); it != EndNV(); ++it) {
178 CString sAdd = it->first;
179 bool bNegated = sAdd.TrimPrefix("!");
180 CString sChan = sAdd.Token(0);
181 CString sSearch = sAdd.Token(1);
182 CString sHost = sAdd.Token(2, true);
183
184 Add(bNegated, sChan, sSearch, sHost);
185 }
186
187 return true;
188 }
189
TryAttach(const CNick & Nick,CChan & Channel,CString & Message)190 void TryAttach(const CNick& Nick, CChan& Channel, CString& Message) {
191 const CString& sChan = Channel.GetName();
192 const CString& sHost = Nick.GetHostMask();
193 const CString& sMessage = Message;
194 VAttachIter it;
195
196 if (!Channel.IsDetached()) return;
197
198 // Any negated match?
199 for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) {
200 if (it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) return;
201 }
202
203 // Now check for a positive match
204 for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) {
205 if (!it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) {
206 Channel.AttachUser();
207 return;
208 }
209 }
210 }
211
OnChanNotice(CNick & Nick,CChan & Channel,CString & sMessage)212 EModRet OnChanNotice(CNick& Nick, CChan& Channel,
213 CString& sMessage) override {
214 TryAttach(Nick, Channel, sMessage);
215 return CONTINUE;
216 }
217
OnChanMsg(CNick & Nick,CChan & Channel,CString & sMessage)218 EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override {
219 TryAttach(Nick, Channel, sMessage);
220 return CONTINUE;
221 }
222
OnChanAction(CNick & Nick,CChan & Channel,CString & sMessage)223 EModRet OnChanAction(CNick& Nick, CChan& Channel,
224 CString& sMessage) override {
225 TryAttach(Nick, Channel, sMessage);
226 return CONTINUE;
227 }
228
FindEntry(const CString & sChan,const CString & sSearch,const CString & sHost)229 VAttachIter FindEntry(const CString& sChan, const CString& sSearch,
230 const CString& sHost) {
231 VAttachIter it = m_vMatches.begin();
232 for (; it != m_vMatches.end(); ++it) {
233 if (sHost.empty() || it->GetHostMask() != sHost) continue;
234 if (sSearch.empty() || it->GetSearch() != sSearch) continue;
235 if (sChan.empty() || it->GetChans() != sChan) continue;
236 return it;
237 }
238 return m_vMatches.end();
239 }
240
Add(bool bNegated,const CString & sChan,const CString & sSearch,const CString & sHost)241 bool Add(bool bNegated, const CString& sChan, const CString& sSearch,
242 const CString& sHost) {
243 CAttachMatch attach(this, sChan, sSearch, sHost, bNegated);
244
245 // Check for duplicates
246 VAttachIter it = m_vMatches.begin();
247 for (; it != m_vMatches.end(); ++it) {
248 if (it->GetHostMask() == attach.GetHostMask() &&
249 it->GetChans() == attach.GetChans() &&
250 it->GetSearch() == attach.GetSearch())
251 return false;
252 }
253
254 m_vMatches.push_back(attach);
255
256 // Also save it for next module load
257 SetNV(attach.ToString(), "");
258
259 return true;
260 }
261
Del(bool bNegated,const CString & sChan,const CString & sSearch,const CString & sHost)262 bool Del(bool bNegated, const CString& sChan, const CString& sSearch,
263 const CString& sHost) {
264 VAttachIter it = FindEntry(sChan, sSearch, sHost);
265 if (it == m_vMatches.end() || it->IsNegated() != bNegated) return false;
266
267 DelNV(it->ToString());
268 m_vMatches.erase(it);
269
270 return true;
271 }
272
273 private:
274 VAttachMatch m_vMatches;
275 };
276
277 template <>
TModInfo(CModInfo & Info)278 void TModInfo<CChanAttach>(CModInfo& Info) {
279 Info.AddType(CModInfo::UserModule);
280 Info.SetWikiPage("autoattach");
281 Info.SetHasArgs(true);
282 Info.SetArgsHelpText(Info.t_s(
283 "List of channel masks and channel masks with ! before them."));
284 }
285
286 NETWORKMODULEDEFS(CChanAttach, t_s("Reattaches you to channels on activity."))
287