added team command, added game command, added about command

added request module, added nfl module, among other items
This commit is contained in:
Wyatt J. Miller 2024-11-10 04:01:34 -05:00
commit 031b2be817
22 changed files with 4027 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.env

2715
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

22
Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "ball-bot"
version = "0.1.0"
edition = "2021"
[dependencies]
serenity = { version = "0.12.2", features = [
"client",
"gateway",
"rustls_backend",
"model",
"collector",
] }
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.12.9", features = ["json", "rustls-tls"] }
tracing = "0.1.40"
dotenvy = "0.15.7"
rand = "0.8.5"
chrono = { version = "0.4.38" }
serde = "1.0.214"
serde_json = "1.0.132"
serde_derive = "1.0.214"

61
flake.lock Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"flake-schemas": {
"locked": {
"lastModified": 1721999734,
"narHash": "sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw=",
"rev": "0a5c42297d870156d9c57d8f99e476b738dcd982",
"revCount": 75,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%2A"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1730741070,
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"revCount": 636376,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.636376%2Brev-d063c1dd113c91ab27959ba540c0d9753409edf3/0192facb-0ae2-71fe-ba33-5b04923dc511/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/%2A"
}
},
"root": {
"inputs": {
"flake-schemas": "flake-schemas",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1730860036,
"narHash": "sha256-u0sfA4B65Q9cRO3xpIkQ4nldB8isfdIb3rWtsnRZ+Iw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "b8eb3aeb21629cbe14968a5e3b1cbaefb0d1b260",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

64
flake.nix Normal file
View File

@ -0,0 +1,64 @@
# This flake was initially generated by fh, the CLI for FlakeHub (version 0.1.16)
{
description = "Discord bot for viewing football statistics, teams, team members, and more!";
inputs = {
flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*";
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
flake-schemas,
nixpkgs,
rust-overlay,
}: let
overlays = [
rust-overlay.overlays.default
(final: prev: {
rustToolchain = final.rust-bin.stable.latest.default.override {extensions = ["rust-src"];};
})
];
supportedSystems = ["x86_64-linux" "aarch64-darwin" "aarch64-linux"];
forEachSupportedSystem = f:
nixpkgs.lib.genAttrs supportedSystems (system:
f {
pkgs = import nixpkgs {inherit overlays system;};
});
in {
schemas = flake-schemas.schemas;
devShells = forEachSupportedSystem ({pkgs}: {
default = pkgs.mkShell {
packages = with pkgs; [
rustToolchain
cargo-bloat
cargo-edit
cargo-outdated
cargo-udeps
cargo-watch
rust-analyzer
curl
git
jq
wget
pkg-config-unwrapped
openssl.dev
];
env = {
RUST_BACKTRACE = "1";
RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library";
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
};
};
});
};
}

16
src/commands/about.rs Normal file
View File

@ -0,0 +1,16 @@
use serenity::builder::CreateCommand;
use serenity::model::application::ResolvedOption;
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
r#"
A Discord bot that gathers information about NFL games, teams, conferences, and more!
Designed, developed, and maintained by Wyatt J. Miller
Copyright 2024, all rights reserved
Got a feature request? Found a bug? Wanna punch me in the face? Email ballbot@wyattjmiller.com
"#
.to_string()
}
pub fn register() -> CreateCommand {
CreateCommand::new("about").description("Get information about this bot")
}

View File

106
src/commands/game.rs Normal file
View File

@ -0,0 +1,106 @@
use crate::{
types,
util::{junk, nfl, request},
};
use serenity::builder::{CreateCommand, CreateCommandOption};
use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
if let Some(ResolvedOption {
value: ResolvedValue::String(team),
..
}) = options.first()
{
let teams = nfl::nfl_teams();
let team_id = match junk::find_team_key(&teams, team.to_lowercase().as_str()) {
Some(t) => t,
None => return junk::get_random_insult(),
};
let req = request::Request::new();
let tmp_url = format!("{:}/nfl/teams/{:}", req.base_url, team_id);
let tmp_result = req
.request_url::<types::team::TeamResponse>(&tmp_url)
.await
.unwrap();
let event_id = tmp_result.team.next_event[0].id.clone();
let url = format!("{:}/nfl/scoreboard/{:}", req.base_url, event_id);
let result = req
.request_url::<types::event::EventResponse>(&url)
.await
.unwrap();
let game = result.competitions[0].clone();
let event_title = format!("{:} ({:})", result.name, result.short_name); // needed
let event_date = format!(
"<t:{:}:R>",
junk::flexible_to_epoch_seconds(result.date.as_str()).unwrap()
); // needed
let clock = result.status.display_clock.clone();
let quarter = result.status.period.to_string().clone();
let venue = game.venue.full_name.clone();
let team_home = game
.competitors
.iter()
.find(|t| t.home_away == "home")
.unwrap();
let team_away = game
.competitors
.iter()
.find(|t| t.home_away == "away")
.unwrap();
let home_team = team_home.team.display_name.clone();
let away_team = team_away.team.display_name.clone();
let home_team_score = team_home.score.clone();
let away_team_score = team_away.score.clone();
let discord_response = GameDiscordResponse {
event_title,
home_team,
home_team_score,
away_team,
away_team_score,
venue,
event_date,
clock,
quarter,
};
discord_response.format()
} else {
junk::get_random_insult()
}
}
pub fn register() -> CreateCommand {
CreateCommand::new("game")
.description("Get the next game's information by specifying a team name")
.add_option(
CreateCommandOption::new(
CommandOptionType::String,
"team",
"The team to query to get team's game info",
)
.required(true),
)
}
pub struct GameDiscordResponse {
pub event_title: String,
pub home_team: String,
pub home_team_score: String,
pub away_team: String,
pub away_team_score: String,
pub venue: String,
pub event_date: String,
pub clock: String,
pub quarter: String,
}
impl GameDiscordResponse {
pub fn format(&self) -> String {
format!(
"\n## {:}\nGame Date: {:}\nHome Team: {:}\nHome Team Score: {:}\nAway Team: {:}\nAway Team Score: {:}\nVenue: {:}\nClock: {:}\nQuarter: {:}",
self.event_title, self.event_date, self.home_team, self.home_team_score, self.away_team, self.away_team_score, self.venue, self.clock, self.quarter
)
}
}

3
src/commands/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod about;
pub mod game;
pub mod team;

69
src/commands/team.rs Normal file
View File

@ -0,0 +1,69 @@
use crate::{
types,
util::{junk, nfl, request},
};
use serenity::builder::{CreateCommand, CreateCommandOption};
use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
if let Some(ResolvedOption {
value: ResolvedValue::String(team),
..
}) = options.first()
{
let teams = nfl::nfl_teams();
let team_id = match junk::find_team_key(&teams, team.to_lowercase().as_str()) {
Some(t) => t,
None => return junk::get_random_insult(),
};
let req = request::Request::new();
let url = format!("{:}/nfl/teams/{:?}", req.base_url, team_id);
let results = req
.request_url::<types::team::TeamResponse>(&url)
.await
.unwrap();
let team_name = results.team.display_name;
let record = results.team.record.items[1].summary.clone();
let next_event = results.team.next_event[0].name.clone();
let game_date_raw = results.team.next_event[0].date.clone();
let game_date = junk::flexible_to_epoch_seconds(game_date_raw.as_str()).unwrap();
let standing = results.team.standing_summary;
let discord_response = TeamDiscordResponse {
team_name,
record,
next_event,
game_date: format!("<t:{:}:R>", game_date),
standing,
};
discord_response.format()
} else {
junk::get_random_insult()
}
}
pub fn register() -> CreateCommand {
CreateCommand::new("team")
.description("Get team members for current season")
.add_option(
CreateCommandOption::new(CommandOptionType::String, "team", "The team to query")
.required(true),
)
}
pub struct TeamDiscordResponse {
pub team_name: String,
pub record: String,
pub next_event: String,
pub game_date: String,
pub standing: String,
}
impl TeamDiscordResponse {
pub fn format(&self) -> String {
format!(
"## {:}\nSeason Record (W-L): {:}\nNext Game: {:}\nNext Game Date: {:}\nStanding: {:}",
self.team_name, self.record, self.next_event, self.game_date, self.standing
)
}
}

11
src/config.rs Normal file
View File

@ -0,0 +1,11 @@
use std::path::PathBuf;
pub struct Configuration {
env: Result<PathBuf, dotenvy::Error>,
}
pub fn config() -> Configuration {
Configuration {
env: dotenvy::dotenv(),
}
}

82
src/main.rs Normal file
View File

@ -0,0 +1,82 @@
mod commands;
mod config;
mod types;
mod util;
use std::env;
use serenity::async_trait;
use serenity::builder::{CreateInteractionResponse, CreateInteractionResponseMessage};
use serenity::model::application::Interaction;
use serenity::model::gateway::Ready;
use serenity::model::id::GuildId;
use serenity::prelude::*;
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if let Interaction::Command(command) = interaction {
println!("Received command interaction: {command:#?}");
let content = match command.data.name.as_str() {
"team" => Some(commands::team::run(&command.data.options()).await),
"about" => Some(commands::about::run(&command.data.options()).await),
"game" => Some(commands::game::run(&command.data.options()).await),
_ => Some("not implemented :(".to_string()),
};
if let Some(content) = content {
let data = CreateInteractionResponseMessage::new().content(content);
let builder = CreateInteractionResponse::Message(data);
if let Err(why) = command.create_response(&ctx.http, builder).await {
println!("Cannot respond to slash command: {why}");
}
}
}
}
async fn ready(&self, ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
let guild_id = GuildId::new(
env::var("GUILD_ID")
.expect("Expected GUILD_ID in environment")
.parse()
.expect("GUILD_ID must be an integer"),
);
let commands = guild_id
.set_commands(
&ctx.http,
vec![
commands::team::register(),
commands::about::register(),
commands::game::register(),
], // TODO: register commands here
)
.await;
println!("Registered guild slash commands: {commands:#?}");
}
}
#[tokio::main]
async fn main() {
// setting up configuration
let _ = config::config();
// setting up the discord bot
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
// setting up the discord client
let mut client = Client::builder(token, GatewayIntents::empty())
.event_handler(Handler)
.await
.expect("Error creating client");
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
}
}

327
src/types/event.rs Normal file
View File

@ -0,0 +1,327 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EventResponse {
pub id: String,
pub uid: String,
pub date: String,
pub name: String,
pub short_name: String,
pub season: Season,
pub week: Week,
pub competitions: Vec<Competition>,
pub links: Vec<Link3>,
pub status: Status2,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Season {
pub year: i64,
#[serde(rename = "type")]
pub type_field: i64,
pub slug: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Week {
pub number: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Competition {
pub id: String,
pub uid: String,
pub date: String,
pub attendance: i64,
#[serde(rename = "type")]
pub type_field: Type,
pub time_valid: bool,
pub neutral_site: bool,
pub conference_competition: bool,
pub play_by_play_available: bool,
pub recent: bool,
pub venue: Venue,
pub competitors: Vec<Competitor>,
pub notes: Vec<Value>,
pub status: Status,
pub broadcasts: Vec<Broadcast>,
pub leaders: Vec<Leader>,
pub format: Format,
pub start_date: String,
pub broadcast: String,
pub geo_broadcasts: Vec<GeoBroadcast>,
pub highlights: Vec<Value>,
pub headlines: Option<Vec<Headline>>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Type {
pub id: String,
pub abbreviation: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Venue {
pub id: String,
pub full_name: String,
pub address: Address,
pub indoor: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Address {
pub city: String,
pub state: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Competitor {
pub id: String,
pub uid: String,
#[serde(rename = "type")]
pub type_field: String,
pub order: i64,
pub home_away: String,
pub winner: Option<bool>,
pub team: Team,
pub score: String,
pub linescores: Option<Vec<Linescore>>,
pub statistics: Vec<Value>,
pub records: Vec<Record>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team {
pub id: String,
pub uid: String,
pub location: String,
pub name: String,
pub abbreviation: String,
pub display_name: String,
pub short_display_name: String,
pub color: String,
pub alternate_color: String,
pub is_active: bool,
pub venue: Venue2,
pub links: Vec<Link>,
pub logo: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Venue2 {
pub id: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Link {
pub rel: Vec<String>,
pub href: String,
pub text: String,
pub is_external: bool,
pub is_premium: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Linescore {
pub value: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Record {
pub name: String,
pub abbreviation: Option<String>,
#[serde(rename = "type")]
pub type_field: String,
pub summary: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Status {
pub clock: f64,
pub display_clock: String,
pub period: i64,
#[serde(rename = "type")]
pub type_field: Type2,
#[serde(rename = "isTBDFlex")]
pub is_tbdflex: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Type2 {
pub id: String,
pub name: String,
pub state: String,
pub completed: bool,
pub description: String,
pub detail: String,
pub short_detail: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Broadcast {
pub market: String,
pub names: Vec<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Leader {
pub name: String,
pub display_name: String,
pub short_display_name: String,
pub abbreviation: String,
pub leaders: Vec<Leader2>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Leader2 {
pub display_value: String,
pub value: f64,
pub athlete: Athlete,
pub team: Team3,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Athlete {
pub id: String,
pub full_name: String,
pub display_name: String,
pub short_name: String,
pub links: Vec<Link2>,
pub headshot: String,
pub jersey: String,
pub position: Position,
pub team: Team2,
pub active: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Link2 {
pub rel: Vec<String>,
pub href: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Position {
pub abbreviation: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team2 {
pub id: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team3 {
pub id: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Format {
pub regulation: Regulation,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Regulation {
pub periods: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeoBroadcast {
#[serde(rename = "type")]
pub type_field: Type3,
pub market: Market,
pub media: Media,
pub lang: String,
pub region: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Type3 {
pub id: String,
pub short_name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Market {
pub id: String,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Media {
pub short_name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Headline {
#[serde(rename = "type")]
pub type_field: String,
pub description: String,
pub short_link_text: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Link3 {
pub language: String,
pub rel: Vec<String>,
pub href: String,
pub text: String,
pub short_text: String,
pub is_external: bool,
pub is_premium: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Status2 {
pub clock: f64,
pub display_clock: String,
pub period: i64,
#[serde(rename = "type")]
pub type_field: Type4,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Type4 {
pub id: String,
pub name: String,
pub state: String,
pub completed: bool,
pub description: String,
pub detail: String,
pub short_detail: String,
}

3
src/types/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod event;
pub mod score;
pub mod team;

19
src/types/score.rs Normal file
View File

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScoreResponse {
#[serde(rename = "$ref")]
pub ref_field: String,
pub value: f64,
pub display_value: String,
pub winner: bool,
pub source: ScoreSource,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScoreSource {
pub id: String,
pub description: String,
}

367
src/types/team.rs Normal file
View File

@ -0,0 +1,367 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamResponse {
pub team: Team,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team {
pub id: String,
pub uid: String,
pub slug: String,
pub location: String,
pub name: String,
pub nickname: String,
pub abbreviation: String,
pub display_name: String,
pub short_display_name: String,
pub color: String,
pub alternate_color: String,
pub is_active: bool,
pub logos: Vec<Logo>,
pub record: TeamRecord,
pub groups: TeamGroups,
pub links: Vec<TeamLink>,
pub franchise: TeamFranchise,
// #[serde(rename = "nextEvent")]
pub next_event: Vec<NextEvent>,
pub standing_summary: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Logo {
pub href: String,
pub width: i64,
pub height: i64,
pub alt: String,
pub rel: Vec<String>,
pub last_updated: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamRecord {
pub items: Vec<Item>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Item {
#[serde(rename = "type")]
pub type_field: String,
pub summary: String,
pub stats: Vec<Stat>,
pub description: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stat {
pub name: String,
pub value: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamGroups {
pub id: String,
pub parent: Parent,
pub is_conference: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Parent {
pub id: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamLink {
pub language: String,
pub rel: Vec<String>,
pub href: String,
pub text: String,
pub short_text: String,
pub is_external: bool,
pub is_premium: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamFranchise {
#[serde(rename = "$ref")]
pub ref_field: String,
pub id: String,
pub uid: String,
pub slug: String,
pub location: String,
pub name: String,
pub nickname: String,
pub abbreviation: String,
pub display_name: String,
pub short_display_name: String,
pub color: String,
pub is_active: bool,
pub venue: TeamVenue,
pub team: Team2,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamVenue {
#[serde(rename = "$ref")]
pub ref_field: String,
pub id: String,
pub full_name: String,
pub address: TeamAddress,
pub grass: bool,
pub indoor: bool,
pub images: Vec<TeamImage>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamAddress {
pub city: String,
pub state: String,
pub zip_code: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamImage {
pub href: String,
pub width: i64,
pub height: i64,
pub alt: String,
pub rel: Vec<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team2 {
#[serde(rename = "$ref")]
pub ref_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NextEvent {
pub id: String,
pub date: String,
pub name: String,
pub short_name: String,
pub season: TeamSeason,
pub season_type: TeamSeasonType,
pub week: TeamWeek,
pub time_valid: bool,
pub competitions: Vec<TeamCompetition>,
pub links: Vec<TeamLink4>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamSeason {
pub year: i64,
pub display_name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamSeasonType {
pub id: String,
#[serde(rename = "type")]
pub type_field: i64,
pub name: String,
pub abbreviation: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamWeek {
pub number: i64,
pub text: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamCompetition {
pub id: String,
pub date: String,
pub attendance: i64,
#[serde(rename = "type")]
pub type_field: TeamType,
pub time_valid: bool,
pub neutral_site: bool,
pub boxscore_available: bool,
pub tickets_available: bool,
pub venue: TeamVenue2,
pub competitors: Vec<TeamCompetitor>,
pub notes: Vec<Value>,
pub broadcasts: Vec<Broadcast>,
pub tickets: Vec<Ticket>,
pub status: TeamStatus,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamType {
pub id: String,
pub text: String,
pub abbreviation: String,
pub slug: String,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamVenue2 {
pub full_name: String,
pub address: TeamAddress2,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamAddress2 {
pub city: String,
pub state: String,
pub zip_code: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamCompetitor {
pub id: String,
#[serde(rename = "type")]
pub type_field: String,
pub order: i64,
pub home_away: String,
pub team: Team3,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Team3 {
pub id: String,
pub location: String,
pub nickname: String,
pub abbreviation: String,
pub display_name: String,
pub short_display_name: String,
pub logos: Vec<Logo2>,
pub links: Vec<TeamLink2>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Logo2 {
pub href: String,
pub width: i64,
pub height: i64,
pub alt: String,
pub rel: Vec<String>,
pub last_updated: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamLink2 {
pub rel: Vec<String>,
pub href: String,
pub text: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Broadcast {
#[serde(rename = "type")]
pub type_field: Type2,
pub market: Market,
pub media: Media,
pub lang: String,
pub region: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Type2 {
pub id: String,
pub short_name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Market {
pub id: String,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Media {
pub short_name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Ticket {
pub id: String,
pub summary: String,
pub description: String,
pub max_price: f64,
pub starting_price: f64,
pub number_available: i64,
pub total_postings: i64,
pub links: Vec<TeamLink3>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamLink3 {
pub rel: Vec<String>,
pub href: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamStatus {
pub clock: f64,
pub display_clock: String,
pub period: i64,
#[serde(rename = "type")]
pub type_field: TeamType3,
#[serde(rename = "isTBDFlex")]
pub is_tbdflex: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamType3 {
pub id: String,
pub name: String,
pub state: String,
pub completed: bool,
pub description: String,
pub detail: String,
pub short_detail: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamLink4 {
pub language: String,
pub rel: Vec<String>,
pub href: String,
pub text: String,
pub short_text: String,
pub is_external: bool,
pub is_premium: bool,
}

0
src/util/discord.rs Normal file
View File

59
src/util/junk.rs Normal file
View File

@ -0,0 +1,59 @@
use chrono::prelude::*;
use chrono::DateTime;
use rand::seq::SliceRandom;
use std::collections::HashMap;
pub fn find_team_key(teams: &HashMap<u32, &str>, team_name: &str) -> Option<u32> {
teams
.iter()
.find(|(_k, &v)| v == team_name)
.map(|(&k, _v)| k)
}
pub fn find_team_name<'a>(teams: &HashMap<u32, &'a str>, identifier: u32) -> Option<&'a str> {
teams
.iter()
.find(|(&k, _v)| k == identifier)
.map(|(_k, &v)| v)
}
pub fn get_random_insult() -> String {
let insults = get_insults();
let result = insults.choose(&mut rand::thread_rng()).unwrap();
result.to_owned().to_string()
}
fn get_insults<'a>() -> Vec<&'a str> {
Vec::from([
"You football bro?",
"Somebody ought to throw pigskin in your face!",
"Who are you, Ian?",
"Your football expertise is about as useful as a screen door on a submarine!",
"You must think a wide receiver is someone who shovels food into their face!",
])
}
pub fn flexible_to_epoch_seconds(time: &str) -> Result<i64, Box<dyn std::error::Error>> {
let normalized = normalize_rfc_time(time);
match DateTime::parse_from_rfc3339(&normalized) {
Ok(dt) => Ok(dt.timestamp()),
Err(_) => {
let datetime = if time.ends_with('Z') {
NaiveDateTime::parse_from_str(&time[..time.len() - 1], "%Y-%m-%dT%H:%M")?
} else {
NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M")?
};
Ok(datetime.and_utc().timestamp())
}
}
}
fn normalize_rfc_time(time: &str) -> String {
if time.ends_with('Z') && time.matches(':').count() == 1 {
let len = time.len();
format!("{}:00{}", &time[..len - 1], &time[len - 1..])
} else {
time.to_string()
}
}

4
src/util/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod discord;
pub mod junk;
pub mod nfl;
pub mod request;

42
src/util/nfl.rs Normal file
View File

@ -0,0 +1,42 @@
use std::collections::HashMap;
pub fn nfl_teams<'a>() -> HashMap<u32, &'a str> {
HashMap::from([
(1, "falcons"),
(2, "bills"),
(3, "bears"),
(4, "bengals"),
(5, "browns"),
(6, "cowboys"),
(7, "broncos"),
(8, "lions"),
(9, "packers"),
(10, "titans"),
(11, "colts"),
(12, "chiefs"),
(13, "raiders"),
(14, "rams"),
(15, "dolphins"),
(16, "vikings"),
(17, "patriots"),
(18, "saints"),
(19, "giants"),
(20, "jets"),
(21, "eagles"),
(22, "cardinals"),
(23, "steelers"),
(24, "chargers"),
(25, "49ers"),
(26, "seahawks"),
(27, "buccaneers"),
(28, "commanders"),
(29, "panthers"),
(30, "jaguars"),
(33, "ravens"),
(34, "texans"),
])
}
pub fn nfl_conferences<'a>() -> HashMap<u32, &'a str> {
HashMap::from([])
}

54
src/util/request.rs Normal file
View File

@ -0,0 +1,54 @@
use reqwest::StatusCode;
pub struct Request<'a> {
pub base_url: &'a str,
pub full_url: Option<&'a str>,
}
impl<'a> Request<'a> {
pub fn new() -> Self {
Request {
base_url: "https://site.api.espn.com/apis/site/v2/sports/football",
full_url: None,
}
}
pub fn base_url(&self) -> &str {
self.base_url
}
pub fn set_base_url(&mut self, base_url: &'a str) {
self.base_url = base_url;
}
pub async fn request_url<T>(&self, url: &String) -> Result<T, Box<dyn std::error::Error>>
where
T: for<'de> serde::Deserialize<'de>,
{
println!("{}", url);
let api_result = match reqwest::get(url).await {
Ok(r) => r,
Err(e) => return Err(Box::new(e)),
};
match api_result.status() {
StatusCode::OK => {
let json = api_result.json::<T>();
let json_result = match json.await {
Ok(j) => j,
Err(e) => return Err(Box::new(e)),
};
Ok(json_result)
}
status => Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unexpected status code: {}", status),
))),
}
}
}
pub struct RequestOptions {
team_id: Option<u32>,
event_id: Option<u32>,
year: Option<u32>,
}