Initial commit

This commit is contained in:
CenTdemeern1 2025-01-12 04:47:05 +01:00
commit 7494237d19
12 changed files with 1890 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/static/**.mjs

1600
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "fedirect"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = "0.5.1"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"

18
README.md Normal file
View file

@ -0,0 +1,18 @@
# FeDirect
Fedi links that open on your preferred instance!
## Building
To compile TypeScript, the build script assumes Deno is installed.
When you have Deno and Rust installed, simply use Cargo to build the project
```sh
# For example, to build for release, you can do
cargo build --release
# Or, to immediately run it
cargo run --release
# Or, to run during development:
cargo run
```

14
build.rs Normal file
View file

@ -0,0 +1,14 @@
use std::process::{Command, Stdio};
fn main() {
println!("cargo::rerun-if-changed=tsconfig.json");
println!("cargo::rerun-if-changed=static/**.mts");
assert!(Command::new("deno")
.args(["run", "-A", "npm:typescript/tsc"])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.unwrap()
.success());
}

89
known-software.json Normal file
View file

@ -0,0 +1,89 @@
{
"software": {
"misskey": {
"aliases": [
"misskey",
"mk"
],
"groups": [
"misskey-compliant",
"misskey-v13"
]
},
"sharkey": {
"aliases": [
"sharkey",
"sk"
],
"groups": [
"misskey-compliant",
"misskey-v13"
],
"forkOf": "misskey"
},
"iceshrimp-js": {
"aliases": [
"iceshrimp-js"
],
"groups": [
"misskey-compliant",
"misskey-v12",
"iceshrimp"
],
"forkOf": "misskey"
},
"iceshrimp-dotnet": {
"aliases": [
"iceshrimp-dotnet"
],
"groups": [
"misskey-compliant",
"misskey-v12",
"iceshrimp"
],
"forkOf": "misskey"
},
"firefish": {
"aliases": [
"firefish",
"calckey"
],
"groups": [
"misskey-compliant",
"misskey-v12"
],
"forkOf": "misskey"
}
},
"groups": {
"misskey-compliant": {
"aliases": [
"misskey-compliant",
"mkc"
]
},
"misskey-v12": {
"aliases": [
"misskey-v12",
"misskeyv12",
"misskey12",
"mkv12",
"mk12"
]
},
"misskey-v13": {
"aliases": [
"misskey-v13",
"misskeyv13",
"misskey13",
"mkv13",
"mk13"
]
},
"iceshrimp": {
"aliases": [
"iceshrimp"
]
}
}
}

67
src/known_software.rs Normal file
View file

@ -0,0 +1,67 @@
use rocket::request::FromParam;
use serde::Deserialize;
use std::{
collections::{HashMap, HashSet},
sync::LazyLock,
};
pub static KNOWN_SOFTWARE: LazyLock<KnownSoftware> =
LazyLock::new(|| serde_json::from_str(include_str!("../known-software.json")).unwrap());
pub static KNOWN_SOFTWARE_NAMES: LazyLock<HashMap<String, String>> =
LazyLock::new(|| KNOWN_SOFTWARE.get_name_map());
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Software {
aliases: HashSet<String>,
groups: HashSet<String>,
fork_of: Option<String>,
}
#[derive(Deserialize)]
pub struct Group {
aliases: HashSet<String>,
}
#[derive(Deserialize)]
pub struct KnownSoftware {
software: HashMap<String, Software>,
groups: HashMap<String, Group>,
}
impl KnownSoftware {
fn get_name_map(&self) -> HashMap<String, String> {
let mut map = HashMap::new();
self.software.iter().for_each(|(name, software)| {
software.aliases.iter().for_each(|alias| {
assert_eq!(map.insert(alias.to_owned(), name.to_owned()), None);
});
});
self.groups.iter().for_each(|(name, group)| {
group.aliases.iter().for_each(|alias: &String| {
assert_eq!(map.insert(alias.to_owned(), name.to_owned()), None);
});
});
map
}
}
pub struct KnownInstanceSoftware<'r> {
pub requested: &'r str,
pub resolved: &'static String,
}
impl<'r> FromParam<'r> for KnownInstanceSoftware<'r> {
type Error = &'r str;
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
if let Some(resolved) = KNOWN_SOFTWARE_NAMES.get(param) {
Ok(KnownInstanceSoftware {
requested: param,
resolved,
})
} else {
Err(param)
}
}
}

51
src/main.rs Normal file
View file

@ -0,0 +1,51 @@
#[macro_use]
extern crate rocket;
use known_software::KnownInstanceSoftware;
use rocket::{
fs::{FileServer, NamedFile},
http::ContentType,
};
use std::path::PathBuf;
mod known_software;
#[get("/known-software.json")]
async fn known_software_json() -> Option<NamedFile> {
NamedFile::open("known-software.json").await.ok()
}
#[get("/<instance>/<route..>", rank = 1)]
async fn route_for_known_instance_software(
instance: KnownInstanceSoftware<'_>,
route: PathBuf,
) -> Option<NamedFile> {
NamedFile::open("static/crossroad.html").await.ok()
}
#[get("/<instance>/<route..>", rank = 2)]
fn route_for_unknown_instance_software(instance: &str, route: PathBuf) -> (ContentType, String) {
(
ContentType::HTML,
format!(
"Hello from <code>{}</code>! The software <code>{}</code> is unknown.",
route.display(),
instance
),
)
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/static", FileServer::from("static").rank(0))
.mount("/api", routes![])
.mount(
"/",
routes![
known_software_json,
route_for_known_instance_software,
route_for_unknown_instance_software
],
)
}

11
static/crossroad.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FeDirect</title>
</head>
<body>
<script type="module" src="/static/crossroad.mjs"></script>
</body>
</html>

2
static/crossroad.mts Normal file
View file

@ -0,0 +1,2 @@
import knownSoftware from "./known_software.mjs";
console.log(knownSoftware);

16
static/known_software.mts Normal file
View file

@ -0,0 +1,16 @@
type Software = {
aliases: string[],
groups: string[],
forkOf?: string,
}
type Group = {
aliases: string[],
}
type KnownSoftware = {
software: Record<string, Software>,
groups: Record<string, Group>,
}
export default await fetch("/known-software.json").then(r => r.json()) as KnownSoftware;

11
tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2023",
"allowJs": true,
"checkJs": true,
"strictNullChecks": true,
},
"include": [
"static/**.mts",
],
}