diff --git a/Cargo.lock b/Cargo.lock index 202ebf4..7760ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,7 +346,7 @@ dependencies = [ [[package]] name = "fedirect" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bytes", "favicon-scraper", @@ -1527,6 +1527,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.7", diff --git a/Cargo.toml b/Cargo.toml index 95fdd5d..ed5a0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "fedirect" -version = "0.1.0" +version = "0.1.0-alpha" edition = "2021" [dependencies] bytes = "1.9.0" favicon-scraper = "0.3.1" -reqwest = { version = "0.12.12", features = ["stream"] } +reqwest = { version = "0.12.12", features = ["stream", "blocking"] } rocket = { version = "0.5.1", features = ["json"] } semver = "1.0.24" serde = { version = "1.0.217", features = ["derive"] } diff --git a/src/api/about.rs b/src/api/about.rs new file mode 100644 index 0000000..657aa5b --- /dev/null +++ b/src/api/about.rs @@ -0,0 +1,54 @@ +use reqwest::blocking::Client; +use rocket::serde::json::Json; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::OnceLock; + +/// Gets the FeDirect version +#[get("/about/version")] +pub async fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +#[derive(Debug, Serialize)] +pub struct Avatars { + charlotte: Option, + kio: Option, +} + +#[derive(Deserialize)] +struct MisskeyUser { + #[serde(rename = "avatarUrl")] + avatar_url: String, +} + +fn get_avatar(client: &Client, origin: &str, user_id: &str) -> Option { + let body = json!({ + "userId": user_id + }) + .to_string(); + let user: MisskeyUser = client + .post(format!("https://{origin}/api/users/show")) + .header("content-type", "application/json") + .body(body) + .send() + .ok()? + .json() + .ok()?; + Some(user.avatar_url) +} + +pub static AVATARS: OnceLock = OnceLock::new(); + +pub fn init() { + let client = Client::new(); + let charlotte = get_avatar(&client, "eepy.moe", "9xt2s326nxev039h"); + let kio = get_avatar(&client, "kitsunes.club", "9810gvfne3"); + AVATARS.set(Avatars { charlotte, kio }).unwrap(); +} + +/// Gets (relatively) up-to-date Nekomata avatars +#[get("/about/nekomata_avatars")] +pub async fn nekomata_avatars() -> Json<&'static Avatars> { + Json(AVATARS.get().unwrap()) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 5faff5a..a3e775a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,11 +1,18 @@ use rocket::Route; +pub mod about; pub mod instance_info; pub mod proxy; +pub fn init() { + about::init(); +} + pub fn get_routes() -> Vec { routes![ instance_info::instance_info, + about::version, + about::nekomata_avatars, // Proxy is temporarily disabled as it's not needed // proxy::proxy ] diff --git a/src/main.rs b/src/main.rs index c12ec48..734b6d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,6 +44,8 @@ fn route_for_unknown_instance_software(instance: &str, route: PathBuf) -> (Conte #[launch] fn rocket() -> _ { + api::init(); + rocket::build() .mount("/static", FileServer::from("static").rank(0)) .mount("/api", api::get_routes()) diff --git a/static/about.mts b/static/about.mts new file mode 100644 index 0000000..6aaa2d0 --- /dev/null +++ b/static/about.mts @@ -0,0 +1,27 @@ +import { Dialog } from "./dialog.mjs"; +import { findAnchorOrFail, findButtonOrFail, findDialogOrFail, findImageOrFail, findSpanOrFail } from "./dom.mjs"; + +const openButton = findAnchorOrFail(document.body, "#aboutLink"); +const dialog = findDialogOrFail(document.body, "#about") +const versionParagraph = findSpanOrFail(dialog, "#version"); +const charlotteImage = findImageOrFail(dialog, "#charlotteAvatar"); +const kioImage = findImageOrFail(dialog, "#kioAvatar"); +const closeButton = findButtonOrFail(dialog, ".close"); + +const aboutDialog = new Dialog(dialog); + +openButton.addEventListener("click", e => aboutDialog.open()); +closeButton.addEventListener("click", e => aboutDialog.close()); + +populateVersion(); +populateUsers(); + +async function populateVersion() { + versionParagraph.innerText = await fetch("/api/about/version").then(r => r.text()); +} + +async function populateUsers() { + const { charlotte, kio } = await fetch("/api/about/nekomata_avatars").then(r => r.json()); + if (charlotte) charlotteImage.src = charlotte; + if (kio) kioImage.src = kio; +} diff --git a/static/config.html b/static/config.html index ac206fb..fb20253 100644 --- a/static/config.html +++ b/static/config.html @@ -11,12 +11,14 @@ +

FeDirect

-

  By Nekomata

+

  By Nekomata

@@ -109,6 +111,36 @@ Unchecking this is not recommended, and this option only exists for exceptional
+ +
+
+

About FeDirect

+

  (v)

+
+

+ FeDirect links the Fediverse together by allowing you to create generic links that + link people to their native instance! +

+ Source code +

About Nekomata

+
+
+ Charlotte's avatar +

Charlotte

+ @CenTdemeern1@eepy.moe +

Programming, design

+
+
+ Charlotte's avatar +

Kio

+ @Kio@kitsunes.club +

Funding, hosting, design

+
+ Nekomata Logo +
+ +
+
diff --git a/static/crossroad.html b/static/crossroad.html index 8ca3031..4fef273 100644 --- a/static/crossroad.html +++ b/static/crossroad.html @@ -11,12 +11,14 @@ +

FeDirect

-

  By Nekomata

+

  By Nekomata

@@ -120,6 +122,36 @@ Unchecking this is not recommended, and this option only exists for exceptional
+ +
+
+

About FeDirect

+

  (v)

+
+

+ FeDirect links the Fediverse together by allowing you to create generic links that + link people to their native instance! +

+ Source code +

About Nekomata

+
+
+ Charlotte's avatar +

Charlotte

+ @CenTdemeern1@eepy.moe +

Programming, design

+
+
+ Charlotte's avatar +

Kio

+ @Kio@kitsunes.club +

Funding, hosting, design

+
+ Nekomata Logo +
+ +
+
diff --git a/static/dom.mts b/static/dom.mts index a4bf983..e879ec8 100644 --- a/static/dom.mts +++ b/static/dom.mts @@ -1,6 +1,13 @@ // I would've LOVED to use generics for this but unfortunately that's not possible. // Type safety, but at what cost... >~< thanks TypeScript +export function findAnchorOrFail(on: Element, selector: string): HTMLAnchorElement { + const element = on.querySelector(selector); + if (!(element instanceof HTMLAnchorElement)) + throw new Error(`${selector} isn't an a`); + return element; +} + export function findDivOrFail(on: Element, selector: string): HTMLDivElement { const element = on.querySelector(selector); if (!(element instanceof HTMLDivElement)) diff --git a/static/main.css b/static/main.css index 7667227..1441b99 100644 --- a/static/main.css +++ b/static/main.css @@ -63,6 +63,10 @@ abbr[title] { /* Generic styling properties */ +.wrap-balance { + text-wrap: balance; +} + .align-start { text-align: start; } @@ -139,6 +143,10 @@ abbr[title] { min-height: 50%; } +.half-width-max { + max-width: 50%; +} + .full-width { min-width: 100%; } @@ -151,6 +159,16 @@ abbr[title] { height: var(--medium); } +.xl-size { + width: var(--xl); + height: var(--xl); +} + +.xl-size-max { + max-width: var(--xl); + max-height: var(--xl); +} + .separator-bottom { border-bottom: solid 1px var(--transparent-black); } @@ -163,6 +181,10 @@ abbr[title] { margin-top: auto; } +.margin-none-top { + margin-top: 0; +} + .margin-large-bottom { margin-bottom: var(--large); } @@ -175,6 +197,13 @@ abbr[title] { aspect-ratio: 1; } +.absolute-centered { + position: absolute; + top: 50%; + left: 50%; + translate: -50% -50%; +} + /* Specialized elements */ .iconContainer { @@ -206,6 +235,31 @@ abbr[title] { margin-top: min(var(--xl), 6vh); } +.circlingMembers { + position: relative; + width: 24em; + height: 24em; + animation-play-state: running; + transition: animation-play-state 1s; +} + +.circlingMembers:hover { + animation-play-state: paused; +} + +.member { + animation: 8s infinite linear orbit; + animation-play-state: inherit; +} + +.member.charlotte { + --orbit-translate-x: 8em; +} + +.member.kio { + --orbit-translate-x: -8em; +} + /* Animations */ .pulse-red { @@ -220,4 +274,14 @@ abbr[title] { 100% { box-shadow: 0px 0px 20px var(--red); } +} + +@keyframes orbit { + 0% { + transform: rotate(0deg) translateX(var(--orbit-translate-x)) rotate(0deg); + } + + 100% { + transform: rotate(360deg) translateX(var(--orbit-translate-x)) rotate(-360deg); + } } \ No newline at end of file