-
Notifications
You must be signed in to change notification settings - Fork 3
/
fpc.py
1886 lines (1616 loc) · 62.4 KB
/
fpc.py
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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
This bot runs as FPCBot on wikimedia commons
It implements vote counting and supports bot runs as FPCBot on wikimedia commons
It implements vote counting and supports
moving the finished nomination to the archive.
Programmed by Daniel78 at Commons.
It adds the following commandline arguments:
-test Perform a testrun against an old log
-close Close and add result to the nominations
-info Just print the vote count info about the current nominations
-park Park closed and verified candidates
-auto Do not ask before commiting edits to articles
-dry Do not submit any edits, just print them
-threads Use threads to speed things up, can't be used in interactive mode
-fpc Handle the featured candidates (if neither -fpc or -delist is used all candidates are handled)
-delist Handle the delisting candidates (if neither -fpc or -delist is used all candidates are handled)
-notime Avoid displaying timestamps in log output
-match pattern Only operate on candidates matching this pattern
"""
import pywikibot, re, datetime, sys, signal
# Imports needed for threading
import threading, time
from pywikibot import config
class NotImplementedException(Exception):
"""Not implemented."""
class ThreadCheckCandidate(threading.Thread):
def __init__(self, candidate, check):
threading.Thread.__init__(self)
self.candidate = candidate
self.check = check
def run(self):
self.check(self.candidate)
class Candidate:
"""
This is one picture candidate
This class just serves as base for the DelistCandidate and FPCandidate classes
"""
def __init__(
self,
page,
ProR,
ConR,
NeuR,
ProString,
ConString,
ReviewedR,
CountedR,
VerifiedR,
):
"""Page is a pywikibot.Page object ."""
# Later perhaps this can be cleaned up by letting the subclasses keep the variables
self.page = page
self._pro = 0
self._con = 0
self._neu = 0
self._proR = ProR # Regexp for positive votes
self._conR = ConR # Regexp for negative votes
self._neuR = NeuR # Regexp for neutral votes
self._proString = ProString
self._conString = ConString
self._ReviewedR = ReviewedR
self._CountedR = CountedR
self._VerifiedR = VerifiedR
self._votesCounted = False
self._daysOld = -1
self._daysSinceLastEdit = -1
self._creationTime = None
self._imgCount = None
self._fileName = None
self._alternative = None
self._listPageName = None
def printAllInfo(self):
"""
Console output of all information sought after
"""
try:
self.countVotes()
out(
"%s: S:%02d O:%02d N:%02d D:%02d De:%02d Se:%d Im:%02d W:%s (%s)"
% (
self.cutTitle(),
self._pro,
self._con,
self._neu,
self.daysOld(),
self.daysSinceLastEdit(),
self.sectionCount(),
self.imageCount(),
self.isWithdrawn(),
self.statusString(),
)
)
except pywikibot.exceptions.NoPageError:
out("%s: -- No such page -- " % self.cutTitle(), color="lightred")
def nominator(self, link=True):
"""Return the link to the user that nominated this candidate."""
history = self.page.revisions(reverse=True, total=1)
for data in history:
username = data.user
if not history:
return "Unknown"
if link:
return "[[User:%s|%s]]" % (username, username)
else:
return username
def creator(self):
"""Return the link to the user that created the image, Not implemented yet."""
pass
def isSet(self):
"""Check if the nomination page has "/[Ss]et/" in it, if yes it must be a set nomination."""
if re.search(r"/[Ss]et", self.page.title()):
return True
else:
return False
def setFiles(self):
"""Try to return list of all files in a set, files in the last gallery in the nomination page."""
m = re.search(r"<gallery([^\]]*)</gallery>", self.page.get(get_redirect=True))
text_inside_gallery = m.group(1)
filesList = []
for line in text_inside_gallery.splitlines():
if line.startswith("File:"):
files = re.sub(r"\|.*", "", line)
filesList.append(files)
return filesList
def findGalleryOfFile(self):
"""Try to find Gallery in the nomination page to make closing users life easier."""
text = self.page.get(get_redirect=True)
RegexGallery = re.compile(
r"(?:.*)Gallery(?:.*)(?:\s.*)\[\[Commons\:Featured[_ ]pictures\/([^\]]{1,180})"
)
matches = RegexGallery.finditer(text)
for m in matches:
Gallery = m.group(1)
try:
Gallery
except:
Gallery = ""
return Gallery
def countVotes(self):
"""
Counts all the votes for this nomination
and subtracts eventual striked out votes
"""
if self._votesCounted:
return
text = self.page.get(get_redirect=True)
if text:
text = filter_content(text)
self._pro = len(re.findall(self._proR, text))
self._con = len(re.findall(self._conR, text))
self._neu = len(re.findall(self._neuR, text))
else:
out("Warning - %s has no content" % self.page, color="lightred")
self._votesCounted = True
def isWithdrawn(self):
"""Withdrawn nominations should not be counted."""
text = self.page.get(get_redirect=True)
text = filter_content(text)
withdrawn = len(re.findall(WithdrawnR, text))
return withdrawn > 0
def isFPX(self):
"""Page marked with FPX template."""
return len(re.findall(FpxR, self.page.get(get_redirect=True)))
def rulesOfFifthDay(self):
"""Check if any of the rules of the fifth day can be applied."""
if self.daysOld() < 5:
return False
self.countVotes()
# First rule of the fifth day
if self._pro <= 1:
return True
# Second rule of the fifth day
if self._pro >= 10 and self._con == 0:
return True
def closePage(self):
"""
Will add the voting results to the page if it is finished.
If it was, True is returned else False
"""
# First make a check that the page actually exist:
if not self.page.exists():
out('"%s" no such page?!' % self.cutTitle())
return
if (self.isWithdrawn() or self.isFPX()) and self.imageCount() <= 1:
# Will close withdrawn nominations if there are more than one
# full day since the last edit
why = "withdrawn" if self.isWithdrawn() else "FPXed"
oldEnough = self.daysSinceLastEdit() > 0
out(
'"%s" %s %s'
% (
self.cutTitle(),
why,
"closing" if oldEnough else "but waiting a day",
)
)
if not oldEnough:
return False
self.moveToLog(why)
return True
# We skip rule of the fifth day if we have several alternatives
fifthDay = False if self.imageCount() > 1 else self.rulesOfFifthDay()
if not fifthDay and not self.isDone():
out('"%s" is still active, ignoring' % self.cutTitle())
return False
old_text = self.page.get(get_redirect=True)
if not old_text:
out("Warning - %s has no content" % self.page, color="lightred")
return False
if re.search(r"{{\s*FPC-closed-ignored.*}}", old_text):
out('"%s" is marked as ignored, so ignoring' % self.cutTitle())
return False
if re.search(self._CountedR, old_text):
out('"%s" needs review, ignoring' % self.cutTitle())
return False
if re.search(self._ReviewedR, old_text):
out('"%s" already closed and reviewed, ignoring' % self.cutTitle())
return False
if self.imageCount() <= 1:
self.countVotes()
result = self.getResultString()
new_text = old_text + result
# Add the featured status to the header
if self.imageCount() <= 1:
new_text = self.fixHeader(new_text)
self.commit(
old_text,
new_text,
self.page,
self.getCloseCommitComment()
+ (" (FifthDay=%s)" % ("yes" if fifthDay else "no")),
)
return True
def fixHeader(self, text, value=None):
"""
Will append the featured status to the header of the candidate
Will return the new text
@param value If specified ("yes" or "no" string will be based on it, otherwise isPassed() is used)
"""
# Check if they are alredy there
if re.match(r"===.*(%s|%s)===" % (self._proString, self._conString), text):
return text
status = ""
if value:
if value == "yes":
status = ", %s" % self._proString
elif value == "no":
status = ", %s" % self._conString
if len(status) < 1:
status = (
", %s" % self._proString
if self.isPassed()
else ", %s" % self._conString
)
return re.sub(r"(===.*)(===)", r"\1%s\2" % status, text, 1)
def getResultString(self):
"""Must be implemented by the subclasses (Text to add to closed pages)."""
raise NotImplementedException()
def getCloseCommitComment(self):
"""Must be implemened by the subclasses (Commit comment for closed pages)."""
raise NotImplementedException()
def creationTime(self):
"""
Find the time that this candidate was created
If we can't find the creation date, for example due to
the page not existing we return now() such that we
will ignore this nomination as too young.
"""
if self._creationTime:
return self._creationTime
history = self.page.revisions(reverse=True, total=1)
if not history:
out(
"Could not retrieve history for '%s', returning now()"
% self.page.title()
)
return datetime.datetime.now()
for data in history:
self._creationTime = data["timestamp"]
# print "C:" + self._creationTime.isoformat()
# print "N:" + datetime.datetime.utcnow().isoformat()
return self._creationTime
def statusString(self):
"""Short status string about the candidate."""
if self.isIgnored():
return "Ignored"
elif self.isWithdrawn():
return "Withdrawn"
elif not self.isDone():
return "Active"
else:
return self._proString if self.isPassed() else self._conString
def daysOld(self):
"""Find the number of days this nomination has existed."""
if self._daysOld != -1:
return self._daysOld
delta = datetime.datetime.utcnow() - self.creationTime()
self._daysOld = delta.days
return self._daysOld
def daysSinceLastEdit(self):
"""
Number of whole days since last edit
If the value can not be found -1 is returned
"""
if self._daysSinceLastEdit != -1:
return self._daysSinceLastEdit
try:
lastEdit = datetime.datetime.strptime(
str(self.page.editTime()), "%Y-%m-%dT%H:%M:%SZ"
)
except:
return -1
delta = datetime.datetime.utcnow() - lastEdit
self._daysSinceLastEdit = delta.days
return self._daysSinceLastEdit
def isDone(self):
"""
Checks if a nomination can be closed
"""
return self.daysOld() >= 9
def isPassed(self):
"""
Find if an image can be featured.
Does not check the age, it needs to be
checked using isDone()
"""
if self.isWithdrawn():
return False
if not self._votesCounted:
self.countVotes()
return self._pro >= 7 and (self._pro >= 2 * self._con)
def isIgnored(self):
"""Some nominations currently require manual check."""
return self.imageCount() > 1
def sectionCount(self):
"""Count the number of sections in this candidate."""
text = self.page.get(get_redirect=True)
return len(re.findall(SectionR, text))
def imageCount(self):
"""
Count the number of images that are displayed
Does not count images that are below a certain threshold
as they probably are just inline icons and not separate
edits of this candidate.
"""
if self._imgCount:
return self._imgCount
text = self.page.get(get_redirect=True)
matches = []
for m in re.finditer(ImagesR, text):
matches.append(m)
count = len(matches)
if count >= 2:
# We have several images, check if they are too small to be counted
for img in matches:
if re.search(ImagesThumbR, img.group(0)):
count -= 1
else:
s = re.search(ImagesSizeR, img.group(0))
if s and (int(s.group(1)) <= 150):
count -= 1
self._imgCount = count
return count
def existingResult(self):
"""
Will scan this nomination and check whether it has
already been closed, and if so parses for the existing
result.
The return value is a list of tuples, and normally
there should only be one such tuple. The tuple
contains four values:
support,oppose,neutral,(featured|not featured)
"""
text = self.page.get(get_redirect=True)
return re.findall(PreviousResultR, text)
def compareResultToCount(self):
"""
If there is an existing result we will compare
it to a new vote count made by this bot and
see if they match. This is for testing purposes
of the bot and to find any incorrect old results.
"""
res = self.existingResult()
if self.isWithdrawn():
out("%s: (ignoring, was withdrawn)" % self.cutTitle())
return
elif self.isFPX():
out("%s: (ignoring, was FPXed)" % self.cutTitle())
return
elif not res:
out("%s: (ignoring, has no results)" % self.cutTitle())
return
elif len(res) > 1:
out("%s: (ignoring, has several results)" % self.cutTitle())
return
# We have one result, so make a vote count and compare
old_res = res[0]
was_featured = old_res[3] == "featured"
ws = int(old_res[0])
wo = int(old_res[1])
wn = int(old_res[2])
self.countVotes()
if (
self._pro == ws
and self._con == wo
and self._neu == wn
and was_featured == self.isPassed()
):
status = "OK"
else:
status = "FAIL"
# List info to console
out(
"%s: S%02d/%02d O:%02d/%02d N%02d/%02d F%d/%d (%s)"
% (
self.cutTitle(),
self._pro,
ws,
self._con,
wo,
self._neu,
wn,
self.isPassed(),
was_featured,
status,
)
)
def cutTitle(self):
"""Returns a fixed width title."""
return re.sub(PrefixR, "", self.page.title())[0:50].ljust(50)
def cleanTitle(self, keepExtension=False):
"""
Returns a title string without prefix and extension
Note that this always operates on the original title and that
a possible change by the alternative parameter is not considered,
but maybe it should be ?
"""
noprefix = re.sub(PrefixR, "", self.page.title())
if keepExtension:
return noprefix
else:
return re.sub(r"\.\w{1,3}$\s*", "", noprefix)
def fileName(self, alternative=True):
"""
Return only the filename of this candidate
This is first based on the title of the page but if that page is not found
then the first image link in the page is used.
Will return the new file name if moved.
@param alternative if false disregard any alternative and return the real filename
"""
# The regexp here also removes any possible crap between the prefix
# and the actual start of the filename.
if alternative and self._alternative:
return self._alternative
if self._fileName:
return self._fileName
self._fileName = re.sub(
"(%s.*?)([Ff]ile|[Ii]mage)" % candPrefix, r"\2", self.page.title()
)
if not pywikibot.Page(G_Site, self._fileName).exists():
match = re.search(ImagesR, self.page.get(get_redirect=True))
if match:
self._fileName = match.group(1)
# Check if file was moved after nomination
page = pywikibot.Page(G_Site, self._fileName)
if page.isRedirectPage():
self._fileName = page.getRedirectTarget().title()
return self._fileName
def addToFeaturedList(self, gallery):
"""
Will add this page to the list of featured images.
This uses just the base of the gallery, like 'Animals'.
Should only be called on closed and verified candidates
This is ==STEP 1== of the parking procedure
@param gallery The categorization gallery
"""
if self.isSet():
file = (self.setFiles())[0] # Add the first file from gallery.
else:
file = self.fileName()
listpage = "Commons:Featured pictures, list"
page = pywikibot.Page(G_Site, listpage)
old_text = page.get(get_redirect=True)
# First check if we are already on the page,
# in that case skip. Can happen if the process
# have been previously interrupted.
if re.search(wikipattern(file), old_text):
out(
"Skipping addToFeaturedList for '%s', page already listed."
% self.cleanTitle(),
color="lightred",
)
return
# This function first needs to find the gallery
# then inside the gallery tags remove the last line and
# add this candidate to the top
# Thanks KODOS for a nice regexp gui
# This adds ourself first in the list of length 4 and removes the last
# all in the chosen gallery
out("Looking for gallery: '%s'" % wikipattern(gallery))
ListPageR = re.compile(
r"(^==\s*{{{\s*\d+\s*\|%s\s*}}}\s*==\s*<gallery.*>\s*)(.*\s*)(.*\s*.*\s*)(.*\s*)(</gallery>)"
% wikipattern(gallery),
re.MULTILINE,
)
new_text = re.sub(ListPageR, r"\1%s\n\2\3\5" % file, old_text)
self.commit(old_text, new_text, page, "Added [[%s]]" % file)
def addToCategorizedFeaturedList(self, gallery):
"""
Adds the candidate to the gallery of
pictures. This is the full gallery with
the section in that particular page.
This is ==STEP 2== of the parking procedure
@param gallery The categorization gallery
If it's a set, will add all file from the list,
else just one if single nomination.
"""
if self.isSet():
files = self.setFiles()
else:
files = []
files.append(self.fileName())
for file in files:
gallery_full_path = "Commons:Featured pictures/" + re.sub(
r"#.*", "", gallery
)
page = pywikibot.Page(G_Site, gallery_full_path)
old_text = page.get(get_redirect=True)
section_R = r"#(.*)"
m = re.search(section_R, gallery)
try:
section = m.group(1)
except AttributeError:
section = None
if section != None:
# Trying to generate a regex for finding the section in a gallery if specified in nomination
# First we are escaping all parentheses, as they are used in regex
# Replacement of all uunderscore with \s , some users just copy the url
# Replacing all \s with \s(?:\s*|)\s, user have linked the section to categories. Why ? To make our lives harder
section = (
section.replace(")", "\)")
.replace("(", "\(")
.replace("_", " ")
.replace(" ", " (?:\[{2}|\]{2}|) ")
)
section_search_R = (
section + r"(?:(?:[^\{\}]|\n)*?)(</gallery>)"
).replace(" ", "(?:\s*|)")
m = re.search(section_search_R, old_text)
try:
old_section = m.group()
except AttributeError:
section = None
# First check if we are already on the page,
# in that case skip. Can happen if the process
# have been previously interrupted.
if re.search(wikipattern(file), old_text):
out(
"Skipping addToCategorizedFeaturedList for '%s', page already listed."
% self.cleanTitle(),
color="lightred",
)
return
# If we found a section, we try to add the image in the section else add to the bottom most gallery (unsorted)
if section != None:
file_info = "%s|%s\n</gallery>" % (file, self.cleanTitle())
updated_section = re.sub(r"</gallery>", file_info, old_section)
new_text = old_text.replace(old_section, updated_section)
else:
# We just need to append to the bottom of the gallery with an added title
# The regexp uses negative lookahead such that we place the candidate in the
# last gallery on the page.
new_text = re.sub(
"(?s)</gallery>(?!.*</gallery>)",
"%s|%s\n</gallery>" % (file, self.cleanTitle()),
old_text,
1,
)
self.commit(old_text, new_text, page, "Added [[%s]]" % file)
def getImagePage(self):
"""Get the image page itself."""
return pywikibot.Page(G_Site, self.fileName())
def addAssessments(self):
"""
Adds the the assessments template to a featured
pictures descripion page.
This is ==STEP 3== of the parking procedure
Will add assessments to all files in a set
"""
if self.isSet():
files = self.setFiles()
else:
files = []
files.append(self.fileName())
for file in files:
page = pywikibot.Page(G_Site, file)
current_page = page
old_text = page.get(get_redirect=True)
AssR = re.compile(r"{{\s*[Aa]ssessments\s*\|(.*)}}")
fn_or = self.fileName(alternative=False) # Original filename
fn_al = self.fileName(alternative=True) # Alternative filename
# We add the com-nom parameter if the original filename
# differs from the alternative filename.
comnom = (
"|com-nom=%s" % fn_or.replace("File:", "") if fn_or != fn_al else ""
)
# The template needs the com-nom to link to the site from file page
if self.isSet():
comnom = "|com-nom=" + (
re.search(r"/[Ss]et/(.*)", self.page.title())
).group(1)
else:
pass
# First check if there already is an assessments template on the page
params = re.search(AssR, old_text)
if params:
# Make sure to remove any existing com/features or subpage params
# TODO: 'com' will be obsolete in the future and can then be removed
# TODO: 'subpage' is the old name of com-nom. Can be removed later.
params = re.sub(r"\|\s*(?:featured|com)\s*=\s*\d+", "", params.group(1))
params = re.sub(r"\|\s*(?:subpage|com-nom)\s*=\s*[^{}|]+", "", params)
params += "|featured=1"
params += comnom
if params.find("|") != 0:
params = "|" + params
new_ass = "{{Assessments%s}}" % params
new_text = re.sub(AssR, new_ass, old_text)
if new_text == old_text:
out(
"No change in addAssessments, '%s' already featured."
% self.cleanTitle()
)
return
else:
# There is no assessments template so just add it
if re.search(r"\{\{(?:|\s*)[Ll]ocation", old_text):
end = findEndOfTemplate(old_text, "[Ll]ocation")
elif re.search(r"\{\{(?:|\s*)[Oo]bject[_\s][Ll]ocation", old_text):
end = findEndOfTemplate(old_text, "[Oo]bject[_\s][Ll]ocation")
else:
end = findEndOfTemplate(old_text, "[Ii]nformation")
new_text = (
old_text[:end]
+ "\n{{Assessments|featured=1%s}}\n" % comnom
+ old_text[end:]
)
# new_text = re.sub(r'({{\s*[Ii]nformation)',r'{{Assessments|featured=1}}\n\1',old_text)
self.commit(old_text, new_text, current_page, "FPC promotion")
def addToCurrentMonth(self):
"""
Adds the candidate to the list of featured picture this month
This is ==STEP 4== of the parking procedure
"""
if self.isSet():
files = (self.setFiles())[:1] # The first file from gallery.
else:
files = []
files.append(self.fileName())
for file in files:
FinalVotesR = re.compile(
r"FPC-results-reviewed\|support=([0-9]{0,3})\|oppose=([0-9]{0,3})\|neutral=([0-9]{0,3})\|"
)
NomPagetext = self.page.get(get_redirect=True)
matches = FinalVotesR.finditer(NomPagetext)
for m in matches:
if m is None:
ws = wo = wn = "x"
else:
ws = m.group(1)
wo = m.group(2)
wn = m.group(3)
today = datetime.date.today()
monthpage = "Commons:Featured_pictures/chronological/%s %s" % (
datetime.datetime.utcnow().strftime("%B"),
today.year,
)
page = pywikibot.Page(G_Site, monthpage)
try:
old_text = page.get(get_redirect=True)
except pywikibot.exceptions.NoPageError:
old_text = ""
# First check if we are already on the page,
# in that case skip. Can happen if the process
# have been previously interrupted.
if re.search(wikipattern(file), old_text):
out(
"Skipping addToCurrentMonth for '%s', page already listed."
% self.cleanTitle(),
color="lightred",
)
return
# Find the number of lines in the gallery, if AttributeError set count as 1
m = re.search(r"(?ms)<gallery>(.*)</gallery>", old_text)
try:
count = m.group(0).count("\n")
except AttributeError:
count = 1
# We just need to append to the bottom of the gallery
# with an added title
# TODO: We lack a good way to find the creator, so it is left out at the moment
if count == 1:
old_text = (
"{{subst:FPArchiveChrono}}\n== %s %s ==\n<gallery>\n</gallery>"
% (
datetime.datetime.utcnow().strftime("%B"),
today.year,
)
)
else:
pass
if self.isSet():
file_title = "'''%s''' - a set of %s files" % (
(re.search(r"/[Ss]et/(.*)", self.page.title())).group(1),
str(len(self.setFiles())),
)
else:
file_title = self.cleanTitle()
new_text = re.sub(
"</gallery>",
"%s|%d '''%s''' <br> uploaded by %s, nominated by %s,<br> {{s|%s}}, {{o|%s}}, {{n|%s}} \n</gallery>"
% (
file,
count,
file_title,
uploader(file),
self.nominator(),
ws,
wo,
wn,
),
old_text,
)
self.commit(old_text, new_text, page, "Added [[%s]]" % file)
def notifyNominator(self):
"""
Add a template to the nominators talk page
This is ==STEP 5== of the parking procedure
"""
talk_link = "User_talk:%s" % self.nominator(link=False)
talk_page = pywikibot.Page(G_Site, talk_link)
try:
old_text = talk_page.get(get_redirect=True)
except pywikibot.exceptions.NoPageError:
out(
"notifyNominator: No such page '%s' but ignoring..." % talk_link,
color="lightred",
)
return
fn_or = self.fileName(alternative=False) # Original filename
fn_al = self.fileName(alternative=True) # Alternative filename
# First check if we are already on the page,
# in that case skip. Can happen if the process
# have been previously interrupted.
# We add the subpage parameter if the original filename
# differs from the alternative filename.
subpage = "|subpage=%s" % fn_or if fn_or != fn_al else ""
# notification for set candidates should add a gallery to talk page and
# it should be special compared to usual promotions.
if self.isSet():
if re.search(r"{{FPpromotionSet\|%s}}" % wikipattern(fn_al), old_text):
return
files_newline_string = converttostr(self.setFiles(), "\n")
new_text = old_text + "\n\n== Set Promoted to FP ==\n<gallery mode=packed heights=80px>%s\n</gallery>\n{{FPpromotionSet|%s%s}} /~~~~" % (
files_newline_string,
fn_al,
subpage,
)
try:
self.commit(
old_text, new_text, talk_page, "FPC promotion of [[%s]]" % fn_al
)
except pywikibot.exceptions.LockedPageError as error:
out(
"Page is locked '%s', but ignoring since it's just the user notification."
% error,
color="lightyellow",
)
return
else:
pass
if re.search(r"{{FPpromotion\|%s}}" % wikipattern(fn_or), old_text):
out(
"Skipping notifyNominator for '%s', page already listed at '%s'."
% (self.cleanTitle(), talk_link),
color="lightred",
)
return
new_text = old_text + "\n\n== FP Promotion ==\n{{FPpromotion|%s%s}} /~~~~" % (
fn_al,
subpage,
)
try:
self.commit(
old_text, new_text, talk_page, "FPC promotion of [[%s]]" % fn_al
)
except pywikibot.exceptions.LockedPageError as error:
out(
"Page is locked '%s', but ignoring since it's just the user notification."
% error,
color="lightyellow",
)
def notifyUploader(self):
"""
Add a template to the uploaders talk page
This is ==STEP 6== of the parking procedure
"""
if self.isSet():
files = self.setFiles()
else:
files = []
files.append(self.fileName())
for file in files:
# Check if nominator and uploaders are same, avoiding adding a template twice
if self.nominator() == uploader(file, link=True):
continue
talk_link = "User_talk:%s" % uploader(file, link=False)
talk_page = pywikibot.Page(G_Site, talk_link)
try:
old_text = talk_page.get(get_redirect=True)
except pywikibot.exceptions.NoPageError:
out(
"notifyUploader: No such page '%s' but ignoring..." % talk_link,
color="lightred",
)
return
fn_or = self.fileName(alternative=False) # Original filename
fn_al = self.fileName(alternative=True) # Alternative filename
# First check if we are already on the page,
# in that case skip. Can happen if the process
# have been previously interrupted.
if re.search(r"{{FPpromotion\|%s}}" % wikipattern(fn_or), old_text):
out(
"Skipping notifyUploader for '%s', page already listed at '%s'."
% (self.cleanTitle(), talk_link),
color="lightred",
)
return
# We add the subpage parameter if the original filename
# differs from the alternative filename.
subpage = "|subpage=%s" % fn_or if fn_or != fn_al else ""
if self.isSet():
subpage = "|subpage=" + (
re.search(r"[Ss]et/(.*)", self.page.title())
).group(0)