commit a1dfffe23781c29fedd890c11c97a1d563372489 Author: Elijah R Date: Sat Jun 1 02:14:08 2024 -0400 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92ab380 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.json +dist/ +node_modules/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/assets/ElectoralCollege2024.svg b/assets/ElectoralCollege2024.svg new file mode 100644 index 0000000..5434e74 --- /dev/null +++ b/assets/ElectoralCollege2024.svg @@ -0,0 +1,521 @@ + + + + + + + image/svg+xml + + 2020 US presidential election results + + + + 2024 US presidential election results + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + 4 + 12 + 8 + 54 + 6 + 11 + 6 + 4 + 4 + 3 + 10 + 5 + 40 + 7 + 6 + 5 + 3 + 3 + 10 + 10 + 19 + 6 + 10 + 6 + 8 + 6 + 9 + 16 + 30 + 9 + 16 + 11 + 8 + 11 + 15 + 17 + 4 + 13 + 19 + 28 + 2 + 1 + 1 + 1 + + + + + NH 4 + VT 3 + MA 11 + RI 4 + CT 7 + NJ 14 + DE 3 + MD 10 + DC 3 + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..e988a00 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "politicsbot", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "scripts": { + "build": "tsc" + }, + "keywords": [], + "author": "Computernewb Development Team", + "license": "WTFPL", + "devDependencies": { + "@types/node": "^20.13.0", + "@types/svgdom": "^0.1.2", + "typescript": "^5.4.5" + }, + "dependencies": { + "@svgdotjs/svg.js": "^3.2.0", + "discord.js": "^14.15.2", + "sharp": "^0.33.4", + "svgdom": "^0.1.19" + }, + "type": "module", + "packageManager": "pnpm@9.1.2+sha512.127dc83b9ea10c32be65d22a8efb4a65fb952e8fefbdfded39bdc3c97efc32d31b48b00420df2c1187ace28c921c902f0cb5a134a4d032b8b5295cbfa2c681e2" +} diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 0000000..340e1d2 --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,8 @@ +const commands = [ + { + name: 'simulate', + description: "Simulate the 2024 United States Presidential Election", + } +]; + +export default commands; \ No newline at end of file diff --git a/src/gopwinodds.ts b/src/gopwinodds.ts new file mode 100644 index 0000000..61b84c6 --- /dev/null +++ b/src/gopwinodds.ts @@ -0,0 +1,67 @@ +const GOP_WIN_ODDS = { + // Data from https://www.insideelections.com/ratings/president + // Toss-up + "AZ": 0.5, + "GA": 0.5, + "MI": 0.5, + "NV": 0.5, + "PA": 0.5, + "WI": 0.5, + // Lean D + "MN": 0.4, + "NH": 0.4, + // Likely D + "ME": 0.2, + "VA": 0.2, + // Solid D + "CA": 0, + "CO": 0, + "CT": 0, + "DC": 0, + "DE": 0, + "HI": 0, + "IL": 0, + "MA": 0, + "MD": 0, + "NJ": 0, + "NM": 0, + "NY": 0, + "OR": 0, + "RI": 0, + "VT": 0, + "WA": 0, + "ME-01": 0, + // Tilt R + "NC": 0.55, + "NE-02": 0.55, + // Lean R + "FL": 0.6, + // Likely R + "IA": 0.8, + "OH": 0.8, + "TX": 0.8, + "ME-02": 0.8, + // Solid R + "AK": 1, + "AL": 1, + "AR": 1, + "ID": 1, + "IN": 1, + "KS": 1, + "KY": 1, + "LA": 1, + "MO": 1, + "MS": 1, + "MT": 1, + "NE": 1, + "ND": 1, + "OK": 1, + "SC": 1, + "SD": 1, + "TN": 1, + "UT": 1, + "WV": 1, + "WY": 1 +} + +export default GOP_WIN_ODDS; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..67f9e10 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,49 @@ +import {REST, Routes, Client, GatewayIntentBits, CommandInteraction, EmbedBuilder } from "discord.js"; +import * as fs from "node:fs"; +import commands from "./commands.js"; +import {MakePrediction} from "./predictor.js"; + +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) { + switch (i.commandName) { + case "simulate": + var result = await MakePrediction(); + var embed = new EmbedBuilder() + .setTitle("2024 United States Presidential Election") + .setDescription(`The CalubViem Press has called the 2024 United States Presidential Election for ${result.winner}!`) + .addFields( + {name: `${result.winner === result.gopCandidate ? ":white_check_mark:" : ""} ${result.gopCandidate}`, value: `${result.gopVotes} Electoral Votes`, inline: true}, + {name: `${result.winner === result.demCandidate ? ":white_check_mark:" : ""} ${result.demCandidate}`, value: `${result.demVotes} Electoral Votes`, inline: true}, + ) + .setImage("attachment://election.png") + .setTimestamp(); + await i.reply({embeds: [embed], files: [{attachment: result.png, name: "election.png"}]}); + break; + } + } + }); + + 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"); +} \ No newline at end of file diff --git a/src/predictor.ts b/src/predictor.ts new file mode 100644 index 0000000..119fedb --- /dev/null +++ b/src/predictor.ts @@ -0,0 +1,57 @@ +import GOP_WIN_ODDS from "./gopwinodds.js"; +import ELECTORAL_COLLEGE from "./states.js" +import { createSVGWindow } from 'svgdom' +import { SVG, registerWindow } from '@svgdotjs/svg.js' +import * as fs from "node:fs/promises"; +import sharp from "sharp"; + +const GOP_CANDIDATE = "Donald J. Trump"; +const DEM_CANDIDATE = "Joseph R. Biden Jr."; +const BASE_SVG = await fs.readFile("assets/ElectoralCollege2024.svg", "utf-8"); + +export function MakePrediction() : Promise { + return new Promise(async res => { + const window = createSVGWindow(); + registerWindow(window, window.document); + var election = {} as any; + var gopVotes = 0; + var demVotes = 0; + var draw = SVG(window.document.documentElement); + draw.svg(BASE_SVG); + Object.keys(ELECTORAL_COLLEGE).forEach(state => { + if ((GOP_WIN_ODDS as any)[state] >= Math.random()) { + election[state] = "R"; + gopVotes += (ELECTORAL_COLLEGE as any)[state]; + // @ts-ignore + draw.find(`#${state}`).fill("#F07763"); + } + else { + election[state] = "D"; + demVotes += (ELECTORAL_COLLEGE as any)[state]; + // @ts-ignore + draw.find(`#${state}`).fill("#698DC5"); + } + }); + var s = sharp(Buffer.from(draw.svg())); + var png = await s.png().toBuffer(); + res({ + gopVotes, + demVotes, + gopCandidate: GOP_CANDIDATE, + demCandidate: DEM_CANDIDATE, + winner: gopVotes > demVotes ? GOP_CANDIDATE : DEM_CANDIDATE, + svg: draw.svg(), + png, + }); + }); +} + +export interface Prediction { + gopVotes: number, + demVotes: number, + gopCandidate: string, + demCandidate: string, + winner: string, + svg: string, + png: Buffer, +} \ No newline at end of file diff --git a/src/states.ts b/src/states.ts new file mode 100644 index 0000000..d5df824 --- /dev/null +++ b/src/states.ts @@ -0,0 +1,58 @@ +const ELECTORAL_COLLEGE = { + "AL": 9, + "AK": 3, + "AZ": 11, + "AR": 6, + "CA": 55, + "CO": 10, + "CT": 7, + "DE": 3, + "DC": 3, + "FL": 30, + "GA": 16, + "HI": 4, + "ID": 4, + "IL": 19, + "IN": 11, + "IA": 6, + "KS": 6, + "KY": 8, + "LA": 8, + "ME": 2, + "ME-01": 1, + "ME-02": 1, + "MD": 10, + "MA": 11, + "MI": 15, + "MN": 10, + "MS": 6, + "MO": 10, + "MT": 4, + "NE": 4, + "NE-02": 1, + "NV": 6, + "NH": 4, + "NJ": 14, + "NM": 5, + "NY": 28, + "NC": 16, + "ND": 3, + "OH": 17, + "OK": 7, + "OR": 8, + "PA": 19, + "RI": 4, + "SC": 9, + "SD": 3, + "TN": 11, + "TX": 40, + "UT": 6, + "VT": 3, + "VA": 13, + "WA": 12, + "WV": 4, + "WI": 10, + "WY": 3 +}; + +export default ELECTORAL_COLLEGE; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..50ca15a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "es2022", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}