diff --git a/.gitignore b/.gitignore index 65d21b1..8294e7c 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,6 @@ node_modules/ # Local docker volume mounts .volumes + +# Editor folders +.vscode diff --git a/.qovery.yml b/.qovery.yml deleted file mode 100644 index 0abf6ae..0000000 --- a/.qovery.yml +++ /dev/null @@ -1,15 +0,0 @@ -application: - name: coffeebot-app - project: coffeebot-project - cloud_region: aws/ap-southeast-2 - publicly_accessible: true -databases: -- type: postgresql - version: "11.5" - name: coffee-db -routers: -- name: main - routes: - - application_name: coffeebot-app - paths: - - / diff --git a/Dockerfile b/Dockerfile index c4db73c..6f8fdf5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:13-alpine +FROM node:16-alpine RUN mkdir -p /usr/src/app diff --git a/README.md b/README.md index 79b129a..efd60c6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ -No readme yet - added to try to get qovery to deploy -+1 +### CoffeeBot + +Coffeebot is a Slackbot used to track the coffee consumption of users in a slack workspace. This was +hacked out just before international coffee day, 2000. + +It was initially set up to try to run on Firebase, but that didn't work well because of the startup +time of workers. It was then set up to try to use Qovery, but Qovery wouldn't work - it just kept +producing unusable environments. It was finally set up as a simple docker container, and now runs +happily on a box running Caprover. + +It's poorly written, hacked together in a short space of time and given virtually no attention +thereafter, but a single instance has run happily for 1.5 years so it seems to be remarkably +stable considering. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f0de603..8c4067f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3" services: db: - image: postgres:12-alpine + image: postgres:15-alpine volumes: - pgdata:/var/lib/postgresql/data ports: diff --git a/src/backup.js b/src/backup.js new file mode 100644 index 0000000..6724429 --- /dev/null +++ b/src/backup.js @@ -0,0 +1,129 @@ +const queries = require("./queries"); +const { DateTime } = require("luxon"); +const AWS = require("aws-sdk"); +const { LexModelBuildingService } = require("aws-sdk"); + +BACKUP_SINCE_QUERIES = [ + { tableName: "abstract_user_v2", query: queries.BACKUP_ABSTRACT_USER_SINCE_DATE_V2_QUERY }, + { tableName: "team_v2", query: queries.BACKUP_TEAM_SINCE_DATE_V2_QUERY }, + { tableName: "user_v2", query: queries.BACKUP_USER_SINCE_DATE_V2_QUERY }, + { tableName: "drink_v2", query: queries.BACKUP_DRINK_SINCE_DATE_V2_QUERY }, +] + +BACKUP_FULL_QUERIES = [ + { tableName: "abstract_user_v2", query: queries.BACKUP_ABSTRACT_USER_ALL_V2_QUERY }, + { tableName: "team_v2", query: queries.BACKUP_TEAM_ALL_V2_QUERY }, + { tableName: "user_v2", query: queries.BACKUP_USER_ALL_V2_QUERY }, + { tableName: "drink_v2", query: queries.BACKUP_DRINK_ALL_V2_QUERY },] + +async function createBackup(pool, awsDetails) { + console.log("Commencing incremental backup"); + const client = await pool.connect(); + + try { + const dt = DateTime.local().setZone("Australia/Melbourne"); + const getLastSuccessfulBackupQuery = await client.query(queries.GET_LAST_SUCCESSFUL_BACKUP_DATETIME_QUERY); + + let backupFromDate = DateTime.fromSeconds(0); + if (getLastSuccessfulBackupQuery.rows.length > 0) { + backupFromDate = DateTime.fromJSDate( + getLastSuccessfulBackupQuery.rows[0].backup_until + ); + } + + const rowsToBackUp = Array(); + + for ({ tableName, query } of BACKUP_SINCE_QUERIES) { + const queryResult = await client.query(query, [ + backupFromDate.toISO(), + ]); + + for (row of queryResult.rows) { + rowsToBackUp.push(JSON.stringify({ tableName: tableName, ...row })) + } + } + + const s3 = new AWS.S3({ + accessKeyId: awsDetails.AWS_ACCESS_KEY_ID, + secretAccessKey: awsDetails.AWS_SECRET_KEY, + region: awsDetails.AWS_REGION, + }); + + const params = { + Bucket: awsDetails.AWS_BUCKET_NAME, + Key: `${awsDetails.AWS_BACKUP_FOLDER}/${dt.toISO()}.v2.rows.incremental.json`, + Body: rowsToBackUp.join("\n"), + }; + + try { + await s3.upload(params).promise(); + await client.query(queries.CREATE_BACKUP_ROW_QUERY, [dt.toISO(), dt.toISO(), true, ""]); + const message = `${rowsToBackUp.length} rows backed up. Filename: ${params.Key}.`; + console.log(message); + return { + response_type: "ephemeral", + text: message, + }; + } catch (err) { + await client.query(queries.CREATE_BACKUP_ROW_QUERY, [dt.toISO(), dt.toISO(), false, err]); + const message = `Incremental backup error: ${err}`; + console.log(message); + return { response_type: "ephemeral", text: message }; + } + } finally { + await client.release(); + } +} + +async function createFullBackup(pool, awsDetails) { + console.log("Commencing full backup"); + const client = await pool.connect(); + + try { + const dt = DateTime.local().setZone("Australia/Melbourne"); + const rowsToBackUp = Array(); + + for ({ tableName, query } of BACKUP_FULL_QUERIES) { + const queryResult = await client.query(query); + + for (row of queryResult.rows) { + rowsToBackUp.push(JSON.stringify({ tableName: tableName, ...row })) + } + } + + const s3 = new AWS.S3({ + accessKeyId: awsDetails.AWS_ACCESS_KEY_ID, + secretAccessKey: awsDetails.AWS_SECRET_KEY, + region: awsDetails.AWS_REGION, + }); + + const params = { + Bucket: awsDetails.AWS_BUCKET_NAME, + Key: `${awsDetails.AWS_BACKUP_FOLDER}/${dt.toISO()}.v2.rows.full.json`, + Body: rowsToBackUp.join("\n"), + }; + + try { + await s3.upload(params).promise(); + await client.query(queries.CREATE_BACKUP_ROW_QUERY, [dt.toISO(), dt.toISO(), true, ""]); + const message = `${rowsToBackUp.length} rows backed up. Filename: ${params.Key}.`; + console.log(message); + return { + response_type: "ephemeral", + text: message, + }; + } catch (err) { + await client.query(queries.CREATE_BACKUP_ROW_QUERY, [dt.toISO(), dt.toISO(), false, err]); + const message = `Full backup error: ${err}`; + console.log(message); + return { response_type: "ephemeral", text: message }; + } + } finally { + await client.release(); + } +} + +module.exports = { + createBackup, + createFullBackup, +} \ No newline at end of file diff --git a/src/example.env b/src/example.env index 9e4bce8..00ce6db 100644 --- a/src/example.env +++ b/src/example.env @@ -1,17 +1,17 @@ -AUTH_KEY="qYe4VZgQMhsAzxqJzmFCJjZi4tVsznq2ogcN3HTm" - -QOVERY_DATABASE_COFFEE_DB_USERNAME="coffeebot" -QOVERY_DATABASE_COFFEE_DB_HOST="db" -QOVERY_DATABASE_COFFEE_DB_DATABASE="coffeebot" -QOVERY_DATABASE_COFFEE_DB_PASSWORD="coffeebot_password" -QOVERY_DATABASE_COFFEE_DB_PORT=5432 +AUTH_KEY= +ADMIN_KEY= +POSTGRES_HOST="db" POSTGRES_USER="coffeebot" POSTGRES_PASSWORD="coffeebot_password" POSTGRES_DB="coffeebot" +POSTGRES_PORT=5432 AWS_ACCESS_KEY_ID= AWS_SECRET_KEY= AWS_BUCKET_NAME="coffeebot-backups" AWS_BACKUP_FOLDER="coffeebot-backups" AWS_REGION= + +REQUEST_PASSTHROUGH_HOST="127.0.0.1" +REQUEST_PASSTHROUGH_PORT="80" \ No newline at end of file diff --git a/src/index.js b/src/index.js index 6a406e6..e56d6cd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,50 +1,130 @@ require("dotenv").config(); + const AUTH_KEY = process.env.AUTH_KEY; -const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; -const AWS_SECRET_KEY = process.env.AWS_SECRET_KEY; -const AWS_BUCKET_NAME = process.env.AWS_BUCKET_NAME; -const AWS_BACKUP_FOLDER = process.env.AWS_BACKUP_FOLDER; -const AWS_REGION = process.env.AWS_REGION; +const ADMIN_KEY = process.env.ADMIN_KEY; + +const awsDetails = { + AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, + AWS_SECRET_KEY: process.env.AWS_SECRET_KEY, + AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME, + AWS_BACKUP_FOLDER: process.env.AWS_BACKUP_FOLDER, + AWS_REGION: process.env.AWS_REGION, +} + +const REQUEST_PASSTHROUGH_HOST = process.env.REQUEST_PASSTHROUGH_HOST; +const REQUEST_PASSTHROUGH_PORT = process.env.REQUEST_PASSTHROUGH_PORT; + +const GENERIC_FAILURE_RESPONSE = { + response_type: "ephemeral", + text: "I'm afraid I don't understand your command. Take another sip and try again.", +}; const MAX_COFFEE_ADD = 5; const MAX_COFFEE_SUBTRACT = 2; const COUNT_DISPLAY_SIZE = 5; +TARGET_MIGRATION_LEVEL = 2; + const Koa = require("koa"); const Router = require("koa-router"); const bodyParser = require("koa-bodyparser"); const { DateTime } = require("luxon"); const { Pool } = require("pg"); -const AWS = require("aws-sdk"); const CronJob = require("cron").CronJob; +// http is used to forward through the incoming requests +// to the old coffeebot version in case rollback is needed +const http = require('http'); + +const queries = require("./queries"); +const migration = require("./migration"); +const backup = require("./backup"); +const wordlist = require("./wordlist"); const app = new Koa(); app.use(bodyParser()); const router = new Router(); const pool = new Pool({ - user: process.env.QOVERY_DATABASE_COFFEE_DB_USERNAME, - host: process.env.QOVERY_DATABASE_COFFEE_DB_HOST, - database: process.env.DATABASE_NAME, - password: process.env.QOVERY_DATABASE_COFFEE_DB_PASSWORD, - port: process.env.QOVERY_DATABASE_COFFEE_DB_PORT, + user: process.env.POSTGRES_USER, + host: process.env.POSTGRES_HOST, + database: process.env.POSTGRES_DB, + password: process.env.POSTGRES_PASSWORD, + port: process.env.POSTGRES_PORT, }); +/** + * Cron Jobs perform backups of data in a + * JSONL format to S3. Note there is no current + * mechanism to restore the data - it would need + * to be manually loaded into the database + * (although that should be fairly straightforward) + */ new CronJob( "00 00 02 * * *", async function () { - await createBackup(); + await backup.createBackup(pool, awsDetails); + }, + null, + true, + "Australia/Melbourne" +); + +new CronJob( + "00 00 03 * * SAT", + async function () { + await backup.createFullBackup(pool, awsDetails); }, null, true, "Australia/Melbourne" ); +function passthroughRequest(ctx) { + /** + * Passes through/repeats the incoming request on to another + * host; basically just so the old coffeebot can continue + * to process incoming data in case the new one screws up + * or isn't working correctly. + */ + if (REQUEST_PASSTHROUGH_HOST === null || REQUEST_PASSTHROUGH_HOST === "") { + return; + } + + const data = JSON.stringify({ ...ctx.request.body }); + setTimeout(() => { + try { + const options = { + protocol: 'http:', + hostname: REQUEST_PASSTHROUGH_HOST, + port: REQUEST_PASSTHROUGH_PORT, + path: ctx.path, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + } + const request = http.request(options); + request.on('error', function (err) { + console.log(`Failed to replace request: inner error ${err}`); + }); + request.write(data); + request.end(); + } catch (e) { + console.log(`Failed to replay request: ${e}`); + } + }); +} + function showHelp() { + /** + * Post simple 'help' screen to Slack, visible only to the user who used the `/coffee help` command + * (because response_type is 'ephemeral') + */ return { response_type: "ephemeral", - text: `Ohai, and welcome to coffeebot. Coffeebot counts the coffees consumed by Common Coders because why not. + text: `Ohai, and welcome to coffeebot. Coffeebot counts the coffees consumed by teams because why not. The most important commands are: @@ -55,203 +135,69 @@ function showHelp() { - \`/coffee -\` - subtract multiple coffees, max 2; but try not to add coffees you're not drinking - \`/coffee count\` - show the total number of coffees, and highest 5 coffee consumers - \`/coffee count-all\` - show the total number of coffees, and _all_ coffee consumers - - \`/coffee stats\` - see summary data from all coffees recorded since the beginning of the bot`, + - \`/coffee stats\` - see summary data from all coffees recorded since the beginning of the bot (currently broken) + - \`/coffee link\` - get a code to link your user between workspaces, so you can log coffees from any of them + - \`/coffee link \` - use a link code to link your account between workspaces + - \`/coffee about\` - about coffeebot`, }; } -// CREATE_DATABASE_QUERY = "CREATE DATABASE drinks ENCODING = 'UTF8'"; -// CHECK_IF_DATABASE_EXISTS_QUERY = "SELECT datname FROM pg_catalog.pg_database WHERE datname = drinks;" -CREATE_BACKUP_TABLE_QUERY = ` -CREATE TABLE IF NOT EXISTS public.backups -( - id bigserial NOT NULL, - created_at timestamp with time zone NOT NULL, - backup_until timestamp with time zone NOT NULL, - successful BOOLEAN NOT NULL, - message TEXT, - PRIMARY KEY (id) -); -`; -GET_LAST_SUCCESSFUL_BACKUP_DATETIME_QUERY = - "SELECT backup_until FROM public.backups WHERE successful = TRUE ORDER BY backup_until DESC LIMIT 1"; -CREATE_BACKUP_ROW_QUERY = - "INSERT INTO public.backups (created_at, backup_until, successful, message) VALUES ($1, $2, $3, $4)"; - -CREATE_DRINK_TABLE_QUERY = ` -CREATE TABLE IF NOT EXISTS public.coffee -( - id bigserial NOT NULL, - user_id character varying(50) NOT NULL, - user_name character varying(200), - created_at timestamp with time zone NOT NULL, - PRIMARY KEY (id) -); -`; - -GET_DRINK_QUERY = - "SELECT user_id, user_name, created_at FROM coffee WHERE user_id = $1 AND user_name = $2 AND created_at = $3"; -ADD_DRINK_QUERY = "INSERT INTO coffee (user_id, user_name, created_at) VALUES($1, $2, $3)"; -COUNT_ALL_DRINKS_QUERY = "SELECT COUNT(*) FROM coffee WHERE created_at > $1 AND created_at < $2"; -COUNT_USER_DRINKS_QUERY = "SELECT COUNT(*) FROM coffee WHERE user_id = $1 AND created_at > $2 AND created_at < $3"; -COUNT_ALL_DRINKS_EVER_QUERY = "SELECT COUNT(*) FROM coffee" -AVERAGE_USER_DRINKS_EVER_QUERY = ` -SELECT user_name, COUNT(coffees_on_day) AS reporting_days, SUM(coffees_on_day) AS total_coffees, AVG(coffees_on_day) AS avg_coffees_per_day, STDDEV(coffees_on_day) AS stddev_coffees_per_day -FROM ( - SELECT user_name, COUNT(*) AS coffees_on_day - FROM coffee - GROUP BY user_name, date(created_at AT TIME ZONE 'Australia/Melbourne') -) AS coffees_per_day -GROUP BY user_name -ORDER BY avg_coffees_per_day DESC`; -TALLY_ALL_DRINKS_QUERY = - "SELECT user_name, COUNT(*) AS drink_count FROM coffee WHERE created_at > $1 AND created_at < $2 GROUP BY user_name ORDER BY drink_count DESC"; -DELETE_N_MOST_RECENT_DRINKS_FOR_USER_QUERY = - "DELETE FROM coffee WHERE id IN (SELECT id FROM coffee WHERE user_id = $1 AND created_at > $2 AND created_at < $3 ORDER BY id DESC LIMIT $4)"; -ALL_DRINKS_SINCE_DATETIME_QUERY = "SELECT id, user_id, user_name, created_at FROM coffee WHERE created_at > $1"; -ALL_DRINKS_QUERY = "SELECT id, user_id, user_name, created_at FROM coffee"; - -async function createBackup() { - const client = await pool.connect(); - - try { - const dt = DateTime.local().setZone("Australia/Melbourne"); - const getLastSuccessfulBackupQuery = await client.query(GET_LAST_SUCCESSFUL_BACKUP_DATETIME_QUERY); - - let backupFromDate = DateTime.fromSeconds(0); - if (getLastSuccessfulBackupQuery.rows.length > 0) { - backupFromDate = DateTime.fromJSDate(getLastSuccessfulBackupQuery.rows[0].backup_until); - } - - const getAllDrinksSinceDatetimeQuery = await client.query(ALL_DRINKS_SINCE_DATETIME_QUERY, [ - backupFromDate.toISO(), - ]); - allDrinksSinceDatetime = getAllDrinksSinceDatetimeQuery.rows; - - if (allDrinksSinceDatetime.length === 0) { - return { - response_type: "ephemeral", - text: `No entries since ${backupFromDate.toISO()} to back up.`, - }; - } - - const rowsToBackUp = Array(); - let maxDate = DateTime.fromSeconds(0); - - for (let idx = 0, len = allDrinksSinceDatetime.length; idx < len; idx++) { - rowsToBackUp.push( - JSON.stringify({ - id: allDrinksSinceDatetime[idx].id, - user_id: allDrinksSinceDatetime[idx].user_id, - user_name: allDrinksSinceDatetime[idx].user_name, - created_at: allDrinksSinceDatetime[idx].created_at, - }) - ); - let thisDate = DateTime.fromJSDate(allDrinksSinceDatetime[idx].created_at); - console.log(thisDate); - if (thisDate > maxDate) { - maxDate = thisDate; - } - } - - const s3 = new AWS.S3({ - accessKeyId: AWS_ACCESS_KEY_ID, - secretAccessKey: AWS_SECRET_KEY, - region: AWS_REGION, - }); - - const params = { - Bucket: AWS_BUCKET_NAME, - Key: `${AWS_BACKUP_FOLDER}/${maxDate.toISO()}.rows.incremental.json`, - Body: rowsToBackUp.join("\n"), - }; +function showAbout(dbTeamLabel) { + /** + * Post simple 'about' screen to Slack, visible only to the user who used the `/coffee help` command + * (because response_type is 'ephemeral') + */ + return { + response_type: "ephemeral", + text: `CoffeeBot is a helpful slack bot dedicated to capturing the coffee consumption habits of ${getTeamLabelOrGenericPlural(dbTeamLabel)}.\n` + + `It was written the night before international coffee day 2020 as a something between ` + + `a joke and an experiment in using firebase. Somehow, it has continued to be used since then (although no longer in Firebase).\n` + + `It was created based on the idea by Bec (of 2020 Common Code) that it would be cool to know how much coffee ` + + `team members drink. I hope you enjoy it. - try { - await s3.upload(params).promise(); - await client.query(CREATE_BACKUP_ROW_QUERY, [dt.toISO(), maxDate.toISO(), true, ""]); - return { - response_type: "ephemeral", - text: `${allDrinksSinceDatetime.length} entries backed up. Filename: ${params.Key}.`, - }; - } catch (err) { - await client.query(CREATE_BACKUP_ROW_QUERY, [dt.toISO(), maxDate.toISO(), false, err]); - return { response_type: "ephemeral", text: `Backup error: ${err}` }; - } - } finally { - await client.release(); + - Simeon` } } -async function createFullBackup() { - const client = await pool.connect(); - - try { - const dt = DateTime.local().setZone("Australia/Melbourne"); - const getAllDrinksQuery = await client.query(ALL_DRINKS_QUERY); - allDrinks = getAllDrinksQuery.rows; - - if (allDrinks.length === 0) { - return { - response_type: "ephemeral", - text: "No entries, ever, to back up - which seems weird and wrong" - }; - } - - rowsToBackUp = allDrinks.map(data => - JSON.stringify({ - id: data.id, - user_id: data.user_id, - user_name: data.user_name, - created_at: data.created_at, - }) - ) - const s3 = new AWS.S3({ - accessKeyId: AWS_ACCESS_KEY_ID, - secretAccessKey: AWS_SECRET_KEY, - region: AWS_REGION, - }); - - const params = { - Bucket: AWS_BUCKET_NAME, - Key: `${AWS_BACKUP_FOLDER}/${dt.toISO()}.rows.full.json`, - Body: rowsToBackUp.join("\n"), - }; - - try { - await s3.upload(params).promise(); - return { - response_type: "ephemeral", - text: `${allDrinks.length} entries backed up. Filename: ${params.Key}.`, - }; - } catch (err) { - return { response_type: "ephemeral", text: `Backup error: ${err}` }; - } - } finally { - await client.release(); - } -} async function createDatabaseBitsIfMissing() { + /** + * Create something resembling a 'database migrations' table + * to track which groups of queries have executed to do database + * structure updates. + */ const client = await pool.connect(); try { - console.log("Attempting to create drink table"); - await client.query(CREATE_DRINK_TABLE_QUERY); - console.log("Attempting to create backup table"); - await client.query(CREATE_BACKUP_TABLE_QUERY); + console.log("Attempting to create migrations table"); + await client.query(queries.CREATE_MIGRATION_TABLE_VX_QUERY); console.log("All table creation complete"); } finally { await client.release(); } } -async function showCoffeeStats() { +async function showCoffeeStats(dbTeamId, dbTeamLabel) { + /** + * Return slack-renderable statistics on reported coffee consumption + * for people in the team for the current workspace. + * NOTE: This (as at 2023-06-20) returns an error in slack. + * Don't know whether that is because of an internal error, + * or the blocks going to slack don't have valid structure/format. + */ const client = await pool.connect(); try { - const totalCoffeeCountQuery = await client.query(COUNT_ALL_DRINKS_EVER_QUERY); + const totalCoffeeCountQuery = await client.query( + queries.COUNT_ALL_DRINKS_EVER_V2_QUERY, + [dbTeamId,] + ); const totalCoffeeCount = totalCoffeeCountQuery.rows[0].count; - const coffeeCountByUserQuery = await client.query(AVERAGE_USER_DRINKS_EVER_QUERY); + const coffeeCountByUserQuery = await client.query( + queries.AVERAGE_USER_DRINKS_EVER_V2_QUERY, + [dbTeamId,] + ); let blocks = []; let textChunks = []; @@ -276,7 +222,7 @@ async function showCoffeeStats() { type: "section", text: { type: "mrkdwn", - text: `*Since CoffeeBot began it's glorious existence*, Common Coders have consumed ${totalCoffeeCount} coffees`, + text: `*Since CoffeeBot began it's glorious existence*, ${getTeamLabelOrGenericPlural(dbTeamLabel)} have consumed ${totalCoffeeCount} coffees`, }, }); @@ -289,7 +235,15 @@ async function showCoffeeStats() { } } -async function showCoffeeCount(numOfItems) { +function getTeamLabelOrGenericPlural(dbTeamLabel) { + // Helper to get a generic team label if one is not set + return dbTeamLabel || 'workspace members'; +} + +async function showCoffeeCount(dbTeamId, dbTeamLabel, numOfItems) { + /** + * Return slack-renderable daily coffee leaderboard + */ const client = await pool.connect(); try { const dt = DateTime.local().setZone("Australia/Melbourne"); @@ -301,15 +255,17 @@ async function showCoffeeCount(numOfItems) { }); const start_of_tomorrow = dt.plus({ days: 1 }).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); - const totalCoffeeCountQuery = await client.query(COUNT_ALL_DRINKS_QUERY, [ + const totalCoffeeCountQuery = await client.query(queries.COUNT_ALL_DRINKS_V2_QUERY, [ start_of_today.toISO(), start_of_tomorrow.toISO(), + dbTeamId, ]); const totalCoffeeCount = totalCoffeeCountQuery.rows[0].count; - const coffeeCountByUserQuery = await client.query(TALLY_ALL_DRINKS_QUERY, [ + const coffeeCountByUserQuery = await client.query(queries.TALLY_ALL_DRINKS_V2_QUERY, [ start_of_today.toISO(), start_of_tomorrow.toISO(), + dbTeamId, ]); let blocks = []; @@ -339,7 +295,7 @@ async function showCoffeeCount(numOfItems) { type: "section", text: { type: "mrkdwn", - text: `*Today*, Common Coders have consumed ${totalCoffeeCount} coffees`, + text: `*Today*, ${getTeamLabelOrGenericPlural(dbTeamLabel)} have consumed ${totalCoffeeCount} coffees`, }, }); @@ -352,7 +308,92 @@ async function showCoffeeCount(numOfItems) { } } -async function addCoffee(userId, userName, inc) { +async function getOrCreateTeam(teamId, teamDomain) { + /** + * Create a 'team' record if one doesn't already exist for the team + * and return the database ID for the team (not the slack team_id) + */ + const client = await pool.connect(); + + try { + const dt = DateTime.local().setZone("Australia/Melbourne"); + const getOrCreateTeamQuery = await client.query( + queries.INSERT_OR_GET_TEAM_V2_QUERY, + [dt.toISO(), teamId, teamDomain] + ); + return { dbTeamId: getOrCreateTeamQuery.rows[0].id, dbTeamLabel: getOrCreateTeamQuery.rows[0].label }; + } catch (e) { + console.log(`Error in getOrCreateTeam: ${e}`); + } finally { + await client.release(); + } +} + +async function getOrCreateUser(userId, userName, dbTeamId) { + /** + * Create a `user` and `abstract_user` record if they don't + * already exist for the user and return the IDs + */ + const client = await pool.connect(); + + try { + // If there is an existing user set up, just return the details + getUserQuery = await client.query(queries.GET_USER_V2_QUERY, [userId, dbTeamId]); + if (getUserQuery.rows.length > 0) { + // If the user's slack name has changed, update it in the user table + const dbUserId = getUserQuery.rows[0].id; + if (getUserQuery.rows[0].user_name !== userName) { + await client.query(queries.UPDATE_USER_NAME_V2_QUERY, [dbUserId, userName]) + } + return { + dbAbstractUserId: getUserQuery.rows[0].abstract_user_id, + dbUserId: dbUserId, + dbUserIsAdmin: getUserQuery.rows[0].is_admin, + }; + } + + // User doesn't exist - we need to create them + try { + await client.query(queries.BEGIN); + const dt = DateTime.local().setZone("Australia/Melbourne"); + + // Create a new abstract user to link to + const insertAbstractUserQuery = await client.query(queries.INSERT_ABSTRACT_USER_V2_QUERY, [dt.toISO()]); + const dbAbstractUserId = insertAbstractUserQuery.rows[0].id + + // Create the main user record - this is created with an insert with on conflict + // in case something else slipped in an insert on us + const getOrCreateUserQuery = await client.query( + queries.INSERT_OR_GET_USER_V2_QUERY, + [dt.toISO(), userId, userName, dbAbstractUserId, dbTeamId] + ); + + await client.query(queries.COMMIT); + + return { + dbAbstractUserId: getOrCreateUserQuery.rows[0].abstract_user_id, + dbUserId: getOrCreateUserQuery.rows[0].id, + dbUserIsAdmin: getOrCreateUserQuery.rows[0].is_admin, + }; + } catch (e) { + console.log(`Error in getOrCreateUser: ${e}`) + await client.query(queries.ROLLBACK); + } + } finally { + await client.release(); + } +} + +async function addCoffee(dbAbstractUserId, dbUserId, dbTeamId, dbTeamLabel, inc) { + /** + * Add coffees to the count for a specific user (or subtract if negative inc value), + * and return the new day total for that user in a slack-renderable form. + * For subtraction, the most recent coffee records are deleted (i.e. lost forever). + * On reflection, just marking them inactive would probably have been better but + * that's not what I've done. + */ + + // sanity check actions if (inc > MAX_COFFEE_ADD) { return { response_type: "ephemeral", @@ -378,26 +419,30 @@ async function addCoffee(userId, userName, inc) { }); const start_of_tomorrow = dt.plus({ days: 1 }).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); + // add or remove drinks if (inc > 0) { for (let idx = 0; idx < inc; idx++) { - await client.query(ADD_DRINK_QUERY, [userId, userName, dt.toISO()]); + await client.query( + queries.ADD_DRINK_V2_QUERY, + [dbAbstractUserId, dbUserId, dt.toISO()] + ); } } else if (inc < 0) { - await client.query(DELETE_N_MOST_RECENT_DRINKS_FOR_USER_QUERY, [ - userId, - start_of_today.toISO(), - start_of_tomorrow.toISO(), - -1 * inc, - ]); + await client.query( + queries.DELETE_N_MOST_RECENT_DRINKS_FOR_USER_V2_QUERY, + [dbAbstractUserId, start_of_today.toISO(), start_of_tomorrow.toISO(), -1 * inc,] + ); } - const totalCoffeeCountQuery = await client.query(COUNT_ALL_DRINKS_QUERY, [ + // calculate current drink count for the user and return as slack-renderable items + const totalCoffeeCountQuery = await client.query(queries.COUNT_ALL_DRINKS_V2_QUERY, [ start_of_today.toISO(), start_of_tomorrow.toISO(), + dbTeamId, ]); const totalCoffeeCount = totalCoffeeCountQuery.rows[0].count; - const userCoffeeCountQuery = await client.query(COUNT_USER_DRINKS_QUERY, [ - userId, + const userCoffeeCountQuery = await client.query(queries.COUNT_USER_DRINKS_V2_QUERY, [ + dbAbstractUserId, start_of_today.toISO(), start_of_tomorrow.toISO(), ]); @@ -405,66 +450,194 @@ async function addCoffee(userId, userName, inc) { return { response_type: "ephemeral", - text: `That's coffee number ${userCoffeeCount} for you today, and number ${totalCoffeeCount} for CC today`, + text: `That's coffee number ${userCoffeeCount} for you today, and number ${totalCoffeeCount} for ${getTeamLabelOrGenericPlural(dbTeamLabel)} today`, }; } finally { await client.release(); } } -PERMISSION_IMPORT = "IMPORT"; -PERMISSION_SLACKACTION = "SLACK_ACTION"; +async function getLinkCode(dbAbstractUserId) { + /** + * Generate a link code for linking a user between workspaces. + * The flow for linking is that the user requests a link code, which gets + * written to the database for that user and returned to the user in slack + * (visible only to that user, because 'ephemeral') + * The user then passes that link code in another workspace connected to + * CoffeeBot to link the accounts between the two spaces. Linking the account + * means that a single coffee count for the user can be generated across the multiple + * workspaces, regardless of where they log the coffee. + */ + const words = wordlist.getWords(4); + const dt = DateTime.local().setZone("Australia/Melbourne"); + const client = await pool.connect(); -async function hasPermission(ctx, action) { - return ctx.request.query.key === AUTH_KEY; + try { + await client.query(queries.INSERT_LINK_WORDS_V2_QUERY, [dbAbstractUserId, words, dt.toISO()]); + return { + response_type: "ephemeral", + text: `Your link code is ${words}. To link another workspace enter /coffee link ${words}`, + }; + } catch (e) { + console.log(`Error in getLinkCode: ${e}`); + } finally { + await client.release(); + } } -router.post("/importFromFirebaseData", async (ctx, next) => { - if (!hasPermission(ctx, PERMISSION_IMPORT)) { - ctx.body = { result: "nope" }; - return; +async function linkUserByCode(dbAbstractUserId, words) { + /** + * Receive a link code for a user, and use it to link the user between workspaces. + * Note that I have _no idea_ what will happen if a link code is passed back + * to the same workspace. Hopefully it doesn't explode things... + */ + const dt = DateTime.local().setZone("Australia/Melbourne"); + const dtLinkCutoff = dt.minus({ days: 1 }); + const client = await pool.connect(); + + try { + // This could probably all be done in a single query + // and there is also possibly a race condition here, although + // hopefully mitigated by a transaction error if two + // clients simultaneously try to delete the same link + // word record + client.query(queries.BEGIN); + const getAbstractUserForLinkWordQuery = await client.query( + queries.GET_ABSTRACT_USER_FOR_LINK_WORD_V2_QUERY, + [words, dtLinkCutoff.toISO()] + ); + + // Delete the link word if it exists; it has either been consumed, + // is too old, or never existing at this point + await client.query( + queries.DELETE_LINK_WORD_V2_QUERY, + [words,] + ); + + if (getAbstractUserForLinkWordQuery.rows.length < 1) { + return { + response_type: "ephemeral", + text: `The link code ${words} could not be found or is too old. Use /coffee link to get a new link code`, + }; + } + + const newDbAbstractUserId = getAbstractUserForLinkWordQuery.rows[0].abstract_user_id; + await client.query( + queries.UPDATE_ABSTRACT_USER_FOR_DRINKS_V2_QUERY, + [dbAbstractUserId, newDbAbstractUserId] + ); + + await client.query( + queries.UPDATE_ABSTRACT_USER_FOR_USER_V2_QUERY, + [dbAbstractUserId, newDbAbstractUserId] + ); + + await client.query(queries.COMMIT); + return { + response_type: "ephemeral", + text: `Your slack user has been linked successfully`, + }; + } catch (e) { + console.log(`Error in linkUserByCode: ${e}`); + client.query(queries.ROLLBACK); + } finally { + await client.release(); } +} - if (ctx.request.body) { - let added = 0; - let existing = 0; - const client = await pool.connect(); - try { - await Promise.all( - ctx.request.body.map(async (dayData) => { - if (!dayData.timestamp) { - return; - } - await Promise.all( - dayData.coffeeTimes.map(async (row) => { - const checkIfExistsQuery = await client.query(GET_DRINK_QUERY, [ - row.user_id, - row.user_name, - row.timestamp, - ]); - if (checkIfExistsQuery.rows.length === 0) { - added++; - await client.query(ADD_DRINK_QUERY, [row.user_id, row.user_name, row.timestamp]); - } else { - existing++; - } - }) - ); - }) - ); - ctx.body = { added: added, existing: existing }; - } finally { - await client.release(); + +async function makeAdmin(dbUserId, identifierKey, dbUserId, dbUserIsAdmin, userId, userName) { + /** + * Given the correct 'admin key' (which is static, which is bad) + * sets the calling user to be an `admin` + */ + if (ADMIN_KEY === null || ADMIN_KEY === "") { + console.log(`Attempt to identify as admin without ADMIN_KEY set: ${dbUserId}:${userId}:${userName}`) + // We return the generic error message for failed attempts to identify + return GENERIC_FAILURE_RESPONSE; + } + + if (identifierKey !== ADMIN_KEY) { + console.log(`Failed attempt to identify as admin: ${dbUserId}:${userId}:${userName}`) + // We return the generic error message for failed attempts to identify + return GENERIC_FAILURE_RESPONSE; + } + + const client = await pool.connect(); + + try { + const makeAdminIfNotAlreadyQuery = await client.query(queries.MAKE_ADMIN_IF_NOT_ALREADY_V2_QUERY, [dbUserId,]); + if (makeAdminIfNotAlreadyQuery.rowCount == 0) { + console.log(`Failed to identify as admin; may already be admin? ${dbUserId}:${userId}:${userName} isAdmin ${dbUserIsAdmin}`) + return GENERIC_FAILURE_RESPONSE; + } else { + console.log(`Identified as admin: ${dbUserId}:${userId}:${userName}`) + return { ...GENERIC_FAILURE_RESPONSE, text: `${GENERIC_FAILURE_RESPONSE.text} ;)` }; } + } catch (e) { + console.log(`makeAdmin failed for user ${dbUserId}:${userId}:${userName}: ${e}`); + } finally { + await client.release() } -}); +} + +async function setTeamLabel(dbTeamId, teamLabel, dbUserId, dbUserIsAdmin, userId, userName) { + /** + * Updates the label of the team to be used when outputting messages. (e.g. Common Coders) + * + * MUST ONLY BE CALLED BY AN ADMIN. THIS FUNCTION DOES ONLY A TRIVIAL SECURITY CHECK. + */ + if (!dbUserIsAdmin) { + console.log(`setTeamLabel was called with dbUserIsAdmin false (User: ${dbUserId}). Please check your values!`); + return GENERIC_FAILURE_RESPONSE; + } + const client = await pool.connect(); + + try { + console.log(`User setting team label. User: ${dbUserId}:${userId}:${userName} TeamId: ${dbTeamId} Label ${teamLabel}`); + await client.query(queries.SET_TEAM_LABEL_V2, [dbTeamId, teamLabel]); + return { + response_type: "in_channel", + text: `The workspace team name has been set to ${teamLabel} by ${userName}`, + }; + } catch (e) { + console.log(`setTeamLabel failed for user ${dbUserId}:${userId}:${userName}: ${e}`); + } finally { + await client.release() + } +} + +async function getMyInfo(dbTeamId, teamId, teamDomain, dbTeamLabel, dbAbstractUserId, dbUserId, dbUserIsAdmin, userId, userName) { + /** + * Outputs a summary of the current user context for debugging + */ + return { + response_type: "ephemeral", + text: `You are on team ${dbTeamId}:${teamId}:${teamDomain}:${dbTeamLabel}\nuser ${dbAbstractUserId}:${dbUserId}:${userId}:${userName}. Your is_admin value is ${dbUserIsAdmin}`, + }; +} + +PERMISSION_SLACKACTION = "SLACK_ACTION"; + +async function hasPermission(ctx, action) { + /** + * Check that the current user has the specified permission, + * except that right now the 'permission' is ignored and it + * is just a dumb key check + */ + return ctx.request.query.key === AUTH_KEY; +} router.post("/addCoffee", async (ctx, next) => { - if (!hasPermission(ctx, PERMISSION_IMPORT)) { + // The permissions are a lie - whatever action you + // pass it does the same thing + if (!hasPermission(ctx, PERMISSION_SLACKACTION)) { ctx.body = { result: "nope" }; return; } + passthroughRequest(ctx); + if (ctx.request.body.command !== "/coffee") { ctx.body = { response_type: "ephemeral", @@ -473,42 +646,115 @@ router.post("/addCoffee", async (ctx, next) => { return; } + if (ctx.request.body.text === "migrate") { + ctx.body = await migration.runMigrations( + pool, + ctx.request.body.user_id, + ctx.request.body.user_name, + ctx.request.body.team_id, + ctx.request.body.team_domain, + ) + return; + } + + if (await migration.areMigrationsPending(pool)) { + ctx.body = { + response_type: "ephemeral", + text: "Migrations must be run before continuing", + }; + return; + } + + const { dbTeamId, dbTeamLabel } = await getOrCreateTeam( + ctx.request.body.team_id, + ctx.request.body.team_domain + ); + const { dbAbstractUserId, dbUserId, dbUserIsAdmin } = await getOrCreateUser( + ctx.request.body.user_id, + ctx.request.body.user_name, + dbTeamId + ); + if (ctx.request.body.text === "help") { ctx.body = showHelp(); return; + } else if (ctx.request.body.text === "about") { + ctx.body = showAbout(dbTeamLabel); + return; + } else if (ctx.request.body.text === "link") { + ctx.body = await getLinkCode(dbAbstractUserId); + return; + } else if (ctx.request.body.text.startsWith("link ")) { + const linkWords = ctx.request.body.text.substring("link ".length); + ctx.body = await linkUserByCode(dbAbstractUserId, linkWords); + return; } else if (ctx.request.body.text === "count") { - ctx.body = await showCoffeeCount(COUNT_DISPLAY_SIZE); + ctx.body = await showCoffeeCount(dbTeamId, dbTeamLabel, COUNT_DISPLAY_SIZE); return; } else if (ctx.request.body.text === "count-all") { - ctx.body = await showCoffeeCount(null); + ctx.body = await showCoffeeCount(dbTeamId, dbTeamLabel, null); return; } else if (ctx.request.body.text === "stats") { - ctx.body = await showCoffeeStats(null); + ctx.body = await showCoffeeStats(dbTeamId, dbTeamLabel); return; } else if (ctx.request.body.text === "stomach-pump") { - ctx.body = await addCoffee(ctx.request.body.user_id, ctx.request.body.user_name, -1); + ctx.body = await addCoffee(dbAbstractUserId, dbUserId, dbTeamId, dbTeamLabel, -1); return; } else if (!isNaN(parseInt(ctx.request.body.text, 10))) { ctx.body = await addCoffee( - ctx.request.body.user_id, - ctx.request.body.user_name, + dbAbstractUserId, + dbUserId, + dbTeamId, + dbTeamLabel, parseInt(ctx.request.body.text, 10) ); return; } else if (ctx.request.body.text === "") { - ctx.body = await addCoffee(ctx.request.body.user_id, ctx.request.body.user_name, 1); + ctx.body = await addCoffee(dbAbstractUserId, dbUserId, dbTeamId, dbTeamLabel, 1); return; - } else if (ctx.request.body.text === "backup") { - ctx.body = await createBackup(); + } else if (ctx.request.body.text.startsWith("auth ")) { + const identifierKey = ctx.request.body.text.substring("auth ".length); + ctx.body = await makeAdmin( + dbUserId, + identifierKey, + dbUserId, + dbUserIsAdmin, + ctx.request.body.user_id, + ctx.request.body.user_name + ); return; - } else if (ctx.request.body.text === "backup-all") { - ctx.body = await createFullBackup(); + } else if (dbUserIsAdmin && ctx.request.body.text.startsWith("teamlabel ")) { + const teamLabel = ctx.request.body.text.substring("teamlabel ".length); + ctx.body = await setTeamLabel( + dbTeamId, + teamLabel, + dbUserId, + dbUserIsAdmin, + ctx.request.body.user_id, + ctx.request.body.user_name + ); + return; + } else if (dbUserIsAdmin && ctx.request.body.text == "myinfo") { + ctx.body = await getMyInfo( + dbTeamId, + ctx.request.body.team_id, + ctx.request.body.team_domain, + dbTeamLabel, + dbAbstractUserId, + dbUserId, + dbUserIsAdmin, + ctx.request.body.user_id, + ctx.request.body.user_name + ); + return; + } else if (dbUserIsAdmin && ctx.request.body.text === "backup") { + ctx.body = await backup.createBackup(pool, awsDetails); + return; + } else if (dbUserIsAdmin && ctx.request.body.text === "backup-all") { + ctx.body = await backup.createFullBackup(pool, awsDetails); return; } else { - ctx.body = { - response_type: "ephemeral", - text: "I'm afraid I don't understand your command. Take another sip and try again.", - }; + ctx.body = GENERIC_FAILURE_RESPONSE; return; } }); @@ -521,11 +767,11 @@ app.listen(3000, async () => { console.log("running on port 3000"); console.log({ - user: process.env.QOVERY_DATABASE_COFFEE_DB_USERNAME, - host: process.env.QOVERY_DATABASE_COFFEE_DB_HOST, - database: process.env.QOVERY_DATABASE_COFFEE_DB_DATABASE, - password: process.env.QOVERY_DATABASE_COFFEE_DB_PASSWORD, - port: process.env.QOVERY_DATABASE_COFFEE_DB_PORT, + user: process.env.POSTGRES_USER, + host: process.env.POSTGRES_HOST, + database: process.env.POSTGRES_DB, + password: process.env.POSTGRES_PASSWORD, + port: process.env.POSTGRES_PORT, key: process.env.AUTH_KEY, }); }); diff --git a/src/migration.js b/src/migration.js new file mode 100644 index 0000000..d637c53 --- /dev/null +++ b/src/migration.js @@ -0,0 +1,115 @@ +const queries = require("./queries"); +const { DateTime } = require("luxon"); + +async function runMigrations(pool, userId, userName, teamId, teamDomain) { + /*** + * Run migrations on the database if required. This can be run by any user (which is a bit + * dubious, but the command also isn't listed anywhere and should be idempotent so 🤷 gotta + * start somewhere) + * + * @param {string} userId - the slack user_id issuing the migrate command + * @param {string} userName - the slack user name issuing the migrate command + * @param {string} teamId - the workspace team_id from which the migrate command is being issued + * @param {string} teamDomain - the workspace team_domain from which the migrate command is being issued + */ + + const client = await pool.connect(); + + try { + const dt = DateTime.local().setZone("Australia/Melbourne"); + const getCurrentMigrationLevelQuery = await client.query(queries.GET_MIGRATION_VX_LEVEL); + + // If there are no migration rows, the max value is null + let currentMigrationLevel = getCurrentMigrationLevelQuery.rows[0].migration_level; + console.log(`Current migration level: ${currentMigrationLevel}`); + + if (currentMigrationLevel === null) { + await client.query(queries.BEGIN); + console.log("Attempting to create drink table"); + await client.query(queries.CREATE_DRINK_TABLE_V1_QUERY); + console.log("Attempting to create backup table"); + await client.query(queries.CREATE_BACKUP_TABLE_QUERY); + await client.query(queries.MIGRATION_V2_SET_MIGRATION_LEVEL, [1, dt.toISO()]); + await client.query(queries.COMMIT); + currentMigrationLevel = 1; + } + if (currentMigrationLevel < 2) { + console.log(`Applying migration level 2`); + await client.query(queries.BEGIN); + await client.query(queries.CREATE_ABSTRACT_USER_TABLE_V2_QUERY); + await client.query(queries.CREATE_TEAM_TABLE_V2_QUERY); + await client.query(queries.CREATE_USER_TABLE_V2_QUERY); + await client.query(queries.CREATE_DRINK_TABLE_V2_QUERY); + await client.query(queries.CREATE_LINK_WORDS_TABLE_V2_QUERY); + + // Now to migrate the data. + // There is an assumption here that all the current data comes from + // the workspace from which the migration is being run. + // If that isn't the case... well, just make sure it is the case OK? + // This could no doubt be implemented directly in SQL, but that's a + // future thing to think about + + // Create the team record + const insertTeamQuery = await client.query(queries.INSERT_OR_GET_TEAM_V2_QUERY, [dt.toISO(), teamId, teamDomain]); + const dbTeamId = insertTeamQuery.rows[0].id; + + // Get a list of distinct users from the drinks table + const getDistinctUsersQuery = await client.query(queries.MIGRATION_V2_GET_DISTINCT_USERS_QUERY); + + for (row of getDistinctUsersQuery.rows) { + // Create an abstract user and get back the identifier + const insertAbstractUserQuery = await client.query(queries.INSERT_ABSTRACT_USER_V2_QUERY, [dt.toISO()]); + const dbAbstractUserId = insertAbstractUserQuery.rows[0].id + + // Create a user record for each distinct user from the drinks table + // on the current team_id pointing to the abstract user and team record + await client.query(queries.INSERT_USER_V2_QUERY, [dt.toISO(), row.user_id, row.user_name, dbTeamId, dbAbstractUserId]) + } + + // Once that has been done for all users, run a single SQL command + // to migrate all the drinks that have been recorded + await client.query(queries.MIGRATION_V2_COPY_DRINKS) + // Step 3 ... Profit? + await client.query(queries.MIGRATION_V2_SET_MIGRATION_LEVEL, [2, dt.toISO()]); + await client.query(queries.COMMIT); + console.log(`Migration level 2 applied successfully`); + currentMigrationLevel = 2; + } + // Add additional migrations here + console.log(`All necessary migrations applied successfully`); + return { + response_type: "ephemeral", + text: `Migrations ran successfully`, + }; + } catch (e) { + await client.query(queries.ROLLBACK); + console.log(`Migrations failed to apply: ${e}`); + return { + response_type: "ephemeral", + text: `Migrations failed to apply`, + }; + } finally { + await client.release(); + } +} + +async function areMigrationsPending(pool) { + /** + * Check if any migrations are pending, and return an error if so + */ + const client = await pool.connect(); + try { + const getCurrentMigrationLevelQuery = await client.query(queries.GET_MIGRATION_VX_LEVEL); + + // If there are no migration rows, the max value is null + const currentMigrationLevel = getCurrentMigrationLevelQuery.rows[0].migration_level; + return currentMigrationLevel === null || currentMigrationLevel < TARGET_MIGRATION_LEVEL; + } finally { + await client.release(); + } +} + +module.exports = { + runMigrations, + areMigrationsPending, +} \ No newline at end of file diff --git a/src/package-lock.json b/src/package-lock.json index e709f14..adeabff 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,43 +1,1097 @@ { "name": "coffeebot", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "coffeebot", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.766.0", + "cron": "^1.8.2", + "dotenv": "^8.2.0", + "koa": "^2.13.0", + "koa-bodyparser": "^4.3.0", + "koa-router": "^9.4.0", + "luxon": "^1.25.0", + "pg": "^8.3.3" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1255.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1255.0.tgz", + "integrity": "sha512-S3oPXrBVOWquVL1bzH79bz88PgF4GqLcUbIph5yJ+pWW0OKNWGWKW1PDwtWi6ma+8mKXJ1gGKgy6R2hD57AsLw==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==" + }, + "node_modules/cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "dependencies": { + "moment-timezone": "^0.5.x" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.4.tgz", + "integrity": "sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-bodyparser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz", + "integrity": "sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==", + "dependencies": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-router": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-9.4.0.tgz", + "integrity": "sha512-RO/Y8XqSNM2J5vQeDaBI/7iRpL50C9QEudY4d3T4D1A2VMKLH0swmfjxDFPiIpVDLuNN6mVD9zBI1eFTHB6QaA==", + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.1.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "engines": { + "node": "*" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.39", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", + "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/pg": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/ylru": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.3.2.tgz", + "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==", + "engines": { + "node": ">= 4.0.0" + } + } + }, "dependencies": { "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.766.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.766.0.tgz", - "integrity": "sha512-msEEF7veBrxU1TlhLL33du4oJdwJ6uAWogf9+S0v437AX2z6K5IFr5J78JBysBudRHO0L6aC8bat6OS3oPEQvQ==", + "version": "2.1255.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1255.0.tgz", + "integrity": "sha512-S3oPXrBVOWquVL1bzH79bz88PgF4GqLcUbIph5yJ+pWW0OKNWGWKW1PDwtWi6ma+8mKXJ1gGKgy6R2hD57AsLw==", "requires": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", - "jmespath": "0.15.0", + "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", - "uuid": "3.3.2", + "util": "^0.12.4", + "uuid": "8.0.0", "xml2js": "0.4.19" } }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "buffer": { "version": "4.9.2", @@ -55,9 +1109,9 @@ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cache-content-type": { "version": "1.0.1", @@ -68,15 +1122,24 @@ "ylru": "^1.2.0" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" }, "co-body": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", - "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", "requires": { "inflation": "^2.0.0", "qs": "^6.5.2", @@ -85,11 +1148,11 @@ } }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" } }, "content-type": { @@ -104,19 +1167,12 @@ "requires": { "depd": "~2.0.0", "keygrip": "~1.1.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } } }, "copy-to": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", - "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=" + "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==" }, "cron": { "version": "1.8.2", @@ -127,102 +1183,140 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==" }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } }, "http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "requires": { "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" - }, - "dependencies": { - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - } + "http-errors": "~1.8.0" } }, "http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" }, "dependencies": { - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" } } }, @@ -242,27 +1336,56 @@ "inflation": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", - "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=" + "integrity": "sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==" }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" }, "keygrip": { "version": "1.1.0", @@ -273,18 +1396,18 @@ } }, "koa": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.0.tgz", - "integrity": "sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==", + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.4.tgz", + "integrity": "sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==", "requires": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", "content-disposition": "~0.5.2", "content-type": "^1.0.4", "cookies": "~0.8.0", - "debug": "~3.1.0", + "debug": "^4.3.2", "delegates": "^1.0.0", - "depd": "^1.1.2", + "depd": "^2.0.0", "destroy": "^1.0.4", "encodeurl": "^1.0.2", "escape-html": "^1.0.3", @@ -293,7 +1416,7 @@ "http-errors": "^1.6.3", "is-generator-function": "^1.0.7", "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", + "koa-convert": "^2.0.0", "on-finished": "^2.3.0", "only": "~0.0.2", "parseurl": "^1.3.2", @@ -317,22 +1440,12 @@ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" }, "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "requires": { "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "requires": { - "any-promise": "^1.1.0" - } - } + "koa-compose": "^4.1.0" } }, "koa-router": { @@ -345,78 +1458,68 @@ "koa-compose": "^4.1.0", "methods": "^1.1.2", "path-to-regexp": "^6.1.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } } }, "luxon": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz", - "integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==" + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==" }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" } }, "moment": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", - "integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "version": "0.5.39", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", + "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", "requires": { "moment": ">= 2.9.0" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -424,7 +1527,7 @@ "only": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" }, "packet-reader": { "version": "1.0.0", @@ -437,29 +1540,28 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", - "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "pg": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.3.3.tgz", - "integrity": "sha512-wmUyoQM/Xzmo62wgOdQAn5tl7u+IA1ZYK7qbuppi+3E+Gj4hlUxVHjInulieWrd0SfHi/ADriTb5ILJ/lsJrSg==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", + "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", - "pg-connection-string": "^2.3.0", - "pg-pool": "^3.2.1", - "pg-protocol": "^1.2.5", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.5.0", "pg-types": "^2.1.0", - "pgpass": "1.x", - "semver": "4.3.2" + "pgpass": "1.x" } }, "pg-connection-string": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.3.0.tgz", - "integrity": "sha512-ukMTJXLI7/hZIwTW7hGMZJ0Lj0S2XQBCJ4Shv4y1zgQ/vqVea+FLhzywvPj0ujSuofu+yA4MYHGZPTsgjBgJ+w==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "pg-int8": { "version": "1.0.1", @@ -467,14 +1569,15 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz", - "integrity": "sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==" + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "requires": {} }, "pg-protocol": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.5.tgz", - "integrity": "sha512-1uYCckkuTfzz/FCefvavRywkowa6M5FohNMF5OjKrqo9PSR8gYc8poVmwwYQaBxhmQdBjhtP514eXy9/Us2xKg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" }, "pg-types": { "version": "2.2.0", @@ -489,11 +1592,11 @@ } }, "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "requires": { - "split": "^1.0.0" + "split2": "^4.1.0" } }, "postgres-array": { @@ -504,7 +1607,7 @@ "postgres-bytea": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" }, "postgres-date": { "version": "1.0.7", @@ -522,47 +1625,55 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" }, "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" }, "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" } } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", @@ -572,40 +1683,37 @@ "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - }, - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "requires": { - "through": "2" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tsscmp": { "version": "1.0.6", @@ -624,26 +1732,51 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", "requires": { "punycode": "1.3.2", "querystring": "0.2.0" } }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } }, "xml2js": { "version": "0.4.19", @@ -657,7 +1790,7 @@ "xmlbuilder": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==" }, "xtend": { "version": "4.0.2", @@ -665,9 +1798,9 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.3.2.tgz", + "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==" } } } diff --git a/src/queries.js b/src/queries.js new file mode 100644 index 0000000..f069f3b --- /dev/null +++ b/src/queries.js @@ -0,0 +1,297 @@ +module.exports = { + // CREATE_DATABASE_QUERY : "CREATE DATABASE drinks ENCODING = 'UTF8'", + // CHECK_IF_DATABASE_EXISTS_QUERY : "SELECT datname FROM pg_catalog.pg_database WHERE datname = drinks;" + BEGIN: 'BEGIN;', + ROLLBACK: 'ROLLBACK', + COMMIT: 'COMMIT', + CREATE_ABSTRACT_USER_TABLE_V2_QUERY: ` + CREATE TABLE IF NOT EXISTS public.abstract_user_v2 + ( + id bigserial NOT NULL, + created_at timestamp with time zone NOT NULL, + PRIMARY KEY (id) + );`, + + CREATE_TEAM_TABLE_V2_QUERY: ` + CREATE TABLE IF NOT EXISTS public.team_v2 + ( + id bigserial NOT NULL, + created_at timestamp with time zone NOT NULL, + -- team_id is the slack team id + team_id character varying(50) NOT NULL, + team_domain character varying(200) NOT NULL, + -- label is an optional label for the team + -- to allow a particular header to be used on the + -- output. Not currently implemented. + label character varying(200), + PRIMARY KEY (id), + CONSTRAINT team_v2_team_id_unique UNIQUE (team_id) + );`, + + CREATE_USER_TABLE_V2_QUERY: ` + CREATE TABLE IF NOT EXISTS public.user_v2 + ( + id bigserial NOT NULL, + created_at timestamp with time zone NOT NULL, + user_id character varying(50) NOT NULL, + user_name character varying(200) NOT NULL, + -- label is an optional label for the user to be used + -- instead of the slack name. Not currently implemented. + label character varying(200), + is_admin bool NOT NULL DEFAULT FALSE, + team_id bigint NOT NULL, + abstract_user_id bigint NOT NULL, + PRIMARY KEY (id), + CONSTRAINT user_v2_user_fk_team FOREIGN KEY(team_id) REFERENCES public.team_v2(id), + CONSTRAINT user_v2_user_fk_abstract_user FOREIGN KEY(abstract_user_id) REFERENCES public.abstract_user_v2(id), + CONSTRAINT user_v2_team_id_user_id_unique UNIQUE (team_id, user_id) + ); + CREATE INDEX user_v2_idx_user_id_team_id ON public.user_v2(user_id, team_id); + `, + + // This includes a `drink` column but it defaults + // to coffee and right now there is no way to log + // anything but a coffee. + // That's not necessarily because coffee is the superior + // drink, Jon. That's not what I'm saying. + CREATE_DRINK_TABLE_V2_QUERY: ` + CREATE TABLE IF NOT EXISTS public.drink_v2 + ( + id bigserial NOT NULL, + created_at timestamp with time zone NOT NULL, + abstract_user_id bigint NOT NULL, + -- having both user_id and abstract_user_id is kind of + -- redundant, but I wasn't sure if it would be desirable + -- to link drinks back to the specific team at some point + user_id bigint NOT NULL, + drink character varying(20) DEFAULT 'coffee', + PRIMARY KEY (id), + CONSTRAINT drink_v2_user_fk_abstract_user FOREIGN KEY(abstract_user_id) REFERENCES public.abstract_user_v2(id), + CONSTRAINT drink_v2_user_fk_user FOREIGN KEY(user_id) REFERENCES public.user_v2(id) + ); + CREATE INDEX drink_v2_idx_created_at ON public.drink_v2(created_at); + CREATE INDEX drink_v2_idx_abstract_user_id ON public.drink_v2(abstract_user_id); + `, + + CREATE_LINK_WORDS_TABLE_V2_QUERY: ` + CREATE TABLE IF NOT EXISTS public.link_words_V2 + ( + abstract_user_id bigint NOT NULL, + words character varying(200), + created_at timestamp with time zone NOT NULL, + PRIMARY KEY (abstract_user_id), + CONSTRAINT link_words_v2_abstract_user_fk_abstract_user FOREIGN KEY(abstract_user_id) REFERENCES public.abstract_user_v2(id) + ); + CREATE INDEX link_words_v2_idx_words ON public.link_words_v2(words); + `, + + + + CREATE_MIGRATION_TABLE_VX_QUERY: ` + CREATE TABLE IF NOT EXISTS public.migrations + ( + id int NOT NULL, + run_at timestamp with time zone NOT NULL, + PRIMARY KEY(id) + );`, + + GET_MIGRATION_VX_LEVEL: "SELECT MAX(id) migration_level FROM public.migrations;", + + INSERT_ABSTRACT_USER_V2_QUERY: "INSERT INTO public.abstract_user_v2 (created_at) VALUES ($1) RETURNING id;", + INSERT_USER_V2_QUERY: ` + INSERT INTO public.user_v2 (created_at, user_id, user_name, team_id, abstract_user_id) + VALUES ($1, $2, $3, $4, $5) RETURNING id; + `, + + // Migration V2 functions + MIGRATION_V2_GET_DISTINCT_USERS_QUERY: "SELECT DISTINCT user_id, user_name FROM public.coffee;", + MIGRATION_V2_COPY_DRINKS: ` + INSERT INTO public.drink_v2 (created_at, abstract_user_id, user_id) + SELECT c.created_at, u1.abstract_user_id, u1.id + FROM public.coffee c + INNER JOIN public.user_v2 u1 ON u1.user_id = c.user_id + `, + + MIGRATION_V2_SET_MIGRATION_LEVEL: "INSERT INTO public.migrations (id, run_at) VALUES ($1, $2);", + + INSERT_OR_GET_TEAM_V2_QUERY: ` + WITH insert_attempt AS ( + INSERT INTO public.team_v2 (created_at, team_id, team_domain) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING + RETURNING id, label + ) + SELECT id, label FROM insert_attempt + UNION + SELECT id, label FROM public.team_v2 WHERE team_id = $2 + `, + + CREATE_BACKUP_TABLE_QUERY: ` + CREATE TABLE IF NOT EXISTS public.backups + ( + id bigserial NOT NULL, + created_at timestamp with time zone NOT NULL, + backup_until timestamp with time zone NOT NULL, + successful BOOLEAN NOT NULL, + message TEXT, + PRIMARY KEY (id) + );`, + GET_LAST_SUCCESSFUL_BACKUP_DATETIME_QUERY: "SELECT backup_until FROM public.backups WHERE successful = TRUE ORDER BY backup_until DESC LIMIT 1", + CREATE_BACKUP_ROW_QUERY: "INSERT INTO public.backups (created_at, backup_until, successful, message) VALUES ($1, $2, $3, $4)", + + CREATE_DRINK_TABLE_V1_QUERY: ` + CREATE TABLE IF NOT EXISTS public.coffee + ( + id bigserial NOT NULL, + user_id character varying(50) NOT NULL, + user_name character varying(200), + created_at timestamp with time zone NOT NULL, + PRIMARY KEY (id) + );`, + + ADD_DRINK_V2_QUERY: ` + INSERT INTO public.drink_v2 (abstract_user_id, user_id, created_at) + VALUES ($1, $2, $3); + `, + COUNT_ALL_DRINKS_V2_QUERY: ` + SELECT COUNT(*) + FROM public.user_v2 u1 + INNER JOIN public.drink_v2 d1 ON d1.abstract_user_id = u1.abstract_user_id + WHERE u1.team_id = $3 AND d1.created_at > $1 AND d1.created_at < $2; + `, + COUNT_USER_DRINKS_V2_QUERY: ` + SELECT COUNT(*) + FROM public.drink_v2 + WHERE abstract_user_id = $1 AND created_at > $2 AND created_at < $3; + `, + COUNT_ALL_DRINKS_EVER_V2_QUERY: ` + SELECT COUNT(*) + FROM public.drink_v2 d2 + INNER JOIN public.user_v2 u2 ON u2.abstract_user_id = d2.abstract_user_id + WHERE u2.team_id = $1; + `, + AVERAGE_USER_DRINKS_EVER_V2_QUERY: ` + SELECT + user_name, + COUNT(coffees_on_day) AS reporting_days, + SUM(coffees_on_day) AS total_coffees, + AVG(coffees_on_day) AS avg_coffees_per_day, + STDDEV(coffees_on_day) AS stddev_coffees_per_day + FROM ( + SELECT u2.user_name, COUNT(*) AS coffees_on_day + FROM public.drink_v2 d2 + INNER JOIN public.user_v2 u2 ON u2.abstract_user_id = d2.abstract_user_id + WHERE u2.team_id = $1 + GROUP BY u2.user_name, date(d2.created_at AT TIME ZONE 'Australia/Melbourne') + ) AS coffees_per_day + GROUP BY user_name + ORDER BY avg_coffees_per_day DESC; + `, + TALLY_ALL_DRINKS_V2_QUERY: ` + SELECT u1.user_name, COUNT(*) AS drink_count + FROM public.user_v2 u1 + INNER JOIN public.drink_v2 d1 ON d1.abstract_user_id = u1.abstract_user_id + WHERE d1.created_at > $1 AND d1.created_at < $2 AND u1.team_id = $3 + GROUP BY u1.user_name + ORDER BY drink_count DESC; + `, + DELETE_N_MOST_RECENT_DRINKS_FOR_USER_V2_QUERY: ` + DELETE FROM drink_v2 + WHERE id IN ( + SELECT d1.id + FROM public.drink_v2 d1 + WHERE d1.abstract_user_id = $1 AND created_at > $2 AND created_at < $3 + ORDER BY d1.id DESC + LIMIT $4 + ); + `, + + GET_USER_V2_QUERY: ` + SELECT id, abstract_user_id, is_admin, user_name + FROM public.user_v2 + WHERE user_id = $1 AND team_id = $2; + `, + INSERT_OR_GET_USER_V2_QUERY: ` + WITH insert_attempt AS ( + INSERT INTO public.user_v2 (created_at, user_id, user_name, abstract_user_id, team_id) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT DO NOTHING + RETURNING abstract_user_id, id, is_admin + ) + SELECT abstract_user_id, id, is_admin + FROM insert_attempt + UNION + SELECT abstract_user_id, id, is_admin + FROM public.user_v2 + WHERE user_id = $2 AND team_id = $5; + `, + + UPDATE_USER_NAME_V2_QUERY: ` + UPDATE public.user_v2 + SET user_name = $2 + WHERE id = $1; + `, + + INSERT_LINK_WORDS_V2_QUERY: ` + INSERT INTO public.link_words_v2 (abstract_user_id, words, created_at) + VALUES ($1, $2, $3) + ON CONFLICT(abstract_user_id) DO UPDATE SET words = $2, created_at = $3; + `, + GET_ABSTRACT_USER_FOR_LINK_WORD_V2_QUERY: ` + SELECT abstract_user_id + FROM public.link_words_v2 + WHERE words = $1 AND created_at > $2; + `, + DELETE_LINK_WORD_V2_QUERY: ` + DELETE FROM public.link_words_v2 + WHERE words = $1; + `, + UPDATE_ABSTRACT_USER_FOR_DRINKS_V2_QUERY: ` + UPDATE public.drink_v2 + SET abstract_user_id = $2 + WHERE abstract_user_id = $1; + `, + UPDATE_ABSTRACT_USER_FOR_USER_V2_QUERY: ` + UPDATE public.user_v2 + SET abstract_user_id = $2 + WHERE abstract_user_id = $1; + `, + + BACKUP_ABSTRACT_USER_SINCE_DATE_V2_QUERY: ` + SELECT * FROM public.abstract_user_v2 WHERE created_at > $1; + `, + BACKUP_ABSTRACT_USER_ALL_V2_QUERY: ` + SELECT * FROM public.abstract_user_v2; + `, + BACKUP_TEAM_SINCE_DATE_V2_QUERY: ` + SELECT * FROM public.team_v2 WHERE created_at > $1; + `, + BACKUP_TEAM_ALL_V2_QUERY: ` + SELECT * FROM public.team_v2; + `, + BACKUP_USER_SINCE_DATE_V2_QUERY: ` + SELECT * FROM public.user_v2 WHERE created_at > $1; + `, + BACKUP_USER_ALL_V2_QUERY: ` + SELECT * FROM public.user_v2; + `, + BACKUP_DRINK_SINCE_DATE_V2_QUERY: ` + SELECT * FROM public.drink_v2 WHERE created_at > $1; + `, + BACKUP_DRINK_ALL_V2_QUERY: ` + SELECT * FROM public.drink_v2; + `, + + MAKE_ADMIN_IF_NOT_ALREADY_V2_QUERY: ` + UPDATE public.user_v2 + SET is_admin = TRUE + WHERE id = $1 AND is_admin = FALSE + RETURNING id, is_admin; + `, + + SET_TEAM_LABEL_V2: ` + UPDATE public.team_v2 + SET label = $2 + WHERE id = $1; + ` +} \ No newline at end of file diff --git a/src/wordlist.js b/src/wordlist.js new file mode 100644 index 0000000..65bd9b9 --- /dev/null +++ b/src/wordlist.js @@ -0,0 +1,7796 @@ +const crypto = require("crypto"); + +function getWords(nWords) { + // Get n words from the diceware EFF 5-dice wordlist + const words = []; + for (let idx = 0; idx < nWords; idx++) { + words.push(wordlist[crypto.randomInt(0, wordlistLength)]); + } + return words.join("-"); +} + + +module.exports = { + getWords, +} + +const wordlist = [ + 'abacus', + 'abdomen', + 'abdominal', + 'abide', + 'abiding', + 'ability', + 'ablaze', + 'able', + 'abnormal', + 'abrasion', + 'abrasive', + 'abreast', + 'abridge', + 'abroad', + 'abruptly', + 'absence', + 'absentee', + 'absently', + 'absinthe', + 'absolute', + 'absolve', + 'abstain', + 'abstract', + 'absurd', + 'accent', + 'acclaim', + 'acclimate', + 'accompany', + 'account', + 'accuracy', + 'accurate', + 'accustom', + 'acetone', + 'achiness', + 'aching', + 'acid', + 'acorn', + 'acquaint', + 'acquire', + 'acre', + 'acrobat', + 'acronym', + 'acting', + 'action', + 'activate', + 'activator', + 'active', + 'activism', + 'activist', + 'activity', + 'actress', + 'acts', + 'acutely', + 'acuteness', + 'aeration', + 'aerobics', + 'aerosol', + 'aerospace', + 'afar', + 'affair', + 'affected', + 'affecting', + 'affection', + 'affidavit', + 'affiliate', + 'affirm', + 'affix', + 'afflicted', + 'affluent', + 'afford', + 'affront', + 'aflame', + 'afloat', + 'aflutter', + 'afoot', + 'afraid', + 'afterglow', + 'afterlife', + 'aftermath', + 'aftermost', + 'afternoon', + 'aged', + 'ageless', + 'agency', + 'agenda', + 'agent', + 'aggregate', + 'aghast', + 'agile', + 'agility', + 'aging', + 'agnostic', + 'agonize', + 'agonizing', + 'agony', + 'agreeable', + 'agreeably', + 'agreed', + 'agreeing', + 'agreement', + 'aground', + 'ahead', + 'ahoy', + 'aide', + 'aids', + 'aim', + 'ajar', + 'alabaster', + 'alarm', + 'albatross', + 'album', + 'alfalfa', + 'algebra', + 'algorithm', + 'alias', + 'alibi', + 'alienable', + 'alienate', + 'aliens', + 'alike', + 'alive', + 'alkaline', + 'alkalize', + 'almanac', + 'almighty', + 'almost', + 'aloe', + 'aloft', + 'aloha', + 'alone', + 'alongside', + 'aloof', + 'alphabet', + 'alright', + 'although', + 'altitude', + 'alto', + 'aluminum', + 'alumni', + 'always', + 'amaretto', + 'amaze', + 'amazingly', + 'amber', + 'ambiance', + 'ambiguity', + 'ambiguous', + 'ambition', + 'ambitious', + 'ambulance', + 'ambush', + 'amendable', + 'amendment', + 'amends', + 'amenity', + 'amiable', + 'amicably', + 'amid', + 'amigo', + 'amino', + 'amiss', + 'ammonia', + 'ammonium', + 'amnesty', + 'amniotic', + 'among', + 'amount', + 'amperage', + 'ample', + 'amplifier', + 'amplify', + 'amply', + 'amuck', + 'amulet', + 'amusable', + 'amused', + 'amusement', + 'amuser', + 'amusing', + 'anaconda', + 'anaerobic', + 'anagram', + 'anatomist', + 'anatomy', + 'anchor', + 'anchovy', + 'ancient', + 'android', + 'anemia', + 'anemic', + 'aneurism', + 'anew', + 'angelfish', + 'angelic', + 'anger', + 'angled', + 'angler', + 'angles', + 'angling', + 'angrily', + 'angriness', + 'anguished', + 'angular', + 'animal', + 'animate', + 'animating', + 'animation', + 'animator', + 'anime', + 'animosity', + 'ankle', + 'annex', + 'annotate', + 'announcer', + 'annoying', + 'annually', + 'annuity', + 'anointer', + 'another', + 'answering', + 'antacid', + 'antarctic', + 'anteater', + 'antelope', + 'antennae', + 'anthem', + 'anthill', + 'anthology', + 'antibody', + 'antics', + 'antidote', + 'antihero', + 'antiquely', + 'antiques', + 'antiquity', + 'antirust', + 'antitoxic', + 'antitrust', + 'antiviral', + 'antivirus', + 'antler', + 'antonym', + 'antsy', + 'anvil', + 'anybody', + 'anyhow', + 'anymore', + 'anyone', + 'anyplace', + 'anything', + 'anytime', + 'anyway', + 'anywhere', + 'aorta', + 'apache', + 'apostle', + 'appealing', + 'appear', + 'appease', + 'appeasing', + 'appendage', + 'appendix', + 'appetite', + 'appetizer', + 'applaud', + 'applause', + 'apple', + 'appliance', + 'applicant', + 'applied', + 'apply', + 'appointee', + 'appraisal', + 'appraiser', + 'apprehend', + 'approach', + 'approval', + 'approve', + 'apricot', + 'april', + 'apron', + 'aptitude', + 'aptly', + 'aqua', + 'aqueduct', + 'arbitrary', + 'arbitrate', + 'ardently', + 'area', + 'arena', + 'arguable', + 'arguably', + 'argue', + 'arise', + 'armadillo', + 'armband', + 'armchair', + 'armed', + 'armful', + 'armhole', + 'arming', + 'armless', + 'armoire', + 'armored', + 'armory', + 'armrest', + 'army', + 'aroma', + 'arose', + 'around', + 'arousal', + 'arrange', + 'array', + 'arrest', + 'arrival', + 'arrive', + 'arrogance', + 'arrogant', + 'arson', + 'art', + 'ascend', + 'ascension', + 'ascent', + 'ascertain', + 'ashamed', + 'ashen', + 'ashes', + 'ashy', + 'aside', + 'askew', + 'asleep', + 'asparagus', + 'aspect', + 'aspirate', + 'aspire', + 'aspirin', + 'astonish', + 'astound', + 'astride', + 'astrology', + 'astronaut', + 'astronomy', + 'astute', + 'atlantic', + 'atlas', + 'atom', + 'atonable', + 'atop', + 'atrium', + 'atrocious', + 'atrophy', + 'attach', + 'attain', + 'attempt', + 'attendant', + 'attendee', + 'attention', + 'attentive', + 'attest', + 'attic', + 'attire', + 'attitude', + 'attractor', + 'attribute', + 'atypical', + 'auction', + 'audacious', + 'audacity', + 'audible', + 'audibly', + 'audience', + 'audio', + 'audition', + 'augmented', + 'august', + 'authentic', + 'author', + 'autism', + 'autistic', + 'autograph', + 'automaker', + 'automated', + 'automatic', + 'autopilot', + 'available', + 'avalanche', + 'avatar', + 'avenge', + 'avenging', + 'avenue', + 'average', + 'aversion', + 'avert', + 'aviation', + 'aviator', + 'avid', + 'avoid', + 'await', + 'awaken', + 'award', + 'aware', + 'awhile', + 'awkward', + 'awning', + 'awoke', + 'awry', + 'axis', + 'babble', + 'babbling', + 'babied', + 'baboon', + 'backache', + 'backboard', + 'backboned', + 'backdrop', + 'backed', + 'backer', + 'backfield', + 'backfire', + 'backhand', + 'backing', + 'backlands', + 'backlash', + 'backless', + 'backlight', + 'backlit', + 'backlog', + 'backpack', + 'backpedal', + 'backrest', + 'backroom', + 'backshift', + 'backside', + 'backslid', + 'backspace', + 'backspin', + 'backstab', + 'backstage', + 'backtalk', + 'backtrack', + 'backup', + 'backward', + 'backwash', + 'backwater', + 'backyard', + 'bacon', + 'bacteria', + 'bacterium', + 'badass', + 'badge', + 'badland', + 'badly', + 'badness', + 'baffle', + 'baffling', + 'bagel', + 'bagful', + 'baggage', + 'bagged', + 'baggie', + 'bagginess', + 'bagging', + 'baggy', + 'bagpipe', + 'baguette', + 'baked', + 'bakery', + 'bakeshop', + 'baking', + 'balance', + 'balancing', + 'balcony', + 'balmy', + 'balsamic', + 'bamboo', + 'banana', + 'banish', + 'banister', + 'banjo', + 'bankable', + 'bankbook', + 'banked', + 'banker', + 'banking', + 'banknote', + 'bankroll', + 'banner', + 'bannister', + 'banshee', + 'banter', + 'barbecue', + 'barbed', + 'barbell', + 'barber', + 'barcode', + 'barge', + 'bargraph', + 'barista', + 'baritone', + 'barley', + 'barmaid', + 'barman', + 'barn', + 'barometer', + 'barrack', + 'barracuda', + 'barrel', + 'barrette', + 'barricade', + 'barrier', + 'barstool', + 'bartender', + 'barterer', + 'bash', + 'basically', + 'basics', + 'basil', + 'basin', + 'basis', + 'basket', + 'batboy', + 'batch', + 'bath', + 'baton', + 'bats', + 'battalion', + 'battered', + 'battering', + 'battery', + 'batting', + 'battle', + 'bauble', + 'bazooka', + 'blabber', + 'bladder', + 'blade', + 'blah', + 'blame', + 'blaming', + 'blanching', + 'blandness', + 'blank', + 'blaspheme', + 'blasphemy', + 'blast', + 'blatancy', + 'blatantly', + 'blazer', + 'blazing', + 'bleach', + 'bleak', + 'bleep', + 'blemish', + 'blend', + 'bless', + 'blighted', + 'blimp', + 'bling', + 'blinked', + 'blinker', + 'blinking', + 'blinks', + 'blip', + 'blissful', + 'blitz', + 'blizzard', + 'bloated', + 'bloating', + 'blob', + 'blog', + 'bloomers', + 'blooming', + 'blooper', + 'blot', + 'blouse', + 'blubber', + 'bluff', + 'bluish', + 'blunderer', + 'blunt', + 'blurb', + 'blurred', + 'blurry', + 'blurt', + 'blush', + 'blustery', + 'boaster', + 'boastful', + 'boasting', + 'boat', + 'bobbed', + 'bobbing', + 'bobble', + 'bobcat', + 'bobsled', + 'bobtail', + 'bodacious', + 'body', + 'bogged', + 'boggle', + 'bogus', + 'boil', + 'bok', + 'bolster', + 'bolt', + 'bonanza', + 'bonded', + 'bonding', + 'bondless', + 'boned', + 'bonehead', + 'boneless', + 'bonelike', + 'boney', + 'bonfire', + 'bonnet', + 'bonsai', + 'bonus', + 'bony', + 'boogeyman', + 'boogieman', + 'book', + 'boondocks', + 'booted', + 'booth', + 'bootie', + 'booting', + 'bootlace', + 'bootleg', + 'boots', + 'boozy', + 'borax', + 'boring', + 'borough', + 'borrower', + 'borrowing', + 'boss', + 'botanical', + 'botanist', + 'botany', + 'botch', + 'both', + 'bottle', + 'bottling', + 'bottom', + 'bounce', + 'bouncing', + 'bouncy', + 'bounding', + 'boundless', + 'bountiful', + 'bovine', + 'boxcar', + 'boxer', + 'boxing', + 'boxlike', + 'boxy', + 'breach', + 'breath', + 'breeches', + 'breeching', + 'breeder', + 'breeding', + 'breeze', + 'breezy', + 'brethren', + 'brewery', + 'brewing', + 'briar', + 'bribe', + 'brick', + 'bride', + 'bridged', + 'brigade', + 'bright', + 'brilliant', + 'brim', + 'bring', + 'brink', + 'brisket', + 'briskly', + 'briskness', + 'bristle', + 'brittle', + 'broadband', + 'broadcast', + 'broaden', + 'broadly', + 'broadness', + 'broadside', + 'broadways', + 'broiler', + 'broiling', + 'broken', + 'broker', + 'bronchial', + 'bronco', + 'bronze', + 'bronzing', + 'brook', + 'broom', + 'brought', + 'browbeat', + 'brownnose', + 'browse', + 'browsing', + 'bruising', + 'brunch', + 'brunette', + 'brunt', + 'brush', + 'brussels', + 'brute', + 'brutishly', + 'bubble', + 'bubbling', + 'bubbly', + 'buccaneer', + 'bucked', + 'bucket', + 'buckle', + 'buckshot', + 'buckskin', + 'bucktooth', + 'buckwheat', + 'buddhism', + 'buddhist', + 'budding', + 'buddy', + 'budget', + 'buffalo', + 'buffed', + 'buffer', + 'buffing', + 'buffoon', + 'buggy', + 'bulb', + 'bulge', + 'bulginess', + 'bulgur', + 'bulk', + 'bulldog', + 'bulldozer', + 'bullfight', + 'bullfrog', + 'bullhorn', + 'bullion', + 'bullish', + 'bullpen', + 'bullring', + 'bullseye', + 'bullwhip', + 'bully', + 'bunch', + 'bundle', + 'bungee', + 'bunion', + 'bunkbed', + 'bunkhouse', + 'bunkmate', + 'bunny', + 'bunt', + 'busboy', + 'bush', + 'busily', + 'busload', + 'bust', + 'busybody', + 'buzz', + 'cabana', + 'cabbage', + 'cabbie', + 'cabdriver', + 'cable', + 'caboose', + 'cache', + 'cackle', + 'cacti', + 'cactus', + 'caddie', + 'caddy', + 'cadet', + 'cadillac', + 'cadmium', + 'cage', + 'cahoots', + 'cake', + 'calamari', + 'calamity', + 'calcium', + 'calculate', + 'calculus', + 'caliber', + 'calibrate', + 'calm', + 'caloric', + 'calorie', + 'calzone', + 'camcorder', + 'cameo', + 'camera', + 'camisole', + 'camper', + 'campfire', + 'camping', + 'campsite', + 'campus', + 'canal', + 'canary', + 'cancel', + 'candied', + 'candle', + 'candy', + 'cane', + 'canine', + 'canister', + 'cannabis', + 'canned', + 'canning', + 'cannon', + 'cannot', + 'canola', + 'canon', + 'canopener', + 'canopy', + 'canteen', + 'canyon', + 'capable', + 'capably', + 'capacity', + 'cape', + 'capillary', + 'capital', + 'capitol', + 'capped', + 'capricorn', + 'capsize', + 'capsule', + 'caption', + 'captivate', + 'captive', + 'captivity', + 'capture', + 'caramel', + 'carat', + 'caravan', + 'carbon', + 'cardboard', + 'carded', + 'cardiac', + 'cardigan', + 'cardinal', + 'cardstock', + 'carefully', + 'caregiver', + 'careless', + 'caress', + 'caretaker', + 'cargo', + 'caring', + 'carless', + 'carload', + 'carmaker', + 'carnage', + 'carnation', + 'carnival', + 'carnivore', + 'carol', + 'carpenter', + 'carpentry', + 'carpool', + 'carport', + 'carried', + 'carrot', + 'carrousel', + 'carry', + 'cartel', + 'cartload', + 'carton', + 'cartoon', + 'cartridge', + 'cartwheel', + 'carve', + 'carving', + 'carwash', + 'cascade', + 'case', + 'cash', + 'casing', + 'casino', + 'casket', + 'cassette', + 'casually', + 'casualty', + 'catacomb', + 'catalog', + 'catalyst', + 'catalyze', + 'catapult', + 'cataract', + 'catatonic', + 'catcall', + 'catchable', + 'catcher', + 'catching', + 'catchy', + 'caterer', + 'catering', + 'catfight', + 'catfish', + 'cathedral', + 'cathouse', + 'catlike', + 'catnap', + 'catnip', + 'catsup', + 'cattail', + 'cattishly', + 'cattle', + 'catty', + 'catwalk', + 'caucasian', + 'caucus', + 'causal', + 'causation', + 'cause', + 'causing', + 'cauterize', + 'caution', + 'cautious', + 'cavalier', + 'cavalry', + 'caviar', + 'cavity', + 'cedar', + 'celery', + 'celestial', + 'celibacy', + 'celibate', + 'celtic', + 'cement', + 'census', + 'ceramics', + 'ceremony', + 'certainly', + 'certainty', + 'certified', + 'certify', + 'cesarean', + 'cesspool', + 'chafe', + 'chaffing', + 'chain', + 'chair', + 'chalice', + 'challenge', + 'chamber', + 'chamomile', + 'champion', + 'chance', + 'change', + 'channel', + 'chant', + 'chaos', + 'chaperone', + 'chaplain', + 'chapped', + 'chaps', + 'chapter', + 'character', + 'charbroil', + 'charcoal', + 'charger', + 'charging', + 'chariot', + 'charity', + 'charm', + 'charred', + 'charter', + 'charting', + 'chase', + 'chasing', + 'chaste', + 'chastise', + 'chastity', + 'chatroom', + 'chatter', + 'chatting', + 'chatty', + 'cheating', + 'cheddar', + 'cheek', + 'cheer', + 'cheese', + 'cheesy', + 'chef', + 'chemicals', + 'chemist', + 'chemo', + 'cherisher', + 'cherub', + 'chess', + 'chest', + 'chevron', + 'chevy', + 'chewable', + 'chewer', + 'chewing', + 'chewy', + 'chief', + 'chihuahua', + 'childcare', + 'childhood', + 'childish', + 'childless', + 'childlike', + 'chili', + 'chill', + 'chimp', + 'chip', + 'chirping', + 'chirpy', + 'chitchat', + 'chivalry', + 'chive', + 'chloride', + 'chlorine', + 'choice', + 'chokehold', + 'choking', + 'chomp', + 'chooser', + 'choosing', + 'choosy', + 'chop', + 'chosen', + 'chowder', + 'chowtime', + 'chrome', + 'chubby', + 'chuck', + 'chug', + 'chummy', + 'chump', + 'chunk', + 'churn', + 'chute', + 'cider', + 'cilantro', + 'cinch', + 'cinema', + 'cinnamon', + 'circle', + 'circling', + 'circular', + 'circulate', + 'circus', + 'citable', + 'citadel', + 'citation', + 'citizen', + 'citric', + 'citrus', + 'city', + 'civic', + 'civil', + 'clad', + 'claim', + 'clambake', + 'clammy', + 'clamor', + 'clamp', + 'clamshell', + 'clang', + 'clanking', + 'clapped', + 'clapper', + 'clapping', + 'clarify', + 'clarinet', + 'clarity', + 'clash', + 'clasp', + 'class', + 'clatter', + 'clause', + 'clavicle', + 'claw', + 'clay', + 'clean', + 'clear', + 'cleat', + 'cleaver', + 'cleft', + 'clench', + 'clergyman', + 'clerical', + 'clerk', + 'clever', + 'clicker', + 'client', + 'climate', + 'climatic', + 'cling', + 'clinic', + 'clinking', + 'clip', + 'clique', + 'cloak', + 'clobber', + 'clock', + 'clone', + 'cloning', + 'closable', + 'closure', + 'clothes', + 'clothing', + 'cloud', + 'clover', + 'clubbed', + 'clubbing', + 'clubhouse', + 'clump', + 'clumsily', + 'clumsy', + 'clunky', + 'clustered', + 'clutch', + 'clutter', + 'coach', + 'coagulant', + 'coastal', + 'coaster', + 'coasting', + 'coastland', + 'coastline', + 'coat', + 'coauthor', + 'cobalt', + 'cobbler', + 'cobweb', + 'cocoa', + 'coconut', + 'cod', + 'coeditor', + 'coerce', + 'coexist', + 'coffee', + 'cofounder', + 'cognition', + 'cognitive', + 'cogwheel', + 'coherence', + 'coherent', + 'cohesive', + 'coil', + 'coke', + 'cola', + 'cold', + 'coleslaw', + 'coliseum', + 'collage', + 'collapse', + 'collar', + 'collected', + 'collector', + 'collide', + 'collie', + 'collision', + 'colonial', + 'colonist', + 'colonize', + 'colony', + 'colossal', + 'colt', + 'coma', + 'come', + 'comfort', + 'comfy', + 'comic', + 'coming', + 'comma', + 'commence', + 'commend', + 'comment', + 'commerce', + 'commode', + 'commodity', + 'commodore', + 'common', + 'commotion', + 'commute', + 'commuting', + 'compacted', + 'compacter', + 'compactly', + 'compactor', + 'companion', + 'company', + 'compare', + 'compel', + 'compile', + 'comply', + 'component', + 'composed', + 'composer', + 'composite', + 'compost', + 'composure', + 'compound', + 'compress', + 'comprised', + 'computer', + 'computing', + 'comrade', + 'concave', + 'conceal', + 'conceded', + 'concept', + 'concerned', + 'concert', + 'conch', + 'concierge', + 'concise', + 'conclude', + 'concrete', + 'concur', + 'condense', + 'condiment', + 'condition', + 'condone', + 'conducive', + 'conductor', + 'conduit', + 'cone', + 'confess', + 'confetti', + 'confidant', + 'confident', + 'confider', + 'confiding', + 'configure', + 'confined', + 'confining', + 'confirm', + 'conflict', + 'conform', + 'confound', + 'confront', + 'confused', + 'confusing', + 'confusion', + 'congenial', + 'congested', + 'congrats', + 'congress', + 'conical', + 'conjoined', + 'conjure', + 'conjuror', + 'connected', + 'connector', + 'consensus', + 'consent', + 'console', + 'consoling', + 'consonant', + 'constable', + 'constant', + 'constrain', + 'constrict', + 'construct', + 'consult', + 'consumer', + 'consuming', + 'contact', + 'container', + 'contempt', + 'contend', + 'contented', + 'contently', + 'contents', + 'contest', + 'context', + 'contort', + 'contour', + 'contrite', + 'control', + 'contusion', + 'convene', + 'convent', + 'copartner', + 'cope', + 'copied', + 'copier', + 'copilot', + 'coping', + 'copious', + 'copper', + 'copy', + 'coral', + 'cork', + 'cornball', + 'cornbread', + 'corncob', + 'cornea', + 'corned', + 'corner', + 'cornfield', + 'cornflake', + 'cornhusk', + 'cornmeal', + 'cornstalk', + 'corny', + 'coronary', + 'coroner', + 'corporal', + 'corporate', + 'corral', + 'correct', + 'corridor', + 'corrode', + 'corroding', + 'corrosive', + 'corsage', + 'corset', + 'cortex', + 'cosigner', + 'cosmetics', + 'cosmic', + 'cosmos', + 'cosponsor', + 'cost', + 'cottage', + 'cotton', + 'couch', + 'cough', + 'could', + 'countable', + 'countdown', + 'counting', + 'countless', + 'country', + 'county', + 'courier', + 'covenant', + 'cover', + 'coveted', + 'coveting', + 'coyness', + 'cozily', + 'coziness', + 'cozy', + 'crabbing', + 'crabgrass', + 'crablike', + 'crabmeat', + 'cradle', + 'cradling', + 'crafter', + 'craftily', + 'craftsman', + 'craftwork', + 'crafty', + 'cramp', + 'cranberry', + 'crane', + 'cranial', + 'cranium', + 'crank', + 'crate', + 'crave', + 'craving', + 'crawfish', + 'crawlers', + 'crawling', + 'crayfish', + 'crayon', + 'crazed', + 'crazily', + 'craziness', + 'crazy', + 'creamed', + 'creamer', + 'creamlike', + 'crease', + 'creasing', + 'creatable', + 'create', + 'creation', + 'creative', + 'creature', + 'credible', + 'credibly', + 'credit', + 'creed', + 'creme', + 'creole', + 'crepe', + 'crept', + 'crescent', + 'crested', + 'cresting', + 'crestless', + 'crevice', + 'crewless', + 'crewman', + 'crewmate', + 'crib', + 'cricket', + 'cried', + 'crier', + 'crimp', + 'crimson', + 'cringe', + 'cringing', + 'crinkle', + 'crinkly', + 'crisped', + 'crisping', + 'crisply', + 'crispness', + 'crispy', + 'criteria', + 'critter', + 'croak', + 'crock', + 'crook', + 'croon', + 'crop', + 'cross', + 'crouch', + 'crouton', + 'crowbar', + 'crowd', + 'crown', + 'crucial', + 'crudely', + 'crudeness', + 'cruelly', + 'cruelness', + 'cruelty', + 'crumb', + 'crummiest', + 'crummy', + 'crumpet', + 'crumpled', + 'cruncher', + 'crunching', + 'crunchy', + 'crusader', + 'crushable', + 'crushed', + 'crusher', + 'crushing', + 'crust', + 'crux', + 'crying', + 'cryptic', + 'crystal', + 'cubbyhole', + 'cube', + 'cubical', + 'cubicle', + 'cucumber', + 'cuddle', + 'cuddly', + 'cufflink', + 'culinary', + 'culminate', + 'culpable', + 'culprit', + 'cultivate', + 'cultural', + 'culture', + 'cupbearer', + 'cupcake', + 'cupid', + 'cupped', + 'cupping', + 'curable', + 'curator', + 'curdle', + 'cure', + 'curfew', + 'curing', + 'curled', + 'curler', + 'curliness', + 'curling', + 'curly', + 'curry', + 'curse', + 'cursive', + 'cursor', + 'curtain', + 'curtly', + 'curtsy', + 'curvature', + 'curve', + 'curvy', + 'cushy', + 'cusp', + 'cussed', + 'custard', + 'custodian', + 'custody', + 'customary', + 'customer', + 'customize', + 'customs', + 'cut', + 'cycle', + 'cyclic', + 'cycling', + 'cyclist', + 'cylinder', + 'cymbal', + 'cytoplasm', + 'cytoplast', + 'dab', + 'dad', + 'daffodil', + 'dagger', + 'daily', + 'daintily', + 'dainty', + 'dairy', + 'daisy', + 'dallying', + 'dance', + 'dancing', + 'dandelion', + 'dander', + 'dandruff', + 'dandy', + 'danger', + 'dangle', + 'dangling', + 'daredevil', + 'dares', + 'daringly', + 'darkened', + 'darkening', + 'darkish', + 'darkness', + 'darkroom', + 'darling', + 'darn', + 'dart', + 'darwinism', + 'dash', + 'dastardly', + 'data', + 'datebook', + 'dating', + 'daughter', + 'daunting', + 'dawdler', + 'dawn', + 'daybed', + 'daybreak', + 'daycare', + 'daydream', + 'daylight', + 'daylong', + 'dayroom', + 'daytime', + 'dazzler', + 'dazzling', + 'deacon', + 'deafening', + 'deafness', + 'dealer', + 'dealing', + 'dealmaker', + 'dealt', + 'dean', + 'debatable', + 'debate', + 'debating', + 'debit', + 'debrief', + 'debtless', + 'debtor', + 'debug', + 'debunk', + 'decade', + 'decaf', + 'decal', + 'decathlon', + 'decay', + 'deceased', + 'deceit', + 'deceiver', + 'deceiving', + 'december', + 'decency', + 'decent', + 'deception', + 'deceptive', + 'decibel', + 'decidable', + 'decimal', + 'decimeter', + 'decipher', + 'deck', + 'declared', + 'decline', + 'decode', + 'decompose', + 'decorated', + 'decorator', + 'decoy', + 'decrease', + 'decree', + 'dedicate', + 'dedicator', + 'deduce', + 'deduct', + 'deed', + 'deem', + 'deepen', + 'deeply', + 'deepness', + 'deface', + 'defacing', + 'defame', + 'default', + 'defeat', + 'defection', + 'defective', + 'defendant', + 'defender', + 'defense', + 'defensive', + 'deferral', + 'deferred', + 'defiance', + 'defiant', + 'defile', + 'defiling', + 'define', + 'definite', + 'deflate', + 'deflation', + 'deflator', + 'deflected', + 'deflector', + 'defog', + 'deforest', + 'defraud', + 'defrost', + 'deftly', + 'defuse', + 'defy', + 'degraded', + 'degrading', + 'degrease', + 'degree', + 'dehydrate', + 'deity', + 'dejected', + 'delay', + 'delegate', + 'delegator', + 'delete', + 'deletion', + 'delicacy', + 'delicate', + 'delicious', + 'delighted', + 'delirious', + 'delirium', + 'deliverer', + 'delivery', + 'delouse', + 'delta', + 'deluge', + 'delusion', + 'deluxe', + 'demanding', + 'demeaning', + 'demeanor', + 'demise', + 'democracy', + 'democrat', + 'demote', + 'demotion', + 'demystify', + 'denatured', + 'deniable', + 'denial', + 'denim', + 'denote', + 'dense', + 'density', + 'dental', + 'dentist', + 'denture', + 'deny', + 'deodorant', + 'deodorize', + 'departed', + 'departure', + 'depict', + 'deplete', + 'depletion', + 'deplored', + 'deploy', + 'deport', + 'depose', + 'depraved', + 'depravity', + 'deprecate', + 'depress', + 'deprive', + 'depth', + 'deputize', + 'deputy', + 'derail', + 'deranged', + 'derby', + 'derived', + 'desecrate', + 'deserve', + 'deserving', + 'designate', + 'designed', + 'designer', + 'designing', + 'deskbound', + 'desktop', + 'deskwork', + 'desolate', + 'despair', + 'despise', + 'despite', + 'destiny', + 'destitute', + 'destruct', + 'detached', + 'detail', + 'detection', + 'detective', + 'detector', + 'detention', + 'detergent', + 'detest', + 'detonate', + 'detonator', + 'detoxify', + 'detract', + 'deuce', + 'devalue', + 'deviancy', + 'deviant', + 'deviate', + 'deviation', + 'deviator', + 'device', + 'devious', + 'devotedly', + 'devotee', + 'devotion', + 'devourer', + 'devouring', + 'devoutly', + 'dexterity', + 'dexterous', + 'diabetes', + 'diabetic', + 'diabolic', + 'diagnoses', + 'diagnosis', + 'diagram', + 'dial', + 'diameter', + 'diaper', + 'diaphragm', + 'diary', + 'dice', + 'dicing', + 'dictate', + 'dictation', + 'dictator', + 'difficult', + 'diffused', + 'diffuser', + 'diffusion', + 'diffusive', + 'dig', + 'dilation', + 'diligence', + 'diligent', + 'dill', + 'dilute', + 'dime', + 'diminish', + 'dimly', + 'dimmed', + 'dimmer', + 'dimness', + 'dimple', + 'diner', + 'dingbat', + 'dinghy', + 'dinginess', + 'dingo', + 'dingy', + 'dining', + 'dinner', + 'diocese', + 'dioxide', + 'diploma', + 'dipped', + 'dipper', + 'dipping', + 'directed', + 'direction', + 'directive', + 'directly', + 'directory', + 'direness', + 'dirtiness', + 'disabled', + 'disagree', + 'disallow', + 'disarm', + 'disarray', + 'disaster', + 'disband', + 'disbelief', + 'disburse', + 'discard', + 'discern', + 'discharge', + 'disclose', + 'discolor', + 'discount', + 'discourse', + 'discover', + 'discuss', + 'disdain', + 'disengage', + 'disfigure', + 'disgrace', + 'dish', + 'disinfect', + 'disjoin', + 'disk', + 'dislike', + 'disliking', + 'dislocate', + 'dislodge', + 'disloyal', + 'dismantle', + 'dismay', + 'dismiss', + 'dismount', + 'disobey', + 'disorder', + 'disown', + 'disparate', + 'disparity', + 'dispatch', + 'dispense', + 'dispersal', + 'dispersed', + 'disperser', + 'displace', + 'display', + 'displease', + 'disposal', + 'dispose', + 'disprove', + 'dispute', + 'disregard', + 'disrupt', + 'dissuade', + 'distance', + 'distant', + 'distaste', + 'distill', + 'distinct', + 'distort', + 'distract', + 'distress', + 'district', + 'distrust', + 'ditch', + 'ditto', + 'ditzy', + 'dividable', + 'divided', + 'dividend', + 'dividers', + 'dividing', + 'divinely', + 'diving', + 'divinity', + 'divisible', + 'divisibly', + 'division', + 'divisive', + 'divorcee', + 'dizziness', + 'dizzy', + 'doable', + 'docile', + 'dock', + 'doctrine', + 'document', + 'dodge', + 'dodgy', + 'doily', + 'doing', + 'dole', + 'dollar', + 'dollhouse', + 'dollop', + 'dolly', + 'dolphin', + 'domain', + 'domelike', + 'domestic', + 'dominion', + 'dominoes', + 'donated', + 'donation', + 'donator', + 'donor', + 'donut', + 'doodle', + 'doorbell', + 'doorframe', + 'doorknob', + 'doorman', + 'doormat', + 'doornail', + 'doorpost', + 'doorstep', + 'doorstop', + 'doorway', + 'doozy', + 'dork', + 'dormitory', + 'dorsal', + 'dosage', + 'dose', + 'dotted', + 'doubling', + 'douche', + 'dove', + 'down', + 'dowry', + 'doze', + 'drab', + 'dragging', + 'dragonfly', + 'dragonish', + 'dragster', + 'drainable', + 'drainage', + 'drained', + 'drainer', + 'drainpipe', + 'dramatic', + 'dramatize', + 'drank', + 'drapery', + 'drastic', + 'draw', + 'dreaded', + 'dreadful', + 'dreadlock', + 'dreamboat', + 'dreamily', + 'dreamland', + 'dreamless', + 'dreamlike', + 'dreamt', + 'dreamy', + 'drearily', + 'dreary', + 'drench', + 'dress', + 'drew', + 'dribble', + 'dried', + 'drier', + 'drift', + 'driller', + 'drilling', + 'drinkable', + 'drinking', + 'dripping', + 'drippy', + 'drivable', + 'driven', + 'driver', + 'driveway', + 'driving', + 'drizzle', + 'drizzly', + 'drone', + 'drool', + 'droop', + 'drop-down', + 'dropbox', + 'dropkick', + 'droplet', + 'dropout', + 'dropper', + 'drove', + 'drown', + 'drowsily', + 'drudge', + 'drum', + 'dry', + 'dubbed', + 'dubiously', + 'duchess', + 'duckbill', + 'ducking', + 'duckling', + 'ducktail', + 'ducky', + 'duct', + 'dude', + 'duffel', + 'dugout', + 'duh', + 'duke', + 'duller', + 'dullness', + 'duly', + 'dumping', + 'dumpling', + 'dumpster', + 'duo', + 'dupe', + 'duplex', + 'duplicate', + 'duplicity', + 'durable', + 'durably', + 'duration', + 'duress', + 'during', + 'dusk', + 'dust', + 'dutiful', + 'duty', + 'duvet', + 'dwarf', + 'dweeb', + 'dwelled', + 'dweller', + 'dwelling', + 'dwindle', + 'dwindling', + 'dynamic', + 'dynamite', + 'dynasty', + 'dyslexia', + 'dyslexic', + 'each', + 'eagle', + 'earache', + 'eardrum', + 'earflap', + 'earful', + 'earlobe', + 'early', + 'earmark', + 'earmuff', + 'earphone', + 'earpiece', + 'earplugs', + 'earring', + 'earshot', + 'earthen', + 'earthlike', + 'earthling', + 'earthly', + 'earthworm', + 'earthy', + 'earwig', + 'easeful', + 'easel', + 'easiest', + 'easily', + 'easiness', + 'easing', + 'eastbound', + 'eastcoast', + 'easter', + 'eastward', + 'eatable', + 'eaten', + 'eatery', + 'eating', + 'eats', + 'ebay', + 'ebony', + 'ebook', + 'ecard', + 'eccentric', + 'echo', + 'eclair', + 'eclipse', + 'ecologist', + 'ecology', + 'economic', + 'economist', + 'economy', + 'ecosphere', + 'ecosystem', + 'edge', + 'edginess', + 'edging', + 'edgy', + 'edition', + 'editor', + 'educated', + 'education', + 'educator', + 'eel', + 'effective', + 'effects', + 'efficient', + 'effort', + 'eggbeater', + 'egging', + 'eggnog', + 'eggplant', + 'eggshell', + 'egomaniac', + 'egotism', + 'egotistic', + 'either', + 'eject', + 'elaborate', + 'elastic', + 'elated', + 'elbow', + 'eldercare', + 'elderly', + 'eldest', + 'electable', + 'election', + 'elective', + 'elephant', + 'elevate', + 'elevating', + 'elevation', + 'elevator', + 'eleven', + 'elf', + 'eligible', + 'eligibly', + 'eliminate', + 'elite', + 'elitism', + 'elixir', + 'elk', + 'ellipse', + 'elliptic', + 'elm', + 'elongated', + 'elope', + 'eloquence', + 'eloquent', + 'elsewhere', + 'elude', + 'elusive', + 'elves', + 'email', + 'embargo', + 'embark', + 'embassy', + 'embattled', + 'embellish', + 'ember', + 'embezzle', + 'emblaze', + 'emblem', + 'embody', + 'embolism', + 'emboss', + 'embroider', + 'emcee', + 'emerald', + 'emergency', + 'emission', + 'emit', + 'emote', + 'emoticon', + 'emotion', + 'empathic', + 'empathy', + 'emperor', + 'emphases', + 'emphasis', + 'emphasize', + 'emphatic', + 'empirical', + 'employed', + 'employee', + 'employer', + 'emporium', + 'empower', + 'emptier', + 'emptiness', + 'empty', + 'emu', + 'enable', + 'enactment', + 'enamel', + 'enchanted', + 'enchilada', + 'encircle', + 'enclose', + 'enclosure', + 'encode', + 'encore', + 'encounter', + 'encourage', + 'encroach', + 'encrust', + 'encrypt', + 'endanger', + 'endeared', + 'endearing', + 'ended', + 'ending', + 'endless', + 'endnote', + 'endocrine', + 'endorphin', + 'endorse', + 'endowment', + 'endpoint', + 'endurable', + 'endurance', + 'enduring', + 'energetic', + 'energize', + 'energy', + 'enforced', + 'enforcer', + 'engaged', + 'engaging', + 'engine', + 'engorge', + 'engraved', + 'engraver', + 'engraving', + 'engross', + 'engulf', + 'enhance', + 'enigmatic', + 'enjoyable', + 'enjoyably', + 'enjoyer', + 'enjoying', + 'enjoyment', + 'enlarged', + 'enlarging', + 'enlighten', + 'enlisted', + 'enquirer', + 'enrage', + 'enrich', + 'enroll', + 'enslave', + 'ensnare', + 'ensure', + 'entail', + 'entangled', + 'entering', + 'entertain', + 'enticing', + 'entire', + 'entitle', + 'entity', + 'entomb', + 'entourage', + 'entrap', + 'entree', + 'entrench', + 'entrust', + 'entryway', + 'entwine', + 'enunciate', + 'envelope', + 'enviable', + 'enviably', + 'envious', + 'envision', + 'envoy', + 'envy', + 'enzyme', + 'epic', + 'epidemic', + 'epidermal', + 'epidermis', + 'epidural', + 'epilepsy', + 'epileptic', + 'epilogue', + 'epiphany', + 'episode', + 'equal', + 'equate', + 'equation', + 'equator', + 'equinox', + 'equipment', + 'equity', + 'equivocal', + 'eradicate', + 'erasable', + 'erased', + 'eraser', + 'erasure', + 'ergonomic', + 'errand', + 'errant', + 'erratic', + 'error', + 'erupt', + 'escalate', + 'escalator', + 'escapable', + 'escapade', + 'escapist', + 'escargot', + 'eskimo', + 'esophagus', + 'espionage', + 'espresso', + 'esquire', + 'essay', + 'essence', + 'essential', + 'establish', + 'estate', + 'esteemed', + 'estimate', + 'estimator', + 'estranged', + 'estrogen', + 'etching', + 'eternal', + 'eternity', + 'ethanol', + 'ether', + 'ethically', + 'ethics', + 'euphemism', + 'evacuate', + 'evacuee', + 'evade', + 'evaluate', + 'evaluator', + 'evaporate', + 'evasion', + 'evasive', + 'even', + 'everglade', + 'evergreen', + 'everybody', + 'everyday', + 'everyone', + 'evict', + 'evidence', + 'evident', + 'evil', + 'evoke', + 'evolution', + 'evolve', + 'exact', + 'exalted', + 'example', + 'excavate', + 'excavator', + 'exceeding', + 'exception', + 'excess', + 'exchange', + 'excitable', + 'exciting', + 'exclaim', + 'exclude', + 'excluding', + 'exclusion', + 'exclusive', + 'excretion', + 'excretory', + 'excursion', + 'excusable', + 'excusably', + 'excuse', + 'exemplary', + 'exemplify', + 'exemption', + 'exerciser', + 'exert', + 'exes', + 'exfoliate', + 'exhale', + 'exhaust', + 'exhume', + 'exile', + 'existing', + 'exit', + 'exodus', + 'exonerate', + 'exorcism', + 'exorcist', + 'expand', + 'expanse', + 'expansion', + 'expansive', + 'expectant', + 'expedited', + 'expediter', + 'expel', + 'expend', + 'expenses', + 'expensive', + 'expert', + 'expire', + 'expiring', + 'explain', + 'expletive', + 'explicit', + 'explode', + 'exploit', + 'explore', + 'exploring', + 'exponent', + 'exporter', + 'exposable', + 'expose', + 'exposure', + 'express', + 'expulsion', + 'exquisite', + 'extended', + 'extending', + 'extent', + 'extenuate', + 'exterior', + 'external', + 'extinct', + 'extortion', + 'extradite', + 'extras', + 'extrovert', + 'extrude', + 'extruding', + 'exuberant', + 'fable', + 'fabric', + 'fabulous', + 'facebook', + 'facecloth', + 'facedown', + 'faceless', + 'facelift', + 'faceplate', + 'faceted', + 'facial', + 'facility', + 'facing', + 'facsimile', + 'faction', + 'factoid', + 'factor', + 'factsheet', + 'factual', + 'faculty', + 'fade', + 'fading', + 'failing', + 'falcon', + 'fall', + 'false', + 'falsify', + 'fame', + 'familiar', + 'family', + 'famine', + 'famished', + 'fanatic', + 'fancied', + 'fanciness', + 'fancy', + 'fanfare', + 'fang', + 'fanning', + 'fantasize', + 'fantastic', + 'fantasy', + 'fascism', + 'fastball', + 'faster', + 'fasting', + 'fastness', + 'faucet', + 'favorable', + 'favorably', + 'favored', + 'favoring', + 'favorite', + 'fax', + 'feast', + 'federal', + 'fedora', + 'feeble', + 'feed', + 'feel', + 'feisty', + 'feline', + 'felt-tip', + 'feminine', + 'feminism', + 'feminist', + 'feminize', + 'femur', + 'fence', + 'fencing', + 'fender', + 'ferment', + 'fernlike', + 'ferocious', + 'ferocity', + 'ferret', + 'ferris', + 'ferry', + 'fervor', + 'fester', + 'festival', + 'festive', + 'festivity', + 'fetal', + 'fetch', + 'fever', + 'fiber', + 'fiction', + 'fiddle', + 'fiddling', + 'fidelity', + 'fidgeting', + 'fidgety', + 'fifteen', + 'fifth', + 'fiftieth', + 'fifty', + 'figment', + 'figure', + 'figurine', + 'filing', + 'filled', + 'filler', + 'filling', + 'film', + 'filter', + 'filth', + 'filtrate', + 'finale', + 'finalist', + 'finalize', + 'finally', + 'finance', + 'financial', + 'finch', + 'fineness', + 'finer', + 'finicky', + 'finished', + 'finisher', + 'finishing', + 'finite', + 'finless', + 'finlike', + 'fiscally', + 'fit', + 'five', + 'flaccid', + 'flagman', + 'flagpole', + 'flagship', + 'flagstick', + 'flagstone', + 'flail', + 'flakily', + 'flaky', + 'flame', + 'flammable', + 'flanked', + 'flanking', + 'flannels', + 'flap', + 'flaring', + 'flashback', + 'flashbulb', + 'flashcard', + 'flashily', + 'flashing', + 'flashy', + 'flask', + 'flatbed', + 'flatfoot', + 'flatly', + 'flatness', + 'flatten', + 'flattered', + 'flatterer', + 'flattery', + 'flattop', + 'flatware', + 'flatworm', + 'flavored', + 'flavorful', + 'flavoring', + 'flaxseed', + 'fled', + 'fleshed', + 'fleshy', + 'flick', + 'flier', + 'flight', + 'flinch', + 'fling', + 'flint', + 'flip', + 'flirt', + 'float', + 'flock', + 'flogging', + 'flop', + 'floral', + 'florist', + 'floss', + 'flounder', + 'flyable', + 'flyaway', + 'flyer', + 'flying', + 'flyover', + 'flypaper', + 'foam', + 'foe', + 'fog', + 'foil', + 'folic', + 'folk', + 'follicle', + 'follow', + 'fondling', + 'fondly', + 'fondness', + 'fondue', + 'font', + 'food', + 'fool', + 'footage', + 'football', + 'footbath', + 'footboard', + 'footer', + 'footgear', + 'foothill', + 'foothold', + 'footing', + 'footless', + 'footman', + 'footnote', + 'footpad', + 'footpath', + 'footprint', + 'footrest', + 'footsie', + 'footsore', + 'footwear', + 'footwork', + 'fossil', + 'foster', + 'founder', + 'founding', + 'fountain', + 'fox', + 'foyer', + 'fraction', + 'fracture', + 'fragile', + 'fragility', + 'fragment', + 'fragrance', + 'fragrant', + 'frail', + 'frame', + 'framing', + 'frantic', + 'fraternal', + 'frayed', + 'fraying', + 'frays', + 'freckled', + 'freckles', + 'freebase', + 'freebee', + 'freebie', + 'freedom', + 'freefall', + 'freehand', + 'freeing', + 'freeload', + 'freely', + 'freemason', + 'freeness', + 'freestyle', + 'freeware', + 'freeway', + 'freewill', + 'freezable', + 'freezing', + 'freight', + 'french', + 'frenzied', + 'frenzy', + 'frequency', + 'frequent', + 'fresh', + 'fretful', + 'fretted', + 'friction', + 'friday', + 'fridge', + 'fried', + 'friend', + 'frighten', + 'frightful', + 'frigidity', + 'frigidly', + 'frill', + 'fringe', + 'frisbee', + 'frisk', + 'fritter', + 'frivolous', + 'frolic', + 'from', + 'front', + 'frostbite', + 'frosted', + 'frostily', + 'frosting', + 'frostlike', + 'frosty', + 'froth', + 'frown', + 'frozen', + 'fructose', + 'frugality', + 'frugally', + 'fruit', + 'frustrate', + 'frying', + 'gab', + 'gaffe', + 'gag', + 'gainfully', + 'gaining', + 'gains', + 'gala', + 'gallantly', + 'galleria', + 'gallery', + 'galley', + 'gallon', + 'gallows', + 'gallstone', + 'galore', + 'galvanize', + 'gambling', + 'game', + 'gaming', + 'gamma', + 'gander', + 'gangly', + 'gangrene', + 'gangway', + 'gap', + 'garage', + 'garbage', + 'garden', + 'gargle', + 'garland', + 'garlic', + 'garment', + 'garnet', + 'garnish', + 'garter', + 'gas', + 'gatherer', + 'gathering', + 'gating', + 'gauging', + 'gauntlet', + 'gauze', + 'gave', + 'gawk', + 'gazing', + 'gear', + 'gecko', + 'geek', + 'geiger', + 'gem', + 'gender', + 'generic', + 'generous', + 'genetics', + 'genre', + 'gentile', + 'gentleman', + 'gently', + 'gents', + 'geography', + 'geologic', + 'geologist', + 'geology', + 'geometric', + 'geometry', + 'geranium', + 'gerbil', + 'geriatric', + 'germicide', + 'germinate', + 'germless', + 'germproof', + 'gestate', + 'gestation', + 'gesture', + 'getaway', + 'getting', + 'getup', + 'giant', + 'gibberish', + 'giblet', + 'giddily', + 'giddiness', + 'giddy', + 'gift', + 'gigabyte', + 'gigahertz', + 'gigantic', + 'giggle', + 'giggling', + 'giggly', + 'gigolo', + 'gilled', + 'gills', + 'gimmick', + 'girdle', + 'giveaway', + 'given', + 'giver', + 'giving', + 'gizmo', + 'gizzard', + 'glacial', + 'glacier', + 'glade', + 'gladiator', + 'gladly', + 'glamorous', + 'glamour', + 'glance', + 'glancing', + 'glandular', + 'glare', + 'glaring', + 'glass', + 'glaucoma', + 'glazing', + 'gleaming', + 'gleeful', + 'glider', + 'gliding', + 'glimmer', + 'glimpse', + 'glisten', + 'glitch', + 'glitter', + 'glitzy', + 'gloater', + 'gloating', + 'gloomily', + 'gloomy', + 'glorified', + 'glorifier', + 'glorify', + 'glorious', + 'glory', + 'gloss', + 'glove', + 'glowing', + 'glowworm', + 'glucose', + 'glue', + 'gluten', + 'glutinous', + 'glutton', + 'gnarly', + 'gnat', + 'goal', + 'goatskin', + 'goes', + 'goggles', + 'going', + 'goldfish', + 'goldmine', + 'goldsmith', + 'golf', + 'goliath', + 'gonad', + 'gondola', + 'gone', + 'gong', + 'good', + 'gooey', + 'goofball', + 'goofiness', + 'goofy', + 'google', + 'goon', + 'gopher', + 'gore', + 'gorged', + 'gorgeous', + 'gory', + 'gosling', + 'gossip', + 'gothic', + 'gotten', + 'gout', + 'gown', + 'grab', + 'graceful', + 'graceless', + 'gracious', + 'gradation', + 'graded', + 'grader', + 'gradient', + 'grading', + 'gradually', + 'graduate', + 'graffiti', + 'grafted', + 'grafting', + 'grain', + 'granddad', + 'grandkid', + 'grandly', + 'grandma', + 'grandpa', + 'grandson', + 'granite', + 'granny', + 'granola', + 'grant', + 'granular', + 'grape', + 'graph', + 'grapple', + 'grappling', + 'grasp', + 'grass', + 'gratified', + 'gratify', + 'grating', + 'gratitude', + 'gratuity', + 'gravel', + 'graveness', + 'graves', + 'graveyard', + 'gravitate', + 'gravity', + 'gravy', + 'gray', + 'grazing', + 'greasily', + 'greedily', + 'greedless', + 'greedy', + 'green', + 'greeter', + 'greeting', + 'grew', + 'greyhound', + 'grid', + 'grief', + 'grievance', + 'grieving', + 'grievous', + 'grill', + 'grimace', + 'grimacing', + 'grime', + 'griminess', + 'grimy', + 'grinch', + 'grinning', + 'grip', + 'gristle', + 'grit', + 'groggily', + 'groggy', + 'groin', + 'groom', + 'groove', + 'grooving', + 'groovy', + 'grope', + 'ground', + 'grouped', + 'grout', + 'grove', + 'grower', + 'growing', + 'growl', + 'grub', + 'grudge', + 'grudging', + 'grueling', + 'gruffly', + 'grumble', + 'grumbling', + 'grumbly', + 'grumpily', + 'grunge', + 'grunt', + 'guacamole', + 'guidable', + 'guidance', + 'guide', + 'guiding', + 'guileless', + 'guise', + 'gulf', + 'gullible', + 'gully', + 'gulp', + 'gumball', + 'gumdrop', + 'gumminess', + 'gumming', + 'gummy', + 'gurgle', + 'gurgling', + 'guru', + 'gush', + 'gusto', + 'gusty', + 'gutless', + 'guts', + 'gutter', + 'guy', + 'guzzler', + 'gyration', + 'habitable', + 'habitant', + 'habitat', + 'habitual', + 'hacked', + 'hacker', + 'hacking', + 'hacksaw', + 'had', + 'haggler', + 'haiku', + 'half', + 'halogen', + 'halt', + 'halved', + 'halves', + 'hamburger', + 'hamlet', + 'hammock', + 'hamper', + 'hamster', + 'hamstring', + 'handbag', + 'handball', + 'handbook', + 'handbrake', + 'handcart', + 'handclap', + 'handclasp', + 'handcraft', + 'handcuff', + 'handed', + 'handful', + 'handgrip', + 'handgun', + 'handheld', + 'handiness', + 'handiwork', + 'handlebar', + 'handled', + 'handler', + 'handling', + 'handmade', + 'handoff', + 'handpick', + 'handprint', + 'handrail', + 'handsaw', + 'handset', + 'handsfree', + 'handshake', + 'handstand', + 'handwash', + 'handwork', + 'handwoven', + 'handwrite', + 'handyman', + 'hangnail', + 'hangout', + 'hangover', + 'hangup', + 'hankering', + 'hankie', + 'hanky', + 'haphazard', + 'happening', + 'happier', + 'happiest', + 'happily', + 'happiness', + 'happy', + 'harbor', + 'hardcopy', + 'hardcore', + 'hardcover', + 'harddisk', + 'hardened', + 'hardener', + 'hardening', + 'hardhat', + 'hardhead', + 'hardiness', + 'hardly', + 'hardness', + 'hardship', + 'hardware', + 'hardwired', + 'hardwood', + 'hardy', + 'harmful', + 'harmless', + 'harmonica', + 'harmonics', + 'harmonize', + 'harmony', + 'harness', + 'harpist', + 'harsh', + 'harvest', + 'hash', + 'hassle', + 'haste', + 'hastily', + 'hastiness', + 'hasty', + 'hatbox', + 'hatchback', + 'hatchery', + 'hatchet', + 'hatching', + 'hatchling', + 'hate', + 'hatless', + 'hatred', + 'haunt', + 'haven', + 'hazard', + 'hazelnut', + 'hazily', + 'haziness', + 'hazing', + 'hazy', + 'headache', + 'headband', + 'headboard', + 'headcount', + 'headdress', + 'headed', + 'header', + 'headfirst', + 'headgear', + 'heading', + 'headlamp', + 'headless', + 'headlock', + 'headphone', + 'headpiece', + 'headrest', + 'headroom', + 'headscarf', + 'headset', + 'headsman', + 'headstand', + 'headstone', + 'headway', + 'headwear', + 'heap', + 'heat', + 'heave', + 'heavily', + 'heaviness', + 'heaving', + 'hedge', + 'hedging', + 'heftiness', + 'hefty', + 'helium', + 'helmet', + 'helper', + 'helpful', + 'helping', + 'helpless', + 'helpline', + 'hemlock', + 'hemstitch', + 'hence', + 'henchman', + 'henna', + 'herald', + 'herbal', + 'herbicide', + 'herbs', + 'heritage', + 'hermit', + 'heroics', + 'heroism', + 'herring', + 'herself', + 'hertz', + 'hesitancy', + 'hesitant', + 'hesitate', + 'hexagon', + 'hexagram', + 'hubcap', + 'huddle', + 'huddling', + 'huff', + 'hug', + 'hula', + 'hulk', + 'hull', + 'human', + 'humble', + 'humbling', + 'humbly', + 'humid', + 'humiliate', + 'humility', + 'humming', + 'hummus', + 'humongous', + 'humorist', + 'humorless', + 'humorous', + 'humpback', + 'humped', + 'humvee', + 'hunchback', + 'hundredth', + 'hunger', + 'hungrily', + 'hungry', + 'hunk', + 'hunter', + 'hunting', + 'huntress', + 'huntsman', + 'hurdle', + 'hurled', + 'hurler', + 'hurling', + 'hurray', + 'hurricane', + 'hurried', + 'hurry', + 'hurt', + 'husband', + 'hush', + 'husked', + 'huskiness', + 'hut', + 'hybrid', + 'hydrant', + 'hydrated', + 'hydration', + 'hydrogen', + 'hydroxide', + 'hyperlink', + 'hypertext', + 'hyphen', + 'hypnoses', + 'hypnosis', + 'hypnotic', + 'hypnotism', + 'hypnotist', + 'hypnotize', + 'hypocrisy', + 'hypocrite', + 'ibuprofen', + 'ice', + 'iciness', + 'icing', + 'icky', + 'icon', + 'icy', + 'idealism', + 'idealist', + 'idealize', + 'ideally', + 'idealness', + 'identical', + 'identify', + 'identity', + 'ideology', + 'idiocy', + 'idiom', + 'idly', + 'igloo', + 'ignition', + 'ignore', + 'iguana', + 'illicitly', + 'illusion', + 'illusive', + 'image', + 'imaginary', + 'imagines', + 'imaging', + 'imbecile', + 'imitate', + 'imitation', + 'immature', + 'immerse', + 'immersion', + 'imminent', + 'immobile', + 'immodest', + 'immorally', + 'immortal', + 'immovable', + 'immovably', + 'immunity', + 'immunize', + 'impaired', + 'impale', + 'impart', + 'impatient', + 'impeach', + 'impeding', + 'impending', + 'imperfect', + 'imperial', + 'impish', + 'implant', + 'implement', + 'implicate', + 'implicit', + 'implode', + 'implosion', + 'implosive', + 'imply', + 'impolite', + 'important', + 'importer', + 'impose', + 'imposing', + 'impotence', + 'impotency', + 'impotent', + 'impound', + 'imprecise', + 'imprint', + 'imprison', + 'impromptu', + 'improper', + 'improve', + 'improving', + 'improvise', + 'imprudent', + 'impulse', + 'impulsive', + 'impure', + 'impurity', + 'iodine', + 'iodize', + 'ion', + 'ipad', + 'iphone', + 'ipod', + 'irate', + 'irk', + 'iron', + 'irregular', + 'irrigate', + 'irritable', + 'irritably', + 'irritant', + 'irritate', + 'islamic', + 'islamist', + 'isolated', + 'isolating', + 'isolation', + 'isotope', + 'issue', + 'issuing', + 'italicize', + 'italics', + 'item', + 'itinerary', + 'itunes', + 'ivory', + 'ivy', + 'jab', + 'jackal', + 'jacket', + 'jackknife', + 'jackpot', + 'jailbird', + 'jailbreak', + 'jailer', + 'jailhouse', + 'jalapeno', + 'jam', + 'janitor', + 'january', + 'jargon', + 'jarring', + 'jasmine', + 'jaundice', + 'jaunt', + 'java', + 'jawed', + 'jawless', + 'jawline', + 'jaws', + 'jaybird', + 'jaywalker', + 'jazz', + 'jeep', + 'jeeringly', + 'jellied', + 'jelly', + 'jersey', + 'jester', + 'jet', + 'jiffy', + 'jigsaw', + 'jimmy', + 'jingle', + 'jingling', + 'jinx', + 'jitters', + 'jittery', + 'job', + 'jockey', + 'jockstrap', + 'jogger', + 'jogging', + 'john', + 'joining', + 'jokester', + 'jokingly', + 'jolliness', + 'jolly', + 'jolt', + 'jot', + 'jovial', + 'joyfully', + 'joylessly', + 'joyous', + 'joyride', + 'joystick', + 'jubilance', + 'jubilant', + 'judge', + 'judgingly', + 'judicial', + 'judiciary', + 'judo', + 'juggle', + 'juggling', + 'jugular', + 'juice', + 'juiciness', + 'juicy', + 'jujitsu', + 'jukebox', + 'july', + 'jumble', + 'jumbo', + 'jump', + 'junction', + 'juncture', + 'june', + 'junior', + 'juniper', + 'junkie', + 'junkman', + 'junkyard', + 'jurist', + 'juror', + 'jury', + 'justice', + 'justifier', + 'justify', + 'justly', + 'justness', + 'juvenile', + 'kabob', + 'kangaroo', + 'karaoke', + 'karate', + 'karma', + 'kebab', + 'keenly', + 'keenness', + 'keep', + 'keg', + 'kelp', + 'kennel', + 'kept', + 'kerchief', + 'kerosene', + 'kettle', + 'kick', + 'kiln', + 'kilobyte', + 'kilogram', + 'kilometer', + 'kilowatt', + 'kilt', + 'kimono', + 'kindle', + 'kindling', + 'kindly', + 'kindness', + 'kindred', + 'kinetic', + 'kinfolk', + 'king', + 'kinship', + 'kinsman', + 'kinswoman', + 'kissable', + 'kisser', + 'kissing', + 'kitchen', + 'kite', + 'kitten', + 'kitty', + 'kiwi', + 'kleenex', + 'knapsack', + 'knee', + 'knelt', + 'knickers', + 'knoll', + 'koala', + 'kooky', + 'kosher', + 'krypton', + 'kudos', + 'kung', + 'labored', + 'laborer', + 'laboring', + 'laborious', + 'labrador', + 'ladder', + 'ladies', + 'ladle', + 'ladybug', + 'ladylike', + 'lagged', + 'lagging', + 'lagoon', + 'lair', + 'lake', + 'lance', + 'landed', + 'landfall', + 'landfill', + 'landing', + 'landlady', + 'landless', + 'landline', + 'landlord', + 'landmark', + 'landmass', + 'landmine', + 'landowner', + 'landscape', + 'landside', + 'landslide', + 'language', + 'lankiness', + 'lanky', + 'lantern', + 'lapdog', + 'lapel', + 'lapped', + 'lapping', + 'laptop', + 'lard', + 'large', + 'lark', + 'lash', + 'lasso', + 'last', + 'latch', + 'late', + 'lather', + 'latitude', + 'latrine', + 'latter', + 'latticed', + 'launch', + 'launder', + 'laundry', + 'laurel', + 'lavender', + 'lavish', + 'laxative', + 'lazily', + 'laziness', + 'lazy', + 'lecturer', + 'left', + 'legacy', + 'legal', + 'legend', + 'legged', + 'leggings', + 'legible', + 'legibly', + 'legislate', + 'lego', + 'legroom', + 'legume', + 'legwarmer', + 'legwork', + 'lemon', + 'lend', + 'length', + 'lens', + 'lent', + 'leotard', + 'lesser', + 'letdown', + 'lethargic', + 'lethargy', + 'letter', + 'lettuce', + 'level', + 'leverage', + 'levers', + 'levitate', + 'levitator', + 'liability', + 'liable', + 'liberty', + 'librarian', + 'library', + 'licking', + 'licorice', + 'lid', + 'life', + 'lifter', + 'lifting', + 'liftoff', + 'ligament', + 'likely', + 'likeness', + 'likewise', + 'liking', + 'lilac', + 'lilly', + 'lily', + 'limb', + 'limeade', + 'limelight', + 'limes', + 'limit', + 'limping', + 'limpness', + 'line', + 'lingo', + 'linguini', + 'linguist', + 'lining', + 'linked', + 'linoleum', + 'linseed', + 'lint', + 'lion', + 'lip', + 'liquefy', + 'liqueur', + 'liquid', + 'lisp', + 'list', + 'litigate', + 'litigator', + 'litmus', + 'litter', + 'little', + 'livable', + 'lived', + 'lively', + 'liver', + 'livestock', + 'lividly', + 'living', + 'lizard', + 'lubricant', + 'lubricate', + 'lucid', + 'luckily', + 'luckiness', + 'luckless', + 'lucrative', + 'ludicrous', + 'lugged', + 'lukewarm', + 'lullaby', + 'lumber', + 'luminance', + 'luminous', + 'lumpiness', + 'lumping', + 'lumpish', + 'lunacy', + 'lunar', + 'lunchbox', + 'luncheon', + 'lunchroom', + 'lunchtime', + 'lung', + 'lurch', + 'lure', + 'luridness', + 'lurk', + 'lushly', + 'lushness', + 'luster', + 'lustfully', + 'lustily', + 'lustiness', + 'lustrous', + 'lusty', + 'luxurious', + 'luxury', + 'lying', + 'lyrically', + 'lyricism', + 'lyricist', + 'lyrics', + 'macarena', + 'macaroni', + 'macaw', + 'mace', + 'machine', + 'machinist', + 'magazine', + 'magenta', + 'maggot', + 'magical', + 'magician', + 'magma', + 'magnesium', + 'magnetic', + 'magnetism', + 'magnetize', + 'magnifier', + 'magnify', + 'magnitude', + 'magnolia', + 'mahogany', + 'maimed', + 'majestic', + 'majesty', + 'majorette', + 'majority', + 'makeover', + 'maker', + 'makeshift', + 'making', + 'malformed', + 'malt', + 'mama', + 'mammal', + 'mammary', + 'mammogram', + 'manager', + 'managing', + 'manatee', + 'mandarin', + 'mandate', + 'mandatory', + 'mandolin', + 'manger', + 'mangle', + 'mango', + 'mangy', + 'manhandle', + 'manhole', + 'manhood', + 'manhunt', + 'manicotti', + 'manicure', + 'manifesto', + 'manila', + 'mankind', + 'manlike', + 'manliness', + 'manly', + 'manmade', + 'manned', + 'mannish', + 'manor', + 'manpower', + 'mantis', + 'mantra', + 'manual', + 'many', + 'map', + 'marathon', + 'marauding', + 'marbled', + 'marbles', + 'marbling', + 'march', + 'mardi', + 'margarine', + 'margarita', + 'margin', + 'marigold', + 'marina', + 'marine', + 'marital', + 'maritime', + 'marlin', + 'marmalade', + 'maroon', + 'married', + 'marrow', + 'marry', + 'marshland', + 'marshy', + 'marsupial', + 'marvelous', + 'marxism', + 'mascot', + 'masculine', + 'mashed', + 'mashing', + 'massager', + 'masses', + 'massive', + 'mastiff', + 'matador', + 'matchbook', + 'matchbox', + 'matcher', + 'matching', + 'matchless', + 'material', + 'maternal', + 'maternity', + 'math', + 'mating', + 'matriarch', + 'matrimony', + 'matrix', + 'matron', + 'matted', + 'matter', + 'maturely', + 'maturing', + 'maturity', + 'mauve', + 'maverick', + 'maximize', + 'maximum', + 'maybe', + 'mayday', + 'mayflower', + 'moaner', + 'moaning', + 'mobile', + 'mobility', + 'mobilize', + 'mobster', + 'mocha', + 'mocker', + 'mockup', + 'modified', + 'modify', + 'modular', + 'modulator', + 'module', + 'moisten', + 'moistness', + 'moisture', + 'molar', + 'molasses', + 'mold', + 'molecular', + 'molecule', + 'molehill', + 'mollusk', + 'mom', + 'monastery', + 'monday', + 'monetary', + 'monetize', + 'moneybags', + 'moneyless', + 'moneywise', + 'mongoose', + 'mongrel', + 'monitor', + 'monkhood', + 'monogamy', + 'monogram', + 'monologue', + 'monopoly', + 'monorail', + 'monotone', + 'monotype', + 'monoxide', + 'monsieur', + 'monsoon', + 'monstrous', + 'monthly', + 'monument', + 'moocher', + 'moodiness', + 'moody', + 'mooing', + 'moonbeam', + 'mooned', + 'moonlight', + 'moonlike', + 'moonlit', + 'moonrise', + 'moonscape', + 'moonshine', + 'moonstone', + 'moonwalk', + 'mop', + 'morale', + 'morality', + 'morally', + 'morbidity', + 'morbidly', + 'morphine', + 'morphing', + 'morse', + 'mortality', + 'mortally', + 'mortician', + 'mortified', + 'mortify', + 'mortuary', + 'mosaic', + 'mossy', + 'most', + 'mothball', + 'mothproof', + 'motion', + 'motivate', + 'motivator', + 'motive', + 'motocross', + 'motor', + 'motto', + 'mountable', + 'mountain', + 'mounted', + 'mounting', + 'mourner', + 'mournful', + 'mouse', + 'mousiness', + 'moustache', + 'mousy', + 'mouth', + 'movable', + 'move', + 'movie', + 'moving', + 'mower', + 'mowing', + 'much', + 'muck', + 'mud', + 'mug', + 'mulberry', + 'mulch', + 'mule', + 'mulled', + 'mullets', + 'multiple', + 'multiply', + 'multitask', + 'multitude', + 'mumble', + 'mumbling', + 'mumbo', + 'mummified', + 'mummify', + 'mummy', + 'mumps', + 'munchkin', + 'mundane', + 'municipal', + 'muppet', + 'mural', + 'murkiness', + 'murky', + 'murmuring', + 'muscular', + 'museum', + 'mushily', + 'mushiness', + 'mushroom', + 'mushy', + 'music', + 'musket', + 'muskiness', + 'musky', + 'mustang', + 'mustard', + 'muster', + 'mustiness', + 'musty', + 'mutable', + 'mutate', + 'mutation', + 'mute', + 'mutilated', + 'mutilator', + 'mutiny', + 'mutt', + 'mutual', + 'muzzle', + 'myself', + 'myspace', + 'mystified', + 'mystify', + 'myth', + 'nacho', + 'nag', + 'nail', + 'name', + 'naming', + 'nanny', + 'nanometer', + 'nape', + 'napkin', + 'napped', + 'napping', + 'nappy', + 'narrow', + 'nastily', + 'nastiness', + 'national', + 'native', + 'nativity', + 'natural', + 'nature', + 'naturist', + 'nautical', + 'navigate', + 'navigator', + 'navy', + 'nearby', + 'nearest', + 'nearly', + 'nearness', + 'neatly', + 'neatness', + 'nebula', + 'nebulizer', + 'nectar', + 'negate', + 'negation', + 'negative', + 'neglector', + 'negligee', + 'negligent', + 'negotiate', + 'nemeses', + 'nemesis', + 'neon', + 'nephew', + 'nerd', + 'nervous', + 'nervy', + 'nest', + 'net', + 'neurology', + 'neuron', + 'neurosis', + 'neurotic', + 'neuter', + 'neutron', + 'never', + 'next', + 'nibble', + 'nickname', + 'nicotine', + 'niece', + 'nifty', + 'nimble', + 'nimbly', + 'nineteen', + 'ninetieth', + 'ninja', + 'nintendo', + 'ninth', + 'nuclear', + 'nuclei', + 'nucleus', + 'nugget', + 'nullify', + 'number', + 'numbing', + 'numbly', + 'numbness', + 'numeral', + 'numerate', + 'numerator', + 'numeric', + 'numerous', + 'nuptials', + 'nursery', + 'nursing', + 'nurture', + 'nutcase', + 'nutlike', + 'nutmeg', + 'nutrient', + 'nutshell', + 'nuttiness', + 'nutty', + 'nuzzle', + 'nylon', + 'oaf', + 'oak', + 'oasis', + 'oat', + 'obedience', + 'obedient', + 'obituary', + 'object', + 'obligate', + 'obliged', + 'oblivion', + 'oblivious', + 'oblong', + 'obnoxious', + 'oboe', + 'obscure', + 'obscurity', + 'observant', + 'observer', + 'observing', + 'obsessed', + 'obsession', + 'obsessive', + 'obsolete', + 'obstacle', + 'obstinate', + 'obstruct', + 'obtain', + 'obtrusive', + 'obtuse', + 'obvious', + 'occultist', + 'occupancy', + 'occupant', + 'occupier', + 'occupy', + 'ocean', + 'ocelot', + 'octagon', + 'octane', + 'october', + 'octopus', + 'ogle', + 'oil', + 'oink', + 'ointment', + 'okay', + 'old', + 'olive', + 'olympics', + 'omega', + 'omen', + 'ominous', + 'omission', + 'omit', + 'omnivore', + 'onboard', + 'oncoming', + 'ongoing', + 'onion', + 'online', + 'onlooker', + 'only', + 'onscreen', + 'onset', + 'onshore', + 'onslaught', + 'onstage', + 'onto', + 'onward', + 'onyx', + 'oops', + 'ooze', + 'oozy', + 'opacity', + 'opal', + 'open', + 'operable', + 'operate', + 'operating', + 'operation', + 'operative', + 'operator', + 'opium', + 'opossum', + 'opponent', + 'oppose', + 'opposing', + 'opposite', + 'oppressed', + 'oppressor', + 'opt', + 'opulently', + 'osmosis', + 'other', + 'otter', + 'ouch', + 'ought', + 'ounce', + 'outage', + 'outback', + 'outbid', + 'outboard', + 'outbound', + 'outbreak', + 'outburst', + 'outcast', + 'outclass', + 'outcome', + 'outdated', + 'outdoors', + 'outer', + 'outfield', + 'outfit', + 'outflank', + 'outgoing', + 'outgrow', + 'outhouse', + 'outing', + 'outlast', + 'outlet', + 'outline', + 'outlook', + 'outlying', + 'outmatch', + 'outmost', + 'outnumber', + 'outplayed', + 'outpost', + 'outpour', + 'output', + 'outrage', + 'outrank', + 'outreach', + 'outright', + 'outscore', + 'outsell', + 'outshine', + 'outshoot', + 'outsider', + 'outskirts', + 'outsmart', + 'outsource', + 'outspoken', + 'outtakes', + 'outthink', + 'outward', + 'outweigh', + 'outwit', + 'oval', + 'ovary', + 'oven', + 'overact', + 'overall', + 'overarch', + 'overbid', + 'overbill', + 'overbite', + 'overblown', + 'overboard', + 'overbook', + 'overbuilt', + 'overcast', + 'overcoat', + 'overcome', + 'overcook', + 'overcrowd', + 'overdraft', + 'overdrawn', + 'overdress', + 'overdrive', + 'overdue', + 'overeager', + 'overeater', + 'overexert', + 'overfed', + 'overfeed', + 'overfill', + 'overflow', + 'overfull', + 'overgrown', + 'overhand', + 'overhang', + 'overhaul', + 'overhead', + 'overhear', + 'overheat', + 'overhung', + 'overjoyed', + 'overkill', + 'overlabor', + 'overlaid', + 'overlap', + 'overlay', + 'overload', + 'overlook', + 'overlord', + 'overlying', + 'overnight', + 'overpass', + 'overpay', + 'overplant', + 'overplay', + 'overpower', + 'overprice', + 'overrate', + 'overreach', + 'overreact', + 'override', + 'overripe', + 'overrule', + 'overrun', + 'overshoot', + 'overshot', + 'oversight', + 'oversized', + 'oversleep', + 'oversold', + 'overspend', + 'overstate', + 'overstay', + 'overstep', + 'overstock', + 'overstuff', + 'oversweet', + 'overtake', + 'overthrow', + 'overtime', + 'overtly', + 'overtone', + 'overture', + 'overturn', + 'overuse', + 'overvalue', + 'overview', + 'overwrite', + 'owl', + 'oxford', + 'oxidant', + 'oxidation', + 'oxidize', + 'oxidizing', + 'oxygen', + 'oxymoron', + 'oyster', + 'ozone', + 'paced', + 'pacemaker', + 'pacific', + 'pacifier', + 'pacifism', + 'pacifist', + 'pacify', + 'padded', + 'padding', + 'paddle', + 'paddling', + 'padlock', + 'pagan', + 'pager', + 'paging', + 'pajamas', + 'palace', + 'palatable', + 'palm', + 'palpable', + 'palpitate', + 'paltry', + 'pampered', + 'pamperer', + 'pampers', + 'pamphlet', + 'panama', + 'pancake', + 'pancreas', + 'panda', + 'pandemic', + 'pang', + 'panhandle', + 'panic', + 'panning', + 'panorama', + 'panoramic', + 'panther', + 'pantomime', + 'pantry', + 'pants', + 'pantyhose', + 'paparazzi', + 'papaya', + 'paper', + 'paprika', + 'papyrus', + 'parabola', + 'parachute', + 'parade', + 'paradox', + 'paragraph', + 'parakeet', + 'paralegal', + 'paralyses', + 'paralysis', + 'paralyze', + 'paramedic', + 'parameter', + 'paramount', + 'parasail', + 'parasite', + 'parasitic', + 'parcel', + 'parched', + 'parchment', + 'pardon', + 'parish', + 'parka', + 'parking', + 'parkway', + 'parlor', + 'parmesan', + 'parole', + 'parrot', + 'parsley', + 'parsnip', + 'partake', + 'parted', + 'parting', + 'partition', + 'partly', + 'partner', + 'partridge', + 'party', + 'passable', + 'passably', + 'passage', + 'passcode', + 'passenger', + 'passerby', + 'passing', + 'passion', + 'passive', + 'passivism', + 'passover', + 'passport', + 'password', + 'pasta', + 'pasted', + 'pastel', + 'pastime', + 'pastor', + 'pastrami', + 'pasture', + 'pasty', + 'patchwork', + 'patchy', + 'paternal', + 'paternity', + 'path', + 'patience', + 'patient', + 'patio', + 'patriarch', + 'patriot', + 'patrol', + 'patronage', + 'patronize', + 'pauper', + 'pavement', + 'paver', + 'pavestone', + 'pavilion', + 'paving', + 'pawing', + 'payable', + 'payback', + 'paycheck', + 'payday', + 'payee', + 'payer', + 'paying', + 'payment', + 'payphone', + 'payroll', + 'pebble', + 'pebbly', + 'pecan', + 'pectin', + 'peculiar', + 'peddling', + 'pediatric', + 'pedicure', + 'pedigree', + 'pedometer', + 'pegboard', + 'pelican', + 'pellet', + 'pelt', + 'pelvis', + 'penalize', + 'penalty', + 'pencil', + 'pendant', + 'pending', + 'penholder', + 'penknife', + 'pennant', + 'penniless', + 'penny', + 'penpal', + 'pension', + 'pentagon', + 'pentagram', + 'pep', + 'perceive', + 'percent', + 'perch', + 'percolate', + 'perennial', + 'perfected', + 'perfectly', + 'perfume', + 'periscope', + 'perish', + 'perjurer', + 'perjury', + 'perkiness', + 'perky', + 'perm', + 'peroxide', + 'perpetual', + 'perplexed', + 'persecute', + 'persevere', + 'persuaded', + 'persuader', + 'pesky', + 'peso', + 'pessimism', + 'pessimist', + 'pester', + 'pesticide', + 'petal', + 'petite', + 'petition', + 'petri', + 'petroleum', + 'petted', + 'petticoat', + 'pettiness', + 'petty', + 'petunia', + 'phantom', + 'phobia', + 'phoenix', + 'phonebook', + 'phoney', + 'phonics', + 'phoniness', + 'phony', + 'phosphate', + 'photo', + 'phrase', + 'phrasing', + 'placard', + 'placate', + 'placidly', + 'plank', + 'planner', + 'plant', + 'plasma', + 'plaster', + 'plastic', + 'plated', + 'platform', + 'plating', + 'platinum', + 'platonic', + 'platter', + 'platypus', + 'plausible', + 'plausibly', + 'playable', + 'playback', + 'player', + 'playful', + 'playgroup', + 'playhouse', + 'playing', + 'playlist', + 'playmaker', + 'playmate', + 'playoff', + 'playpen', + 'playroom', + 'playset', + 'plaything', + 'playtime', + 'plaza', + 'pleading', + 'pleat', + 'pledge', + 'plentiful', + 'plenty', + 'plethora', + 'plexiglas', + 'pliable', + 'plod', + 'plop', + 'plot', + 'plow', + 'ploy', + 'pluck', + 'plug', + 'plunder', + 'plunging', + 'plural', + 'plus', + 'plutonium', + 'plywood', + 'poach', + 'pod', + 'poem', + 'poet', + 'pogo', + 'pointed', + 'pointer', + 'pointing', + 'pointless', + 'pointy', + 'poise', + 'poison', + 'poker', + 'poking', + 'polar', + 'police', + 'policy', + 'polio', + 'polish', + 'politely', + 'polka', + 'polo', + 'polyester', + 'polygon', + 'polygraph', + 'polymer', + 'poncho', + 'pond', + 'pony', + 'popcorn', + 'pope', + 'poplar', + 'popper', + 'poppy', + 'popsicle', + 'populace', + 'popular', + 'populate', + 'porcupine', + 'pork', + 'porous', + 'porridge', + 'portable', + 'portal', + 'portfolio', + 'porthole', + 'portion', + 'portly', + 'portside', + 'poser', + 'posh', + 'posing', + 'possible', + 'possibly', + 'possum', + 'postage', + 'postal', + 'postbox', + 'postcard', + 'posted', + 'poster', + 'posting', + 'postnasal', + 'posture', + 'postwar', + 'pouch', + 'pounce', + 'pouncing', + 'pound', + 'pouring', + 'pout', + 'powdered', + 'powdering', + 'powdery', + 'power', + 'powwow', + 'pox', + 'praising', + 'prance', + 'prancing', + 'pranker', + 'prankish', + 'prankster', + 'prayer', + 'praying', + 'preacher', + 'preaching', + 'preachy', + 'preamble', + 'precinct', + 'precise', + 'precision', + 'precook', + 'precut', + 'predator', + 'predefine', + 'predict', + 'preface', + 'prefix', + 'preflight', + 'preformed', + 'pregame', + 'pregnancy', + 'pregnant', + 'preheated', + 'prelaunch', + 'prelaw', + 'prelude', + 'premiere', + 'premises', + 'premium', + 'prenatal', + 'preoccupy', + 'preorder', + 'prepaid', + 'prepay', + 'preplan', + 'preppy', + 'preschool', + 'prescribe', + 'preseason', + 'preset', + 'preshow', + 'president', + 'presoak', + 'press', + 'presume', + 'presuming', + 'preteen', + 'pretended', + 'pretender', + 'pretense', + 'pretext', + 'pretty', + 'pretzel', + 'prevail', + 'prevalent', + 'prevent', + 'preview', + 'previous', + 'prewar', + 'prewashed', + 'prideful', + 'pried', + 'primal', + 'primarily', + 'primary', + 'primate', + 'primer', + 'primp', + 'princess', + 'print', + 'prior', + 'prism', + 'prison', + 'prissy', + 'pristine', + 'privacy', + 'private', + 'privatize', + 'prize', + 'proactive', + 'probable', + 'probably', + 'probation', + 'probe', + 'probing', + 'probiotic', + 'problem', + 'procedure', + 'process', + 'proclaim', + 'procreate', + 'procurer', + 'prodigal', + 'prodigy', + 'produce', + 'product', + 'profane', + 'profanity', + 'professed', + 'professor', + 'profile', + 'profound', + 'profusely', + 'progeny', + 'prognosis', + 'program', + 'progress', + 'projector', + 'prologue', + 'prolonged', + 'promenade', + 'prominent', + 'promoter', + 'promotion', + 'prompter', + 'promptly', + 'prone', + 'prong', + 'pronounce', + 'pronto', + 'proofing', + 'proofread', + 'proofs', + 'propeller', + 'properly', + 'property', + 'proponent', + 'proposal', + 'propose', + 'props', + 'prorate', + 'protector', + 'protegee', + 'proton', + 'prototype', + 'protozoan', + 'protract', + 'protrude', + 'proud', + 'provable', + 'proved', + 'proven', + 'provided', + 'provider', + 'providing', + 'province', + 'proving', + 'provoke', + 'provoking', + 'provolone', + 'prowess', + 'prowler', + 'prowling', + 'proximity', + 'proxy', + 'prozac', + 'prude', + 'prudishly', + 'prune', + 'pruning', + 'pry', + 'psychic', + 'public', + 'publisher', + 'pucker', + 'pueblo', + 'pug', + 'pull', + 'pulmonary', + 'pulp', + 'pulsate', + 'pulse', + 'pulverize', + 'puma', + 'pumice', + 'pummel', + 'punch', + 'punctual', + 'punctuate', + 'punctured', + 'pungent', + 'punisher', + 'punk', + 'pupil', + 'puppet', + 'puppy', + 'purchase', + 'pureblood', + 'purebred', + 'purely', + 'pureness', + 'purgatory', + 'purge', + 'purging', + 'purifier', + 'purify', + 'purist', + 'puritan', + 'purity', + 'purple', + 'purplish', + 'purposely', + 'purr', + 'purse', + 'pursuable', + 'pursuant', + 'pursuit', + 'purveyor', + 'pushcart', + 'pushchair', + 'pusher', + 'pushiness', + 'pushing', + 'pushover', + 'pushpin', + 'pushup', + 'pushy', + 'putdown', + 'putt', + 'puzzle', + 'puzzling', + 'pyramid', + 'pyromania', + 'python', + 'quack', + 'quadrant', + 'quail', + 'quaintly', + 'quake', + 'quaking', + 'qualified', + 'qualifier', + 'qualify', + 'quality', + 'qualm', + 'quantum', + 'quarrel', + 'quarry', + 'quartered', + 'quarterly', + 'quarters', + 'quartet', + 'quench', + 'query', + 'quicken', + 'quickly', + 'quickness', + 'quicksand', + 'quickstep', + 'quiet', + 'quill', + 'quilt', + 'quintet', + 'quintuple', + 'quirk', + 'quit', + 'quiver', + 'quizzical', + 'quotable', + 'quotation', + 'quote', + 'rabid', + 'race', + 'racing', + 'racism', + 'rack', + 'racoon', + 'radar', + 'radial', + 'radiance', + 'radiantly', + 'radiated', + 'radiation', + 'radiator', + 'radio', + 'radish', + 'raffle', + 'raft', + 'rage', + 'ragged', + 'raging', + 'ragweed', + 'raider', + 'railcar', + 'railing', + 'railroad', + 'railway', + 'raisin', + 'rake', + 'raking', + 'rally', + 'ramble', + 'rambling', + 'ramp', + 'ramrod', + 'ranch', + 'rancidity', + 'random', + 'ranged', + 'ranger', + 'ranging', + 'ranked', + 'ranking', + 'ransack', + 'ranting', + 'rants', + 'rare', + 'rarity', + 'rascal', + 'rash', + 'rasping', + 'ravage', + 'raven', + 'ravine', + 'raving', + 'ravioli', + 'ravishing', + 'reabsorb', + 'reach', + 'reacquire', + 'reaction', + 'reactive', + 'reactor', + 'reaffirm', + 'ream', + 'reanalyze', + 'reappear', + 'reapply', + 'reappoint', + 'reapprove', + 'rearrange', + 'rearview', + 'reason', + 'reassign', + 'reassure', + 'reattach', + 'reawake', + 'rebalance', + 'rebate', + 'rebel', + 'rebirth', + 'reboot', + 'reborn', + 'rebound', + 'rebuff', + 'rebuild', + 'rebuilt', + 'reburial', + 'rebuttal', + 'recall', + 'recant', + 'recapture', + 'recast', + 'recede', + 'recent', + 'recess', + 'recharger', + 'recipient', + 'recital', + 'recite', + 'reckless', + 'reclaim', + 'recliner', + 'reclining', + 'recluse', + 'reclusive', + 'recognize', + 'recoil', + 'recollect', + 'recolor', + 'reconcile', + 'reconfirm', + 'reconvene', + 'recopy', + 'record', + 'recount', + 'recoup', + 'recovery', + 'recreate', + 'rectal', + 'rectangle', + 'rectified', + 'rectify', + 'recycled', + 'recycler', + 'recycling', + 'reemerge', + 'reenact', + 'reenter', + 'reentry', + 'reexamine', + 'referable', + 'referee', + 'reference', + 'refill', + 'refinance', + 'refined', + 'refinery', + 'refining', + 'refinish', + 'reflected', + 'reflector', + 'reflex', + 'reflux', + 'refocus', + 'refold', + 'reforest', + 'reformat', + 'reformed', + 'reformer', + 'reformist', + 'refract', + 'refrain', + 'refreeze', + 'refresh', + 'refried', + 'refueling', + 'refund', + 'refurbish', + 'refurnish', + 'refusal', + 'refuse', + 'refusing', + 'refutable', + 'refute', + 'regain', + 'regalia', + 'regally', + 'reggae', + 'regime', + 'region', + 'register', + 'registrar', + 'registry', + 'regress', + 'regretful', + 'regroup', + 'regular', + 'regulate', + 'regulator', + 'rehab', + 'reheat', + 'rehire', + 'rehydrate', + 'reimburse', + 'reissue', + 'reiterate', + 'rejoice', + 'rejoicing', + 'rejoin', + 'rekindle', + 'relapse', + 'relapsing', + 'relatable', + 'related', + 'relation', + 'relative', + 'relax', + 'relay', + 'relearn', + 'release', + 'relenting', + 'reliable', + 'reliably', + 'reliance', + 'reliant', + 'relic', + 'relieve', + 'relieving', + 'relight', + 'relish', + 'relive', + 'reload', + 'relocate', + 'relock', + 'reluctant', + 'rely', + 'remake', + 'remark', + 'remarry', + 'rematch', + 'remedial', + 'remedy', + 'remember', + 'reminder', + 'remindful', + 'remission', + 'remix', + 'remnant', + 'remodeler', + 'remold', + 'remorse', + 'remote', + 'removable', + 'removal', + 'removed', + 'remover', + 'removing', + 'rename', + 'renderer', + 'rendering', + 'rendition', + 'renegade', + 'renewable', + 'renewably', + 'renewal', + 'renewed', + 'renounce', + 'renovate', + 'renovator', + 'rentable', + 'rental', + 'rented', + 'renter', + 'reoccupy', + 'reoccur', + 'reopen', + 'reorder', + 'repackage', + 'repacking', + 'repaint', + 'repair', + 'repave', + 'repaying', + 'repayment', + 'repeal', + 'repeated', + 'repeater', + 'repent', + 'rephrase', + 'replace', + 'replay', + 'replica', + 'reply', + 'reporter', + 'repose', + 'repossess', + 'repost', + 'repressed', + 'reprimand', + 'reprint', + 'reprise', + 'reproach', + 'reprocess', + 'reproduce', + 'reprogram', + 'reps', + 'reptile', + 'reptilian', + 'repugnant', + 'repulsion', + 'repulsive', + 'repurpose', + 'reputable', + 'reputably', + 'request', + 'require', + 'requisite', + 'reroute', + 'rerun', + 'resale', + 'resample', + 'rescuer', + 'reseal', + 'research', + 'reselect', + 'reseller', + 'resemble', + 'resend', + 'resent', + 'reset', + 'reshape', + 'reshoot', + 'reshuffle', + 'residence', + 'residency', + 'resident', + 'residual', + 'residue', + 'resigned', + 'resilient', + 'resistant', + 'resisting', + 'resize', + 'resolute', + 'resolved', + 'resonant', + 'resonate', + 'resort', + 'resource', + 'respect', + 'resubmit', + 'result', + 'resume', + 'resupply', + 'resurface', + 'resurrect', + 'retail', + 'retainer', + 'retaining', + 'retake', + 'retaliate', + 'retention', + 'rethink', + 'retinal', + 'retired', + 'retiree', + 'retiring', + 'retold', + 'retool', + 'retorted', + 'retouch', + 'retrace', + 'retract', + 'retrain', + 'retread', + 'retreat', + 'retrial', + 'retrieval', + 'retriever', + 'retry', + 'return', + 'retying', + 'retype', + 'reunion', + 'reunite', + 'reusable', + 'reuse', + 'reveal', + 'reveler', + 'revenge', + 'revenue', + 'reverb', + 'revered', + 'reverence', + 'reverend', + 'reversal', + 'reverse', + 'reversing', + 'reversion', + 'revert', + 'revisable', + 'revise', + 'revision', + 'revisit', + 'revivable', + 'revival', + 'reviver', + 'reviving', + 'revocable', + 'revoke', + 'revolt', + 'revolver', + 'revolving', + 'reward', + 'rewash', + 'rewind', + 'rewire', + 'reword', + 'rework', + 'rewrap', + 'rewrite', + 'rhyme', + 'ribbon', + 'ribcage', + 'rice', + 'riches', + 'richly', + 'richness', + 'rickety', + 'ricotta', + 'riddance', + 'ridden', + 'ride', + 'riding', + 'rifling', + 'rift', + 'rigging', + 'rigid', + 'rigor', + 'rimless', + 'rimmed', + 'rind', + 'rink', + 'rinse', + 'rinsing', + 'riot', + 'ripcord', + 'ripeness', + 'ripening', + 'ripping', + 'ripple', + 'rippling', + 'riptide', + 'rise', + 'rising', + 'risk', + 'risotto', + 'ritalin', + 'ritzy', + 'rival', + 'riverbank', + 'riverbed', + 'riverboat', + 'riverside', + 'riveter', + 'riveting', + 'roamer', + 'roaming', + 'roast', + 'robbing', + 'robe', + 'robin', + 'robotics', + 'robust', + 'rockband', + 'rocker', + 'rocket', + 'rockfish', + 'rockiness', + 'rocking', + 'rocklike', + 'rockslide', + 'rockstar', + 'rocky', + 'rogue', + 'roman', + 'romp', + 'rope', + 'roping', + 'roster', + 'rosy', + 'rotten', + 'rotting', + 'rotunda', + 'roulette', + 'rounding', + 'roundish', + 'roundness', + 'roundup', + 'roundworm', + 'routine', + 'routing', + 'rover', + 'roving', + 'royal', + 'rubbed', + 'rubber', + 'rubbing', + 'rubble', + 'rubdown', + 'ruby', + 'ruckus', + 'rudder', + 'rug', + 'ruined', + 'rule', + 'rumble', + 'rumbling', + 'rummage', + 'rumor', + 'runaround', + 'rundown', + 'runner', + 'running', + 'runny', + 'runt', + 'runway', + 'rupture', + 'rural', + 'ruse', + 'rush', + 'rust', + 'rut', + 'sabbath', + 'sabotage', + 'sacrament', + 'sacred', + 'sacrifice', + 'sadden', + 'saddlebag', + 'saddled', + 'saddling', + 'sadly', + 'sadness', + 'safari', + 'safeguard', + 'safehouse', + 'safely', + 'safeness', + 'saffron', + 'saga', + 'sage', + 'sagging', + 'saggy', + 'said', + 'saint', + 'sake', + 'salad', + 'salami', + 'salaried', + 'salary', + 'saline', + 'salon', + 'saloon', + 'salsa', + 'salt', + 'salutary', + 'salute', + 'salvage', + 'salvaging', + 'salvation', + 'same', + 'sample', + 'sampling', + 'sanction', + 'sanctity', + 'sanctuary', + 'sandal', + 'sandbag', + 'sandbank', + 'sandbar', + 'sandblast', + 'sandbox', + 'sanded', + 'sandfish', + 'sanding', + 'sandlot', + 'sandpaper', + 'sandpit', + 'sandstone', + 'sandstorm', + 'sandworm', + 'sandy', + 'sanitary', + 'sanitizer', + 'sank', + 'santa', + 'sapling', + 'sappiness', + 'sappy', + 'sarcasm', + 'sarcastic', + 'sardine', + 'sash', + 'sasquatch', + 'sassy', + 'satchel', + 'satiable', + 'satin', + 'satirical', + 'satisfied', + 'satisfy', + 'saturate', + 'saturday', + 'sauciness', + 'saucy', + 'sauna', + 'savage', + 'savanna', + 'saved', + 'savings', + 'savior', + 'savor', + 'saxophone', + 'say', + 'scabbed', + 'scabby', + 'scalded', + 'scalding', + 'scale', + 'scaling', + 'scallion', + 'scallop', + 'scalping', + 'scam', + 'scandal', + 'scanner', + 'scanning', + 'scant', + 'scapegoat', + 'scarce', + 'scarcity', + 'scarecrow', + 'scared', + 'scarf', + 'scarily', + 'scariness', + 'scarring', + 'scary', + 'scavenger', + 'scenic', + 'schedule', + 'schematic', + 'scheme', + 'scheming', + 'schilling', + 'schnapps', + 'scholar', + 'science', + 'scientist', + 'scion', + 'scoff', + 'scolding', + 'scone', + 'scoop', + 'scooter', + 'scope', + 'scorch', + 'scorebook', + 'scorecard', + 'scored', + 'scoreless', + 'scorer', + 'scoring', + 'scorn', + 'scorpion', + 'scotch', + 'scoundrel', + 'scoured', + 'scouring', + 'scouting', + 'scouts', + 'scowling', + 'scrabble', + 'scraggly', + 'scrambled', + 'scrambler', + 'scrap', + 'scratch', + 'scrawny', + 'screen', + 'scribble', + 'scribe', + 'scribing', + 'scrimmage', + 'script', + 'scroll', + 'scrooge', + 'scrounger', + 'scrubbed', + 'scrubber', + 'scruffy', + 'scrunch', + 'scrutiny', + 'scuba', + 'scuff', + 'sculptor', + 'sculpture', + 'scurvy', + 'scuttle', + 'secluded', + 'secluding', + 'seclusion', + 'second', + 'secrecy', + 'secret', + 'sectional', + 'sector', + 'secular', + 'securely', + 'security', + 'sedan', + 'sedate', + 'sedation', + 'sedative', + 'sediment', + 'seduce', + 'seducing', + 'segment', + 'seismic', + 'seizing', + 'seldom', + 'selected', + 'selection', + 'selective', + 'selector', + 'self', + 'seltzer', + 'semantic', + 'semester', + 'semicolon', + 'semifinal', + 'seminar', + 'semisoft', + 'semisweet', + 'senate', + 'senator', + 'send', + 'senior', + 'senorita', + 'sensation', + 'sensitive', + 'sensitize', + 'sensually', + 'sensuous', + 'sepia', + 'september', + 'septic', + 'septum', + 'sequel', + 'sequence', + 'sequester', + 'series', + 'sermon', + 'serotonin', + 'serpent', + 'serrated', + 'serve', + 'service', + 'serving', + 'sesame', + 'sessions', + 'setback', + 'setting', + 'settle', + 'settling', + 'setup', + 'sevenfold', + 'seventeen', + 'seventh', + 'seventy', + 'severity', + 'shabby', + 'shack', + 'shaded', + 'shadily', + 'shadiness', + 'shading', + 'shadow', + 'shady', + 'shaft', + 'shakable', + 'shakily', + 'shakiness', + 'shaking', + 'shaky', + 'shale', + 'shallot', + 'shallow', + 'shame', + 'shampoo', + 'shamrock', + 'shank', + 'shanty', + 'shape', + 'shaping', + 'share', + 'sharpener', + 'sharper', + 'sharpie', + 'sharply', + 'sharpness', + 'shawl', + 'sheath', + 'shed', + 'sheep', + 'sheet', + 'shelf', + 'shell', + 'shelter', + 'shelve', + 'shelving', + 'sherry', + 'shield', + 'shifter', + 'shifting', + 'shiftless', + 'shifty', + 'shimmer', + 'shimmy', + 'shindig', + 'shine', + 'shingle', + 'shininess', + 'shining', + 'shiny', + 'ship', + 'shirt', + 'shivering', + 'shock', + 'shone', + 'shoplift', + 'shopper', + 'shopping', + 'shoptalk', + 'shore', + 'shortage', + 'shortcake', + 'shortcut', + 'shorten', + 'shorter', + 'shorthand', + 'shortlist', + 'shortly', + 'shortness', + 'shorts', + 'shortwave', + 'shorty', + 'shout', + 'shove', + 'showbiz', + 'showcase', + 'showdown', + 'shower', + 'showgirl', + 'showing', + 'showman', + 'shown', + 'showoff', + 'showpiece', + 'showplace', + 'showroom', + 'showy', + 'shrank', + 'shrapnel', + 'shredder', + 'shredding', + 'shrewdly', + 'shriek', + 'shrill', + 'shrimp', + 'shrine', + 'shrink', + 'shrivel', + 'shrouded', + 'shrubbery', + 'shrubs', + 'shrug', + 'shrunk', + 'shucking', + 'shudder', + 'shuffle', + 'shuffling', + 'shun', + 'shush', + 'shut', + 'shy', + 'siamese', + 'siberian', + 'sibling', + 'siding', + 'sierra', + 'siesta', + 'sift', + 'sighing', + 'silenced', + 'silencer', + 'silent', + 'silica', + 'silicon', + 'silk', + 'silliness', + 'silly', + 'silo', + 'silt', + 'silver', + 'similarly', + 'simile', + 'simmering', + 'simple', + 'simplify', + 'simply', + 'sincere', + 'sincerity', + 'singer', + 'singing', + 'single', + 'singular', + 'sinister', + 'sinless', + 'sinner', + 'sinuous', + 'sip', + 'siren', + 'sister', + 'sitcom', + 'sitter', + 'sitting', + 'situated', + 'situation', + 'sixfold', + 'sixteen', + 'sixth', + 'sixties', + 'sixtieth', + 'sixtyfold', + 'sizable', + 'sizably', + 'size', + 'sizing', + 'sizzle', + 'sizzling', + 'skater', + 'skating', + 'skedaddle', + 'skeletal', + 'skeleton', + 'skeptic', + 'sketch', + 'skewed', + 'skewer', + 'skid', + 'skied', + 'skier', + 'skies', + 'skiing', + 'skilled', + 'skillet', + 'skillful', + 'skimmed', + 'skimmer', + 'skimming', + 'skimpily', + 'skincare', + 'skinhead', + 'skinless', + 'skinning', + 'skinny', + 'skintight', + 'skipper', + 'skipping', + 'skirmish', + 'skirt', + 'skittle', + 'skydiver', + 'skylight', + 'skyline', + 'skype', + 'skyrocket', + 'skyward', + 'slab', + 'slacked', + 'slacker', + 'slacking', + 'slackness', + 'slacks', + 'slain', + 'slam', + 'slander', + 'slang', + 'slapping', + 'slapstick', + 'slashed', + 'slashing', + 'slate', + 'slather', + 'slaw', + 'sled', + 'sleek', + 'sleep', + 'sleet', + 'sleeve', + 'slept', + 'sliceable', + 'sliced', + 'slicer', + 'slicing', + 'slick', + 'slider', + 'slideshow', + 'sliding', + 'slighted', + 'slighting', + 'slightly', + 'slimness', + 'slimy', + 'slinging', + 'slingshot', + 'slinky', + 'slip', + 'slit', + 'sliver', + 'slobbery', + 'slogan', + 'sloped', + 'sloping', + 'sloppily', + 'sloppy', + 'slot', + 'slouching', + 'slouchy', + 'sludge', + 'slug', + 'slum', + 'slurp', + 'slush', + 'sly', + 'small', + 'smartly', + 'smartness', + 'smasher', + 'smashing', + 'smashup', + 'smell', + 'smelting', + 'smile', + 'smilingly', + 'smirk', + 'smite', + 'smith', + 'smitten', + 'smock', + 'smog', + 'smoked', + 'smokeless', + 'smokiness', + 'smoking', + 'smoky', + 'smolder', + 'smooth', + 'smother', + 'smudge', + 'smudgy', + 'smuggler', + 'smuggling', + 'smugly', + 'smugness', + 'snack', + 'snagged', + 'snaking', + 'snap', + 'snare', + 'snarl', + 'snazzy', + 'sneak', + 'sneer', + 'sneeze', + 'sneezing', + 'snide', + 'sniff', + 'snippet', + 'snipping', + 'snitch', + 'snooper', + 'snooze', + 'snore', + 'snoring', + 'snorkel', + 'snort', + 'snout', + 'snowbird', + 'snowboard', + 'snowbound', + 'snowcap', + 'snowdrift', + 'snowdrop', + 'snowfall', + 'snowfield', + 'snowflake', + 'snowiness', + 'snowless', + 'snowman', + 'snowplow', + 'snowshoe', + 'snowstorm', + 'snowsuit', + 'snowy', + 'snub', + 'snuff', + 'snuggle', + 'snugly', + 'snugness', + 'speak', + 'spearfish', + 'spearhead', + 'spearman', + 'spearmint', + 'species', + 'specimen', + 'specked', + 'speckled', + 'specks', + 'spectacle', + 'spectator', + 'spectrum', + 'speculate', + 'speech', + 'speed', + 'spellbind', + 'speller', + 'spelling', + 'spendable', + 'spender', + 'spending', + 'spent', + 'spew', + 'sphere', + 'spherical', + 'sphinx', + 'spider', + 'spied', + 'spiffy', + 'spill', + 'spilt', + 'spinach', + 'spinal', + 'spindle', + 'spinner', + 'spinning', + 'spinout', + 'spinster', + 'spiny', + 'spiral', + 'spirited', + 'spiritism', + 'spirits', + 'spiritual', + 'splashed', + 'splashing', + 'splashy', + 'splatter', + 'spleen', + 'splendid', + 'splendor', + 'splice', + 'splicing', + 'splinter', + 'splotchy', + 'splurge', + 'spoilage', + 'spoiled', + 'spoiler', + 'spoiling', + 'spoils', + 'spoken', + 'spokesman', + 'sponge', + 'spongy', + 'sponsor', + 'spoof', + 'spookily', + 'spooky', + 'spool', + 'spoon', + 'spore', + 'sporting', + 'sports', + 'sporty', + 'spotless', + 'spotlight', + 'spotted', + 'spotter', + 'spotting', + 'spotty', + 'spousal', + 'spouse', + 'spout', + 'sprain', + 'sprang', + 'sprawl', + 'spray', + 'spree', + 'sprig', + 'spring', + 'sprinkled', + 'sprinkler', + 'sprint', + 'sprite', + 'sprout', + 'spruce', + 'sprung', + 'spry', + 'spud', + 'spur', + 'sputter', + 'spyglass', + 'squabble', + 'squad', + 'squall', + 'squander', + 'squash', + 'squatted', + 'squatter', + 'squatting', + 'squeak', + 'squealer', + 'squealing', + 'squeamish', + 'squeegee', + 'squeeze', + 'squeezing', + 'squid', + 'squiggle', + 'squiggly', + 'squint', + 'squire', + 'squirt', + 'squishier', + 'squishy', + 'stability', + 'stabilize', + 'stable', + 'stack', + 'stadium', + 'staff', + 'stage', + 'staging', + 'stagnant', + 'stagnate', + 'stainable', + 'stained', + 'staining', + 'stainless', + 'stalemate', + 'staleness', + 'stalling', + 'stallion', + 'stamina', + 'stammer', + 'stamp', + 'stand', + 'stank', + 'staple', + 'stapling', + 'starboard', + 'starch', + 'stardom', + 'stardust', + 'starfish', + 'stargazer', + 'staring', + 'stark', + 'starless', + 'starlet', + 'starlight', + 'starlit', + 'starring', + 'starry', + 'starship', + 'starter', + 'starting', + 'startle', + 'startling', + 'startup', + 'starved', + 'starving', + 'stash', + 'state', + 'static', + 'statistic', + 'statue', + 'stature', + 'status', + 'statute', + 'statutory', + 'staunch', + 'stays', + 'steadfast', + 'steadier', + 'steadily', + 'steadying', + 'steam', + 'steed', + 'steep', + 'steerable', + 'steering', + 'steersman', + 'stegosaur', + 'stellar', + 'stem', + 'stench', + 'stencil', + 'step', + 'stereo', + 'sterile', + 'sterility', + 'sterilize', + 'sterling', + 'sternness', + 'sternum', + 'stew', + 'stick', + 'stiffen', + 'stiffly', + 'stiffness', + 'stifle', + 'stifling', + 'stillness', + 'stilt', + 'stimulant', + 'stimulate', + 'stimuli', + 'stimulus', + 'stinger', + 'stingily', + 'stinging', + 'stingray', + 'stingy', + 'stinking', + 'stinky', + 'stipend', + 'stipulate', + 'stir', + 'stitch', + 'stock', + 'stoic', + 'stoke', + 'stole', + 'stomp', + 'stonewall', + 'stoneware', + 'stonework', + 'stoning', + 'stony', + 'stood', + 'stooge', + 'stool', + 'stoop', + 'stoplight', + 'stoppable', + 'stoppage', + 'stopped', + 'stopper', + 'stopping', + 'stopwatch', + 'storable', + 'storage', + 'storeroom', + 'storewide', + 'storm', + 'stout', + 'stove', + 'stowaway', + 'stowing', + 'straddle', + 'straggler', + 'strained', + 'strainer', + 'straining', + 'strangely', + 'stranger', + 'strangle', + 'strategic', + 'strategy', + 'stratus', + 'straw', + 'stray', + 'streak', + 'stream', + 'street', + 'strength', + 'strenuous', + 'strep', + 'stress', + 'stretch', + 'strewn', + 'stricken', + 'strict', + 'stride', + 'strife', + 'strike', + 'striking', + 'strive', + 'striving', + 'strobe', + 'strode', + 'stroller', + 'strongbox', + 'strongly', + 'strongman', + 'struck', + 'structure', + 'strudel', + 'struggle', + 'strum', + 'strung', + 'strut', + 'stubbed', + 'stubble', + 'stubbly', + 'stubborn', + 'stucco', + 'stuck', + 'student', + 'studied', + 'studio', + 'study', + 'stuffed', + 'stuffing', + 'stuffy', + 'stumble', + 'stumbling', + 'stump', + 'stung', + 'stunned', + 'stunner', + 'stunning', + 'stunt', + 'stupor', + 'sturdily', + 'sturdy', + 'styling', + 'stylishly', + 'stylist', + 'stylized', + 'stylus', + 'suave', + 'subarctic', + 'subatomic', + 'subdivide', + 'subdued', + 'subduing', + 'subfloor', + 'subgroup', + 'subheader', + 'subject', + 'sublease', + 'sublet', + 'sublevel', + 'sublime', + 'submarine', + 'submerge', + 'submersed', + 'submitter', + 'subpanel', + 'subpar', + 'subplot', + 'subprime', + 'subscribe', + 'subscript', + 'subsector', + 'subside', + 'subsiding', + 'subsidize', + 'subsidy', + 'subsoil', + 'subsonic', + 'substance', + 'subsystem', + 'subtext', + 'subtitle', + 'subtly', + 'subtotal', + 'subtract', + 'subtype', + 'suburb', + 'subway', + 'subwoofer', + 'subzero', + 'succulent', + 'such', + 'suction', + 'sudden', + 'sudoku', + 'suds', + 'sufferer', + 'suffering', + 'suffice', + 'suffix', + 'suffocate', + 'suffrage', + 'sugar', + 'suggest', + 'suing', + 'suitable', + 'suitably', + 'suitcase', + 'suitor', + 'sulfate', + 'sulfide', + 'sulfite', + 'sulfur', + 'sulk', + 'sullen', + 'sulphate', + 'sulphuric', + 'sultry', + 'superbowl', + 'superglue', + 'superhero', + 'superior', + 'superjet', + 'superman', + 'supermom', + 'supernova', + 'supervise', + 'supper', + 'supplier', + 'supply', + 'support', + 'supremacy', + 'supreme', + 'surcharge', + 'surely', + 'sureness', + 'surface', + 'surfacing', + 'surfboard', + 'surfer', + 'surgery', + 'surgical', + 'surging', + 'surname', + 'surpass', + 'surplus', + 'surprise', + 'surreal', + 'surrender', + 'surrogate', + 'surround', + 'survey', + 'survival', + 'survive', + 'surviving', + 'survivor', + 'sushi', + 'suspect', + 'suspend', + 'suspense', + 'sustained', + 'sustainer', + 'swab', + 'swaddling', + 'swagger', + 'swampland', + 'swan', + 'swapping', + 'swarm', + 'sway', + 'swear', + 'sweat', + 'sweep', + 'swell', + 'swept', + 'swerve', + 'swifter', + 'swiftly', + 'swiftness', + 'swimmable', + 'swimmer', + 'swimming', + 'swimsuit', + 'swimwear', + 'swinger', + 'swinging', + 'swipe', + 'swirl', + 'switch', + 'swivel', + 'swizzle', + 'swooned', + 'swoop', + 'swoosh', + 'swore', + 'sworn', + 'swung', + 'sycamore', + 'sympathy', + 'symphonic', + 'symphony', + 'symptom', + 'synapse', + 'syndrome', + 'synergy', + 'synopses', + 'synopsis', + 'synthesis', + 'synthetic', + 'syrup', + 'system', + 't-shirt', + 'tabasco', + 'tabby', + 'tableful', + 'tables', + 'tablet', + 'tableware', + 'tabloid', + 'tackiness', + 'tacking', + 'tackle', + 'tackling', + 'tacky', + 'taco', + 'tactful', + 'tactical', + 'tactics', + 'tactile', + 'tactless', + 'tadpole', + 'taekwondo', + 'tag', + 'tainted', + 'take', + 'taking', + 'talcum', + 'talisman', + 'tall', + 'talon', + 'tamale', + 'tameness', + 'tamer', + 'tamper', + 'tank', + 'tanned', + 'tannery', + 'tanning', + 'tantrum', + 'tapeless', + 'tapered', + 'tapering', + 'tapestry', + 'tapioca', + 'tapping', + 'taps', + 'tarantula', + 'target', + 'tarmac', + 'tarnish', + 'tarot', + 'tartar', + 'tartly', + 'tartness', + 'task', + 'tassel', + 'taste', + 'tastiness', + 'tasting', + 'tasty', + 'tattered', + 'tattle', + 'tattling', + 'tattoo', + 'taunt', + 'tavern', + 'thank', + 'that', + 'thaw', + 'theater', + 'theatrics', + 'thee', + 'theft', + 'theme', + 'theology', + 'theorize', + 'thermal', + 'thermos', + 'thesaurus', + 'these', + 'thesis', + 'thespian', + 'thicken', + 'thicket', + 'thickness', + 'thieving', + 'thievish', + 'thigh', + 'thimble', + 'thing', + 'think', + 'thinly', + 'thinner', + 'thinness', + 'thinning', + 'thirstily', + 'thirsting', + 'thirsty', + 'thirteen', + 'thirty', + 'thong', + 'thorn', + 'those', + 'thousand', + 'thrash', + 'thread', + 'threaten', + 'threefold', + 'thrift', + 'thrill', + 'thrive', + 'thriving', + 'throat', + 'throbbing', + 'throng', + 'throttle', + 'throwaway', + 'throwback', + 'thrower', + 'throwing', + 'thud', + 'thumb', + 'thumping', + 'thursday', + 'thus', + 'thwarting', + 'thyself', + 'tiara', + 'tibia', + 'tidal', + 'tidbit', + 'tidiness', + 'tidings', + 'tidy', + 'tiger', + 'tighten', + 'tightly', + 'tightness', + 'tightrope', + 'tightwad', + 'tigress', + 'tile', + 'tiling', + 'till', + 'tilt', + 'timid', + 'timing', + 'timothy', + 'tinderbox', + 'tinfoil', + 'tingle', + 'tingling', + 'tingly', + 'tinker', + 'tinkling', + 'tinsel', + 'tinsmith', + 'tint', + 'tinwork', + 'tiny', + 'tipoff', + 'tipped', + 'tipper', + 'tipping', + 'tiptoeing', + 'tiptop', + 'tiring', + 'tissue', + 'trace', + 'tracing', + 'track', + 'traction', + 'tractor', + 'trade', + 'trading', + 'tradition', + 'traffic', + 'tragedy', + 'trailing', + 'trailside', + 'train', + 'traitor', + 'trance', + 'tranquil', + 'transfer', + 'transform', + 'translate', + 'transpire', + 'transport', + 'transpose', + 'trapdoor', + 'trapeze', + 'trapezoid', + 'trapped', + 'trapper', + 'trapping', + 'traps', + 'trash', + 'travel', + 'traverse', + 'travesty', + 'tray', + 'treachery', + 'treading', + 'treadmill', + 'treason', + 'treat', + 'treble', + 'tree', + 'trekker', + 'tremble', + 'trembling', + 'tremor', + 'trench', + 'trend', + 'trespass', + 'triage', + 'trial', + 'triangle', + 'tribesman', + 'tribunal', + 'tribune', + 'tributary', + 'tribute', + 'triceps', + 'trickery', + 'trickily', + 'tricking', + 'trickle', + 'trickster', + 'tricky', + 'tricolor', + 'tricycle', + 'trident', + 'tried', + 'trifle', + 'trifocals', + 'trillion', + 'trilogy', + 'trimester', + 'trimmer', + 'trimming', + 'trimness', + 'trinity', + 'trio', + 'tripod', + 'tripping', + 'triumph', + 'trivial', + 'trodden', + 'trolling', + 'trombone', + 'trophy', + 'tropical', + 'tropics', + 'trouble', + 'troubling', + 'trough', + 'trousers', + 'trout', + 'trowel', + 'truce', + 'truck', + 'truffle', + 'trump', + 'trunks', + 'trustable', + 'trustee', + 'trustful', + 'trusting', + 'trustless', + 'truth', + 'try', + 'tubby', + 'tubeless', + 'tubular', + 'tucking', + 'tuesday', + 'tug', + 'tuition', + 'tulip', + 'tumble', + 'tumbling', + 'tummy', + 'turban', + 'turbine', + 'turbofan', + 'turbojet', + 'turbulent', + 'turf', + 'turkey', + 'turmoil', + 'turret', + 'turtle', + 'tusk', + 'tutor', + 'tutu', + 'tux', + 'tweak', + 'tweed', + 'tweet', + 'tweezers', + 'twelve', + 'twentieth', + 'twenty', + 'twerp', + 'twice', + 'twiddle', + 'twiddling', + 'twig', + 'twilight', + 'twine', + 'twins', + 'twirl', + 'twistable', + 'twisted', + 'twister', + 'twisting', + 'twisty', + 'twitch', + 'twitter', + 'tycoon', + 'tying', + 'tyke', + 'udder', + 'ultimate', + 'ultimatum', + 'ultra', + 'umbilical', + 'umbrella', + 'umpire', + 'unabashed', + 'unable', + 'unadorned', + 'unadvised', + 'unafraid', + 'unaired', + 'unaligned', + 'unaltered', + 'unarmored', + 'unashamed', + 'unaudited', + 'unawake', + 'unaware', + 'unbaked', + 'unbalance', + 'unbeaten', + 'unbend', + 'unbent', + 'unbiased', + 'unbitten', + 'unblended', + 'unblessed', + 'unblock', + 'unbolted', + 'unbounded', + 'unboxed', + 'unbraided', + 'unbridle', + 'unbroken', + 'unbuckled', + 'unbundle', + 'unburned', + 'unbutton', + 'uncanny', + 'uncapped', + 'uncaring', + 'uncertain', + 'unchain', + 'unchanged', + 'uncharted', + 'uncheck', + 'uncivil', + 'unclad', + 'unclaimed', + 'unclamped', + 'unclasp', + 'uncle', + 'unclip', + 'uncloak', + 'unclog', + 'unclothed', + 'uncoated', + 'uncoiled', + 'uncolored', + 'uncombed', + 'uncommon', + 'uncooked', + 'uncork', + 'uncorrupt', + 'uncounted', + 'uncouple', + 'uncouth', + 'uncover', + 'uncross', + 'uncrown', + 'uncrushed', + 'uncured', + 'uncurious', + 'uncurled', + 'uncut', + 'undamaged', + 'undated', + 'undaunted', + 'undead', + 'undecided', + 'undefined', + 'underage', + 'underarm', + 'undercoat', + 'undercook', + 'undercut', + 'underdog', + 'underdone', + 'underfed', + 'underfeed', + 'underfoot', + 'undergo', + 'undergrad', + 'underhand', + 'underline', + 'underling', + 'undermine', + 'undermost', + 'underpaid', + 'underpass', + 'underpay', + 'underrate', + 'undertake', + 'undertone', + 'undertook', + 'undertow', + 'underuse', + 'underwear', + 'underwent', + 'underwire', + 'undesired', + 'undiluted', + 'undivided', + 'undocked', + 'undoing', + 'undone', + 'undrafted', + 'undress', + 'undrilled', + 'undusted', + 'undying', + 'unearned', + 'unearth', + 'unease', + 'uneasily', + 'uneasy', + 'uneatable', + 'uneaten', + 'unedited', + 'unelected', + 'unending', + 'unengaged', + 'unenvied', + 'unequal', + 'unethical', + 'uneven', + 'unexpired', + 'unexposed', + 'unfailing', + 'unfair', + 'unfasten', + 'unfazed', + 'unfeeling', + 'unfiled', + 'unfilled', + 'unfitted', + 'unfitting', + 'unfixable', + 'unfixed', + 'unflawed', + 'unfocused', + 'unfold', + 'unfounded', + 'unframed', + 'unfreeze', + 'unfrosted', + 'unfrozen', + 'unfunded', + 'unglazed', + 'ungloved', + 'unglue', + 'ungodly', + 'ungraded', + 'ungreased', + 'unguarded', + 'unguided', + 'unhappily', + 'unhappy', + 'unharmed', + 'unhealthy', + 'unheard', + 'unhearing', + 'unheated', + 'unhelpful', + 'unhidden', + 'unhinge', + 'unhitched', + 'unholy', + 'unhook', + 'unicorn', + 'unicycle', + 'unified', + 'unifier', + 'uniformed', + 'uniformly', + 'unify', + 'unimpeded', + 'uninjured', + 'uninstall', + 'uninsured', + 'uninvited', + 'union', + 'uniquely', + 'unisexual', + 'unison', + 'unissued', + 'unit', + 'universal', + 'universe', + 'unjustly', + 'unkempt', + 'unkind', + 'unknotted', + 'unknowing', + 'unknown', + 'unlaced', + 'unlatch', + 'unlawful', + 'unleaded', + 'unlearned', + 'unleash', + 'unless', + 'unleveled', + 'unlighted', + 'unlikable', + 'unlimited', + 'unlined', + 'unlinked', + 'unlisted', + 'unlit', + 'unlivable', + 'unloaded', + 'unloader', + 'unlocked', + 'unlocking', + 'unlovable', + 'unloved', + 'unlovely', + 'unloving', + 'unluckily', + 'unlucky', + 'unmade', + 'unmanaged', + 'unmanned', + 'unmapped', + 'unmarked', + 'unmasked', + 'unmasking', + 'unmatched', + 'unmindful', + 'unmixable', + 'unmixed', + 'unmolded', + 'unmoral', + 'unmovable', + 'unmoved', + 'unmoving', + 'unnamable', + 'unnamed', + 'unnatural', + 'unneeded', + 'unnerve', + 'unnerving', + 'unnoticed', + 'unopened', + 'unopposed', + 'unpack', + 'unpadded', + 'unpaid', + 'unpainted', + 'unpaired', + 'unpaved', + 'unpeeled', + 'unpicked', + 'unpiloted', + 'unpinned', + 'unplanned', + 'unplanted', + 'unpleased', + 'unpledged', + 'unplowed', + 'unplug', + 'unpopular', + 'unproven', + 'unquote', + 'unranked', + 'unrated', + 'unraveled', + 'unreached', + 'unread', + 'unreal', + 'unreeling', + 'unrefined', + 'unrelated', + 'unrented', + 'unrest', + 'unretired', + 'unrevised', + 'unrigged', + 'unripe', + 'unrivaled', + 'unroasted', + 'unrobed', + 'unroll', + 'unruffled', + 'unruly', + 'unrushed', + 'unsaddle', + 'unsafe', + 'unsaid', + 'unsalted', + 'unsaved', + 'unsavory', + 'unscathed', + 'unscented', + 'unscrew', + 'unsealed', + 'unseated', + 'unsecured', + 'unseeing', + 'unseemly', + 'unseen', + 'unselect', + 'unselfish', + 'unsent', + 'unsettled', + 'unshackle', + 'unshaken', + 'unshaved', + 'unshaven', + 'unsheathe', + 'unshipped', + 'unsightly', + 'unsigned', + 'unskilled', + 'unsliced', + 'unsmooth', + 'unsnap', + 'unsocial', + 'unsoiled', + 'unsold', + 'unsolved', + 'unsorted', + 'unspoiled', + 'unspoken', + 'unstable', + 'unstaffed', + 'unstamped', + 'unsteady', + 'unsterile', + 'unstirred', + 'unstitch', + 'unstopped', + 'unstuck', + 'unstuffed', + 'unstylish', + 'unsubtle', + 'unsubtly', + 'unsuited', + 'unsure', + 'unsworn', + 'untagged', + 'untainted', + 'untaken', + 'untamed', + 'untangled', + 'untapped', + 'untaxed', + 'unthawed', + 'unthread', + 'untidy', + 'untie', + 'until', + 'untimed', + 'untimely', + 'untitled', + 'untoasted', + 'untold', + 'untouched', + 'untracked', + 'untrained', + 'untreated', + 'untried', + 'untrimmed', + 'untrue', + 'untruth', + 'unturned', + 'untwist', + 'untying', + 'unusable', + 'unused', + 'unusual', + 'unvalued', + 'unvaried', + 'unvarying', + 'unveiled', + 'unveiling', + 'unvented', + 'unviable', + 'unvisited', + 'unvocal', + 'unwanted', + 'unwarlike', + 'unwary', + 'unwashed', + 'unwatched', + 'unweave', + 'unwed', + 'unwelcome', + 'unwell', + 'unwieldy', + 'unwilling', + 'unwind', + 'unwired', + 'unwitting', + 'unwomanly', + 'unworldly', + 'unworn', + 'unworried', + 'unworthy', + 'unwound', + 'unwoven', + 'unwrapped', + 'unwritten', + 'unzip', + 'upbeat', + 'upchuck', + 'upcoming', + 'upcountry', + 'update', + 'upfront', + 'upgrade', + 'upheaval', + 'upheld', + 'uphill', + 'uphold', + 'uplifted', + 'uplifting', + 'upload', + 'upon', + 'upper', + 'upright', + 'uprising', + 'upriver', + 'uproar', + 'uproot', + 'upscale', + 'upside', + 'upstage', + 'upstairs', + 'upstart', + 'upstate', + 'upstream', + 'upstroke', + 'upswing', + 'uptake', + 'uptight', + 'uptown', + 'upturned', + 'upward', + 'upwind', + 'uranium', + 'urban', + 'urchin', + 'urethane', + 'urgency', + 'urgent', + 'urging', + 'urologist', + 'urology', + 'usable', + 'usage', + 'useable', + 'used', + 'uselessly', + 'user', + 'usher', + 'usual', + 'utensil', + 'utility', + 'utilize', + 'utmost', + 'utopia', + 'utter', + 'vacancy', + 'vacant', + 'vacate', + 'vacation', + 'vagabond', + 'vagrancy', + 'vagrantly', + 'vaguely', + 'vagueness', + 'valiant', + 'valid', + 'valium', + 'valley', + 'valuables', + 'value', + 'vanilla', + 'vanish', + 'vanity', + 'vanquish', + 'vantage', + 'vaporizer', + 'variable', + 'variably', + 'varied', + 'variety', + 'various', + 'varmint', + 'varnish', + 'varsity', + 'varying', + 'vascular', + 'vaseline', + 'vastly', + 'vastness', + 'veal', + 'vegan', + 'veggie', + 'vehicular', + 'velcro', + 'velocity', + 'velvet', + 'vendetta', + 'vending', + 'vendor', + 'veneering', + 'vengeful', + 'venomous', + 'ventricle', + 'venture', + 'venue', + 'venus', + 'verbalize', + 'verbally', + 'verbose', + 'verdict', + 'verify', + 'verse', + 'version', + 'versus', + 'vertebrae', + 'vertical', + 'vertigo', + 'very', + 'vessel', + 'vest', + 'veteran', + 'veto', + 'vexingly', + 'viability', + 'viable', + 'vibes', + 'vice', + 'vicinity', + 'victory', + 'video', + 'viewable', + 'viewer', + 'viewing', + 'viewless', + 'viewpoint', + 'vigorous', + 'village', + 'villain', + 'vindicate', + 'vineyard', + 'vintage', + 'violate', + 'violation', + 'violator', + 'violet', + 'violin', + 'viper', + 'viral', + 'virtual', + 'virtuous', + 'virus', + 'visa', + 'viscosity', + 'viscous', + 'viselike', + 'visible', + 'visibly', + 'vision', + 'visiting', + 'visitor', + 'visor', + 'vista', + 'vitality', + 'vitalize', + 'vitally', + 'vitamins', + 'vivacious', + 'vividly', + 'vividness', + 'vixen', + 'vocalist', + 'vocalize', + 'vocally', + 'vocation', + 'voice', + 'voicing', + 'void', + 'volatile', + 'volley', + 'voltage', + 'volumes', + 'voter', + 'voting', + 'voucher', + 'vowed', + 'vowel', + 'voyage', + 'wackiness', + 'wad', + 'wafer', + 'waffle', + 'waged', + 'wager', + 'wages', + 'waggle', + 'wagon', + 'wake', + 'waking', + 'walk', + 'walmart', + 'walnut', + 'walrus', + 'waltz', + 'wand', + 'wannabe', + 'wanted', + 'wanting', + 'wasabi', + 'washable', + 'washbasin', + 'washboard', + 'washbowl', + 'washcloth', + 'washday', + 'washed', + 'washer', + 'washhouse', + 'washing', + 'washout', + 'washroom', + 'washstand', + 'washtub', + 'wasp', + 'wasting', + 'watch', + 'water', + 'waviness', + 'waving', + 'wavy', + 'whacking', + 'whacky', + 'wham', + 'wharf', + 'wheat', + 'whenever', + 'whiff', + 'whimsical', + 'whinny', + 'whiny', + 'whisking', + 'whoever', + 'whole', + 'whomever', + 'whoopee', + 'whooping', + 'whoops', + 'why', + 'wick', + 'widely', + 'widen', + 'widget', + 'widow', + 'width', + 'wieldable', + 'wielder', + 'wife', + 'wifi', + 'wikipedia', + 'wildcard', + 'wildcat', + 'wilder', + 'wildfire', + 'wildfowl', + 'wildland', + 'wildlife', + 'wildly', + 'wildness', + 'willed', + 'willfully', + 'willing', + 'willow', + 'willpower', + 'wilt', + 'wimp', + 'wince', + 'wincing', + 'wind', + 'wing', + 'winking', + 'winner', + 'winnings', + 'winter', + 'wipe', + 'wired', + 'wireless', + 'wiring', + 'wiry', + 'wisdom', + 'wise', + 'wish', + 'wisplike', + 'wispy', + 'wistful', + 'wizard', + 'wobble', + 'wobbling', + 'wobbly', + 'wok', + 'wolf', + 'wolverine', + 'womanhood', + 'womankind', + 'womanless', + 'womanlike', + 'womanly', + 'womb', + 'woof', + 'wooing', + 'wool', + 'woozy', + 'word', + 'work', + 'worried', + 'worrier', + 'worrisome', + 'worry', + 'worsening', + 'worshiper', + 'worst', + 'wound', + 'woven', + 'wow', + 'wrangle', + 'wrath', + 'wreath', + 'wreckage', + 'wrecker', + 'wrecking', + 'wrench', + 'wriggle', + 'wriggly', + 'wrinkle', + 'wrinkly', + 'wrist', + 'writing', + 'written', + 'wrongdoer', + 'wronged', + 'wrongful', + 'wrongly', + 'wrongness', + 'wrought', + 'xbox', + 'xerox', + 'yahoo', + 'yam', + 'yanking', + 'yapping', + 'yard', + 'yarn', + 'yeah', + 'yearbook', + 'yearling', + 'yearly', + 'yearning', + 'yeast', + 'yelling', + 'yelp', + 'yen', + 'yesterday', + 'yiddish', + 'yield', + 'yin', + 'yippee', + 'yo-yo', + 'yodel', + 'yoga', + 'yogurt', + 'yonder', + 'yoyo', + 'yummy', + 'zap', + 'zealous', + 'zebra', + 'zen', + 'zeppelin', + 'zero', + 'zestfully', + 'zesty', + 'zigzagged', + 'zipfile', + 'zipping', + 'zippy', + 'zips', + 'zit', + 'zodiac', + 'zombie', + 'zone', + 'zoning', + 'zookeeper', + 'zoologist', + 'zoology', + 'zoom' +] + +const wordlistLength = wordlist.length; \ No newline at end of file