-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathChecks.js
250 lines (201 loc) · 7.33 KB
/
Checks.js
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
class Checks {
static run_checks(ballot_data) {
const initial_vote_count = ballot_data.length
// Duplicate check is outside of checks just so that
// the ballot would be pruned of duplicates for the
// remaining checks
ballot_data = this.check_duplicates(ballot_data)
const checks = [
this.check_blacklist,
this.check_durations,
this.check_upload_dates,
this.check_uploader_diversity,
this.check_other,
// this.check_fuzzy,
// Must be last since fail annotation should be applied
// to a vote with another annotation first if present
this.check_uploader_occurences
]
checks.forEach(check => check(ballot_data))
if (Config.log_detected_vote_eligibility)
ballot_data.forEach(video_data => {
Logger.log(`${video_data.url} : ${video_data.annotations.length === 0 ? "eligible" : video_data.annotations.join(", ")}`)
})
let eligible_votes = ballot_data.length
eligible_votes -= ballot_data.reduce(
(sum, next_vote) => sum + (next_vote.annotations.length !== 0),
0
)
const assumed_eligible = eligible_votes + this.skipped_votes - this.skipped_and_failing_votes
this.user_errs["Eligible votes:"] = [`${assumed_eligible < 5 ? 0 : assumed_eligible} / ${initial_vote_count + this.skipped_votes}`]
// compare ballot with no duplicates with user_errs for the 5 votes minimum check
return this.user_errs
}
static throw_remaining_errs() {
if (!this.other_errs.length)
return
Logger.log("\n\n\n")
this.other_errs.forEach(err => Logger.log(`${err.name}\n${err.stack})`))
throw new Error("oops")
}
/**
* Initializes the first error instance for a given url
* Use this if at most one error is expected to be
* caught per url before the checking phase
*/
static report_err(url, e) {
if (e.name === UserError.name) {
this.skipped_and_failing_votes++
this.user_errs[e.message] = [url]
}
else
this.other_errs.push(e)
}
static check_fail(issue, problematic) {
if (issue in this.user_errs)
this.user_errs[issue].push(problematic)
else
this.user_errs[issue] = [problematic]
}
// TODO: Maybe alternate platform aliases should also be considered
static same_video(video1, video2) {
return (
video1.title === video2.title &&
video1.uploader === video2.uploader &&
Math.abs(video1.duration - video2.duration) < 3
)
}
static check_duplicates(ballot) {
if (!ballot.length)
return []
const sim_matrix = [[ballot[0]]]
let found_similar
for (let i = 1; i < ballot.length; ++i) {
found_similar = false
for (const sim_array of sim_matrix) {
if (this.same_video(ballot[i], sim_array[0])) {
sim_array.push(ballot[i])
ballot[i].annotations.push("duplicate")
found_similar = true
break
}
}
if (!found_similar)
sim_matrix.push([ballot[i]])
}
sim_matrix.forEach(sim_array => {
if (sim_array.length > 1) {
Checks.check_fail(
"Potential duplicates found:",`${
sim_array.reduce((result, next) => `${result}\n${next.url}`, sim_array.pop().url) + "\n"
}`)
}
})
return sim_matrix.map(sim_array => sim_array[0])
}
// TODO
static check_blacklist(ballot) {}
static check_durations(ballot) {
ballot.forEach(video_data => {
if (video_data.duration <= 30) {
video_data.annotations.push("too short")
Checks.check_fail("Too short:", video_data.url)
}
else if (video_data.duration <= 45) {
Checks.check_fail("May be too short:", video_data.url)
}
})
}
static check_upload_dates(ballot) {
ballot.forEach(video_data => {
const now = new Date()
const
current_year = now.getUTCFullYear(),
upload_year = video_data.upload_date.getUTCFullYear(),
current_month = now.getUTCMonth(),
upload_month = video_data.upload_date.getUTCMonth()
if (current_month === 0) {
if (current_year - 1 === upload_year && upload_month !== 11)
return
}
else if (current_month - 1 === upload_month && current_year === upload_year)
return
const issue = current_year === upload_year && current_month === upload_month ? "Video too new" : "Video too old"
video_data.annotations.push(issue.toLowerCase())
Checks.check_fail(`${issue}:`, video_data.url)
})
}
/**
* Check against a spreadsheet of ineligible pony videos that are likely to
* be voted for despite violating a rule that can't be automatically checked
* for. e.g. pony art slideshows
*/
static check_other(ballot) {
// TODO
ballot.forEach(video_data => {})
}
static check_uploader_diversity(ballot) {
const s = new Set()
ballot.forEach(video_data => s.add(video_data.uploader))
if (s.size < 5) {
Checks.check_fail("Uploader Diversity:", `${s.size} out of a minimum of 5 different uploaders voted for`)
ballot.forEach(video_data => video_data.annotations.push("5 channel rule"))
}
}
static check_uploader_occurences(ballot) {
const occurences = new Map()
ballot.forEach(video_data => {
const initial_value = occurences.get(video_data.uploader)
occurences.set(video_data.uploader, initial_value === undefined ? 1 : initial_value + 1)
})
occurences.forEach((count, uploader) => {
if (count >= 3)
Checks.check_fail(
"More than 2 videos by the following uploaders have been voted for, which is not allowed:",
`${uploader}: ${count} votes`
)
else
occurences.delete(uploader)
})
const uploader_array = Array.from(occurences).map(entry => entry[0])
const rule_violating = ballot
.filter(video_data => occurences.has(video_data.uploader))
.sort((v1, v2) => uploader_array.indexOf(v1.uploader) - uploader_array.indexOf(v2.uploader))
// ^ ignoring really bad time complexity bc of the 10 vote limit
if (!rule_violating.length) return
let removal_candidates = []
let annotated_candidates = 0
let group_index = 0
// iterating over each uploader group to annotate videos to remove
// while the uploader limit is exceeded and with priority given
// to videos that are already annotated
for (const count of occurences.values()) {
const to_remove = count - 2
for (
let i = 0, relative_i;
i < count && annotated_candidates < to_remove;
++i
) {
relative_i = group_index + i
if (removal_candidates.length < to_remove) {
removal_candidates.push(rule_violating[relative_i])
if (rule_violating[relative_i].annotations.length)
++annotated_candidates
}
else if (rule_violating[relative_i].annotations.length) {
removal_candidates[
removal_candidates.find(video_data => !video_data.annotations.length)
] = rule_violating[relative_i]
++annotated_candidates
}
}
removal_candidates.forEach(video_data => video_data.annotations.push("exceeds uploader occurence (not a rule violator annotaton)"))
removal_candidates = []
group_index += count
}
}
}
Checks.user_errs = {}
Checks.other_errs = []
Checks.skipped_votes = 0
Checks.skipped_and_failing_votes = 0