Add all and cmore commands. And some small updates to others to integrate better for all

This commit is contained in:
Tom 2022-11-28 12:31:17 +01:00
parent 8ac7702d8c
commit 12b934fcb4
7 changed files with 453 additions and 54 deletions

View file

@ -74,11 +74,11 @@ fn sky(input: &str) -> String {
fn sky_open(input: &str) -> Option<(String, String)> {
match input.rsplit_once('{') {
None => (return None),
None => return None,
Some((left, end)) => {
if left.ends_with('\\') {
match sky_open(left) {
None => (return None),
None => return None,
Some((left, right)) => {
let mut end: String = end.to_string();
end.push('{');
@ -95,11 +95,11 @@ fn sky_open(input: &str) -> Option<(String, String)> {
fn sky_closed(input: &str) -> Option<(String, String)> {
match input.split_once('}') {
None => (return None),
None => return None,
Some((left, end)) => {
if left.ends_with('\\') {
match sky_closed(end) {
None => (return None),
None => return None,
Some((mid, right)) => {
let mut start: String = left.to_string();
start.push('}');

148
src/commands/links/all.rs Normal file
View file

@ -0,0 +1,148 @@
use crate::{commands::utils, Context, Error};
use cached::proc_macro::cached;
use chrono::{DateTime, Utc};
use log::info;
use super::{
cmore::{self, CmoreEvent},
eurosport::{self, ESEvents},
nfl::{self, NFLEvent},
viaplay::{self, ViaplayEvent},
Timeframe,
};
#[derive(Clone)]
enum Events {
EuroSport(ESEvents),
Viaplay(ViaplayEvent),
NFL(NFLEvent),
Cmore(CmoreEvent),
}
impl Events {
fn filter(&self, filter: &str) -> bool {
if filter.is_empty() {
return true;
};
match self {
Events::EuroSport(event) => event.filter(&filter),
Events::Viaplay(event) => event.filter(&filter),
Events::NFL(event) => event.filter(&filter),
Events::Cmore(event) => event.filter(&filter),
}
}
fn to_string(&self) -> String {
match self {
Events::EuroSport(event) => event.to_string(),
Events::Viaplay(event) => event.to_string(),
Events::NFL(event) => event.to_string(),
Events::Cmore(event) => event.to_string(),
}
}
fn get_key(&self) -> (DateTime<Utc>, String) {
match self {
Events::EuroSport(event) => event.get_key(),
Events::Viaplay(event) => event.get_key(),
Events::NFL(event) => event.get_key(),
Events::Cmore(event) => event.get_key(),
}
}
pub fn comp(&self, when: &Timeframe) -> bool {
match self {
Events::EuroSport(event) => event.comp(when),
Events::Viaplay(event) => event.comp(when),
Events::NFL(event) => event.comp(when),
Events::Cmore(event) => event.comp(when),
}
}
}
async fn get_euro() -> Vec<Events> {
let url = super::super::super::SETTINGS
.read()
.unwrap()
.get_table("eurosport")
.expect("Expecting an eurosport section in the config")
.get("url")
.expect("Config error, please set the eurosport[url] value")
.clone()
.into_string()
.expect("Config error, please make sure eurosport[url] is a string");
let events = eurosport::get_eurosport_events(url).await;
match events {
Some(events) => events
.iter()
.map(|e| Events::EuroSport(e.to_owned()))
.collect(),
_ => vec![],
}
}
async fn get_viaplay() -> Vec<Events> {
match viaplay::get_schedule().await {
Some(events) => events
.iter()
.map(|e| Events::Viaplay(e.to_owned()))
.collect(),
None => vec![],
}
}
async fn get_nfl() -> Vec<Events> {
match nfl::get_current_schedule().await {
Some(events) => events.iter().map(|e| Events::NFL(e.to_owned())).collect(),
None => vec![],
}
}
async fn get_cmore() -> Vec<Events> {
match cmore::get_schedule().await {
Some(events) => events.iter().map(|e| Events::Cmore(e.to_owned())).collect(),
None => vec![],
}
}
#[cached(time = 3600)]
async fn get_events() -> Vec<Events> {
let mut events: Vec<Events> = vec![];
events.extend(get_euro().await);
events.extend(get_viaplay().await);
events.extend(get_nfl().await);
events.extend(get_cmore().await);
events.sort_unstable_by_key(|event| (event.get_key()));
events
}
// All events filtered (Eurosport, NFL, Viaplay)
#[poise::command(slash_command)]
pub async fn all(
ctx: Context<'_>,
#[description = "Filter sessions for when they are/were happening"] timeframe: Timeframe,
#[description = "Content to filter on"] filter: Option<String>,
) -> Result<(), Error> {
let events = get_events().await;
match events {
events if events.len() == 0 => {
ctx.say("No events found. Either it's not among the implemented providers or your search is too stringent").await?;
}
events => {
info!("Found {} events from all events", events.len());
let strings = events
.into_iter()
.filter(|e| e.comp(&timeframe))
.filter(|e| match &filter {
None => true,
Some(f) => e.filter(f.as_str()),
})
.map(|e| e.to_string())
.collect();
let pages = utils::paginator(strings, 1900, "\n".to_string());
utils::paginate_string(ctx, pages).await?;
}
}
Ok(())
}

188
src/commands/links/cmore.rs Normal file
View file

@ -0,0 +1,188 @@
use crate::{commands::utils, Context, Error};
use cached::proc_macro::cached;
use chrono::{DateTime, Utc};
use log::{info, warn};
use reqwest::header::AUTHORIZATION;
use serde::Deserialize;
use super::Timeframe;
#[derive(Deserialize, Clone)]
pub struct CmoreEvent {
#[serde(rename = "assetId")]
id: u32,
#[serde(rename = "asset")]
data: CmoreData,
}
#[derive(Deserialize, Clone)]
pub struct CmoreData {
#[serde(default)]
description: String,
title: String,
subtitle: String,
duration: i64,
// #[serde(rename = "humanDuration")]
// hduration: String,
#[serde(rename = "liveBroadcastTime")]
#[serde(with = "cmore_date")]
start: DateTime<Utc>,
// #[serde(rename = "mainCategoryTitle")]
// category: String,
}
impl CmoreEvent {
pub fn filter(&self, filter: &str) -> bool {
if filter.is_empty() {
return true;
};
if self.data.description.contains(filter) {
return true;
}
if self.data.title.contains(filter) {
return true;
}
if self.data.subtitle.contains(filter) {
return true;
}
false
}
pub fn comp(&self, when: &Timeframe) -> bool {
let now = Utc::now();
match when {
Timeframe::Everything => true,
Timeframe::Current => {
self.data.start <= now
&& self.data.start + chrono::Duration::seconds(self.data.duration) >= now
}
Timeframe::Future => {
self.data.start + chrono::Duration::seconds(self.data.duration) >= now
}
Timeframe::Past => {
self.data.start + chrono::Duration::seconds(self.data.duration) <= now
} // _ => self.data.times.end >= now,
}
}
pub fn to_string(&self) -> String {
format!(
"```md\n[{title}]({subtitle})```(<t:{start}:R>-<t:{end}:R>) https://tom.al/ms/cm/{id}",
title = self.data.title,
subtitle = self.data.subtitle,
start = self.data.start.timestamp(),
end = self.data.start.timestamp() + self.data.duration,
id = self.id
)
}
pub fn get_key(&self) -> (DateTime<Utc>, String) {
(self.data.start, self.data.description.clone())
}
}
mod cmore_date {
use chrono::{DateTime, TimeZone, Utc};
use serde::{self, Deserialize, Deserializer};
const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%SZ";
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Utc.datetime_from_str(&s, FORMAT)
.map_err(serde::de::Error::custom)
}
}
#[cached(time = 3600)]
pub async fn get_schedule() -> Option<Vec<CmoreEvent>> {
let token = super::super::super::SETTINGS
.read()
.unwrap()
.get_table("morningstreams")
.unwrap()
.get("token")
.expect("Config error, please set the morningstreams[token] value")
.clone()
.into_string()
.expect("Config error, please make sure morningstreams[token] is a string");
let client = reqwest::Client::new();
let req = client
.get(format!(
"https://api.morningstreams.com/api/hightier/cmore/schedule"
))
.header(AUTHORIZATION, token)
.send()
.await;
let result: Option<Vec<CmoreEvent>> = match req {
Err(e) => {
warn!("Error getting Cmore schedule {}", e);
None
}
Ok(req) if req.status().as_u16() == 404 => {
warn!("404 on getting cmore events");
None
}
Ok(req) if req.status().as_u16() == 200 => {
let data = req.json::<Vec<CmoreEvent>>().await;
match data {
Ok(d) => {
Some(d) // .iter().map(|e| e.asset.clone()).collect())
}
Err(e) => {
warn!("Error getting Cmore schedule {}", e);
None
}
}
}
Ok(req) => {
warn!(
"Unhandled status when parsing Cmore request {}",
req.status()
);
None
}
};
result
}
//Cmore events listing
#[poise::command(slash_command)]
pub async fn cmore(
ctx: Context<'_>,
#[description = "Filter sessions for when they are/were happening, defaults to future"]
timeframe: Option<super::Timeframe>,
#[description = "Content to filter on"] filter: Option<String>,
) -> Result<(), Error> {
let tf = match timeframe {
None => Timeframe::Future,
Some(tf) => tf,
};
let events: Option<Vec<CmoreEvent>> = get_schedule().await;
match events {
None => {
ctx.say("Unable to get the events, try again later (it's cached so wait a bit...)")
.await?;
}
Some(evs) => {
info!("Found {} events from cmore", evs.len());
let filtered: Vec<String> = evs
.into_iter()
.filter(|e| e.comp(&tf))
.filter(|e| match &filter {
None => true,
Some(f) => e.filter(f.as_str()),
})
.map(|e| e.to_string())
.collect();
let pages = utils::paginator(filtered, 1900, "\n".to_string());
utils::paginate_string(ctx, pages).await?;
}
};
Ok(())
}

View file

@ -108,7 +108,7 @@ struct Eurosport {
}
#[derive(Debug, Clone)]
struct ESEvents {
pub struct ESEvents {
id: String,
sport: Option<String>,
name: String,
@ -119,7 +119,7 @@ struct ESEvents {
}
impl ESEvents {
fn filter(&self, filter: &str) -> bool {
pub fn filter(&self, filter: &str) -> bool {
if self.name.to_lowercase().contains(filter) {
return true;
};
@ -137,18 +137,18 @@ impl ESEvents {
return false;
}
fn comp(&self, when: &Option<Timeframe>) -> bool {
pub fn comp(&self, when: &Timeframe) -> bool {
let now = Utc::now();
match when {
Some(Timeframe::Everything) => true,
Some(Timeframe::Current) => self.start <= now && self.end >= now,
Some(Timeframe::Future) => self.end >= now,
Some(Timeframe::Past) => self.end <= now,
_ => self.end >= now,
Timeframe::Everything => true,
Timeframe::Current => self.start <= now && self.end >= now,
Timeframe::Future => self.end >= now,
Timeframe::Past => self.end <= now,
// _ => self.end >= now,
}
}
fn to_string(&self) -> String {
pub fn to_string(&self) -> String {
match &self.sport {
// None => format!("```md\n({}) {}```\n", self.name, self.secondary),
None => format!(
@ -172,6 +172,10 @@ impl ESEvents {
),
}
}
pub fn get_key(&self) -> (DateTime<Utc>, String) {
(self.start, self.name.clone())
}
}
fn get_events(v: Eurosport) -> Result<Vec<ESEvents>, serde_json::Error> {
@ -233,7 +237,7 @@ fn get_events(v: Eurosport) -> Result<Vec<ESEvents>, serde_json::Error> {
#[cached(time = 3600)]
#[allow(dead_code)]
async fn get_eurosport_events(url: String) -> Option<Vec<ESEvents>> {
pub async fn get_eurosport_events(url: String) -> Option<Vec<ESEvents>> {
let cookie = super::super::super::SETTINGS
.read()
.unwrap()
@ -282,7 +286,13 @@ async fn get_eurosport_events(url: String) -> Option<Vec<ESEvents>> {
}
}
}
_ => None,
Ok(req) => {
warn!(
"Eurosport Unhandled request result {}",
req.status().as_u16()
);
None
}
};
return result;
}
@ -295,6 +305,11 @@ pub async fn eurosport(
timeframe: Option<Timeframe>,
#[description = "Content to filter on"] filter: Option<String>,
) -> Result<(), Error> {
let tf = match timeframe {
None => Timeframe::Future,
Some(tf) => tf,
};
let url = super::super::super::SETTINGS
.read()
.unwrap()
@ -314,7 +329,7 @@ pub async fn eurosport(
info!("Found {} events from eurosport", evs.len());
let strings = evs
.into_iter()
.filter(|e| e.comp(&timeframe))
.filter(|e| e.comp(&tf))
.filter(|e| match &filter {
None => true,
Some(f) => e.filter(f.as_str()),
@ -336,6 +351,10 @@ pub async fn proc_olympics(
timeframe: Option<Timeframe>,
filter: Option<String>,
) -> Result<(), Error> {
let tf = match timeframe {
None => Timeframe::Future,
Some(tf) => tf,
};
let url = super::super::super::SETTINGS
.read()
.unwrap()
@ -355,7 +374,7 @@ pub async fn proc_olympics(
info!("Found {} events from eurosport olympics ", evs.len());
let strings = evs
.into_iter()
.filter(|e| e.comp(&timeframe))
.filter(|e| e.comp(&tf))
.filter(|e| match &filter {
None => true,
Some(f) => e.filter(f.as_str()),

View file

@ -1,5 +1,7 @@
use crate::{Context, Error};
mod all;
mod cmore;
mod eurosport;
mod f1;
mod nfl;
@ -60,7 +62,15 @@ pub enum Timeframe {
#[poise::command(
slash_command,
subcommands("viaplay::viaplay", "eurosport::eurosport", "wrc::wrc", "f1::f1", "nfl::nfl")
subcommands(
"viaplay::viaplay",
"eurosport::eurosport",
"wrc::wrc",
"f1::f1",
"nfl::nfl",
"cmore::cmore",
"all::all"
)
)]
pub async fn links(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("Hello there!").await?;

View file

@ -1,9 +1,9 @@
use crate::{commands::utils, Context, Error};
use cached::proc_macro::cached;
use chrono::{DateTime, Duration, Utc};
use log::{warn, info};
use log::{info, warn};
use reqwest::header::AUTHORIZATION;
use serde::Deserialize;
use crate::{commands::utils, Context, Error};
use super::Timeframe;
@ -46,7 +46,7 @@ struct NFLContext {
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct NFLEvent {
pub struct NFLEvent {
home_nick_name: String,
visitor_nick_name: String,
// game_id: String,
@ -65,7 +65,7 @@ struct NFLVideo {
}
impl NFLEvent {
fn filter(&self, filter: &str) -> bool {
pub fn filter(&self, filter: &str) -> bool {
if filter.is_empty() {
return true;
};
@ -75,29 +75,34 @@ impl NFLEvent {
false
}
fn comp(&self, when: &Option<Timeframe>) -> bool {
pub fn comp(&self, when: &Timeframe) -> bool {
let now = Utc::now();
match when {
Some(Timeframe::Everything) => true,
Some(Timeframe::Current) => {
self.game_date_time_utc <= now && (self.game_date_time_utc + Duration::minutes(240)) >= now
Timeframe::Everything => true,
Timeframe::Current => {
self.game_date_time_utc <= now
&& (self.game_date_time_utc + Duration::minutes(240)) >= now
}
Some(Timeframe::Future) => (self.game_date_time_utc + Duration::minutes(240)) >= now,
Some(Timeframe::Past) => self.game_date_time_utc <= now,
_ => (self.game_date_time_utc + Duration::minutes(240)) >= now,
Timeframe::Future => (self.game_date_time_utc + Duration::minutes(240)) >= now,
Timeframe::Past => self.game_date_time_utc <= now,
// _ => (self.game_date_time_utc + Duration::minutes(240)) >= now,
}
}
fn to_string(&self) -> String {
pub fn to_string(&self) -> String {
format!(
"```fix\n{home}-{away} ||{title}```<t:{time}:R> https://tom.al/ms/nfl/{id}",
title = self.video.title,
id = self.video.video_id,
time = self.game_date_time_utc.timestamp(),
home= self.home_nick_name,
home = self.home_nick_name,
away = self.visitor_nick_name,
)
}
pub fn get_key(&self) -> (DateTime<Utc>, String) {
(self.game_date_time_utc, self.video.title.to_owned())
}
}
#[cached(time = 3600)]
@ -134,6 +139,13 @@ async fn get_week() -> Option<NFLContext> {
}
}
pub async fn get_current_schedule() -> Option<Vec<NFLEvent>> {
match get_week().await {
None => return None,
Some(w) => return get_schedule(w.current_week).await,
}
}
#[cached(time = 3600)]
async fn get_schedule(week: u8) -> Option<Vec<NFLEvent>> {
let token = super::super::super::SETTINGS
@ -183,7 +195,6 @@ async fn get_schedule(week: u8) -> Option<Vec<NFLEvent>> {
result
}
//NFL events listing
#[poise::command(slash_command)]
pub async fn nfl(
@ -191,17 +202,23 @@ pub async fn nfl(
#[description = "Filter sessions for when they are/were happening, defaults to future"]
timeframe: Option<super::Timeframe>,
#[description = "Content to filter on"] filter: Option<String>,
#[description = "Which game week? (Defaults to current)"]
week: Option<u8>,
#[description = "Which game week? (Defaults to current)"] week: Option<u8>,
) -> Result<(), Error> {
let get_week: u8 = match week{
let tf = match timeframe {
None => Timeframe::Future,
Some(tf) => tf,
};
let get_week: u8 = match week {
Some(w) => w,
None => {
match get_week().await {
None => { ctx.say("Error getting current week data, try setting one manually").await?; return Ok(())},
Some(w) => w.current_week,
None => match get_week().await {
None => {
ctx.say("Error getting current week data, try setting one manually")
.await?;
return Ok(());
}
}
Some(w) => w.current_week,
},
};
let events: Option<Vec<NFLEvent>> = get_schedule(get_week).await;
@ -214,7 +231,7 @@ pub async fn nfl(
info!("Found {} events from NFL", evs.len());
let filtered: Vec<String> = evs
.into_iter()
.filter(|e| e.comp(&timeframe))
.filter(|e| e.comp(&tf))
.filter(|e| match &filter {
None => true,
Some(f) => e.filter(f.as_str()),

View file

@ -24,7 +24,7 @@ use super::Timeframe;
// }
#[derive(Deserialize, Clone)]
struct ViaplayEvent {
pub struct ViaplayEvent {
content: Content,
#[serde(rename = "epg")]
times: EPG,
@ -32,28 +32,37 @@ struct ViaplayEvent {
}
impl ViaplayEvent {
fn filter(&self, filter: &str) -> bool {
pub fn filter(&self, filter: &str) -> bool {
if filter.is_empty() {
return true;
};
if self.content.format.sport.contains(filter) {
return true;
}
if self.content.description.contains(filter) {
return true;
}
if self.content.title.contains(filter) {
return true;
}
if self.content.synopsis.contains(filter) {
return true;
}
false
}
fn comp(&self, when: &Option<Timeframe>) -> bool {
pub fn comp(&self, when: &Timeframe) -> bool {
let now = Utc::now();
match when {
Some(Timeframe::Everything) => true,
Some(Timeframe::Current) => self.times.start <= now && self.times.end >= now,
Some(Timeframe::Future) => self.times.end >= now,
Some(Timeframe::Past) => self.times.end <= now,
_ => self.times.end >= now,
Timeframe::Everything => true,
Timeframe::Current => self.times.start <= now && self.times.end >= now,
Timeframe::Future => self.times.end >= now,
Timeframe::Past => self.times.end <= now,
// _ => self.times.end >= now,
}
}
fn to_string(&self) -> String {
pub fn to_string(&self) -> String {
format!("```md\n[{sport}]({title}) {synopsis}```(<t:{start}:R>-<t:{end}:R>) {desc}\nhttps://tom.al/ms/vp/{id}", sport=self.content.format.sport, title=self.content.title, synopsis=self.content.synopsis, start=self.times.start.timestamp(), end=self.times.end.timestamp(), desc=self.content.description, id=self.system.product_key)
}
@ -63,6 +72,10 @@ impl ViaplayEvent {
Some(s) => self.content.format.sport.contains(s),
}
}
pub fn get_key(&self) -> (DateTime<Utc>, String) {
(self.times.start, self.content.title.clone())
}
}
impl fmt::Display for ViaplayEvent {
@ -74,7 +87,7 @@ impl fmt::Display for ViaplayEvent {
}
#[derive(Deserialize, Clone)]
struct Content {
pub struct Content {
format: Format,
title: String,
#[serde(rename = "originalTitle")]
@ -96,7 +109,7 @@ struct Format {
sport: String,
}
#[derive(Deserialize, Clone)]
struct EPG {
pub struct EPG {
#[serde(with = "viaplay_date")]
start: DateTime<Utc>,
#[serde(with = "viaplay_date")]
@ -145,7 +158,7 @@ mod viaplay_sport {
}
#[cached(time = 3600)]
async fn get_schedule() -> Option<Vec<ViaplayEvent>> {
pub async fn get_schedule() -> Option<Vec<ViaplayEvent>> {
let token = super::super::super::SETTINGS
.read()
.unwrap()
@ -238,6 +251,10 @@ pub async fn viaplay(
#[autocomplete = "autocomplete_sport"]
sport: Option<String>,
) -> Result<(), Error> {
let tf = match timeframe {
None => Timeframe::Future,
Some(tf) => tf,
};
let events: Option<Vec<ViaplayEvent>> = get_schedule().await;
match events {
None => {
@ -248,7 +265,7 @@ pub async fn viaplay(
info!("Found {} events from viaplay", evs.len());
let filtered: Vec<String> = evs
.into_iter()
.filter(|e| e.comp(&timeframe))
.filter(|e| e.comp(&tf))
.filter(|e| e.check_sport(&sport))
.filter(|e| match &filter {
None => true,