Compare commits
No commits in common. "d8f013e81d0ebba14b9f407485fd93cdd59de4a9" and "f5e46c24a40953916e081cbfb455a370c1bba150" have entirely different histories.
d8f013e81d
...
f5e46c24a4
2
.gitattributes
vendored
@ -5,4 +5,4 @@
|
||||
|
||||
# Ignore binary files >:(
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpg binary
|
38
.github/workflows/node-ci.yml
vendored
@ -1,38 +0,0 @@
|
||||
name: Node.js CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: Lint / Build / Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Files
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js v${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint Source Files
|
||||
run: npm run lint
|
||||
|
||||
- name: Verify Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run Tests
|
||||
run: npm run test
|
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/node_modules/
|
||||
.env
|
||||
/prod/
|
||||
/coverage/
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"useTabs": false,
|
||||
"singleQuote": false
|
||||
}
|
6
.vscode/settings.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.validate": ["typescript"]
|
||||
}
|
3
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Code of Conduct
|
||||
|
||||
Our Code of Conduct can be found here: https://docs.nhcarrigan.com/#/coc
|
3
CONTRIBUTING.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Contributing
|
||||
|
||||
Our contributing guidelines can be found here: https://docs.nhcarrigan.com/#/contributing
|
5
LICENSE.md
Normal file
@ -0,0 +1,5 @@
|
||||
# License
|
||||
|
||||
This software is licensed under our [global software license](https://docs.nhcarrigan.com/#/license).
|
||||
|
||||
Copyright held by Naomi Carrigan.
|
3
PRIVACY.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Privacy Policy
|
||||
|
||||
Our privacy policy can be found here: https://docs.nhcarrigan.com/#/privacy
|
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# New Repository Template
|
||||
|
||||
This template contains all of our basic files for a new GitHub repository. There is also a handy workflow that will create an issue on a new repository made from this template, with a checklist for the steps we usually take in setting up a new repository.
|
||||
|
||||
If you're starting a Node.JS project with TypeScript, we have a [specific template](https://github.com/naomi-lgbt/nodejs-typescript-template) for that purpose.
|
||||
|
||||
## Readme
|
||||
|
||||
Delete all of the above text (including this line), and uncomment the below text to use our standard readme template.
|
||||
|
||||
<!-- # Project Name
|
||||
|
||||
Project Description
|
||||
|
||||
## Live Version
|
||||
|
||||
This page is currently deployed. [View the live website.]
|
||||
|
||||
## Feedback and Bugs
|
||||
|
||||
If you have feedback or a bug report, please feel free to open a GitHub issue!
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute to the project, you may create a Pull Request containing your proposed changes and we will review it as soon as we are able! Please review our [contributing guidelines](CONTRIBUTING.md) first.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Before interacting with our community, please read our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under our [global software license](https://docs.nhcarrigan.com/#/license).
|
||||
|
||||
Copyright held by Naomi Carrigan.
|
||||
|
||||
## Contact
|
||||
|
||||
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`. -->
|
@ -1,6 +0,0 @@
|
||||
This is a bot that does Tingle things.
|
||||
|
||||
## Dice Roller
|
||||
* **^roll \< XdY \>** Roll dice of Y sides X times
|
||||
* ^roll 1d20
|
||||
* ^roll 3d4
|
3
SECURITY.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Security Policy
|
||||
|
||||
Our security policy can be found here: https://docs.nhcarrigan.com/#/security
|
3
TERMS.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Terms of Service
|
||||
|
||||
Our Terms of Service can be found here: https://docs.nhcarrigan.com/#/terms
|
@ -1,13 +0,0 @@
|
||||
import NaomisConifg from "@nhcarrigan/eslint-config";
|
||||
|
||||
export default [
|
||||
...NaomisConifg,
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"max-lines": "off",
|
||||
"max-lines-per-function": "off",
|
||||
"max-statements": "off",
|
||||
}
|
||||
}
|
||||
]
|
37
package.json
@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "tinglebot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "eslint src test --max-warnings 0",
|
||||
"start": "op run --env-file=./prod.env --no-masking -- node prod/index.js",
|
||||
"test": "vitest run --coverage"
|
||||
},
|
||||
"author": "",
|
||||
"license": "See license in LICENSE.md",
|
||||
"engines": {
|
||||
"node": "22",
|
||||
"pnpm": "9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhcarrigan/eslint-config": "5.0.0-rc1",
|
||||
"@nhcarrigan/typescript-config": "4.0.0",
|
||||
"@types/node-schedule": "2.1.7",
|
||||
"@types/uuid": "10.0.0",
|
||||
"@vitest/coverage-istanbul": "2.1.1",
|
||||
"eslint": "9.11.1",
|
||||
"typescript": "5.6.2",
|
||||
"vitest": "2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "14.16.2",
|
||||
"fastify": "5.0.0",
|
||||
"node-schedule": "2.1.1",
|
||||
"sharp": "0.33.5",
|
||||
"uuid": "10.0.0",
|
||||
"winston": "3.14.2"
|
||||
}
|
||||
}
|
5360
pnpm-lock.yaml
generated
6
prod.env
@ -1,6 +0,0 @@
|
||||
DISCORD_TOKEN="op://Environment Variables - Naomi/Tingle Bot/token"
|
||||
HOME_GUILD_ID="op://Environment Variables - Naomi/Tingle Bot/home_guild"
|
||||
RUDANIA_ID="op://Environment Variables - Naomi/Tingle Bot/rudania_channel"
|
||||
INARIKO_ID="op://Environment Variables - Naomi/Tingle Bot/inariko_channel"
|
||||
VHINTL_ID="op://Environment Variables - Naomi/Tingle Bot/vhintl_channel"
|
||||
DEBUG_HOOK="op://Environment Variables - Naomi/Tingle Bot/debug_hook"
|
Before Width: | Height: | Size: 275 KiB |
Before Width: | Height: | Size: 251 KiB |
Before Width: | Height: | Size: 196 KiB |
Before Width: | Height: | Size: 292 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 298 KiB |
Before Width: | Height: | Size: 266 KiB |
Before Width: | Height: | Size: 240 KiB |
Before Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 211 KiB |
Before Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 330 KiB |
Before Width: | Height: | Size: 315 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 28 KiB |
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { forecast } from "./forecast.js";
|
||||
|
||||
/**
|
||||
* TODO: Migrate this to an automated import.
|
||||
*/
|
||||
export const commandList = [ forecast ];
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import { forecastChoices } from "../config/forecastChoices.js";
|
||||
import { generateWeatherEmbed } from "../modules/generateWeatherEmbed.js";
|
||||
import { getWeatherForecast } from "../modules/getWeatherForecast.js";
|
||||
import { errorHandler } from "../utils/errorHandler.js";
|
||||
import type { Command } from "../interfaces/commands/command.js";
|
||||
import type { RegionName } from "../interfaces/weather/names/regionName.js";
|
||||
|
||||
const isRegionName = (region: string): region is RegionName => {
|
||||
return [ "Rudania", "Inariko", "Vhintl" ].includes(region);
|
||||
};
|
||||
|
||||
export const forecast: Command = {
|
||||
data: new SlashCommandBuilder().
|
||||
setName("forecast").
|
||||
setDescription("Get the weather forecast for a specific region.").
|
||||
addStringOption((option) => {
|
||||
return option.
|
||||
setName("region").
|
||||
setDescription("The region to get a forecast for.").
|
||||
setRequired(true).
|
||||
addChoices(forecastChoices);
|
||||
}),
|
||||
run: async(interaction, cache) => {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
const region = interaction.options.getString("region", true);
|
||||
if (!isRegionName(region)) {
|
||||
await interaction.editReply({ content: `Invalid region: ${region}` });
|
||||
return;
|
||||
}
|
||||
const forecastResult = cache[region] ?? getWeatherForecast(region);
|
||||
const response = await generateWeatherEmbed(forecastResult);
|
||||
await interaction.editReply(response);
|
||||
} catch (error) {
|
||||
const response = await errorHandler(error, "forecast command");
|
||||
await interaction.editReply(response);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { RegionName } from "../interfaces/weather/names/regionName.js";
|
||||
import type { APIApplicationCommandOptionChoice } from "discord.js";
|
||||
|
||||
export const forecastChoices:
|
||||
Array<APIApplicationCommandOptionChoice<RegionName>> = [
|
||||
{ name: "Rudania", value: "Rudania" },
|
||||
{ name: "Inariko", value: "Inariko" },
|
||||
{ name: "Vhintl", value: "Vhintl" },
|
||||
];
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { GatewayIntentBits } from "discord.js";
|
||||
|
||||
export const intentOptions = [ GatewayIntentBits.Guilds ];
|
@ -1,168 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { Precipitation } from "../../interfaces/weather/precipitation.js";
|
||||
|
||||
export const precipitations: Array<Precipitation> = [
|
||||
{
|
||||
emote: "❄️",
|
||||
name: "Blizzard",
|
||||
temps: [ "Cold", "Freezing", "Frigid" ],
|
||||
winds: [ "Strong", "Gale", "Storm", "Hurricane" ],
|
||||
},
|
||||
{
|
||||
emote: "🔥",
|
||||
name: "Cinder Storm",
|
||||
temps: "any",
|
||||
winds: [ "Strong", "Gale", "Storm", "Hurricane" ],
|
||||
},
|
||||
{
|
||||
emote: "☁️",
|
||||
name: "Cloudy",
|
||||
temps: "any",
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌫️",
|
||||
name: "Fog",
|
||||
temps: "any",
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "☁️🧊",
|
||||
name: "Hail",
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
emote: "🌡️⚡",
|
||||
name: "Heat Lightning",
|
||||
temps: [ "Warm", "Hot", "Scorching", "Heat Wave" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
emote: "🌧️",
|
||||
name: "Heavy Rain",
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌨️",
|
||||
name: "Heavy Snow",
|
||||
temps: [ "Chilly", "Cold", "Freezing", "Frigid" ],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "☔",
|
||||
name: "Light Rain",
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌨️",
|
||||
name: "Light Snow",
|
||||
temps: [ "Chilly", "Cold", "Freezing", "Frigid" ],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "⛅",
|
||||
name: "Partly Cloudy",
|
||||
temps: "any",
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌧️",
|
||||
name: "Rain",
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌈",
|
||||
name: "Rainbow",
|
||||
temps: "any",
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "☁️🧊",
|
||||
name: "Sleet",
|
||||
temps: [ "Brisk", "Chilly" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
emote: "🌨️",
|
||||
name: "Snow",
|
||||
temps: [ "Chilly", "Cold", "Freezing", "Frigid" ],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "🌦️",
|
||||
name: "Sun Shower",
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: [ "Gale", "Strong", "Fresh", "Moderate", "Breeze", "Calm" ],
|
||||
},
|
||||
{
|
||||
emote: "☀️",
|
||||
name: "Sunny",
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
emote: "🌨️⚡",
|
||||
name: "Thundersnow",
|
||||
temps: [ "Chilly", "Cold", "Freezing", "Frigid" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
emote: "⛈️",
|
||||
name: "Thunderstorm",
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: "any",
|
||||
},
|
||||
];
|
@ -1,160 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { RegionRestriction }
|
||||
from "../../../interfaces/weather/regions/regionRestriction.js";
|
||||
|
||||
export const inarikoSeasons: Array<RegionRestriction> = [
|
||||
{
|
||||
precipitation: [
|
||||
"Blizzard",
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Heavy Snow",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Sleet",
|
||||
"Snow",
|
||||
"Sunny",
|
||||
"Thundersnow",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Winter",
|
||||
special: [ "Avalanche", "Meteor Shower", "Blight Rain" ],
|
||||
temps: [ "Frigid", "Freezing", "Cold", "Chilly", "Brisk" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Snow",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Spring",
|
||||
special: [
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Chilly", "Brisk", "Cool", "Mild", "Perfect", "Warm" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Summer",
|
||||
special: [
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Mild", "Perfect", "Warm", "Hot", "Scorching" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Blizzard",
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Heavy Snow",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Snow",
|
||||
"Sunny",
|
||||
"Thundersnow",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Fall",
|
||||
special: [
|
||||
"Avalanche",
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Cold", "Chilly", "Brisk", "Cool", "Mild", "Perfect" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
];
|
@ -1,159 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { RegionRestriction }
|
||||
from "../../../interfaces/weather/regions/regionRestriction.js";
|
||||
|
||||
export const rudaniaSeasons: Array<RegionRestriction> = [
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Snow",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Winter",
|
||||
special: [ "Flood", "Meteor Shower", "Rock Slide", "Blight Rain" ],
|
||||
temps: [ "Cold", "Chilly", "Brisk", "Cool", "Mild", "Perfect" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cinder Storm",
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Spring",
|
||||
special: [
|
||||
"Drought",
|
||||
"Fairy Circle",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Brisk", "Cool", "Mild", "Perfect", "Warm", "Hot", "Scorching" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cinder Storm",
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Summer",
|
||||
special: [
|
||||
"Drought",
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Rock Slide",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Mild", "Perfect", "Warm", "Hot", "Scorching", "Heat Wave" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cinder Storm",
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Fall",
|
||||
special: [
|
||||
"Drought",
|
||||
"Fairy Circle",
|
||||
"Flower Bloom",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Rock Slide",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Cold", "Chilly", "Brisk", "Cool", "Mild", "Perfect" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
];
|
@ -1,153 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { RegionRestriction }
|
||||
from "../../../interfaces/weather/regions/regionRestriction.js";
|
||||
|
||||
export const vhintlSeasons: Array<RegionRestriction> = [
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Sleet",
|
||||
"Snow",
|
||||
"Sunny",
|
||||
"Thundersnow",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Winter",
|
||||
special: [ "Fairy Circle", "Meteor Shower", "Blight Rain" ],
|
||||
temps: [ "Cold", "Chilly", "Brisk", "Cool", "Mild" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Spring",
|
||||
special: [
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Chilly", "Brisk", "Cool", "Mild", "Perfect", "Warm", "Hot" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heat Lightning",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sun Shower",
|
||||
"Sunny",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Summer",
|
||||
special: [
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Mild", "Perfect", "Warm", "Hot", "Scorching", "Heat Wave" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
{
|
||||
precipitation: [
|
||||
"Cloudy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Heavy Rain",
|
||||
"Light Rain",
|
||||
"Light Snow",
|
||||
"Partly Cloudy",
|
||||
"Rain",
|
||||
"Rainbow",
|
||||
"Sleet",
|
||||
"Sunny",
|
||||
"Thundersnow",
|
||||
"Thunderstorm",
|
||||
],
|
||||
season: "Fall",
|
||||
special: [
|
||||
"Fairy Circle",
|
||||
"Flood",
|
||||
"Flower Bloom",
|
||||
"Jubilee",
|
||||
"Meteor Shower",
|
||||
"Muggy",
|
||||
"Blight Rain",
|
||||
],
|
||||
temps: [ "Cold", "Chilly", "Brisk", "Cool", "Mild", "Perfect" ],
|
||||
wind: [
|
||||
"Calm",
|
||||
"Breeze",
|
||||
"Moderate",
|
||||
"Fresh",
|
||||
"Strong",
|
||||
"Gale",
|
||||
"Storm",
|
||||
"Hurricane",
|
||||
],
|
||||
},
|
||||
];
|
@ -1,129 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { Special } from "../../interfaces/weather/special.js";
|
||||
|
||||
export const specials: Array<Special> = [
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"There has been an avalanche and some roads are blocked! Travel to and from this village today is impossible.",
|
||||
emote: "🏔️",
|
||||
name: "Avalanche",
|
||||
precipitations: [ "Snow" ],
|
||||
temps: [ "Chilly", "Cold", "Freezing", "Frigid" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Blighted rain falls from the sky, staining the ground and creating sickly maroon-tinged puddles... if you roll for gathering today you must also `/tableroll blightrain` to see if you get infected! If you miss doing this roll with your gather, blighting will be automatic.",
|
||||
emote: "🌧️🧿",
|
||||
name: "Blight Rain",
|
||||
precipitations: [ "Rain" ],
|
||||
temps: [
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"A drought has dried up the smaller vegetation surrounding the village... any plants or mushrooms rolled today are found dead and will not be gathered.",
|
||||
emote: "🌵",
|
||||
name: "Drought",
|
||||
precipitations: [ "Sunny" ],
|
||||
temps: [ "Scorching", "Heat Wave" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Fairy circles have popped up all over Hyrule! All residents and visitors may use `/tableroll fairycircle` to gather mushrooms today!",
|
||||
emote: "🍄",
|
||||
name: "Fairy Circle",
|
||||
precipitations: "any",
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"There has been a Flood! Travelling to and from this village is impossible today due to the danger.",
|
||||
emote: "🌊",
|
||||
name: "Flood",
|
||||
precipitations: [ "Rain" ],
|
||||
temps: [
|
||||
"Cold",
|
||||
"Chilly",
|
||||
"Brisk",
|
||||
"Cool",
|
||||
"Mild",
|
||||
"Perfect",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"Scorching",
|
||||
"Heat Wave",
|
||||
],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"An overabundance of plants and flowers have been spotted growing in and around the village! All residents and visitors may `/tableroll flowerbloom` to gather today!",
|
||||
emote: "🌼",
|
||||
name: "Flower Bloom",
|
||||
precipitations: "any",
|
||||
temps: [ "Perfect", "Warm", "Hot", "Scorching", "Heat Wave" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Fish are practically jumping out of the water! All residents and visitors may `/tableroll jubilee` to catch some fish!",
|
||||
emote: "🐟",
|
||||
name: "Jubilee",
|
||||
precipitations: "any",
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Shooting starts have been spotted streaking through the sky! Quick, all residents and visitors make a wish and use `/tableroll meteorshower` for a chance to find a star fragment!",
|
||||
emote: "☄️",
|
||||
name: "Meteor Shower",
|
||||
precipitations: [ "Sunny" ],
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Oof! Sure is humid today! Critters are out and about more than usual. All residents and visitors may use `/tableroll muggy` to catch some critters!",
|
||||
emote: "🐛",
|
||||
name: "Muggy",
|
||||
precipitations: [ "Rain", "Fog", "Cloudy" ],
|
||||
temps: [ "Perfect", "Warm", "Hot", "Scorching", "Heat Wave" ],
|
||||
winds: "any",
|
||||
},
|
||||
{
|
||||
description:
|
||||
// eslint-disable-next-line stylistic/max-len -- We can't break this string up.
|
||||
"Oh no there's been a rock slide! Travelling to and from this village is impossible today. All residents and visitors may use `/tableroll rockslide` to help clear the road! You might just find something interesting while you work...",
|
||||
emote: "⛏️",
|
||||
name: "Rock Slide",
|
||||
precipitations: "any",
|
||||
temps: "any",
|
||||
winds: "any",
|
||||
},
|
||||
];
|
@ -1,81 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { Temperature } from "../../interfaces/weather/temperature.js";
|
||||
|
||||
export const temperatures: Array<Temperature> = [
|
||||
{
|
||||
celsius: -18,
|
||||
emote: "🥶",
|
||||
fahrenheit: 0,
|
||||
name: "Frigid",
|
||||
},
|
||||
{
|
||||
celsius: -14,
|
||||
emote: "🐧",
|
||||
fahrenheit: 8,
|
||||
name: "Freezing",
|
||||
},
|
||||
{
|
||||
celsius: -4,
|
||||
emote: "☃️",
|
||||
fahrenheit: 24,
|
||||
name: "Cold",
|
||||
},
|
||||
{
|
||||
celsius: 2,
|
||||
emote: "🧊",
|
||||
fahrenheit: 36,
|
||||
name: "Chilly",
|
||||
},
|
||||
{
|
||||
celsius: 6,
|
||||
emote: "🔷",
|
||||
fahrenheit: 44,
|
||||
name: "Brisk",
|
||||
},
|
||||
{
|
||||
celsius: 11,
|
||||
emote: "🆒",
|
||||
fahrenheit: 52,
|
||||
name: "Cool",
|
||||
},
|
||||
{
|
||||
celsius: 16,
|
||||
emote: "😐",
|
||||
fahrenheit: 61,
|
||||
name: "Mild",
|
||||
},
|
||||
{
|
||||
celsius: 22,
|
||||
emote: "👌",
|
||||
fahrenheit: 72,
|
||||
name: "Perfect",
|
||||
},
|
||||
{
|
||||
celsius: 28,
|
||||
emote: "🌡️",
|
||||
fahrenheit: 24,
|
||||
name: "Warm",
|
||||
},
|
||||
{
|
||||
celsius: 32,
|
||||
emote: "🌶️",
|
||||
fahrenheit: 89,
|
||||
name: "Hot",
|
||||
},
|
||||
{
|
||||
celsius: 36,
|
||||
emote: "🥵",
|
||||
fahrenheit: 97,
|
||||
name: "Scorching",
|
||||
},
|
||||
{
|
||||
celsius: 38,
|
||||
emote: "💯",
|
||||
fahrenheit: 100,
|
||||
name: "Heat Wave",
|
||||
},
|
||||
];
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { Wind } from "../../interfaces/weather/wind.js";
|
||||
|
||||
export const winds: Array<Wind> = [
|
||||
{
|
||||
emote: "😌",
|
||||
highSpeed: 1,
|
||||
lowSpeed: 0,
|
||||
name: "Calm",
|
||||
},
|
||||
{
|
||||
emote: "🎐",
|
||||
highSpeed: 12,
|
||||
lowSpeed: 2,
|
||||
name: "Breeze",
|
||||
},
|
||||
{
|
||||
emote: "🍃",
|
||||
highSpeed: 30,
|
||||
lowSpeed: 13,
|
||||
name: "Moderate",
|
||||
},
|
||||
{
|
||||
emote: "🌬️",
|
||||
highSpeed: 40,
|
||||
lowSpeed: 31,
|
||||
name: "Fresh",
|
||||
},
|
||||
{
|
||||
emote: "💫",
|
||||
highSpeed: 62,
|
||||
lowSpeed: 41,
|
||||
name: "Strong",
|
||||
},
|
||||
{
|
||||
emote: "💨",
|
||||
highSpeed: 87,
|
||||
lowSpeed: 63,
|
||||
name: "Gale",
|
||||
},
|
||||
{
|
||||
emote: "🌀",
|
||||
highSpeed: 117,
|
||||
lowSpeed: 88,
|
||||
name: "Storm",
|
||||
},
|
||||
{
|
||||
emote: "🌪️",
|
||||
highSpeed: 150,
|
||||
lowSpeed: 118,
|
||||
name: "Hurricane",
|
||||
},
|
||||
];
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { onInteraction } from "./handlers/onInteraction.js";
|
||||
import { onReady } from "./handlers/onReady.js";
|
||||
import type { WeatherCache } from "../interfaces/weatherCache.js";
|
||||
import type { Client } from "discord.js";
|
||||
|
||||
/**
|
||||
* Mounts listeners for the Discord gateway events.
|
||||
* @param bot - The bot's discord instance.
|
||||
* @param cache - The cache of weather data.
|
||||
*/
|
||||
export const handleEvents = (bot: Client, cache: WeatherCache): void => {
|
||||
bot.on("ready", async() => {
|
||||
await onReady(bot, cache);
|
||||
});
|
||||
|
||||
bot.on(
|
||||
"interactionCreate",
|
||||
async(interaction) => {
|
||||
await onInteraction(interaction, cache);
|
||||
},
|
||||
);
|
||||
};
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { commandList } from "../../commands/_commandList.js";
|
||||
import type { WeatherCache } from "../../interfaces/weatherCache.js";
|
||||
import type { Interaction } from "discord.js";
|
||||
|
||||
/**
|
||||
* Handles the INTERACTION_CREATE event from Discord. Checks if the interaction is
|
||||
* an application command, and if there is a matching command, and runs it.
|
||||
* @param interaction - The interaction payload from Discord.
|
||||
* @param cache - The cache of weather data.
|
||||
*/
|
||||
export const onInteraction = async(
|
||||
interaction: Interaction,
|
||||
cache: WeatherCache,
|
||||
): Promise<void> => {
|
||||
if (!interaction.isChatInputCommand()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = commandList.find(
|
||||
(command) => {
|
||||
return command.data.name === interaction.commandName;
|
||||
},
|
||||
);
|
||||
|
||||
if (!target) {
|
||||
await interaction.reply(`Command ${interaction.commandName} not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await target.run(interaction, cache);
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { REST, Routes, type Client, WebhookClient } from "discord.js";
|
||||
import { commandList } from "../../commands/_commandList.js";
|
||||
import { loadChannels } from "../../modules/loadChannels.js";
|
||||
import { scheduleForecasts } from "../../modules/scheduleForecasts.js";
|
||||
import { logHandler } from "../../utils/logHandler.js";
|
||||
import type { WeatherCache } from "../../interfaces/weatherCache.js";
|
||||
|
||||
/**
|
||||
* Handler for the READY event from Discord. Logs that the bot is connected,
|
||||
* then registers the guild slash commands.
|
||||
* @param bot - The bot's Discord instance.
|
||||
* @param cache - The cache of weather data.
|
||||
*/
|
||||
export const onReady = async(
|
||||
bot: Client,
|
||||
cache: WeatherCache,
|
||||
): Promise<void> => {
|
||||
const webhook = new WebhookClient({ url: process.env.DEBUG_HOOK as string });
|
||||
|
||||
await webhook.send("Ruu Bot is online!");
|
||||
logHandler.log("info", "Connected to Discord!");
|
||||
|
||||
const rest = new REST({ version: "10" }).setToken(
|
||||
process.env.DISCORD_TOKEN ?? "",
|
||||
);
|
||||
|
||||
const commandData = commandList.map((command) => {
|
||||
return command.data.toJSON();
|
||||
});
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
bot.user?.id ?? "oopsie whoopsie",
|
||||
process.env.HOME_GUILD_ID ?? "",
|
||||
),
|
||||
{ body: commandData },
|
||||
);
|
||||
|
||||
await webhook.send("Registered commands!");
|
||||
|
||||
cache.channels = await loadChannels(bot);
|
||||
|
||||
await webhook.send("Loaded channels!");
|
||||
scheduleForecasts(cache);
|
||||
};
|
28
src/index.ts
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { Client } from "discord.js";
|
||||
import { intentOptions } from "./config/intentOptions.js";
|
||||
import { handleEvents } from "./events/handleEvents.js";
|
||||
import { getWeatherForecast } from "./modules/getWeatherForecast.js";
|
||||
// Import { loadChannels } from "./modules/loadChannels.js";
|
||||
import { instantiateServer } from "./server/serve.js";
|
||||
import { errorHandler } from "./utils/errorHandler.js";
|
||||
import type { WeatherCache } from "./interfaces/weatherCache.js";
|
||||
|
||||
const bot = new Client({ intents: intentOptions });
|
||||
|
||||
const cache: WeatherCache = {
|
||||
Inariko: getWeatherForecast("Inariko"),
|
||||
Rudania: getWeatherForecast("Rudania"),
|
||||
Vhintl: getWeatherForecast("Vhintl"),
|
||||
} as WeatherCache;
|
||||
|
||||
await bot.login(process.env.DISCORD_TOKEN as string).catch(async(error: unknown) => {
|
||||
return await errorHandler(error, "login");
|
||||
});
|
||||
|
||||
handleEvents(bot, cache);
|
||||
await instantiateServer(bot);
|
@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export interface AttachmentData {
|
||||
attachmentString: string;
|
||||
filePath: string;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { WeatherCache } from "../weatherCache.js";
|
||||
import type {
|
||||
SlashCommandSubcommandsOnlyBuilder,
|
||||
ChatInputCommandInteraction,
|
||||
SlashCommandOptionsOnlyBuilder,
|
||||
} from "discord.js";
|
||||
|
||||
export interface Command {
|
||||
data:
|
||||
SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder;
|
||||
run: (
|
||||
interaction: ChatInputCommandInteraction,
|
||||
cache: WeatherCache
|
||||
)=> Promise<void>;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type PrecipitationName =
|
||||
| "Blizzard"
|
||||
| "Cinder Storm"
|
||||
| "Cloudy"
|
||||
| "Fog"
|
||||
| "Hail"
|
||||
| "Heat Lightning"
|
||||
| "Heavy Rain"
|
||||
| "Heavy Snow"
|
||||
| "Light Rain"
|
||||
| "Light Snow"
|
||||
| "Partly Cloudy"
|
||||
| "Rain"
|
||||
| "Rainbow"
|
||||
| "Sleet"
|
||||
| "Snow"
|
||||
| "Sun Shower"
|
||||
| "Sunny"
|
||||
| "Thundersnow"
|
||||
| "Thunderstorm";
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type RegionName = "Rudania" | "Inariko" | "Vhintl";
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type Season = "Spring" | "Summer" | "Fall" | "Winter";
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type SpecialName =
|
||||
| "Avalanche"
|
||||
| "Blight Rain"
|
||||
| "Drought"
|
||||
| "Fairy Circle"
|
||||
| "Flood"
|
||||
| "Flower Bloom"
|
||||
| "Jubilee"
|
||||
| "Meteor Shower"
|
||||
| "Muggy"
|
||||
| "Rock Slide";
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type TemperatureName =
|
||||
| "Frigid"
|
||||
| "Freezing"
|
||||
| "Cold"
|
||||
| "Chilly"
|
||||
| "Brisk"
|
||||
| "Cool"
|
||||
| "Mild"
|
||||
| "Perfect"
|
||||
| "Warm"
|
||||
| "Hot"
|
||||
| "Scorching"
|
||||
| "Heat Wave";
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export type WindName =
|
||||
| "Calm"
|
||||
| "Breeze"
|
||||
| "Moderate"
|
||||
| "Fresh"
|
||||
| "Strong"
|
||||
| "Gale"
|
||||
| "Storm"
|
||||
| "Hurricane";
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { PrecipitationName } from "./names/precipitationName.js";
|
||||
import type { TemperatureName } from "./names/temperatureName.js";
|
||||
import type { WindName } from "./names/windName.js";
|
||||
|
||||
export interface Precipitation {
|
||||
name: PrecipitationName;
|
||||
temps: Array<TemperatureName> | "any";
|
||||
winds: Array<WindName> | "any";
|
||||
emote: string;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { PrecipitationName } from "../names/precipitationName.js";
|
||||
import type { Season } from "../names/season.js";
|
||||
import type { SpecialName } from "../names/specialName.js";
|
||||
import type { TemperatureName } from "../names/temperatureName.js";
|
||||
import type { WindName } from "../names/windName.js";
|
||||
|
||||
export interface RegionRestriction {
|
||||
season: Season;
|
||||
temps: Array<TemperatureName>;
|
||||
wind: Array<WindName>;
|
||||
precipitation: Array<PrecipitationName>;
|
||||
special: Array<SpecialName>;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { PrecipitationName } from "./names/precipitationName.js";
|
||||
import type { SpecialName } from "./names/specialName.js";
|
||||
import type { TemperatureName } from "./names/temperatureName.js";
|
||||
import type { WindName } from "./names/windName.js";
|
||||
|
||||
export interface Special {
|
||||
name: SpecialName;
|
||||
temps: Array<TemperatureName> | "any";
|
||||
winds: Array<WindName> | "any";
|
||||
precipitations: Array<PrecipitationName> | "any";
|
||||
description: string;
|
||||
emote: string;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { TemperatureName } from "./names/temperatureName.js";
|
||||
|
||||
export interface Temperature {
|
||||
fahrenheit: number;
|
||||
celsius: number;
|
||||
name: TemperatureName;
|
||||
emote: string;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { RegionName } from "./names/regionName.js";
|
||||
import type { Season } from "./names/season.js";
|
||||
import type { Precipitation } from "./precipitation.js";
|
||||
import type { Special } from "./special.js";
|
||||
import type { Temperature } from "./temperature.js";
|
||||
import type { Wind } from "./wind.js";
|
||||
|
||||
export interface WeatherForecast {
|
||||
region: RegionName;
|
||||
season: Season;
|
||||
temperature: Temperature;
|
||||
wind: Wind;
|
||||
precipitation: Precipitation | null;
|
||||
special: Special | null;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { WindName } from "./names/windName.js";
|
||||
|
||||
export interface Wind {
|
||||
lowSpeed: number;
|
||||
highSpeed: number;
|
||||
name: WindName;
|
||||
emote: string;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { WeatherForecast } from "./weather/weatherForecast.js";
|
||||
import type { NewsChannel, TextChannel } from "discord.js";
|
||||
|
||||
export interface WeatherCache {
|
||||
Rudania: WeatherForecast | null;
|
||||
Inariko: WeatherForecast | null;
|
||||
Vhintl: WeatherForecast | null;
|
||||
channels: {
|
||||
Rudania: TextChannel | NewsChannel;
|
||||
Inariko: TextChannel | NewsChannel;
|
||||
Vhintl: TextChannel | NewsChannel;
|
||||
};
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { EmbedBuilder, type APIEmbedField, type MessageCreateOptions }
|
||||
from "discord.js";
|
||||
import { generateBanner } from "./images/generateBanner.js";
|
||||
import { getSeasonIcon } from "./images/getSeasonIcon.js";
|
||||
import type { WeatherForecast } from "../interfaces/weather/weatherForecast.js";
|
||||
|
||||
/**
|
||||
* Parses a weather forecast into a Discord message embed.
|
||||
* @param forecast - The forecast to parse.
|
||||
* @returns A Discord message embed.
|
||||
*/
|
||||
export const generateWeatherEmbed = async(
|
||||
forecast: WeatherForecast | null,
|
||||
): Promise<MessageCreateOptions> => {
|
||||
if (!forecast) {
|
||||
const embed = new EmbedBuilder().
|
||||
setTitle("Error").
|
||||
setDescription("No forecast was generated.");
|
||||
return { embeds: [ embed ] };
|
||||
}
|
||||
|
||||
const weatherEmbed = new EmbedBuilder();
|
||||
weatherEmbed.setTitle(`${forecast.region}'s Daily Weather Forecast`);
|
||||
|
||||
let emoteString = forecast.temperature.emote + forecast.wind.emote;
|
||||
|
||||
const fields: Array<APIEmbedField> = [
|
||||
{
|
||||
name: "Temperature",
|
||||
value: `${String(forecast.temperature.fahrenheit)}°F / ${String(forecast.temperature.celsius)}°C [${forecast.temperature.name}]`,
|
||||
},
|
||||
{
|
||||
name: "Wind",
|
||||
value: `${forecast.wind.name} [${String(forecast.wind.lowSpeed)} - ${String(forecast.wind.highSpeed)}kph]`,
|
||||
},
|
||||
];
|
||||
|
||||
if (forecast.precipitation) {
|
||||
fields.push({
|
||||
name: "Precipitation",
|
||||
value: forecast.precipitation.name,
|
||||
});
|
||||
emoteString = emoteString + forecast.precipitation.emote;
|
||||
}
|
||||
if (forecast.special) {
|
||||
fields.push({
|
||||
name: forecast.special.name,
|
||||
value: forecast.special.description,
|
||||
});
|
||||
emoteString = emoteString + forecast.special.emote;
|
||||
}
|
||||
weatherEmbed.setDescription(emoteString);
|
||||
weatherEmbed.addFields(fields);
|
||||
|
||||
const seasonIcon = getSeasonIcon(forecast.season);
|
||||
weatherEmbed.setThumbnail(seasonIcon.attachmentString);
|
||||
const banner = await generateBanner(forecast);
|
||||
weatherEmbed.setImage(banner.attachmentString);
|
||||
|
||||
switch (forecast.region) {
|
||||
case "Rudania":
|
||||
weatherEmbed.setColor(0xFF_00_00);
|
||||
break;
|
||||
case "Inariko":
|
||||
weatherEmbed.setColor(0x00_00_FF);
|
||||
break;
|
||||
case "Vhintl":
|
||||
weatherEmbed.setColor(0x00_FF_00);
|
||||
break;
|
||||
default:
|
||||
weatherEmbed.setColor(0x00_00_00);
|
||||
}
|
||||
|
||||
return {
|
||||
embeds: [ weatherEmbed ],
|
||||
files: [ seasonIcon.filePath, banner.filePath ],
|
||||
};
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/**
|
||||
* Module to select a random value from an array.
|
||||
* @param array - The array to select from.
|
||||
* @param weight - Optional parameter to prefer lower or higher values.
|
||||
* @returns The selected value.
|
||||
*/
|
||||
export const getRandomValue
|
||||
= <T>(array: Array<T>, weight?: "low" | "high"): T => {
|
||||
let random = Math.floor(Math.random() * array.length);
|
||||
switch (weight) {
|
||||
case "low":
|
||||
random = Math.min(
|
||||
Math.floor(Math.random() * array.length),
|
||||
Math.floor(Math.random() * array.length),
|
||||
Math.floor(Math.random() * array.length),
|
||||
);
|
||||
break;
|
||||
case "high":
|
||||
random = Math.max(
|
||||
Math.floor(Math.random() * array.length),
|
||||
Math.floor(Math.random() * array.length),
|
||||
Math.floor(Math.random() * array.length),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return array[random] as T;
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { temperatures } from "../data/weather/temperatures.js";
|
||||
import { winds } from "../data/weather/winds.js";
|
||||
import { errorHandler } from "../utils/errorHandler.js";
|
||||
import { getRandomValue } from "./getRandomValue.js";
|
||||
import { getPrecipitation } from "./weather/getPrecipitation.js";
|
||||
import { getRegionRestrictions } from "./weather/getRegionRestrictions.js";
|
||||
import { getSeason } from "./weather/getSeason.js";
|
||||
import { getSpecial } from "./weather/getSpecial.js";
|
||||
import type { RegionName } from "../interfaces/weather/names/regionName.js";
|
||||
import type { WeatherForecast } from "../interfaces/weather/weatherForecast.js";
|
||||
|
||||
/**
|
||||
* Generates a weather forecast for the specified region, using the current
|
||||
* date to determine the season.
|
||||
* @param region - The name of the region to forecast for. Must be one of "Rudania", "Inariko", or "Vhintl".
|
||||
* @returns The weather forecast.
|
||||
*/
|
||||
export const getWeatherForecast = (
|
||||
region: RegionName,
|
||||
): WeatherForecast | null => {
|
||||
try {
|
||||
const season = getSeason();
|
||||
const allowedWeather = getRegionRestrictions(region, season);
|
||||
|
||||
if (!allowedWeather) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const temporaryName = getRandomValue(allowedWeather.temps);
|
||||
const temperature = temperatures.find((element) => {
|
||||
return element.name === temporaryName;
|
||||
});
|
||||
const windName = getRandomValue(allowedWeather.wind, "low");
|
||||
const wind = winds.find((element) => {
|
||||
return element.name === windName;
|
||||
});
|
||||
|
||||
if (!temperature || !wind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const precipitation = getPrecipitation(allowedWeather, temporaryName, windName);
|
||||
const special = getSpecial(
|
||||
allowedWeather,
|
||||
temporaryName,
|
||||
windName,
|
||||
precipitation?.name,
|
||||
);
|
||||
|
||||
return { precipitation, region, season, special, temperature, wind };
|
||||
} catch (error) {
|
||||
void errorHandler(error, "get weather forecast");
|
||||
return null;
|
||||
}
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { getBannerImage } from "./getBannerImage.js";
|
||||
import { getOverlayImage } from "./getOverlayImage.js";
|
||||
import { overlayImages } from "./overlayImages.js";
|
||||
import type { AttachmentData }
|
||||
from "../../interfaces/commands/attachmentData.js";
|
||||
import type { WeatherForecast }
|
||||
from "../../interfaces/weather/weatherForecast.js";
|
||||
|
||||
/**
|
||||
* Generates the banner image. If an overlay is available, constructs a new banner image by overlaying the overlay on the banner.
|
||||
* Otherwise, returns the banner itself.
|
||||
* @param forecast - The weather forecast.
|
||||
* @returns The banner image attachment data.
|
||||
*/
|
||||
export const generateBanner = async(
|
||||
forecast: WeatherForecast,
|
||||
): Promise<AttachmentData> => {
|
||||
const background = getBannerImage(forecast.region);
|
||||
const overlayQuery
|
||||
= forecast.special?.name === "Blight Rain"
|
||||
? "Blight Rain"
|
||||
: forecast.precipitation?.name;
|
||||
|
||||
if (overlayQuery === undefined) {
|
||||
return background;
|
||||
}
|
||||
|
||||
const overlayPath = getOverlayImage(overlayQuery);
|
||||
|
||||
if (overlayPath === null) {
|
||||
return background;
|
||||
}
|
||||
|
||||
const overlay = await overlayImages(background.filePath, overlayPath);
|
||||
|
||||
return overlay;
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { join } from "node:path";
|
||||
import type { AttachmentData }
|
||||
from "../../interfaces/commands/attachmentData.js";
|
||||
import type { RegionName } from "../../interfaces/weather/names/regionName.js";
|
||||
|
||||
/**
|
||||
* Selects one of the random banner images from the banner folder.
|
||||
* @param region - The name of the region.
|
||||
* @returns The banner image attachment data.
|
||||
*/
|
||||
export const getBannerImage = (region: RegionName): AttachmentData => {
|
||||
const fileName
|
||||
= `${region}${String(Math.ceil(Math.random() * 3))}.png`;
|
||||
const filePath = join(process.cwd(), "src", "assets", "banners", fileName);
|
||||
return {
|
||||
attachmentString: `attachment://${fileName}`,
|
||||
filePath,
|
||||
};
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { join } from "node:path";
|
||||
import type { PrecipitationName }
|
||||
from "../../interfaces/weather/names/precipitationName.js";
|
||||
import type { SpecialName }
|
||||
from "../../interfaces/weather/names/specialName.js";
|
||||
|
||||
/**
|
||||
* Checks if the current weather conditions have an overlay.
|
||||
* @param name - The name of the weather condition to look for.
|
||||
* @returns The file path to the overlay, or null if there is no overlay.
|
||||
*/
|
||||
export const getOverlayImage = (
|
||||
name: SpecialName | PrecipitationName,
|
||||
): string | null => {
|
||||
let fileName: string | null = null;
|
||||
switch (name) {
|
||||
case "Blight Rain":
|
||||
fileName = "ROOTS-blightrain.png";
|
||||
break;
|
||||
case "Blizzard":
|
||||
fileName = "ROOTS-blizzard.png";
|
||||
break;
|
||||
case "Cinder Storm":
|
||||
fileName = "ROOTS-cinderstorm.png";
|
||||
break;
|
||||
case "Cloudy":
|
||||
case "Partly Cloudy":
|
||||
fileName = "ROOTS-cloudy.png";
|
||||
break;
|
||||
case "Fog":
|
||||
fileName = "ROOTS-fog.png";
|
||||
break;
|
||||
case "Hail":
|
||||
fileName = "ROOTS-hail.png";
|
||||
break;
|
||||
case "Heat Lightning":
|
||||
fileName = "ROOTS-heatlightning.png";
|
||||
break;
|
||||
case "Rain":
|
||||
case "Light Rain":
|
||||
case "Heavy Rain":
|
||||
fileName = "ROOTS-rain.png";
|
||||
break;
|
||||
case "Rainbow":
|
||||
fileName = "ROOTS-rainbow.png";
|
||||
break;
|
||||
case "Sleet":
|
||||
fileName = "ROOTS-sleet.png";
|
||||
break;
|
||||
case "Snow":
|
||||
case "Light Snow":
|
||||
case "Heavy Snow":
|
||||
fileName = "ROOTS-snow.png";
|
||||
break;
|
||||
case "Thundersnow":
|
||||
fileName = "ROOTS-thundersnow.png";
|
||||
break;
|
||||
case "Thunderstorm":
|
||||
fileName = "ROOTS-thunderstorm.png";
|
||||
break;
|
||||
default:
|
||||
fileName = null;
|
||||
}
|
||||
if (fileName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filePath = join(process.cwd(), "src", "assets", "overlays", fileName);
|
||||
return filePath;
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { join } from "node:path";
|
||||
import type { AttachmentData } from "../../interfaces/commands/attachmentData.js";
|
||||
import type { Season }
|
||||
from "../../interfaces/weather/names/season.js";
|
||||
|
||||
/**
|
||||
* Module to generate the season icon attachment.
|
||||
* @param season - The season for which to get the icon.
|
||||
* @returns The icon attachment data.
|
||||
*/
|
||||
export const getSeasonIcon = (season: Season): AttachmentData => {
|
||||
const fileName = `${season.toLowerCase()}.png`;
|
||||
const filePath = join(process.cwd(), "src", "assets", "seasons", fileName);
|
||||
return {
|
||||
attachmentString: `attachment://${fileName}`,
|
||||
filePath,
|
||||
};
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { join } from "node:path";
|
||||
import sharp from "sharp";
|
||||
import type { AttachmentData }
|
||||
from "../../interfaces/commands/attachmentData.js";
|
||||
|
||||
/**
|
||||
* Module to combine an overlay and a banner.
|
||||
* @param banner - The file path for the banner.
|
||||
* @param overlay - The file path for the overlay.
|
||||
* @returns The attachment data for the new banner.
|
||||
*/
|
||||
export const overlayImages = async(
|
||||
banner: string,
|
||||
overlay: string,
|
||||
): Promise<AttachmentData> => {
|
||||
await sharp(banner).
|
||||
composite([ { gravity: "center", input: overlay } ]).
|
||||
toFile(join(process.cwd(), "src", "assets", "overlay.png"));
|
||||
return {
|
||||
attachmentString: `attachment://overlay.png`,
|
||||
filePath: join(process.cwd(), "src", "assets", "overlay.png"),
|
||||
};
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { ChannelType, type Client } from "discord.js";
|
||||
import { logHandler } from "../utils/logHandler.js";
|
||||
import type { WeatherCache } from "../interfaces/weatherCache.js";
|
||||
|
||||
/**
|
||||
* Module to fetch the configured channels for sending weather forecasts.
|
||||
* @param bot - The bot's Discord instance.
|
||||
* @returns The configured channels for sending weather forecasts.
|
||||
*/
|
||||
export const loadChannels = async(
|
||||
bot: Client,
|
||||
): Promise<WeatherCache["channels"]> => {
|
||||
try {
|
||||
const guildId = process.env.HOME_GUILD_ID ?? "";
|
||||
const rudaniaId = process.env.RUDANIA_ID ?? "";
|
||||
const inarikoId = process.env.INARIKO_ID ?? "";
|
||||
const vhintlId = process.env.VHINTL_ID ?? "";
|
||||
|
||||
const guild = await bot.guilds.fetch(guildId);
|
||||
const Rudania = await guild.channels.fetch(rudaniaId);
|
||||
const Inariko = await guild.channels.fetch(inarikoId);
|
||||
const Vhintl = await guild.channels.fetch(vhintlId);
|
||||
|
||||
if (
|
||||
!Rudania
|
||||
|| !Inariko
|
||||
|| !Vhintl
|
||||
|| !("send" in Rudania && "send" in Inariko && "send" in Vhintl)
|
||||
|| Rudania.type !== ChannelType.GuildText
|
||||
|| Inariko.type !== ChannelType.GuildText
|
||||
|| Vhintl.type !== ChannelType.GuildText
|
||||
) {
|
||||
logHandler.log("error", "Cannot locate the forecast channels!");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { Inariko, Rudania, Vhintl };
|
||||
} catch (error) {
|
||||
logHandler.log("error", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { scheduleJob } from "node-schedule";
|
||||
import { errorHandler } from "../utils/errorHandler.js";
|
||||
import { logHandler } from "../utils/logHandler.js";
|
||||
import { generateWeatherEmbed } from "./generateWeatherEmbed.js";
|
||||
import { getWeatherForecast } from "./getWeatherForecast.js";
|
||||
import type { WeatherCache } from "../interfaces/weatherCache.js";
|
||||
|
||||
/**
|
||||
* Schedules a CRON job for sending weather forecasts to the appropriate channels.
|
||||
* @param cache - The weather cache.
|
||||
*/
|
||||
export const scheduleForecasts = (cache: WeatherCache): void => {
|
||||
logHandler.log("info", "Scheduling weather forecasts...");
|
||||
// Run daily at 5AM PST to get 8AM EST.
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
scheduleJob("0 0 5 * * *", async() => {
|
||||
try {
|
||||
cache.Rudania = getWeatherForecast("Rudania");
|
||||
cache.Inariko = getWeatherForecast("Inariko");
|
||||
cache.Vhintl = getWeatherForecast("Vhintl");
|
||||
|
||||
const rudania = await generateWeatherEmbed(cache.Rudania);
|
||||
await cache.channels.Rudania.send(rudania);
|
||||
const inariko = await generateWeatherEmbed(cache.Inariko);
|
||||
await cache.channels.Inariko.send(inariko);
|
||||
const vhintl = await generateWeatherEmbed(cache.Vhintl);
|
||||
await cache.channels.Vhintl.send(vhintl);
|
||||
} catch (error) {
|
||||
const errorId = await errorHandler(error, "scheduled forecast");
|
||||
await cache.channels.Rudania.send(errorId);
|
||||
}
|
||||
});
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { precipitations } from "../../data/weather/precipitations.js";
|
||||
import { getRandomValue } from "../getRandomValue.js";
|
||||
import type { TemperatureName }
|
||||
from "../../interfaces/weather/names/temperatureName.js";
|
||||
import type { WindName } from "../../interfaces/weather/names/windName.js";
|
||||
import type { Precipitation }
|
||||
from "../../interfaces/weather/precipitation.js";
|
||||
import type { RegionRestriction } from "../../interfaces/weather/regions/regionRestriction.js";
|
||||
|
||||
/**
|
||||
* Get a precipitation forecast for the region and season.
|
||||
* @param options - The allowed weather for the region + season.
|
||||
* @param temperature - The current temperature.
|
||||
* @param wind - The current wind.
|
||||
* @returns The precipitation, or null if there are no valid forecasts.
|
||||
*/
|
||||
export const getPrecipitation = (
|
||||
options: RegionRestriction,
|
||||
temperature: TemperatureName,
|
||||
wind: WindName,
|
||||
): Precipitation | null => {
|
||||
const restrictedPrecipitation = precipitations.filter((element) => {
|
||||
return options.precipitation.includes(element.name);
|
||||
});
|
||||
const validPrecipitations = restrictedPrecipitation.filter(
|
||||
(element) => {
|
||||
return (element.temps.includes(temperature) || element.temps === "any")
|
||||
&& (element.winds.includes(wind) || element.winds === "any");
|
||||
},
|
||||
);
|
||||
|
||||
if (validPrecipitations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRandomValue(validPrecipitations);
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { inarikoSeasons } from "../../data/weather/regions/inarikoSeasons.js";
|
||||
import { rudaniaSeasons } from "../../data/weather/regions/rudaniaSeasons.js";
|
||||
import { vhintlSeasons } from "../../data/weather/regions/vhintlSeasons.js";
|
||||
import type { RegionName } from "../../interfaces/weather/names/regionName.js";
|
||||
import type { Season } from "../../interfaces/weather/names/season.js";
|
||||
import type { RegionRestriction } from "../../interfaces/weather/regions/regionRestriction.js";
|
||||
|
||||
/**
|
||||
* Module to get the allowed weather for a region based on the season.
|
||||
* Will throw an error if the data is not found.
|
||||
* @param region - The name of the region.
|
||||
* @param season - The season the region is in.
|
||||
* @returns The allowed weather for the region.
|
||||
*/
|
||||
export const getRegionRestrictions = (
|
||||
region: RegionName,
|
||||
season: Season,
|
||||
): RegionRestriction | null => {
|
||||
let restrictions = null;
|
||||
switch (region) {
|
||||
case "Rudania":
|
||||
restrictions = rudaniaSeasons;
|
||||
break;
|
||||
case "Inariko":
|
||||
restrictions = inarikoSeasons;
|
||||
break;
|
||||
case "Vhintl":
|
||||
restrictions = vhintlSeasons;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (restrictions === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const seasonalRestriction = restrictions.find((element) => {
|
||||
return element.season === season;
|
||||
});
|
||||
|
||||
if (!seasonalRestriction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return seasonalRestriction;
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import type { Season } from "../../interfaces/weather/names/season.js";
|
||||
|
||||
/**
|
||||
* Module to get the season based on today's date.
|
||||
* Winter: Dec 21 - March 20.
|
||||
* Spring: March 21 - June 20.
|
||||
* Summer: June 21st - September 22.
|
||||
* Fall: September 23 - December 21.
|
||||
* @returns The season name.
|
||||
*/
|
||||
export const getSeason = (): Season => {
|
||||
const date = new Date();
|
||||
const month = date.getMonth();
|
||||
const day = date.getDate();
|
||||
|
||||
if (month < 2 || month === 2 && day < 21 || month === 11 && day > 20) {
|
||||
return "Winter";
|
||||
}
|
||||
if (month < 5 || month === 5 && day < 21) {
|
||||
return "Spring";
|
||||
}
|
||||
if (month < 8 || month === 8 && day < 23) {
|
||||
return "Summer";
|
||||
}
|
||||
return "Fall";
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { specials } from "../../data/weather/specials.js";
|
||||
import { getRandomValue } from "../getRandomValue.js";
|
||||
import type { PrecipitationName } from "../../interfaces/weather/names/precipitationName.js";
|
||||
import type { TemperatureName } from "../../interfaces/weather/names/temperatureName.js";
|
||||
import type { WindName } from "../../interfaces/weather/names/windName.js";
|
||||
import type { RegionRestriction } from "../../interfaces/weather/regions/regionRestriction.js";
|
||||
import type { Special } from "../../interfaces/weather/special.js";
|
||||
|
||||
/**
|
||||
* Module to get a special weather event for a region. Checks if the temp, wind, and precipitation
|
||||
* allow for a special event. If so, has a 30% chance to trigger one.
|
||||
* @param options - The allowed weather for the region + season.
|
||||
* @param temperature - The current temperature.
|
||||
* @param wind - The current wind.
|
||||
* @param precipitation - The current precipitation.
|
||||
* @returns The special event, or null if there are no valid forecasts.
|
||||
*/
|
||||
export const getSpecial = (
|
||||
options: RegionRestriction,
|
||||
temperature: TemperatureName,
|
||||
wind: WindName,
|
||||
precipitation: PrecipitationName | undefined,
|
||||
): Special | null => {
|
||||
const restrictedSpecial = specials.filter((element) => {
|
||||
return options.special.includes(element.name);
|
||||
});
|
||||
const validSpecials = precipitation !== undefined
|
||||
? restrictedSpecial.filter(
|
||||
(element) => {
|
||||
return (element.temps.includes(temperature) || element.temps === "any")
|
||||
&& (element.winds.includes(wind) || element.winds === "any")
|
||||
&& (element.precipitations.includes(precipitation)
|
||||
|| element.precipitations === "any");
|
||||
},
|
||||
)
|
||||
: restrictedSpecial.filter(
|
||||
(element) => {
|
||||
return (element.temps.includes(temperature) || element.temps === "any")
|
||||
&& (element.winds.includes(wind) || element.winds === "any");
|
||||
},
|
||||
);
|
||||
|
||||
if (validSpecials.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shouldSpecialTrigger = Math.floor(Math.random() * 100) < 30;
|
||||
|
||||
if (!shouldSpecialTrigger) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRandomValue(validSpecials);
|
||||
};
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { type Client, WebhookClient } from "discord.js";
|
||||
import fastify from "fastify";
|
||||
import { errorHandler } from "../utils/errorHandler.js";
|
||||
|
||||
/**
|
||||
* Starts up a web server for health monitoring.
|
||||
* @param bot - The bot's Discord instance.
|
||||
*/
|
||||
export const instantiateServer = async(bot: Client) => {
|
||||
try {
|
||||
const server = fastify({
|
||||
https: {
|
||||
cert: await readFile(
|
||||
"/etc/letsencrypt/live/ruubot.nhcarrigan.com/cert.pem",
|
||||
),
|
||||
key: await readFile(
|
||||
"/etc/letsencrypt/live/ruubot.nhcarrigan.com/privkey.pem",
|
||||
),
|
||||
},
|
||||
logger: false,
|
||||
});
|
||||
|
||||
server.get("/", (_request, response) => {
|
||||
response.header("Content-Type", "text/html");
|
||||
response.send(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tingle Bot</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Custom bot for my friend Ruu's Zelda-themed RP server" />
|
||||
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Tingle Bot</h1>
|
||||
<section>
|
||||
<p>Custom bot for my friend Ruu's Zelda-themed RP server.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Links</h2>
|
||||
<p>
|
||||
<a href="https://codeberg.org/nhcarrigan/tingle-bot">
|
||||
<i class="fa-solid fa-code"></i> Source Code
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://docs.nhcarrigan.com/">
|
||||
<i class="fa-solid fa-book"></i> Documentation
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://chat.nhcarrigan.com">
|
||||
<i class="fa-solid fa-circle-info"></i> Support
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>`);
|
||||
});
|
||||
|
||||
server.listen({ port: 4443 }, (error) => {
|
||||
if (error) {
|
||||
void errorHandler(error, "start server");
|
||||
return;
|
||||
}
|
||||
const hook = new WebhookClient({ url: process.env.DEBUG_HOOK as string });
|
||||
void hook.send({
|
||||
avatarURL:
|
||||
bot.user?.displayAvatarURL()
|
||||
?? "https://cdn.nhcarrigan.com/profile.png",
|
||||
content: "Fastify server live on port 1443~!",
|
||||
username: bot.user?.username ?? "Boost Monitor",
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
await errorHandler(error, "instantiate server");
|
||||
}
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { EmbedBuilder, WebhookClient } from "discord.js";
|
||||
import { v4 } from "uuid";
|
||||
import { logHandler } from "./logHandler.js";
|
||||
|
||||
/**
|
||||
* Formats an error into an embed and sends it to the developer debug webhook.
|
||||
* @param error - The error instance from Node.
|
||||
* @param context - A description of where the error occurred.
|
||||
* @returns The UUID tied to the error.
|
||||
*/
|
||||
export const errorHandler = async(
|
||||
error: unknown,
|
||||
context: string,
|
||||
): Promise<string> => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const typedError = error as Error;
|
||||
logHandler.log("error", `There was an error in the ${context}:`);
|
||||
logHandler.log(
|
||||
"error",
|
||||
JSON.stringify(
|
||||
{ errorMessage: typedError.message, errorStack: typedError.stack },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
const errorId = v4();
|
||||
|
||||
const errorEmbed = new EmbedBuilder();
|
||||
errorEmbed.setTitle(`RuuBot had a ${context} error!`);
|
||||
errorEmbed.setColor(0xFF_00_00);
|
||||
errorEmbed.setDescription(typedError.message.slice(0, 4000));
|
||||
errorEmbed.addFields([
|
||||
{ name: "Stack Trace",
|
||||
value: typedError.stack?.slice(0, 1000) ?? "No stack trace available." },
|
||||
{ name: "Error ID", value: errorId },
|
||||
]);
|
||||
errorEmbed.setTimestamp();
|
||||
|
||||
const webhook = new WebhookClient({ url: process.env.DEBUG_HOOK ?? "" });
|
||||
|
||||
await webhook.send({ embeds: [ errorEmbed ] });
|
||||
|
||||
return `The ${context} logic had an error. Please contact the developer with this ID: \`${errorId}\``;
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { createLogger, format, transports, config } from "winston";
|
||||
|
||||
const { combine, timestamp, colorize, printf } = format;
|
||||
|
||||
/**
|
||||
* Standard log handler, using winston to wrap and format
|
||||
* messages. Call with `logHandler.log(level, message)`.
|
||||
* @param {string} level - The log level to use.
|
||||
* @param {string} message - The message to log.
|
||||
*/
|
||||
export const logHandler = createLogger({
|
||||
exitOnError: false,
|
||||
format: combine(
|
||||
timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss",
|
||||
}),
|
||||
colorize(),
|
||||
printf((info) => {
|
||||
return `${info.level}: ${info.timestamp}: ${info.message}`;
|
||||
}),
|
||||
),
|
||||
level: "silly",
|
||||
levels: config.npm.levels,
|
||||
transports: [ new transports.Console() ],
|
||||
});
|
@ -1,132 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { inarikoSeasons } from "../src/data/weather/regions/inarikoSeasons.ts";
|
||||
import { rudaniaSeasons } from "../src/data/weather/regions/rudaniaSeasons.ts";
|
||||
import { vhintlSeasons } from "../src/data/weather/regions/vhintlSeasons.ts";
|
||||
|
||||
describe("region Tests", () => {
|
||||
describe("inariko", () => {
|
||||
it("seasons should be unique", () => {
|
||||
const seasons = new Set(inarikoSeasons.map((element) => {
|
||||
return element.season;
|
||||
}));
|
||||
expect(seasons.size, "seasons are not unique!").toBe(
|
||||
inarikoSeasons.length,
|
||||
);
|
||||
});
|
||||
|
||||
for (const season of inarikoSeasons) {
|
||||
it(`${season.season} should have unique temps`, () => {
|
||||
const temps = new Set(season.temps);
|
||||
expect(temps.size, `${season.season} temps are not unique!`).toBe(
|
||||
season.temps.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique winds`, () => {
|
||||
const winds = new Set(season.wind);
|
||||
expect(winds.size, `${season.season} winds are not unique!`).toBe(
|
||||
season.wind.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique precipitation`, () => {
|
||||
const precipitation = new Set(season.precipitation);
|
||||
expect(
|
||||
precipitation.size,
|
||||
`${season.season} precipitation is not unique!`,
|
||||
).toBe(season.precipitation.length);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique specials`, () => {
|
||||
const specials = new Set(season.special);
|
||||
expect(specials.size, `${season.season} specials are not unique!`).toBe(
|
||||
season.special.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("rudania", () => {
|
||||
it("seasons should be unique", () => {
|
||||
const seasons = new Set(rudaniaSeasons.map((element) => {
|
||||
return element.season;
|
||||
}));
|
||||
expect(seasons.size, "seasons are not unique!").toBe(
|
||||
rudaniaSeasons.length,
|
||||
);
|
||||
});
|
||||
|
||||
for (const season of rudaniaSeasons) {
|
||||
it(`${season.season} should have unique temps`, () => {
|
||||
const temps = new Set(season.temps);
|
||||
expect(temps.size, `${season.season} temps are not unique!`).toBe(
|
||||
season.temps.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique winds`, () => {
|
||||
const winds = new Set(season.wind);
|
||||
expect(winds.size, `${season.season} winds are not unique!`).toBe(
|
||||
season.wind.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique precipitation`, () => {
|
||||
const precipitation = new Set(season.precipitation);
|
||||
expect(
|
||||
precipitation.size,
|
||||
`${season.season} precipitation is not unique!`,
|
||||
).toBe(season.precipitation.length);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique specials`, () => {
|
||||
const specials = new Set(season.special);
|
||||
expect(specials.size, `${season.season} specials are not unique!`).toBe(
|
||||
season.special.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("vhintl", () => {
|
||||
it("seasons should be unique", () => {
|
||||
const seasons = new Set(vhintlSeasons.map((element) => {
|
||||
return element.season;
|
||||
}));
|
||||
expect(seasons.size, "seasons are not unique!").toBe(
|
||||
vhintlSeasons.length,
|
||||
);
|
||||
});
|
||||
|
||||
for (const season of vhintlSeasons) {
|
||||
it(`${season.season} should have unique temps`, () => {
|
||||
const temps = new Set(season.temps);
|
||||
expect(temps.size, `${season.season} temps are not unique!`).toBe(
|
||||
season.temps.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique winds`, () => {
|
||||
const winds = new Set(season.wind);
|
||||
expect(winds.size, `${season.season} winds are not unique!`).toBe(
|
||||
season.wind.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique precipitation`, () => {
|
||||
const precipitation = new Set(season.precipitation);
|
||||
expect(
|
||||
precipitation.size,
|
||||
`${season.season} precipitation is not unique!`,
|
||||
).toBe(season.precipitation.length);
|
||||
});
|
||||
|
||||
it(`${season.season} should have unique specials`, () => {
|
||||
const specials = new Set(season.special);
|
||||
expect(specials.size, `${season.season} specials are not unique!`).toBe(
|
||||
season.special.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { precipitations } from "../src/data/weather/precipitations.ts";
|
||||
import { specials } from "../src/data/weather/specials.ts";
|
||||
import { temperatures } from "../src/data/weather/temperatures.ts";
|
||||
import { winds } from "../src/data/weather/winds.ts";
|
||||
|
||||
describe("weather Tests", () => {
|
||||
describe("temperature", () => {
|
||||
const temps = new Set(temperatures.map((element) => {
|
||||
return element.name;
|
||||
}));
|
||||
it("temperatures should be unique", () => {
|
||||
expect(temps.size, "temperatures are not unique!").toBe(
|
||||
temperatures.length,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("winds", () => {
|
||||
const wind = new Set(winds.map((element) => {
|
||||
return element.name;
|
||||
}));
|
||||
it("winds should be unique", () => {
|
||||
expect(wind.size, "winds are not unique!").toBe(winds.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("precipitations", () => {
|
||||
const precip = new Set(precipitations.map((element) => {
|
||||
return element.name;
|
||||
}));
|
||||
it("precipitations should be unique", () => {
|
||||
expect(precip.size, "precipitations are not unique!").toBe(
|
||||
precipitations.length,
|
||||
);
|
||||
});
|
||||
|
||||
for (const precipitation of precipitations) {
|
||||
it(`${precipitation.name} should have unique temps`, () => {
|
||||
if (precipitation.temps === "any") {
|
||||
return;
|
||||
}
|
||||
const temps = new Set(precipitation.temps);
|
||||
expect(temps.size, `${precipitation.name} temps are not unique!`).toBe(
|
||||
precipitation.temps.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${precipitation.name} should have unique winds`, () => {
|
||||
if (precipitation.winds === "any") {
|
||||
return;
|
||||
}
|
||||
const winds = new Set(precipitation.winds);
|
||||
expect(winds.size, `${precipitation.name} winds are not unique!`).toBe(
|
||||
precipitation.winds.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("specials", () => {
|
||||
const spec = new Set(specials.map((element) => {
|
||||
return element.name;
|
||||
}));
|
||||
it("specials should be unique", () => {
|
||||
expect(spec.size, "specials are not unique!").toBe(specials.length);
|
||||
});
|
||||
|
||||
for (const special of specials) {
|
||||
it(`${special.name} should have unique temps`, () => {
|
||||
if (special.temps === "any") {
|
||||
return;
|
||||
}
|
||||
const temps = new Set(special.temps);
|
||||
expect(temps.size, `${special.name} temps are not unique!`).toBe(
|
||||
special.temps.length,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${special.name} should have unique winds`, () => {
|
||||
if (special.winds === "any") {
|
||||
return;
|
||||
}
|
||||
const winds = new Set(special.winds);
|
||||
expect(special.winds, "Special winds are not unique!").toHaveLength(
|
||||
winds.size,
|
||||
);
|
||||
});
|
||||
|
||||
it(`${special.name} should have unique precipitation`, () => {
|
||||
if (special.precipitations === "any") {
|
||||
return;
|
||||
}
|
||||
const precip = new Set(special.precipitations);
|
||||
expect(
|
||||
precip.size,
|
||||
`${special.name} precipitation is not unique!`,
|
||||
).toBe(special.precipitations.length);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "@nhcarrigan/typescript-config",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./prod",
|
||||
"exactOptionalPropertyTypes": false
|
||||
},
|
||||
"exclude": ["./test", "vitest.config.ts"]
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: "istanbul",
|
||||
reporter: ["text", "html"],
|
||||
all: true,
|
||||
allowExternal: true,
|
||||
thresholds: {
|
||||
lines: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|