- Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathtemplate_protector.rb
233 lines (193 loc) · 6.41 KB
/
template_protector.rb
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
$LOAD_PATH << '..'
require'musikbot'
moduleTemplateProtector
PROTECTION_WEIGHT={
:sysop=>40,
:templateeditor=>30,
:extendedconfirmed=>20,
:autoconfirmed=>10
}
defself.run
@mb=MusikBot::Session.new(inspect)
@thresholds=@mb.config[:thresholds]
.select{|_k,v| v.present?}# Non-nil.
.sort_by(&:last)# Sort by transclusion count.
.reverse
.to_h
# Exclusions as strings with underscores instead of spaces.
exclusions=@mb.config[:exclusions].keys.map(&:to_s).map(&:score)
# Minimum transclusion count to check for.
lowest_threshold=@thresholds.values.min
@mb.config[:namespaces].eachdo |ns_id|
# Mainspace is not supported (nor should it be).
nextifns_id == 0
ns_name=namespace_map[ns_id]
@thresholds.keys.eachdo |level|
@mb.log("NAMESPACE #{ns_id} / THRESHOLD #{level}")
fetch_templates(ns_id,level).eachdo |row|
row['title']=row['title'].force_encoding('utf-8')
title="#{ns_name}:#{row['title']}"
edit_level=prot_level(row['count'])
# Skip if excluded.
ifexclusions.include?(title)
@mb.log(">> #{title} excluded")
next
end
# Skip if matches regex exclusions.
ifregex_excluded?(title)
exclusions << title
@mb.log(">> #{title} regex-excluded")
next
end
# Skip if recently protected.
ifrecently_protected?(ns_id,row['title'])
exclusions << title
@mb.log(">> #{title} recently protected")
next
end
# Skip if title is blacklisted.
iftitle_blacklisted?(title,edit_level)
exclusions << title
@mb.log(">> #{title} already blacklisted")
next
end
# Protect!
protect(row,title,edit_level)
end
end
end
rescue=>e
@mb.report_error('Fatal error',e)
end
defself.protect(row,title,edit_level)
old_move_level=get_move_level(row['id'])
# Use the same as edit_level for move, unless it is higher than the edit_level.
move_level=PROTECTION_WEIGHT[old_move_level].to_i > PROTECTION_WEIGHT[edit_level] ? old_move_level : edit_level
@mb.log("PROTECT: #{edit_level}/#{move_level} ~ #{title} ~ #{row['count']}")
if@mb.opts[:dry]
return
end
protections=[{action: 'edit',group: edit_level}]
ifmove_level != :autoconfirmed
protections << {
action: 'move',
group: move_level
}
end
begin
@mb.gateway.protect(title,protections,
reason: @mb.config[:summary].sub('$1',row['count'].to_s)
)
rescueMediaWiki::APIError=>e
ife.code.to_s == 'tpt-target-page'
return
elsife.code.to_s == 'cantedit'
puts"WARNING: Unable to edit [[#{title}]]"
return
else
raisee
end
end
end
defself.fetch_templates(ns,level)
# Minimum transclusion count to check for.
low_threshold=@thresholds[level]
having_clause="HAVING COUNT(*) >= ? "
high_threshold=@thresholds.values.max
iflow_threshold == high_threshold
# We're currently at the highest (sysop) threshold, so only need one HAVING clause.
high_threshold=nil
else
having_clause += " AND COUNT(*) < ?"
end
# Build the list of protection levels we AREN'T looking for.
# This should include everything of lower weight than the given 'level'.
# The SQL clause should include all types for autoconfirmed.
levels=PROTECTION_WEIGHT.keys
iflevel != :autoconfirmed
levels.reject!{ |key| PROTECTION_WEIGHT[key] < PROTECTION_WEIGHT[level]}
# TODO: get consensus for use of EC protection for templates and remove this line.
levels << :extendedconfirmedunlesslevels.include?(:extendedconfirmed)
end
levels.map!(&:to_s)
sql=%{
SELECT page_title AS title, page_id AS id, COUNT(*) AS count
FROM page
JOIN linktarget ON lt_title = page_title
AND lt_namespace = page_namespace
JOIN templatelinks ON tl_target_id = lt_id
LEFT JOIN page_restrictions ON pr_page = page_id
AND pr_level IN (#{Array.new(levels.length,'?').join(',')})
AND pr_type = 'edit'
WHERE lt_namespace = ?
AND pr_page IS NULL
GROUP BY page_title
#{having_clause}
}
args=[*levels,ns,low_threshold]
ifhigh_threshold
args << high_threshold
end
@mb.repl_query(sql, *args)
end
defself.recently_protected?(ns,title)
sql=%{
SELECT 1
FROM logging_logindex
WHERE log_namespace = ?
AND log_title = ?
AND log_action IN ('protect', 'unprotect')
AND log_timestamp > DATE_SUB(NOW(), INTERVAL ? DAY)
LIMIT 1
}
@mb.repl_query(sql,ns,title,@mb.config[:ignore_offset]).any?
end
defself.title_blacklisted?(title,level)
# Needs to be done while logged out.
ret=@mb.http_get(
"https://#{@mb.opts[:project]}.org/w/api.php?action=titleblacklist&tbtitle=#{URI.escape(title)}&tbaction=edit&format=json"
)
# A page is considered blacklisted only if the noedit level matches the one we want to apply.
ret['titleblacklist']['result'] == 'blacklisted' && ret['titleblacklist']['line'] =~ /noedit.*?#{level}/
end
defself.regex_excluded?(title)
unless@title_regex
regexes=[]
@mb.config[:regex_exclusions].keys.eachdo |regex|
regexes << Regexp.new(regex.to_s)
end
@title_regex=Regexp.union(regexes)
end
!!@title_regex.match(title.descore)
rescue=>e
@mb.report_error('Regex error',e)
end
defself.prot_level(count)
@thresholds.eachdo |level,threshold|
returnlevelifcount >= threshold
end
end
defself.get_move_level(page_id)
sql=%{
SELECT pr_level
FROM page_restrictions
WHERE pr_page = ?
AND pr_type = 'move'
}
ret=@mb.repl_query(sql,page_id).to_a
ret.any? ? ret[0]['pr_level'].to_sym : nil
end
defself.namespace_map
return@namespace_mapif@namespace_map
api_obj=@mb.gateway.custom_query(
meta: 'siteinfo',
siprop: 'namespaces'
)
ns_map={}
api_obj.elements['namespaces'].eachdo |element|
ns_map[element.attributes['id'].to_i]=element[0]
end
@namespace_map=ns_map
end
end
TemplateProtector.run