-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpolls.js
More file actions
293 lines (286 loc) · 13.6 KB
/
polls.js
File metadata and controls
293 lines (286 loc) · 13.6 KB
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
// for polls (TODO: turn this into a class...)
let currentPoll = {}
let pollVoters = {}
let pollRunning = false
let pollRunner = []
let pollTotalVotes = 0
let pollExpiryTimeout = null
let pollMinAnswers = 1
let pollMaxAnswers = 1
let pollChannel = null
let bot = null
bot.on("message", async message => {
if (message.content.startsWith('!poll')) {
const authorId = message.author.id
let nickname = "Anonymous"
if (message.channel.type === "text") {
nickname = message.channel.members.get(authorId).displayName
}
if (message.content.startsWith('!poll new')) {
if (message.channel.type === "dm") {
message.reply("umfragen bitte nicht im privatchat erstellen...")
return
}
if (pollRunning) {
message.reply("es läuft bereits eine umfrage! (beenden mit `!poll end` oder anzeigen mit `!poll`)")
return
}
const alphabet = [...Array(26)].map(a=>a = String.fromCharCode(i++),i=65)
let qAndA = message.content.substring(9).split(";").map(str => str.trim())
if (qAndA[qAndA.length - 1].length === 0) qAndA.pop() // delete last answer if last letter was ";"
if (qAndA.length > alphabet.length) {
return message.reply("zu viele antwortmöglichkeiten... maximal 26 sind erlaubt")
}
if (qAndA.length < 3) {
return message.reply("es muss mindestens 2 antwortmöglichkeiten geben... frage und antworten werden mit einem semikolon getrennt.")
}
let question = qAndA.shift()
// check whether new poll is multiple-choice (!poll new <min>:<max> question; ...)
if (question.indexOf(":") != -1 && question.indexOf(" ") != -1) {
const q_split = question.split(" ")
const numbers = q_split[0]
if (numbers.indexOf(":") != -1) {
const minMax = numbers.split(":").map(str => Number.parseInt(str))
// if x:y aren't numbers, interpret them as part of the question (no multi)
if (!isNaN(minMax[0]) && !isNaN(minMax[1])) {
if (minMax[1] <= qAndA.length) {
if (minMax[0] <= minMax[1]) {
if (minMax[0] > 0) {
pollMinAnswers = minMax[0]
pollMaxAnswers = minMax[1]
question = q_split.slice(1, q_split.length).join(" ")
} else {
return message.reply(`die mindestanzahl an antworten (${minMax[0]}) muss größer als 0 sein...`)
}
} else {
return message.reply(`die mindestanzahl an antworten (${minMax[0]}) ist größer als die maximalanzahl... (${minMax[1]})`)
}
} else {
return message.reply(`die maximale anzahl an wählbaren antworten (${minMax[1]}) überschreitet die anzahl der antworten... (${qAndA.length})`)
}
}
}
}
currentPoll.q = question
currentPoll.a = {}
for (let qA of qAndA) {
const thisLetter = alphabet.shift()
currentPoll.a[thisLetter] = {
"text": qA,
"votes": 0,
"votedUsers": []
}
}
// default: 24 hours until expiry
pollExpiryTimeout = setTimeout(() => {
endPoll(false)
}, 24 * 3600000)
pollRunning = true
pollRunner = [authorId, nickname]
pollTotalVotes = 0
pollVoters = {}
pollChannel = message.channel
displayPoll(true)
} else {
if (pollRunning) {
if (message.content.startsWith('!poll extend')) {
// reset the timer
if (authorId === pollRunner[0]) {
clearTimeout(pollExpiryTimeout)
pollExpiryTimeout = setTimeout(() => {
endPoll(false)
}, 24 * 3600000)
message.reply("die umfrage wurde um 24 stunden verlängert.")
} else {
message.reply(`nur ${pollRunner[1]} kann diese Umfrage verlängern`)
}
}
else if (message.content.startsWith('!poll end')) {
// show final results
if (authorId === pollRunner[0]) {
endPoll(true)
} else {
message.reply(`nur ${pollRunner[1]} kann diese Umfrage beenden`)
}
}
else if (message.content === "!poll unvote") {
if (pollVoters.hasOwnProperty(authorId)) {
for (let votedAnswer of pollVoters[authorId].votedFor) {
const votedUsersIndex = currentPoll.a[votedAnswer].votedUsers.indexOf(authorId)
currentPoll.a[votedAnswer].votedUsers.splice(votedUsersIndex, 1)
currentPoll.a[votedAnswer].votes--
pollTotalVotes--
}
delete pollVoters[authorId]
displayPoll(false)
} else {
message.reply("du hast noch nicht abgestimmt!")
}
}
else if (message.content === "!poll") {
displayPoll(false)
}
else {
// vote
// check whether user has reached max allowed votes
let alreadyVoted = []
if (pollVoters.hasOwnProperty(authorId)) {
alreadyVoted = pollVoters[authorId].votedFor
if (alreadyVoted.length === pollMaxAnswers) {
if (pollMaxAnswers === 1) {
return message.reply("du hast bereits abgestimmt!")
} else {
return message.reply("du hast die maximalzahl an votes erreicht!")
}
}
}
// extract votes
const voteString = message.content.substring(6)
let votedLetters = []
if (voteString.indexOf(",") != -1) {
votedLetters = voteString.split(",").map(str => str.trim().toUpperCase())
} else if (voteString.indexOf(";") != -1) {
votedLetters = voteString.split(";").map(str => str.trim().toUpperCase())
} else {
votedLetters = [voteString.trim().toUpperCase()]
}
// match number of votes and allowed no. of votes
if ((votedLetters.length + alreadyVoted.length) > pollMaxAnswers) {
return message.reply(`du darfst insgesamt maximal ${pollMaxAnswers} stimmen abgeben!`)
} else if ((votedLetters.length + alreadyVoted.length) < pollMinAnswers) {
return message.reply(`du musst mindestens ${pollMinAnswers} stimmen abgeben!`)
} else {
for (let vote of votedLetters) {
// check if voted letters are valid
if (!currentPoll.a.hasOwnProperty(vote)) {
return message.reply(`antwort "${vote}" nicht gefunden. mögliche antworten: ${Object.keys(currentPoll.a).join(", ")}`)
}
// check of user has already voted for this answer
if (currentPoll.a[vote].votedUsers.includes(authorId)) {
return message.reply(`du hast bereits für antwort ${vote} abgestimmt!`)
}
}
// add vote(s)
for (let vote of votedLetters) {
currentPoll.a[vote].votes++
currentPoll.a[vote].votedUsers.push(authorId)
}
pollTotalVotes += votedLetters.length
// update user
if (alreadyVoted.length > 0) {
pollVoters[authorId].votedFor = alreadyVoted.concat(votedLetters)
pollVoters[authorId].name = nickname // name may have changed, overwrite
} else {
pollVoters[authorId] = { "votedFor": votedLetters, "name": nickname }
}
displayPoll(false)
}
}
} else {
message.reply("es läuft momentan keine umfrage. erstelle eine neue mit `!poll new Frage; Antwort A; Antwort B; Antwort C; ...` (achtung: semikolon!)")
}
}
}
});
// for polls
function sortPollAfterVotes() {
let pollAnswerCounts = []
for (let answer of Object.keys(currentPoll.a)) {
pollAnswerCounts.push([answer, currentPoll.a[answer].votes])
}
pollAnswerCounts.sort(function(a, b) {
return b[1] - a[1]
})
return pollAnswerCounts
}
function displayPoll(created) {
if (!pollChannel)
return
const pollAnswerCounts = sortPollAfterVotes()
let outputString = ""
if (created) {
outputString = `${pollRunner[1]} möchte wissen:\n`
}
outputString += `**${currentPoll.q}**\n`
for (let answer of pollAnswerCounts) {
const answerLetter = answer[0]
const theseVotes = answer[1]
const theseVoters = currentPoll.a[answerLetter].votedUsers.map(user => pollVoters[user].name).join(", ") // get nicknames
const s = (theseVotes === 1) ? "vote" : "votes"
outputString += `**${answerLetter}** - ${currentPoll.a[answerLetter].text} (${theseVotes} ${s})\n`
if (theseVotes > 0) {
outputString += `*voted by: ${theseVoters}*\n`
}
}
outputString += `insgesamt: ${pollTotalVotes}\n`
// multiple-choice?
if (pollMaxAnswers > 1) {
outputString += "Dies ist eine **Multiple-Choice**-Umfrage. "
if (pollMinAnswers === pollMaxAnswers) {
outputString += `Es müssen genau **${pollMinAnswers} Stimmen** abgegeben werden.\n`
} else {
outputString += `Es müssen mindestens **${pollMinAnswers}**, aber maximal **${pollMaxAnswers} Stimmen** abgegeben werden.\n`
}
outputString += "Um abzustimmen, sende `!poll` und die Antwortbuchstaben (z.B. `!poll A, B`)"
} else {
outputString += "Um abzustimmen, sende `!poll` und den Antwortbuchstaben (z.B. `!poll A`)"
}
pollChannel.send(outputString)
}
function endPoll(creatorEnded) {
// reset everything and show result
let pollAnswerCounts = sortPollAfterVotes()
let outputString = ""
if (creatorEnded) {
outputString += `Der Fragesteller hat die aktuelle Umfrage beendet.\n`
} else {
outputString += `Die aktuelle Umfrage wurde nach Ablauf der Zeit automatisch beendet.\n`
}
outputString += `Q: **${currentPoll.q}**\n`
// multiple answers won?
let prevVotes = pollAnswerCounts[0][1]
if (prevVotes === 0) {
outputString += "Es wurden keine Stimmen abgegeben."
} else {
let pollAnswerCountsWon = [pollAnswerCounts.shift()]
for (let answer of pollAnswerCounts) {
if (answer[1] === prevVotes) {
pollAnswerCountsWon.push(answer)
} else {
break
}
prevVotes = answer[1]
}
if (pollAnswerCountsWon.length === 1) {
const winner = pollAnswerCountsWon[0]
const percentVotes = (winner[1] > 0) ? winner[1] / pollTotalVotes * 100 : 0
const theseVoters = currentPoll.a[winner[0]].votedUsers.map(user => pollVoters[user].name).join(", ") // get nicknames
const n = (winner[1] === 1) ? "Stimme" : "Stimmen"
outputString += `Gewonnen hat:\n`
outputString += `**${currentPoll.a[winner[0]].text}**\n`
outputString += `mit **${winner[1]}** ${n} (${percentVotes.toFixed(0)}%), gewählt von: ${theseVoters}`
} else if (pollAnswerCountsWon.length > 1) {
const percentVotes = (prevVotes > 0) ? prevVotes / pollTotalVotes * 100 : 0
const n = (prevVotes === 1) ? "Stimme" : "Stimmen"
outputString += `Gewonnen haben:\n`
for (let winner of pollAnswerCountsWon) {
outputString += `**${currentPoll.a[winner[0]].text}**\n`
}
outputString += `mit jeweils **${pollAnswerCountsWon[0][1]}** ${n}! (${percentVotes.toFixed(0)}%)`
} else {
outputString += `beim zusammenzählen der ergebnisse ist ein fehler aufgetreten...`
log("endPoll", `pollAnswerCounts: ${pollAnswerCounts}`)
log("endPoll", `pollAnswerCountsWon: ${pollAnswerCountsWon}`)
}
}
currentPoll = {}
pollVoters = {}
pollRunning = false
pollRunner = []
pollTotalVotes = 0
pollMinAnswers = 1
pollMaxAnswers = 1
clearTimeout(pollExpiryTimeout)
pollChannel.send(outputString)
pollChannel = null
}