/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ use self::playersync::{r_playersync, PlayersyncChannels}; use crate::{database::Database, routes::ui::error::MyResult}; use api::{ r_api_account_login, r_api_asset_token_raw, r_api_nodes_modified_since, r_api_root, r_api_version, }; use base64::Engine; use compat::{ jellyfin::{ r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css, r_jellyfin_displaypreferences_usersettings, r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items, r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop, r_jellyfin_items_intros, r_jellyfin_items_item, r_jellyfin_items_playbackinfo, r_jellyfin_items_similar, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons, r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled, r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing, r_jellyfin_sessions_playing_progress, r_jellyfin_shows_nextup, r_jellyfin_socket, r_jellyfin_system_endpoint, r_jellyfin_system_info, r_jellyfin_system_info_public, r_jellyfin_system_info_public_case, r_jellyfin_users_authenticatebyname, r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items, r_jellyfin_users_items_item, r_jellyfin_users_public, r_jellyfin_users_views, r_jellyfin_video_stream, }, youtube::{r_youtube_channel, r_youtube_embed, r_youtube_watch}, }; use jellybase::{federation::Federation, CONF, SECRETS}; use log::warn; use rand::random; use rocket::{ catchers, config::SecretKey, fairing::AdHoc, fs::FileServer, get, http::Header, response::{self, Responder}, routes, shield::Shield, Build, Config, Request, Rocket, }; use std::fs::File; use stream::r_stream; use ui::{ account::{ r_account_login, r_account_login_post, r_account_logout, r_account_logout_post, r_account_register, r_account_register_post, settings::{r_account_settings, r_account_settings_post}, }, admin::{ log::{r_admin_log, r_admin_log_stream}, r_admin_dashboard, r_admin_delete_cache, r_admin_import, r_admin_invite, r_admin_remove_invite, r_admin_transcode_posters, r_admin_update_search, user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users}, }, assets::{r_asset, r_item_backdrop, r_item_poster, r_node_thumbnail, r_person_asset}, browser::r_all_items_filter, error::{r_api_catch, r_catch}, home::r_home, node::r_library_node_filter, player::r_player, r_index, search::r_search, stats::r_stats, style::{r_assets_font, r_assets_js, r_assets_js_map, r_assets_style} }; use userdata::{ r_node_userdata, r_node_userdata_progress, r_node_userdata_rating, r_node_userdata_watched, }; pub mod api; pub mod compat; pub mod playersync; pub mod stream; pub mod ui; pub mod userdata; #[macro_export] macro_rules! uri { ($kk:stmt) => { &rocket::uri!($kk).to_string() }; } pub fn build_rocket(database: Database, federation: Federation) -> Rocket { rocket::build() .configure(Config { address: std::env::var("BIND_ADDR") .map(|e| e.parse().unwrap()) .unwrap_or("127.0.0.1".parse().unwrap()), port: std::env::var("PORT") .map(|e| e.parse().unwrap()) .unwrap_or(8000), secret_key: SecretKey::derive_from( SECRETS .cookie_key .clone() .unwrap_or_else(|| { warn!("cookie_key not configured, generating a random one."); base64::engine::general_purpose::STANDARD.encode([(); 32].map(|_| random())) }) .as_bytes(), ), ip_header: Some("x-forwarded-for".into()), ..Default::default() }) .manage(database) .manage(federation) .manage(PlayersyncChannels::default()) .attach(AdHoc::on_response("set server header", |_req, res| { res.set_header(Header::new("server", "jellything")); Box::pin(async {}) })) // TODO this would be useful but needs to handle not only the entry-point // .attach(AdHoc::on_response("frame options", |req, resp| { // if !req.uri().path().as_str().starts_with("/embed") { // resp.set_raw_header("X-Frame-Options", "SAMEORIGIN"); // } // Box::pin(async {}) // })) .attach(Shield::new()) .register("/", catchers![r_catch]) .register("/api", catchers![r_api_catch]) .mount("/assets", FileServer::from(&CONF.asset_path)) .mount( "/", routes![ // Frontend r_account_login_post, r_account_login, r_account_logout_post, r_account_logout, r_account_register_post, r_account_register, r_account_settings_post, r_account_settings, r_admin_dashboard, r_admin_delete_cache, r_admin_import, r_admin_invite, r_admin_log_stream, r_admin_log, r_admin_remove_invite, r_admin_remove_user, r_admin_transcode_posters, r_admin_update_search, r_admin_user_permission, r_admin_user, r_admin_users, r_all_items_filter, r_asset, r_assets_font, r_assets_js_map, r_assets_js, r_assets_style, r_favicon, r_home, r_index, r_item_backdrop, r_item_poster, r_library_node_filter, r_node_thumbnail, r_node_userdata_progress, r_node_userdata_rating, r_node_userdata_watched, r_node_userdata, r_person_asset, r_player, r_playersync, r_search, r_stats, r_stream, // API r_api_account_login, r_api_asset_token_raw, r_api_nodes_modified_since, r_api_root, r_api_version, // Compat r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css, r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_displaypreferences_usersettings, r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop, r_jellyfin_items_intros, r_jellyfin_items_item, r_jellyfin_items_playbackinfo, r_jellyfin_items_similar, r_jellyfin_items, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons, r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled, r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing_progress, r_jellyfin_sessions_playing, r_jellyfin_shows_nextup, r_jellyfin_socket, r_jellyfin_system_endpoint, r_jellyfin_system_info_public_case, r_jellyfin_system_info_public, r_jellyfin_system_info, r_jellyfin_users_authenticatebyname, r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items_item, r_jellyfin_users_items, r_jellyfin_users_public, r_jellyfin_users_views, r_jellyfin_video_stream, r_youtube_channel, r_youtube_embed, r_youtube_watch, ], ) } #[get("/favicon.ico")] fn r_favicon() -> MyResult { Ok(File::open(CONF.asset_path.join("favicon.ico"))?) } pub struct Cors(pub T); impl<'r, T: Responder<'r, 'static>> Responder<'r, 'static> for Cors { fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> { let mut r = self.0.respond_to(request)?; r.adjoin_header(Header::new("access-controll-allow-origin", "*")); Ok(r) } }