Skip to content

Commit e1b3bcd

Browse files
committed
Release: 1.0.0
0 parents  commit e1b3bcd

File tree

5 files changed

+224
-0
lines changed

5 files changed

+224
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "tennu-poll",
3+
"version": "1.0.0",
4+
"description": "Channel-specific polls plugin for Tennu IRC bot framework.",
5+
"main": "plugin.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/Tennu/tennu-poll.git"
12+
},
13+
"keywords": [
14+
"tennu-plugin",
15+
"tennu",
16+
"irc",
17+
"poll",
18+
"vote"
19+
],
20+
"author": "Ryan Scheel (Havvy)",
21+
"license": "ISC",
22+
"bugs": {
23+
"url": "https://github.com/Tennu/tennu-poll/issues"
24+
},
25+
"homepage": "https://github.com/Tennu/tennu-poll",
26+
"dependencies": {
27+
"r-result": "^1.0.0"
28+
}
29+
}

plugin.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
const format = require('util').format;
2+
const Poll = require('./poll');
3+
4+
const helps = {
5+
"open": [
6+
"{{!}}poll-open option-a option-b ... option-z",
7+
"",
8+
"Starts a poll in the channel.",
9+
"Aliases: {{!}}poll {{!}}pollopen {{!}}open-poll {{!}}openpoll {{!}}poll-start {{!}}pollstart {{!}}start-poll {{!}}startpoll"
10+
],
11+
12+
"close": [
13+
"{{!}}poll-close",
14+
"",
15+
"Closes the poll in the channel, displaying the results of the poll.",
16+
"Aliases: {{!}}pollclose {{!}}close-poll {{!}}closepoll {{!}}poll-end {{!}}pollend {{!}}end-poll {{!}}endpoll"
17+
],
18+
19+
"vote": [
20+
"{{!}}vote option",
21+
"",
22+
"Vote for an option in the current channel poll.",
23+
"Aliases: {{!}}poll-vote"
24+
]
25+
};
26+
27+
module.exports = {
28+
init: function (client, imports) {
29+
// Dict<ChannelName, Poll>
30+
const openPolls = Object.create(null);
31+
const requiresPermission = imports.admin.requiresPermission || imports.admin.requiresAdmin;
32+
33+
34+
const requiresPoller = function (handler) {
35+
return requiresPermission(handler, "poller");
36+
};
37+
38+
return {
39+
handlers: {
40+
"!poll !poll-open !pollopen !open-poll !openpoll !poll-start !pollstart !start-poll !startpoll": requiresPoller(function (command) {
41+
if (command.isQuery) {
42+
return "Sorry, polls can only be started in channels.";
43+
}
44+
45+
if (openPolls[command.channel]) {
46+
return "Cannot start a new poll while another poll is already active.";
47+
}
48+
49+
if (command.args.length < 2) {
50+
return "A poll must have at least two options.";
51+
}
52+
53+
openPolls[command.channel] = Poll(command.args);
54+
return "Poll started.";
55+
}),
56+
57+
"!poll-close !pollclose !close-poll !closepoll !poll-end !pollend !end-poll !endpoll": requiresPoller(function (command) {
58+
if (!openPolls[command.channel]) {
59+
return "There is no open poll in here to close.";
60+
}
61+
62+
const results = openPolls[command.channel].close();
63+
delete openPolls[command.channel];
64+
65+
return [
66+
"Poll is now closed! Results:",
67+
68+
Object.keys(results)
69+
.map(function (key) { return [key, results[key]]; })
70+
.sort(function (lhs, rhs) { return lhs[1] <= rhs[1]; })
71+
.map(function (result) { return format("%s: %s", result[0], result[1]); })
72+
.join(" | ")
73+
];
74+
}),
75+
76+
"!poll-vote !vote": function (command) {
77+
if (!openPolls[command.channel]) {
78+
return "There is no open poll to vote for.";
79+
}
80+
81+
if (command.args.length === 0) {
82+
return "You must specify an option.";
83+
}
84+
85+
return openPolls[command.channel]
86+
.vote(command.args[0], command.nickname)
87+
.map(function () {
88+
return format("%s voted for %s.", command.nickname, command.args[0]);
89+
})
90+
.unwrapOrElse(function (failureReason) {
91+
switch (failureReason) {
92+
case "no-option": return format("%s: %s is not an option!", command.nickname, command.args[0]);
93+
case "already-voted": return format("%s: You've already voted for this poll.", command.nickname);
94+
default:
95+
client.error("PluginPoll", format("Unhandled failure reason in !poll-vote: %s", failureReason));
96+
return format("Error: Unhandled failure reason in text replacement ('%s').", failureReason);
97+
}
98+
});
99+
},
100+
101+
"privmsg": function (message) {
102+
if (!openPolls[message.channel]) { return; }
103+
const words = message.message.split(" ");
104+
if (words.length !== 1) { return; }
105+
const option = words[0];
106+
const result = openPolls[message.channel].vote(option, message.nickname);
107+
if (result.isOk()) {
108+
return format("%s voted for %s.", message.nickname, option);
109+
}
110+
}
111+
},
112+
113+
"commands": ["poll-open", "poll-close", "vote"],
114+
115+
"help": {
116+
"poll": helps.open,
117+
"poll-open": helps.open,
118+
"pollopen": helps.open,
119+
"open-poll": helps.open,
120+
"openpoll": helps.open,
121+
"poll-start": helps.open,
122+
"pollstart": helps.open,
123+
"start-poll": helps.open,
124+
"startpoll": helps.open,
125+
"poll-close": helps.close,
126+
"pollclose": helps.close,
127+
"close-poll": helps.close,
128+
"closepoll": helps.close,
129+
"poll-end": helps.close,
130+
"pollend": helps.close,
131+
"end-poll": helps.close,
132+
"endpoll": helps.close,
133+
"poll-vote": helps.vote,
134+
"vote": helps.vote
135+
}
136+
};
137+
},
138+
139+
requiresRoles: ["admin"]
140+
};

poll.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Example usage.
2+
// var Poll = require('./poll');
3+
// var myPoll = Poll(['up', 'down', 'left', 'right']);
4+
// myPoll.vote('up', 'voter-a');
5+
// myPoll.vote('down', 'voter-b');
6+
// myPoll.vote('up', 'voter-a');
7+
// var results = myPoll.close();
8+
9+
const Result = require('r-result');
10+
const Ok = Result.Ok;
11+
const Fail = Result.Fail;
12+
13+
// [String] -> fns
14+
const Poll = function (options) {
15+
const votes = options.reduce(function (votes, option) {
16+
votes[option.toLowerCase()] = 0;
17+
return votes;
18+
}, Object.create(null));
19+
20+
const voters = [];
21+
22+
var open = true;
23+
24+
return {
25+
// (String, String) -> bool
26+
// Calling this function after the poll is closed is an error.
27+
vote: function (option, voter) {
28+
if (!open) {
29+
throw new Error("Cannot vote on closed poll.");
30+
}
31+
32+
option = option.toLowerCase();
33+
34+
if (typeof votes[option] !== "number") {
35+
return Fail("no-option");
36+
} else if (voters.indexOf(voter) !== -1) {
37+
return Fail("already-voted");
38+
} else {
39+
votes[option] += 1;
40+
voters.push(voter);
41+
return Ok();
42+
}
43+
},
44+
45+
// Closes the poll, and transfers ownership of votes tally to caller.
46+
close: function () {
47+
open = false;
48+
return votes;
49+
}
50+
};
51+
};
52+
53+
module.exports = Poll;

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
See [here](https://tennu.github.io/plugins/poll).

0 commit comments

Comments
 (0)