FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
NotaBot/module_poll.lua at master · DigitalPulseSoftware/NotaBot · GitHub
Uh oh!
There was an error while loading.
Please reload this page
.
DigitalPulseSoftware
/
NotaBot
Public
Notifications
You must be signed in to change notification settings
Fork
13
Star
25
Code
Issues
13
Pull requests
9
Actions
Projects
Wiki
Security and quality
0
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security and quality
Insights
Expand file tree
Breadcrumbs
NotaBot
/
module_poll.lua
Copy path
More file actions
More file actions
Latest commit
History
History
History
556 lines (468 loc) · 15.2 KB
Breadcrumbs
NotaBot
/
module_poll.lua
Copy path
File metadata and controls
556 lines (468 loc) · 15.2 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
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
--
Copyright (C) 2020 Antoine James Tournepiche & Jérôme Leclercq
--
This file is part of the "Not a Bot" application
--
For conditions of distribution and use, see copyright notice in LICENSE
local
client
=
Client
local
discordia
=
Discordia
local
bot
=
Bot
local
enums
=
discordia
.
enums
Module
.
Name
=
"
poll
"
function
Module
:
IsAllowedToSpecifyChannel
(
member
,
config
)
return
member
:
hasPermission
(
enums
.
permission
.
administrator
)
or
(
config
.
SpecifyChannelAllowedRoles
~=
nil
and
util
.
MemberHasAnyRole
(
member
,
config
.
SpecifyChannelAllowedRoles
))
end
function
Module
:
FormatChoiceResult
(
choiceVotes
,
barScale
,
totalVotes
,
asProgressBars
)
--
Pluralization
local
voteText
=
choiceVotes
>
1
and
"
votes
"
or
"
vote
"
--
Configuration says we use progress bars.
if
asProgressBars
then
local
progressLength
=
20
--
Determines the length (in characters) of the progress bar
local
progressCharacter
=
'
=
'
local
ratio
=
0
if
barScale
>
0
then
ratio
=
choiceVotes
/
barScale
--
barScale is the number of votes needed to have a full length bar
end
local
choiceProgressLength
=
math.floor
(
ratio
*
progressLength
)
local
progressText
=
string.rep
(
progressCharacter
,
choiceProgressLength
)
..
string.rep
(
'
'
,
progressLength
-
choiceProgressLength
)
local
percentage
=
choiceVotes
/
totalVotes
*
100
return
string.format
(
'
`[%s]` **%d** %s (%d%%)
'
,
progressText
,
choiceVotes
,
voteText
,
percentage
)
end
--
Configuration says we use normal output.
return
string.format
(
'
**%d** %s
'
,
choiceVotes
,
voteText
)
end
function
Module
:
GetPollFooter
(
member
,
duration
,
isResults
)
local
text
=
"
Poll requested by
"
..
member
.
tag
if
duration
==
nil
then
return
text
end
local
verb
=
isResults
and
"
Lasted
"
or
"
Lasts
"
if
duration
<
60
then
duration
=
60
end
text
=
string.format
(
"
%s. %s for %s.
"
,
text
,
verb
,
util
.
FormatTime
(
duration
))
return
text
end
function
Module
:
AddEmbedReactions
(
member
,
message
)
local
data
=
self
:
GetData
(
member
.
guild
)
local
poll
=
data
.
Polls
[
member
.
id
]
if
poll
==
nil
or
#
poll
.
choices
==
0
then
return
end
for
_
,
choice
in
ipairs
(
poll
.
choices
)
do
if
choice
.
emoji
~=
nil
then
message
:
addReaction
(
choice
.
emoji
.
Emoji
or
choice
.
emoji
.
Id
)
end
end
end
function
Module
:
CheckPermissions
(
member
)
if
member
:
hasPermission
(
enums
.
permission
.
administrator
)
then
return
true
end
return
util
.
MemberHasAnyRole
(
member
,
self
:
GetConfig
(
member
.
guild
).
AllowedRoles
)
end
--
TODO? Ajouter option de cooldown entre 2 sondages pour un même membre
--
TODO? (plus tard) Ajouter option pour changer la couleur (param par défaut et action 'color')
function
Module
:
GetConfigTable
()
return
{
{
Array
=
true
,
Name
=
"
AllowedRoles
"
,
Description
=
"
Roles allowed to create polls
"
,
Type
=
bot
.
ConfigType
.
Role
,
Default
=
{}
},
{
Array
=
true
,
Name
=
"
SpecifyChannelAllowedRoles
"
,
Description
=
"
Roles allowed to specify where to send a poll
"
,
Type
=
bot
.
ConfigType
.
Role
,
Default
=
{}
},
{
Name
=
"
DefaultPollChannel
"
,
Description
=
"
Where should polls be sent if no channel is set on init
"
,
Type
=
bot
.
ConfigType
.
Channel
,
Optional
=
true
},
{
Name
=
"
DefaultPollDuration
"
,
Description
=
"
Default poll duration if no duration is set on init
"
,
Type
=
bot
.
ConfigType
.
Duration
,
Default
=
24
*
60
*
60
},
{
Name
=
"
DeletePollOnExpiration
"
,
Description
=
"
Delete original poll message on expiration
"
,
Type
=
bot
.
ConfigType
.
Boolean
,
Default
=
true
},
{
Name
=
"
UseProgressBars
"
,
Description
=
"
Use progress bars to fancy out results
"
,
Type
=
bot
.
ConfigType
.
Boolean
,
Default
=
true
},
{
Name
=
"
MostVotedRelative
"
,
Description
=
"
Make Progress bars relative to the most voted choice, instead of being relative to total votes
"
,
Type
=
bot
.
ConfigType
.
Boolean
,
Default
=
false
,
}
}
end
function
Module
:
OnUnload
()
self
.
Timer
:
Stop
()
end
function
Module
:
OnEnable
(
guild
)
local
data
=
self
:
GetData
(
guild
)
data
.
Polls
=
{}
return
true
end
function
Module
:
OnLoaded
()
self
.
Timer
=
Bot
:
CreateRepeatTimer
(
1
,
-
1
,
function
()
local
now
=
os.time
()
self
:
ForEachGuild
(
function
(
guildId
,
config
,
data
,
persistentData
)
local
guild
=
client
:
getGuild
(
guildId
)
local
config
=
self
:
GetConfig
(
guild
)
if
persistentData
.
runningPolls
==
nil
then
return
--
This is a callback so return instead of break
end
for
index
,
poll
in
ipairs
(
persistentData
.
runningPolls
)
do
local
pollTime
=
poll
[
2
]
local
duration
=
poll
[
3
]
--
Return if the poll isn't finished.
if
now
>=
(
pollTime
+
duration
)
then
local
channel
=
guild
:
getChannel
(
poll
[
4
])
local
member
=
guild
:
getMember
(
poll
[
1
])
if
(
not
channel
or
not
member
)
then
goto
remove
end
do
local
message
=
channel
:
getMessage
(
poll
[
5
])
if
(
not
message
)
then
goto
remove
end
local
totalVotes
=
0
local
mostVotedCount
=
0
local
map
=
{}
local
reactions
=
message
.
reactions
:
toArray
()
local
fields
=
message
.
embed
.
fields
local
emojiNames
=
poll
[
6
]
--
This is stored in the same order as fields
for
_
,
reaction
in
ipairs
(
reactions
)
do
local
rEmojiData
=
Bot
:
GetEmojiData
(
guild
,
reaction
.
emojiId
or
reaction
.
emojiName
)
--
This is nil when it is an extern emoji
if
not
rEmojiData
then
break
end
local
rEmojiName
=
rEmojiData
.
Name
for
i
,
emojiName
in
ipairs
(
emojiNames
)
do
if
rEmojiName
==
emojiName
then
table.insert
(
map
, {
count
=
reaction
.
count
-
1
,
title
=
fields
[
i
].
value
})
local
choiceVotes
=
reaction
.
count
-
1
if
choiceVotes
>
mostVotedCount
then
mostVotedCount
=
choiceVotes
end
totalVotes
=
totalVotes
+
choiceVotes
break
end
end
end
assert
(
#
fields
>=
#
map
)
if
#
fields
>
#
map
then
for
_
,
field
in
ipairs
(
fields
)
do
local
wasIn
=
false
for
_
,
mapElem
in
ipairs
(
map
)
do
if
mapElem
.
title
==
field
.
value
then
wasIn
=
true
break
end
end
if
not
wasIn
then
table.insert
(
map
, {
count
=
0
,
title
=
field
.
value
..
"
*(**deleted**)*
"
})
end
end
end
local
results
=
{
author
=
{
name
=
"
Poll results
"
,
icon_url
=
member
.
avatarURL
},
title
=
message
.
embed
.
title
,
fields
=
{},
footer
=
{
text
=
self
:
GetPollFooter
(
member
,
duration
,
true
)}
}
table.sort
(
map
,
function
(
a
,
b
)
return
a
.
count
>
b
.
count
end
)
local
barScale
if
config
.
MostVotedRelative
then
barScale
=
mostVotedCount
else
barScale
=
totalVotes
end
for
_
,
choice
in
ipairs
(
map
)
do
table.insert
(
results
.
fields
, {
name
=
choice
.
title
,
value
=
self
:
FormatChoiceResult
(
choice
.
count
,
barScale
,
totalVotes
,
config
.
UseProgressBars
)
})
end
if
not
config
.
DeletePollOnExpiration
then
results
.
url
=
message
.
link
end
channel
:
send
({
embed
=
results
})
if
config
.
DeletePollOnExpiration
then
local
succeed
=
message
:
delete
()
if
not
succeed
then
channel
:
send
(
"
**ERROR** Failed to delete original poll message!
"
)
end
end
end
::
remove
::
table.remove
(
persistentData
.
runningPolls
,
index
)
end
end
end
)
end
)
self
:
RegisterCommand
({
Name
=
"
createpoll
"
,
Args
=
{
{
Name
=
"
title
"
,
Type
=
bot
.
ConfigType
.
String
},
{
Name
=
"
channel
"
,
Type
=
bot
.
ConfigType
.
Channel
,
Optional
=
true
},
{
Name
=
"
duration
"
,
Type
=
bot
.
ConfigType
.
Duration
,
Optional
=
true
}
},
PrivilegeCheck
=
function
(
member
)
return
self
:
CheckPermissions
(
member
)
end
,
Help
=
"
Creates a poll (title format:
\"
title
\"
)
"
,
Func
=
function
(
commandMessage
,
title
,
channel
,
duration
)
local
member
=
commandMessage
.
member
local
guild
=
member
.
guild
local
data
=
self
:
GetData
(
guild
)
local
polls
=
data
.
Polls
local
config
=
self
:
GetConfig
(
member
.
guild
)
local
pollChannel
=
channel
or
config
.
DefaultPollChannel
local
pollDuration
=
duration
or
config
.
DefaultPollDuration
if
pollChannel
==
nil
then
commandMessage
:
reply
(
"
You need to either specify a channel, or configure one with the `config poll` command.
"
)
return
end
if
channel
~=
nil
and
not
self
:
IsAllowedToSpecifyChannel
(
member
,
config
)
then
commandMessage
:
reply
(
"
You are not allowed to specify a channel.
"
)
return
end
if
(
not
polls
[
member
.
id
])
then
polls
[
member
.
id
]
=
{
title
=
title
,
channel
=
pollChannel
,
duration
=
pollDuration
,
choices
=
{}
}
commandMessage
:
reply
(
'
Poll created! Set it up using the `poll` command.
'
)
else
commandMessage
:
reply
(
"
You are already setting up a poll.
\n
Use `cancelpoll` to abort the previous poll.
"
)
end
end
})
self
:
RegisterCommand
({
Name
=
"
cancelpoll
"
,
Args
=
{},
PrivilegeCheck
=
function
(
member
)
return
self
:
CheckPermissions
(
member
)
end
,
Help
=
"
Cancels your current pending poll
"
,
Func
=
function
(
commandMessage
)
local
member
=
commandMessage
.
member
local
data
=
self
:
GetData
(
member
.
guild
)
local
polls
=
data
.
Polls
if
(
polls
[
member
.
id
])
then
polls
[
member
.
id
]
=
nil
commandMessage
:
reply
(
"
You can now create a new poll.
"
)
else
commandMessage
:
reply
(
"
You don't have a pending poll.
"
)
end
end
})
self
:
RegisterCommand
({
Name
=
"
poll
"
,
Args
=
{
{
Name
=
"
action
"
,
Type
=
bot
.
ConfigType
.
String
},
{
Name
=
"
emoji
"
,
Type
=
bot
.
ConfigType
.
Emoji
,
Optional
=
true
},
{
Name
=
"
text
"
,
Type
=
bot
.
ConfigType
.
String
,
Optional
=
true
}
},
PrivilegeCheck
=
function
(
member
)
return
self
:
CheckPermissions
(
member
)
end
,
Help
=
"
Sets up a poll
"
,
Func
=
function
(
commandMessage
,
action
,
emoji
,
text
)
local
member
=
commandMessage
.
member
local
guild
=
member
.
guild
local
data
=
self
:
GetData
(
member
.
guild
)
local
polls
=
data
.
Polls
local
poll
=
polls
[
member
.
id
]
if
(
not
poll
)
then
commandMessage
:
reply
(
"
You must create a poll in order to use this command!
"
)
return
end
if
action
==
"
add
"
then
if
#
poll
.
choices
>=
20
then
commandMessage
:
reply
(
"
You can't add more than 20 choices!
"
)
return
end
if
text
==
nil
or
text
==
'
'
then
commandMessage
:
reply
(
"
You can't add a choice without text!
"
)
return
end
if
emoji
~=
nil
then
if
self
:
IsAChoice
(
poll
,
emoji
)
then
commandMessage
:
reply
(
"
This emoji is already used for a choice! Can't add it : use `update` action if you want to update it!
\n
"
)
return
end
table.insert
(
poll
.
choices
, {
emoji
=
emoji
,
text
=
text
})
else
commandMessage
:
reply
(
"
This emoji is unknown. If it is a Discord one, please contact Lynix for him to update the internal emoji list.
"
)
return
end
local
message
=
commandMessage
:
reply
({
embed
=
self
:
FormatPoll
(
member
, {},
nil
,
true
)
})
self
:
AddEmbedReactions
(
member
,
message
)
return
end
if
action
==
"
remove
"
then
local
function
RemoveChoice
(
emoji
)
local
choices
=
{}
local
wasIn
=
false
for
_
,
choice
in
ipairs
(
poll
.
choices
)
do
if
choice
.
emoji
.
Name
~=
emoji
.
Name
then
table.insert
(
choices
,
choice
)
else
wasIn
=
true
end
end
poll
.
choices
=
choices
return
wasIn
end
local
reply
=
"
"
if
text
~=
nil
then
reply
=
reply
..
"
**WARN** The specified text is useless and will be ignored!
\n
"
end
if
(
RemoveChoice
(
emoji
))
then
reply
=
reply
..
emoji
.
MentionString
..
"
has been removed!
\n
"
else
reply
=
reply
..
emoji
.
MentionString
..
"
doesn't match any choice. It was not removed.
\n
"
end
local
message
=
commandMessage
:
reply
({
embed
=
self
:
FormatPoll
(
member
, {},
reply
,
true
)
})
self
:
AddEmbedReactions
(
member
,
message
)
return
end
if
action
==
"
update
"
then
if
text
==
nil
then
commandMessage
:
reply
(
"
Can't update a choice without text! To remove a choice, use the `remove` action.
"
)
return
end
local
reply
=
emoji
.
MentionString
..
"
text update has failed.
"
for
_
,
choice
in
ipairs
(
poll
.
choices
)
do
if
choice
.
emoji
.
Name
==
emoji
.
Name
then
choice
.
text
=
text
reply
=
emoji
.
MentionString
..
"
text updated successfully.
"
break
end
end
local
message
=
commandMessage
:
reply
({
embed
=
self
:
FormatPoll
(
member
, {},
reply
,
true
)
})
self
:
AddEmbedReactions
(
member
,
message
)
return
end
if
action
==
"
title
"
then
if
text
==
nil
then
commandMessage
:
reply
(
"
Invalid title! No title set!
"
)
return
end
polls
[
member
.
id
].
title
=
text
commandMessage
:
reply
(
"
Title set to `
"
..
text
..
"
`
"
)
return
end
if
action
==
"
send
"
then
if
#
polls
[
member
.
id
].
choices
<
2
then
commandMessage
:
reply
(
"
You can't send a poll without at least 2 choices! Set some using the `add` action!
"
)
return
end
local
channel
=
guild
:
getChannel
(
poll
.
channel
)
local
data
=
self
:
GetPersistentData
(
guild
)
local
message
=
channel
:
send
({
embed
=
self
:
FormatPoll
(
member
, {},
nil
,
false
)
})
self
:
AddEmbedReactions
(
member
,
message
)
data
.
runningPolls
=
data
.
runningPolls
or
{}
--
TODO? Ajouter option pour empêcher un membre de faire un sondage s'il en a déjà un en cours
local
emojiNames
=
{}
for
i
,
choice
in
ipairs
(
poll
.
choices
)
do
emojiNames
[
i
]
=
choice
.
emoji
.
Name
end
table.insert
(
data
.
runningPolls
, {
member
.
id
,
os.time
(),
poll
.
duration
,
channel
.
id
,
message
.
id
,
emojiNames
})
polls
[
member
.
id
]
=
nil
commandMessage
:
reply
(
string.format
(
"
Poll successfully sent to %s (#%s).
"
,
channel
.
mentionString
,
channel
.
name
))
return
end
commandMessage
:
reply
(
"
Invalid action. It can only be `add`, `remove`, `update`, `title` or `send`.
"
)
end
})
return
true
end
function
Module
:
IsAChoice
(
poll
,
emoji
)
for
_
,
choice
in
ipairs
(
poll
.
choices
)
do
if
choice
.
emoji
.
Id
==
emoji
.
Id
then
return
true
end
end
return
false
end
--
TODO Respect limitations : https://birdie0.github.io/discord-webhooks-guide/other/field_limits.html
function
Module
:
FormatPoll
(
member
,
embed
,
footer
,
preview
)
local
guild
=
member
.
guild
local
data
=
self
:
GetData
(
guild
)
local
fields
=
{}
local
poll
=
data
.
Polls
[
member
.
id
]
local
title
=
preview
and
"
[Preview]
"
..
poll
.
title
or
poll
.
title
for
i
,
choice
in
ipairs
(
poll
.
choices
)
do
if
Bot
:
GetEmojiData
(
guild
,
choice
.
emoji
.
Name
)
~=
nil
then
table.insert
(
fields
, {
name
=
"
Choice n°
"
..
i
,
value
=
string.format
(
"
%s %s
"
,
choice
.
emoji
.
MentionString
,
choice
.
text
)
})
else
--
Deinit the poll
data
.
Polls
[
member
.
id
]
=
nil
client
:
info
(
"
An emoji was deleted during the configuration of a poll that was using it.
"
)
return
{
title
=
"
An emoji is broken.
"
,
fields
=
{
{
name
=
"
This is not a bot error.
"
,
value
=
"
This happens when an emoji in the poll is deleted during its configuration.
"
},
{
name
=
"
How to fix it?
"
,
value
=
"
You can't! Your poll has been cancelled.
"
},
{
name
=
"
What to do now?
"
,
value
=
"
Just use the command `createpoll` and redo everything.
"
}
}
}
end
end
--
TODO? Add expiration date to the footer OR add launch time!
embed
.
title
=
title
embed
.
fields
=
fields
if
footer
~=
nil
then
embed
.
footer
=
{
text
=
footer
}
else
embed
.
footer
=
{
text
=
self
:
GetPollFooter
(
member
,
poll
.
duration
) }
end
return
embed
end
Back
|
FazBrowse Home
|
New Git URL