diff --git a/src/commands/links/mod.rs b/src/commands/links/mod.rs
index 240a310..8116b5f 100644
--- a/src/commands/links/mod.rs
+++ b/src/commands/links/mod.rs
@@ -1,8 +1,10 @@
 use crate::{commands::utils, Context, Error};
 
 pub mod eurosport;
+pub mod viaplay;
+pub mod wrc;
 
-#[derive(Debug, poise::SlashChoiceParameter)]
+#[derive(Debug, poise::ChoiceParameter)]
 pub enum Timeframe {
     #[name = "Currently happening"]
     Current,
@@ -14,33 +16,52 @@ pub enum Timeframe {
     Everything,
 }
 
-#[derive(Debug, poise::SlashChoiceParameter)]
-pub enum Source {
-    #[name = "Get links for the Eurosport player"]
-    Eurosport,
-    #[name = "Get links for the apocalympics Eurosport player"]
-    Olympics,
-}
+// #[derive(Debug, poise::ChoiceParameter)]
+// pub enum Source {
+//     #[name = "Get links for the Eurosport player"]
+//     Eurosport,
+//     // #[name = "Get links for the apocalympics Eurosport player"]
+//     // Olympics,
+//     #[name = "Get WRC links for the WRC player"]
+//     WRC,
+//     #[name = "Get Viaplay links for the Viaplay player"]
+//     Viaplay,
+//     // #[name = "F1 content for the weekend"]
+//     // F1,
+// }
 
 /// Get links for high tier commands.
-#[poise::command(slash_command)]
-pub async fn links(
-    ctx: Context<'_>,
-    #[description = "Where to git the juicy links from?"] source: Source,
-    #[description = "Filter sessions for when they are/were happening, defaults to future"]
-    timeframe: Option<Timeframe>,
-    #[description = "Content to filter on"] filter: Option<String>,
-) -> Result<(), Error> {
-    if !utils::high_tier(ctx).await {
-        ctx.say("This command can only be used in high tier channels for security")
-            .await?;
-        return Ok(());
-    }
+// #[poise::command(slash_command)]
+// pub async fn links2(
+//     ctx: Context<'_>,
+//     #[description = "Where to git the juicy links from?"] source: Source,
+//     #[description = "Filter sessions for when they are/were happening, defaults to future"]
+//     timeframe: Option<Timeframe>,
+//     #[description = "Content to filter on"] filter: Option<String>,
+// ) -> Result<(), Error> {
+//     if !utils::high_tier(ctx).await {
+//         ctx.say("This command can only be used in high tier channels for security")
+//             .await?;
+//         return Ok(());
+//     }
 
-    match source {
-        Source::Eurosport => eurosport::proc_eurosport(ctx, timeframe, filter).await,
-        Source::Olympics => eurosport::proc_olympics(ctx, timeframe, filter).await,
-    }
+//     match source {
+//         Source::Eurosport => eurosport::proc_eurosport(ctx, timeframe, filter).await,
+//         // Source::Olympics => eurosport::proc_olympics(ctx, timeframe, filter).await,
+//         Source::WRC => wrc::wrc(ctx).await,
+//         Source::Viaplay => viaplay::viaplay(ctx, timeframe, filter).await,
+//         // Source::F1 => f1::proc_f1(ctx, timeframe, filter).await,
+//     }
 
-    // Ok(())
+//     // Ok(())
+// }
+
+#[poise::command(
+    prefix_command,
+    slash_command,
+    subcommands("viaplay::viaplay", "eurosport::eurosport", "wrc::wrc")
+)]
+pub async fn links(ctx: Context<'_>) -> Result<(), Error> {
+    ctx.say("Hello there!").await?;
+    Ok(())
 }
diff --git a/src/commands/links/viaplay.rs b/src/commands/links/viaplay.rs
new file mode 100644
index 0000000..996bd67
--- /dev/null
+++ b/src/commands/links/viaplay.rs
@@ -0,0 +1,281 @@
+use std::{collections::HashSet, fmt};
+
+use crate::{commands::utils, Context, Error};
+use cached::proc_macro::cached;
+use chrono::{DateTime, Utc};
+use futures::{Stream, StreamExt};
+use log::{info, warn};
+use reqwest::header::AUTHORIZATION;
+use serde::Deserialize;
+
+use super::Timeframe;
+
+// const translations: HashMap<String, String> = HashMap::from([
+//     ("Valioliiga", "Premier league"),
+//     ("Gjensidige Kvindeliga", "Mutual Women's League (Norway)"),
+//     ("Tanskan 1. divisioona", "Danish 1st Division"),
+//     ("Bundesliiga", "Bundesliga"),
+//     ("2. Bundesliiga", "2. Bundesliga"),
+// ]);
+
+// #[derive(Deserialize, Clone)]
+// struct ViaplaySchedule {
+//     events: Vec<ViaplayEvent>,
+// }
+
+#[derive(Deserialize, Clone)]
+struct ViaplayEvent {
+    content: Content,
+    #[serde(rename = "epg")]
+    times: EPG,
+    system: System,
+}
+
+impl ViaplayEvent {
+    fn filter(&self, filter: &str) -> bool {
+        if filter.is_empty() {
+            return true;
+        };
+        if self.content.format.sport.contains(filter) {
+            return true;
+        }
+        false
+    }
+
+    fn comp(&self, when: &Option<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,
+        }
+    }
+
+    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)
+    }
+
+    fn check_sport(&self, sport: &Option<String>) -> bool {
+        match sport {
+            None => true,
+            Some(s) => self.content.format.sport.contains(s),
+        }
+    }
+}
+
+impl fmt::Display for ViaplayEvent {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // Use `self.number` to refer to each positional data point.
+        // write!(f, "```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)
+        write!(f, "{}", self.to_string())
+    }
+}
+
+#[derive(Deserialize, Clone)]
+struct Content {
+    format: Format,
+    title: String,
+    #[serde(rename = "originalTitle")]
+    #[serde(default)]
+    description: String,
+    #[serde(default)]
+    synopsis: String,
+}
+
+fn default_sport() -> String {
+    "Unknown".to_string()
+}
+
+#[derive(Deserialize, Clone)]
+struct Format {
+    #[serde(rename = "title")]
+    #[serde(default = "default_sport")]
+    #[serde(with = "viaplay_sport")]
+    sport: String,
+}
+#[derive(Deserialize, Clone)]
+struct EPG {
+    #[serde(with = "viaplay_date")]
+    start: DateTime<Utc>,
+    #[serde(with = "viaplay_date")]
+    end: DateTime<Utc>,
+}
+#[derive(Deserialize, Clone)]
+struct System {
+    #[serde(rename = "productKey")]
+    product_key: String,
+}
+
+mod viaplay_date {
+    use chrono::{DateTime, TimeZone, Utc};
+    use serde::{self, Deserialize, Deserializer};
+
+    const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S.%fZ";
+
+    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)
+    }
+}
+
+mod viaplay_sport {
+    use serde::{self, Deserialize, Deserializer};
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = String::deserialize(deserializer)?;
+        match s.as_str() {
+            "Valioliiga" => Ok("Premier league".to_string()),
+            "Gjensidige Kvindeliga" => Ok("Mutual Women's League (Norway)".to_string()),
+            "Tanskan 1. divisioona" => Ok("Danish 1st Division".to_string()),
+            "Bundesliiga" => Ok("Bundesliga".to_string()),
+            "2. Bundesliiga" => Ok("2. Bundesliga".to_string()),
+            "Hevosurheilu" => Ok("Equestrian sport".to_string()),
+            _ => Ok(s.to_string()),
+        }
+    }
+}
+
+#[cached(time = 3600)]
+async fn get_schedule() -> Option<Vec<ViaplayEvent>> {
+    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/viaplay/schedule"
+        ))
+        .header(AUTHORIZATION, token)
+        .send()
+        .await;
+
+    let result: Option<Vec<ViaplayEvent>> = match req {
+        Err(e) => {
+            warn!("Error getting Viaplay schedule {}", e);
+            None
+        }
+        Ok(req) if req.status().as_u16() == 404 => {
+            warn!("404 on getting VP events");
+            None
+        }
+        Ok(req) if req.status().as_u16() == 200 => {
+            let data = req.json::<Vec<ViaplayEvent>>().await;
+            match data {
+                Ok(d) => Some(d),
+                Err(e) => {
+                    warn!("Error getting Viaplay schedule {}", e);
+                    None
+                }
+            }
+        }
+        Ok(req) => {
+            warn!(
+                "Unhandled status when parsing viaplay request {}",
+                req.status()
+            );
+            None
+        }
+    };
+    result
+}
+
+#[cached(time = 3600)]
+pub async fn get_sports() -> Vec<String> {
+    // let events = get_schedule();
+    if let Some(events) = get_schedule().await {
+        let mut result: HashSet<String> = HashSet::new();
+        for event in events {
+            result.insert(event.content.format.sport);
+        }
+        return Vec::from_iter(result);
+    } else {
+        return vec![];
+    }
+}
+
+// #[allow(dead_code)]
+// async fn autocomplete_sport(_ctx: Context<'_>, partial: String) -> impl Stream<Item = String> {
+//     // futures::stream::iter(get_sports().iter())
+//     // .filter(move |name| futures::future::ready(name.contains(&partial)))
+//     // .map(|name| name.to_string())
+//     futures::stream::iter(get_sports().await)
+//         .filter(move |name| futures::future::ready(name.contains(&partial)))
+// }
+
+async fn autocomplete_sport<'a>(
+    _ctx: Context<'_>,
+    partial: &'a str,
+) -> impl Stream<Item = String> + 'a {
+    futures::stream::iter(get_sports().await)
+        .filter(move |name| futures::future::ready(name.contains(&partial)))
+        .map(|name| name.to_string())
+}
+
+//Viaplay events listing
+#[poise::command(slash_command)]
+pub async fn viaplay(
+    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>,
+    #[description = "Filter for which sport to list"]
+    #[autocomplete = "autocomplete_sport"]
+    sport: Option<String>,
+) -> Result<(), Error> {
+    let events: Option<Vec<ViaplayEvent>> = 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 viaplay", evs.len());
+            let filtered: Vec<String> = evs
+                .into_iter()
+                .filter(|e| e.comp(&timeframe))
+                .filter(|e| e.check_sport(&sport))
+                .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(())
+}
+
+// /// Another subcommand of `parent`
+// #[poise::command(slash_command)]
+// pub async fn child3(
+//     ctx: Context<'_>,
+//     #[description = "Where to git the juicy links from?"]
+//     _source: super::Source,
+//     #[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 = "Filter for which sport to list (only Viaplay)"]
+//     #[autocomplete = "autocomplete_sport"]
+//     sport: Option<String>,
+// ) -> Result<(), Error> {
+//     ctx.say("You invoked the second child command!").await?;
+//     Ok(())
+// }
diff --git a/src/commands/links/wrc.rs b/src/commands/links/wrc.rs
new file mode 100644
index 0000000..7efa160
--- /dev/null
+++ b/src/commands/links/wrc.rs
@@ -0,0 +1,118 @@
+use crate::{commands::utils, Context, Error};
+
+use chrono::{DateTime, Utc};
+use log::*;
+use serde::Deserialize;
+
+mod wrc_date {
+    use chrono::{DateTime, TimeZone, Utc};
+    use serde::{self, Deserialize, Deserializer};
+
+    const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S%:z";
+
+    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)
+    }
+}
+
+#[derive(Deserialize)]
+struct WRC {
+    // #[serde(rename = "earliestPlayableStart")]
+    // #[serde(with = "es_date_time")]
+    // earliest_playable_start: DateTime<Utc>,
+    // id: u32,
+    name: String,
+    #[serde(rename = "eventDays")]
+    days: Vec<WRCDays>,
+}
+
+#[derive(Deserialize)]
+struct WRCDays {
+    // id: u32,
+    #[serde(rename = "eventDay")]
+    event_day: String,
+    #[serde(rename = "spottChannel")]
+    spott_channel: WRCChannel,
+}
+
+#[derive(Deserialize)]
+struct WRCChannel {
+    // id: u32,
+    // #[serde(rename = "displayName")]
+    // name: String,
+    assets: Vec<WRCAssets>,
+}
+
+#[derive(Deserialize)]
+struct WRCAssets {
+    // id: u32,
+    #[serde(with = "wrc_date")]
+    start: DateTime<Utc>,
+    #[serde(with = "wrc_date")]
+    end: DateTime<Utc>,
+    // duration: u32,
+    content: WRCContent,
+}
+
+#[derive(Deserialize)]
+struct WRCContent {
+    title: String,
+    id: u32,
+    #[serde(default)]
+    description: Option<String>,
+}
+
+async fn get_schedule() -> Result<Option<WRC>, Error> {
+    let client = reqwest::Client::new();
+    let req = client
+        .get("https://api.wrc.com/sdb/rallyevent/active/")
+        .send()
+        .await?;
+    if req.status().as_u16() == 404 {
+        info!("Error 404 on getting wrc schedule");
+        Ok(None)
+    } else if req.status().as_u16() == 200 {
+        let data: WRC = req.json().await?;
+        Ok(Some(data))
+    } else {
+        Ok(None)
+    }
+}
+
+// WRC sessions
+#[poise::command(slash_command)]
+pub async fn wrc(
+    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 wrc = get_schedule().await?;
+    match wrc {
+        None => {
+            ctx.say("No WRC sessions found").await?;
+            Ok(())
+        }
+        Some(wrc_data) => {
+            let mut events: Vec<String> = vec![];
+            for day in wrc_data.days {
+                for session in day.spott_channel.assets {
+                    let desc = if let Some(d) = session.content.description {
+                        d
+                    } else {
+                        "".to_string()
+                    };
+                    events.push(format!("{}: <t:{}:R>- <t:{}:R>: [{}](https://morningstreams.com/wrcplayer.html?id={}) {} {}", day.event_day, session.start.timestamp(), session.end.timestamp(), session.content.id, session.content.id, session.content.title, desc));
+                }
+            }
+            let pages = utils::paginator(events, 2400, "\n".to_owned());
+            utils::paginate_string_embed(ctx, format!("WRC {}", wrc_data.name), pages).await?;
+            Ok(())
+        }
+    }
+}