-
Notifications
You must be signed in to change notification settings - Fork 86
/
warnings.cpp
423 lines (388 loc) · 19 KB
/
warnings.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
#include "warnings.hpp"
#include <QtXml>
#include "configuration.hpp"
#include "exception.hpp"
#include "generic.hpp"
#include "huggleparser.hpp"
#include "hooks.hpp"
#include "querypool.hpp"
#include "message.hpp"
#include "revertquery.hpp"
#include "localization.hpp"
#include "hooks.hpp"
#include "syslog.hpp"
#include "wikisite.hpp"
#include "wikiutil.hpp"
#include "wikiuser.hpp"
using namespace Huggle;
QList<PendingWarning*> PendingWarning::PendingWarnings;
PendingWarning::PendingWarning(Message *message, QString warning, WikiEdit *edit)
{
this->Template = warning;
this->RelatedEdit = edit;
// we register a unique consumer here in case that multiple warnings pointers to same
edit->IncRef();
this->Warning = message;
}
PendingWarning::~PendingWarning()
{
this->RelatedEdit->DecRef();
this->Warning->UnregisterConsumer(HUGGLECONSUMER_CORE_MESSAGE);
}
// Fixes https://phabricator.wikimedia.org/T170449
QString sanitize_page_name(QString page_name)
{
if (page_name.startsWith("/"))
{
return ":" + page_name;
}
return page_name;
}
PendingWarning *Warnings::WarnUser(QString warning_type, RevertQuery *dependency, WikiEdit *edit, bool *report)
{
*report = false;
if (edit == nullptr)
{
throw new Huggle::NullPointerException("WikiEdit *Edit", BOOST_CURRENT_FUNCTION);
}
if (Configuration::HuggleConfiguration->DeveloperMode)
{
Generic::DeveloperError();
return nullptr;
}
if (hcfg->UserConfig->ConfirmWarningOnVeryOldEdits || hcfg->UserConfig->SkipWarningOnConfirm)
{
// User doesn't want to send warnings for edits that are too old check if this edit is not too old
if (edit->Time.addDays(1) < QDateTime::currentDateTime())
{
// The edit is older than 1 day, so let's either ignore the request to send warning or ask user if they really want to send it
if (hcfg->UserConfig->SkipWarningOnConfirm)
{
HUGGLE_LOG("Not sending warning to " + edit->User->Username + " for their edit to " + edit->Page->PageName + " on " + edit->GetSite()->Name +
" because it's older than 1 day");
return nullptr;
} else
{
// Ask user if they really want to send a warning here
if (!Hooks::ShowYesNoQuestion("Really send a warning?", "Edit to " + edit->Page->PageName + " on " + edit->GetSite()->Name + " by " +
edit->User->Username + " was is older than 1 day, do you really want to send them a warning message?", false))
return nullptr;
}
}
}
// check if user wasn't changed and if was, let's update the info
edit->User->Resync();
if (edit->User->GetWarningLevel() >= edit->GetSite()->GetProjectConfig()->WarningLevel)
{
// we should report this user instead
if (edit->User->IsReported)
{
// the user is already reported we don't need to do anything
return nullptr;
}
if (!edit->GetSite()->GetProjectConfig()->AIV)
{
// there is no AIV function for this wiki
Syslog::HuggleLogs->WarningLog("This user has already reached level 4 warning and there is no AIV "\
"supported on this wiki, you should block the user now");
return nullptr;
}
if (hcfg->UserConfig->AutomaticReports || Generic::ReportPreFlightCheck())
{
*report = true;
}
return nullptr;
}
edit->User->IncrementWarningLevel();
// We need to update the user so that new user warning level gets propagated everywhere on interface of huggle
edit->User->Update();
// Create a warning template and message to send
QString message_template = warning_type + QString::number(edit->User->GetWarningLevel());
QString message_text = Warnings::RetrieveTemplateToWarn(message_template, edit->GetSite());
// In case that message is empty, stop and show error
if (!message_text.size())
{
Syslog::HuggleLogs->ErrorLog(_l("missing-warning", message_template));
return nullptr;
}
message_text = message_text.replace("$2", edit->GetFullUrl()).replace("$1", edit->Page->PageName);
QString message_summary;
if (!edit->GetSite()->GetProjectConfig()->WarningSummaries.contains(edit->User->GetWarningLevel()))
{
message_summary = edit->GetSite()->GetProjectConfig()->WarningSummaries[1];
} else
{
message_summary = edit->GetSite()->GetProjectConfig()->WarningSummaries[edit->User->GetWarningLevel()];
}
message_summary = message_summary.replace("$1", sanitize_page_name(edit->Page->PageName));
// Configure message heading as defined in project config
QString message_head = edit->GetSite()->GetProjectConfig()->TemplateHeader;
message_head = message_head.replace("$1", sanitize_page_name(edit->Page->PageName));
if (edit->GetSite()->GetProjectConfig()->MessageHeadings == HeadingsStandard)
{
QDateTime server_time = edit->GetSite()->GetProjectConfig()->ServerTime();
message_head = WikiUtil::MonthText(server_time.date().month(), edit->GetSite()) + " " + QString::number(server_time.date().year());
} else if (edit->GetSite()->GetProjectConfig()->MessageHeadings == HeadingsNone)
{
message_head = "";
}
message_text = Warnings::UpdateSharedIPTemplate(edit->User, message_text, edit->GetSite());
// Create only - safety check for API in case that user didn't have a user talk page, we use this parameter for API so that is fails in case someone creates a talk page meanwhile
bool create_only = edit->User->TalkPage_GetContents().isEmpty();
if (hcfg->UserConfig->AutomaticallyWatchlistWarnedUsers)
WikiUtil::Watchlist(edit->User->GetTalkPage());
PendingWarning *pw = new PendingWarning(WikiUtil::MessageUser(edit->User, message_text, message_head, message_summary, true, dependency, false, hcfg->UserConfig->SectionKeep, false,
edit->TPRevBaseTime, create_only, true), warning_type, edit);
Hooks::OnWarning(edit->User);
return pw;
}
void Warnings::ResendWarnings()
{
int warning_ix = 0;
while (warning_ix < PendingWarning::PendingWarnings.count())
{
PendingWarning *warning = PendingWarning::PendingWarnings.at(warning_ix);
if (warning->Query != nullptr)
{
// we are already getting talk page so we need to check if it finished here
if (warning->Query->IsProcessed())
{
// this query is done so we check if it fallen to error now
if (warning->Query->IsFailed())
{
// there was some error, which suck, we print it to console and delete this warning, there is a little point
// in doing anything else to fix it.
Syslog::HuggleLogs->ErrorLog("Unable to retrieve a new version of talk page for user " + warning->RelatedEdit->User->Username
+ " the warning will not be delivered to this user");
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
// we get the new talk page
QDomDocument talk_page;
talk_page.setContent(warning->Query->Result->Data);
QDomNodeList revisions_ = talk_page.elementsByTagName("rev");
QDomNodeList pages_ = talk_page.elementsByTagName("page");
QString TPRevBaseTime = "";
if (pages_.count() > 0)
{
QDomElement e = pages_.at(0).toElement();
if (e.attributes().contains("missing"))
{
// the talk page which existed was probably deleted by someone
Syslog::HuggleLogs->ErrorLog("Unable to retrieve a new version of talk page for user "
+ warning->RelatedEdit->User->Username
+ " because it was deleted meanwhile, the warning will not be delivered to this user");
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
}
// get last id
if (revisions_.count() > 0)
{
QDomElement e = revisions_.at(0).toElement();
if (e.nodeName() == "rev")
{
if (!e.attributes().contains("timestamp"))
{
Huggle::Syslog::HuggleLogs->ErrorLog("Talk page timestamp of " + warning->RelatedEdit->User->Username +
" couldn't be retrieved, mediawiki returned no data for it");
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
} else
{
TPRevBaseTime = e.attribute("timestamp");
}
warning->RelatedEdit->User->TalkPage_SetContents(e.text());
} else
{
// there was some error, which suck, we print it to console and delete this warning, there is a little point
// in doing anything else to fix it.
Syslog::HuggleLogs->ErrorLog("Unable to retrieve a new version of talk page for user "
+ warning->RelatedEdit->User->Username
+ " the warning will not be delivered to this user, check debug logs for more");
Syslog::HuggleLogs->DebugLog(warning->Query->Result->Data);
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
} else
{
// there was some error, which suck, we print it to console and delete this warning, there is a little point
// in doing anything else to fix it.
Syslog::HuggleLogs->ErrorLog("Unable to retrieve a new version of talk page for user " + warning->RelatedEdit->User->Username
+ " the warning will not be delivered to this user, check debug logs for more");
Syslog::HuggleLogs->DebugLog(warning->Query->Result->Data);
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
// so we now have the new talk page content so we need to reclassify the user
warning->RelatedEdit->User->ParseTP(QDate::currentDate());
warning->RelatedEdit->User->Update(true);
// now when we have the new level of warning we can try to send a new warning and hope that talk page wasn't
// changed meanwhile again lol :D
warning->RelatedEdit->TPRevBaseTime = TPRevBaseTime;
bool Report_;
PendingWarning *ptr_warning_ = Warnings::WarnUser(warning->Template, nullptr, warning->RelatedEdit, &Report_);
if (Report_)
{
if (hcfg->UserConfig->AutomaticReports)
{
Hooks::SilentReport(warning->RelatedEdit->User);
}
else
{
Hooks::ReportUser(warning->RelatedEdit->User);
}
}
if (ptr_warning_ != nullptr)
PendingWarning::PendingWarnings.append(ptr_warning_);
// we can delete this warning now because we created another one
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
// in case that it isn't processed yet we can continue on next warning
warning_ix++;
continue;
}
if (warning->Warning->IsFinished())
{
if (!warning->Warning->IsFailed())
{
Hooks::WarningFinished(warning->RelatedEdit);
// we no longer need to care about this one
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
Syslog::HuggleLogs->DebugLog("Failed to deliver message to " + warning->Warning->User->Username);
// we need to dec. the warning level of that user because we didn't deliver the warning message
warning->RelatedEdit->User->DecrementWarningLevel();
warning->RelatedEdit->User->Update();
// check if the warning wasn't delivered because someone edited the page
if (warning->Warning->Error == Huggle::MessageError_Obsolete || warning->Warning->Error == Huggle::MessageError_ArticleExist)
{
Syslog::HuggleLogs->DebugLog("Someone changed the content of " + warning->Warning->User->Username + " reparsing it now");
// we need to fetch the talk page again and later we need to issue new warning
if (warning->Query != nullptr)
{
Syslog::HuggleLogs->DebugLog("Possible memory leak in MainWindow::ResendWarning: warning->Query != nullptr");
}
warning->Query = new Huggle::ApiQuery(ActionQuery, warning->RelatedEdit->GetSite());
warning->Query->Parameters = "prop=revisions&rvprop=" + QUrl::toPercentEncoding("timestamp|user|comment|content") +
"&titles=" + QUrl::toPercentEncoding(warning->Warning->User->GetTalk());
HUGGLE_QP_APPEND(warning->Query);
warning->Query->Target = _l("main-user-retrieving-tp", warning->Warning->User->Username);
warning->Query->Process();
} else if (warning->Warning->Error == Huggle::MessageError_Expired)
{
Syslog::HuggleLogs->DebugLog("Expired " + warning->Warning->User->Username + " reparsing it now");
// we need to fetch the talk page again and later we need to issue new warning
warning->Query = new Huggle::ApiQuery(ActionQuery, warning->RelatedEdit->GetSite());
warning->Query->Parameters = "prop=revisions&rvprop=" + QUrl::toPercentEncoding("timestamp|user|comment|content") +
"&titles=" + QUrl::toPercentEncoding(warning->Warning->User->GetTalk());
HUGGLE_QP_APPEND(warning->Query);
warning->Query->Target = _l("main-user-retrieving-tp", warning->Warning->User->Username);
warning->Query->Process();
} else
{
PendingWarning::PendingWarnings.removeAt(warning_ix);
delete warning;
continue;
}
}
warning_ix++;
continue;
}
}
void Warnings::ForceWarn(int level, WikiEdit *edit)
{
if (Configuration::HuggleConfiguration->DeveloperMode)
{
Generic::DeveloperError();
return;
}
bool instant = false;
if (level == 0)
{
level = 4;
instant = true;
}
if (instant && !edit->GetSite()->GetProjectConfig()->InstantWarnings)
{
Syslog::HuggleLogs->ErrorLog(_l("no-instant", edit->GetSite()->Name, edit->User->UnderscorelessUsername()));
return;
}
if (edit == nullptr)
return;
QString warning_template = edit->GetSite()->GetProjectConfig()->DefaultTemplate + QString::number(level);
QString message_text = Warnings::RetrieveTemplateToWarn(warning_template, edit->GetSite(), instant);
if (!message_text.size())
{
// this is very rare error no need to translate it
Syslog::HuggleLogs->Log("There is no such warning template " + warning_template);
return;
}
message_text = message_text.replace("$2", edit->GetFullUrl()).replace("$1", edit->Page->PageName);
QString message_summary;
if (!edit->GetSite()->GetProjectConfig()->WarningSummaries.contains(edit->User->GetWarningLevel()))
{
message_summary = edit->GetSite()->GetProjectConfig()->WarningSummaries[1];
} else
{
message_summary = edit->GetSite()->GetProjectConfig()->WarningSummaries[edit->User->GetWarningLevel()];
}
message_summary = message_summary.replace("$1", sanitize_page_name(edit->Page->PageName));
QString message_head = edit->GetSite()->GetProjectConfig()->TemplateHeader;
message_head = message_head.replace("$1", sanitize_page_name(edit->Page->PageName));
if (hcfg->UserConfig->EnforceMonthsAsHeaders)
{
QDateTime date_ = edit->GetSite()->GetProjectConfig()->ServerTime();
message_head = WikiUtil::MonthText(date_.date().month(), edit->GetSite()) + " " + QString::number(date_.date().year());
}
message_text = Warnings::UpdateSharedIPTemplate(edit->User, message_text, edit->GetSite());
if (hcfg->UserConfig->AutomaticallyWatchlistWarnedUsers)
WikiUtil::Watchlist(edit->User->GetTalkPage());
WikiUtil::MessageUser(edit->User, message_text, message_head, message_summary, true, nullptr, false, hcfg->UserConfig->SectionKeep, true, edit->TPRevBaseTime);
}
QString Warnings::RetrieveTemplateToWarn(QString type, WikiSite *site, bool force)
{
int x=0;
QString result = "";
while (x < site->GetProjectConfig()->WarningTemplates.count())
{
if (HuggleParser::GetKeyFromSSItem(site->GetProjectConfig()->WarningTemplates.at(x)) == type)
{
result = HuggleParser::GetValueFromSSItem(site->GetProjectConfig()->WarningTemplates.at(x));
if (force)
result += "im";
return result;
}
x++;
}
return "";
}
QString Warnings::UpdateSharedIPTemplate(WikiUser *user, QString text, WikiSite *site)
{
if (!user->IsIP() || site->GetProjectConfig()->SharedIPTemplate.isEmpty())
{
return text;
}
if (!user->TalkPage_ContainsSharedIPTemplate())
{
text += "\n" + site->GetProjectConfig()->SharedIPTemplate + "\n";
}
return text;
}