ElectionsBot/src/index.ts

122 lines
6 KiB
TypeScript
Raw Normal View History

2024-06-04 00:43:42 -04:00
import {REST, Routes, Client, GatewayIntentBits, CommandInteraction, EmbedBuilder, CommandInteractionOption, CommandInteractionOptionResolver } from "discord.js";
2024-06-01 02:14:08 -04:00
import * as fs from "node:fs";
import commands from "./commands.js";
import {MakePrediction} from "./predictor.js";
2024-06-04 00:43:42 -04:00
import Election from "./election.js";
import Presidential_2024 from "./elections/2024.js";
import Elections from "./elections/elections.js";
import VoteType, { VoteTypeString } from "./VoteType.js";
2024-06-01 02:14:08 -04:00
const configraw = fs.readFileSync("config.json", "utf-8");
const config = JSON.parse(configraw);
if (!config.token) {
console.error("Please provide a Token and Client ID in config.json");
process.exit(1);
}
(async () => {
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('ready', async () => {
console.log(`Logged in as ${client.user!.tag}!`);
await publishSlashCommands(client.application!.id);
});
client.on('interactionCreate', async i => {
if (i instanceof CommandInteraction) {
await i.deferReply();
2024-06-01 02:14:08 -04:00
switch (i.commandName) {
2024-06-05 18:58:23 -04:00
case "approval":
let poll_data = JSON.parse(fs.readFileSync('data/approval_rating.json', "utf-8"));
let polls = "";
2024-06-05 18:58:23 -04:00
var source = i.options.get('source')?.value as string
switch (source) {
case "thehill":
// source: https://elections2024.thehill.com/national/biden-approval-rating/
polls = `${poll_data.thehill.description}:\n\n`;
Object.keys(poll_data.thehill.polls).forEach(poll => {
polls += `:red_circle: **${poll_data.thehill.polls[poll].pollster}** - (**${poll_data.thehill.polls[poll].date}**), ${poll_data.thehill.polls[poll].sample}, **${poll_data.thehill.polls[poll].approve}**% approve, **${poll_data.thehill.polls[poll].disapprove}**% disapprove (**${poll_data.thehill.polls[poll].spread}**)\n`;
});
break;
case "fivethirtyeight":
// source: https://projects.fivethirtyeight.com/biden-approval-rating/
polls = `${poll_data.fivethirtyeight.description}:\n\n`;
Object.keys(poll_data.fivethirtyeight.polls).forEach(poll => {
polls += `:red_circle: **${poll_data.fivethirtyeight.polls[poll].pollster}** - (**${poll_data.fivethirtyeight.polls[poll].date}**), ${poll_data.fivethirtyeight.polls[poll].sample}, **${poll_data.fivethirtyeight.polls[poll].approve}**% approve, **${poll_data.fivethirtyeight.polls[poll].disapprove}**% disapprove (adjusted: **${poll_data.fivethirtyeight.polls[poll].approve_adjusted}**% approve, **${poll_data.fivethirtyeight.polls[poll].disapprove_adjusted}**% disapprove) (**${poll_data.fivethirtyeight.polls[poll].spread}**)\n`;
});
break;
default:
// source: https://www.realclearpolling.com/polls/approval/joe-biden/approval-rating
polls = `${poll_data.rcp.description}:\n\n`;
Object.keys(poll_data.rcp.polls).forEach(poll => {
polls += `:red_circle: **${poll_data.rcp.polls[poll].pollster}** - (**${poll_data.rcp.polls[poll].date}**), ${poll_data.rcp.polls[poll].sample}, **${poll_data.rcp.polls[poll].approve}**% approve, **${poll_data.rcp.polls[poll].disapprove}**% disapprove (spread: **${poll_data.rcp.polls[poll].spread}**)\n`;
});
break;
}
await i.editReply(`${polls}`);
break;
2024-06-01 02:14:08 -04:00
case "simulate":
2024-06-04 00:43:42 -04:00
var electionname = (i.options as CommandInteractionOptionResolver).getSubcommand();
2024-06-04 00:43:42 -04:00
var election = structuredClone(Elections[electionname]);
for (const candidate of election.candidates) {
var option = (i.options as CommandInteractionOptionResolver).getString(candidate.party.toLowerCase().replace(/ /g, "_") + "_candidate");
if (option) {
candidate.name = option;
}
var bias = (i.options as CommandInteractionOptionResolver).getNumber(candidate.party.toLowerCase().replace(/ /g, "_") + "_bias");
if (bias) {
for (const state of Object.keys(election.states)) {
election.states[state].odds[candidate.party] += bias;
}
2024-06-04 00:43:42 -04:00
}
}
2024-06-04 00:43:42 -04:00
var newcandidate = (i.options as CommandInteractionOptionResolver).getString("add_candidate");
if (newcandidate) {
var party = (i.options as CommandInteractionOptionResolver).getString("with_party") || "Independent";
var color = (i.options as CommandInteractionOptionResolver).getString("with_color") || "#bfab22";
var odds = (i.options as CommandInteractionOptionResolver).getNumber("with_odds") || 0.33;
if (!/^#[0-9A-Fa-f]{3,6}$/.test(color)) {
await i.editReply("Please provide a valid hex color code");
2024-06-04 00:43:42 -04:00
return;
}
if (election.candidates.some(c => c.party === party || c.name === newcandidate)) {
await i.editReply("A candidate with that name or party already exists");
2024-06-04 00:43:42 -04:00
return
}
election.candidates.push({
name: newcandidate,
party: party,
color: color
});
for (const state of Object.keys(election.states))
election.states[state].odds[party] = odds;
}
var result = await MakePrediction(election);
var embed = new EmbedBuilder()
.setTitle(election.title)
.setDescription(election.description.replace("$WINNER", result.winner))
.addFields(result.candidates.map(c => {
return {
name: `${(result.winner === c.name ? ":white_check_mark:" : "")} ${c.name} (${c.party})`,
value: `${c.votes} ${VoteTypeString(election.voteType)}${election.voteType === VoteType.Popular ? ` (${((c.votes / result.totalVotes) * 100).toFixed(2)}%)` : ""}`,
2024-06-04 00:43:42 -04:00
inline: true
}
}))
.setImage("attachment://election.png")
.setTimestamp();
await i.editReply({embeds: [embed], files: [{attachment: result.png, name: "election.png"}]});
2024-06-01 02:14:08 -04:00
}
}
});
client.login(config.token);
})();
async function publishSlashCommands(clientid : string) {
const rest = new REST({ version: '10' }).setToken(config.token);
await rest.put(Routes.applicationCommands(clientid), {body: commands});
console.log("Successfully registered slash commands");
}