Compare commits

...

17 commits

Author SHA1 Message Date
asonix 171e35d656 Check without default features in drone for all targets
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-08-17 17:01:29 -05:00
asonix e624f88536 Check without default features in drone
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-17 16:59:07 -05:00
asonix 0835c0118e Make awc optional
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-17 16:57:49 -05:00
asonix 5dec9f6427 Update flake 2023-08-17 16:57:39 -05:00
asonix c4d97f378f Update pretty env logger
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-03 13:12:28 -05:00
asonix 2f5cdc93f7 Add nix 2023-06-03 13:09:59 -05:00
Aode (Lion) 8ab8f74821 Update versions and examples
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-08 11:56:05 -06:00
Aode (Lion) fe105531f6 Fix repo link
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-03-08 11:14:01 -06:00
Aode (Lion) a5c5877010 Stable deps
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-03-08 11:13:43 -06:00
Aode (lion) 27c472c196 Clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-12-30 12:14:29 -06:00
Aode (lion) 1a40bd0355 Add drone
Some checks failed
continuous-integration/drone/tag Build is failing
2021-12-30 12:08:38 -06:00
Aode (lion) 7a5787e3ca Update to aw-b.18 2021-12-30 12:06:02 -06:00
Aode (lion) b089972fd8 Bump version 2021-11-18 10:33:24 -06:00
Aode (lion) 9f214fbbff Support arbitrary schemes 2021-11-18 10:18:06 -06:00
Aode (Lion) b42225d3e5 2021 2021-10-21 16:21:44 -05:00
asonix 35e7220b1d Update example to latest betas 2021-06-26 18:07:27 -05:00
asonix 5849e8ae5e Update to latest actix betas 2021-03-09 19:58:42 -06:00
9 changed files with 510 additions and 51 deletions

311
.drone.yml Normal file
View file

@ -0,0 +1,311 @@
kind: pipeline
type: docker
name: clippy
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: clippy
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- rustup component add clippy
- cargo clippy -- -D warnings
- cargo clippy --no-default-features -- -D warnings
- cargo clippy --example fetch -- -D warnings
- cargo clippy --example resolver -- -D warnings
trigger:
event:
- tag
- push
- pull_request
---
kind: pipeline
type: docker
name: tests
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: tests
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo test
trigger:
event:
- tag
- push
- pull_request
---
kind: pipeline
type: docker
name: check-amd64
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo check --target=$TARGET
- cargo check --target=$TARGET --no-default-features
- cargo check --target=$TARGET --example fetch
- cargo check --target=$TARGET --example resolver
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: check-arm64v8
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm64v8
pull: always
commands:
- cargo check --target=$TARGET
- cargo check --target=$TARGET --no-default-features
- cargo check --target=$TARGET --example fetch
- cargo check --target=$TARGET --example resolver
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: check-arm32v7
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm32v7
pull: always
commands:
- cargo check --target=$TARGET
- cargo check --target=$TARGET --no-default-features
- cargo check --target=$TARGET --example fetch
- cargo check --target=$TARGET --example resolver
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: build-amd64
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo build --target=$TARGET
- cargo build --target=$TARGET --example fetch
- cargo build --target=$TARGET --example resolver
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: build-arm64v8
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm64v8
pull: always
commands:
- cargo build --target=$TARGET
- cargo build --target=$TARGET --example fetch
- cargo build --target=$TARGET --example resolver
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: build-arm32v7
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: build
image: asonix/rust-builder:latest-linux-arm32v7
pull: always
commands:
- cargo build --target=$TARGET
- cargo build --target=$TARGET --example fetch
- cargo build --target=$TARGET --example resolver
trigger:
event:
- tag
---
kind: pipeline
type: docker
name: publish-crate
platform:
arch: amd64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: publish
image: asonix/rust-builder:latest-linux-amd64
pull: always
environment:
CRATES_IO_TOKEN:
from_secret: crates_io_token
commands:
- cargo publish --token $CRATES_IO_TOKEN
depends_on:
- clippy
- tests
- build-amd64
- build-arm64v8
- build-arm32v7
trigger:
event:
- tag

4
.gitignore vendored
View file

@ -1,3 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
Cargo.lock
.direnv
.envrc

View file

@ -1,22 +1,25 @@
[package]
name = "actix-webfinger"
description = "Types and helpers to create and fetch Webfinger resources"
version = "0.4.0-beta.2"
version = "0.5.0"
license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/Aardwolf/actix-webfinger"
repository = "https://git.asonix.dog/asonix/actix-webfinger"
readme = "README.md"
edition = "2018"
edition = "2021"
[features]
default = ["client"]
client = ["dep:awc"]
[dependencies]
actix-rt = "2.0.2"
actix-web = { version = "4.0.0-beta.3", default-features = false }
actix-rt = "2.6.0"
actix-web = { version = "4.0.1", default-features = false }
awc = { version = "3.0.0", default-features = false, optional = true }
serde = "1.0"
serde_derive = "1.0"
thiserror = "1.0"
[dev-dependencies]
actix-rt = "2.0.2"
actix-web = { version = "4.0.0-beta.3", features = ["openssl"] }
pretty_env_logger = "0.4"
pretty_env_logger = "0.5"
serde_json = "1.0"

View file

@ -13,23 +13,30 @@ First, add Actix Webfinger as a dependency
```toml
[dependencies]
actix-rt = "=2.0.0-beta.1"
actix-web = "4.0.0-alpha.1"
actix-webfinger = "0.4.0-beta.1"
actix-rt = "2.6.0"
actix-web = "4.0.0"
actix-webfinger = "0.4.0"
```
Then use it in your application
#### Client Example
```rust
use actix_web::client::Client;
use actix_webfinger::Webfinger;
use awc::Client;
use std::error::Error;
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = Client::default();
let wf = Webfinger::fetch(&client, "asonix@asonix.dog", "localhost:8000", false).await?;
let wf = Webfinger::fetch(
&client,
Some("acct:"),
"asonix@localhost:8000",
"localhost:8000",
false,
)
.await?;
println!("asonix's webfinger:\n{:#?}", wf);
Ok(())
@ -38,7 +45,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
#### Server Example
```rust
use actix_web::{web::Data, App, HttpServer};
use actix_web::{middleware::Logger, web::Data, App, HttpServer};
use actix_webfinger::{Resolver, Webfinger};
use std::{error::Error, future::Future, pin::Pin};
@ -49,16 +56,19 @@ pub struct MyState {
pub struct MyResolver;
type LocalBoxFuture<'a, Output> = Pin<Box<dyn Future<Output = Output> + 'a>>;
impl Resolver for MyResolver {
type State = Data<MyState>;
type Error = actix_web::error::JsonPayloadError;
fn find(
scheme: Option<&str>,
account: &str,
domain: &str,
state: Data<MyState>,
) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
let w = if domain == state.domain {
) -> LocalBoxFuture<'static, Result<Option<Webfinger>, Self::Error>> {
let w = if scheme == Some("acct:") && domain == state.domain {
Some(Webfinger::new(&format!("{}@{}", account, domain)))
} else {
None
@ -70,12 +80,15 @@ impl Resolver for MyResolver {
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn Error>> {
std::env::set_var("RUST_LOG", "info");
pretty_env_logger::init();
HttpServer::new(|| {
App::new()
.data(MyState {
domain: "asonix.dog".to_owned(),
})
.service(actix_webfinger::resource::<_, MyResolver>())
.app_data(Data::new(MyState {
domain: "localhost:8000".to_owned(),
}))
.wrap(Logger::default())
.service(actix_webfinger::resource::<MyResolver>())
})
.bind("127.0.0.1:8000")?
.run()

View file

@ -1,11 +1,18 @@
use actix_web::client::Client;
use actix_webfinger::Webfinger;
use awc::Client;
use std::error::Error;
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = Client::default();
let wf = Webfinger::fetch(&client, "asonix@localhost:8000", "localhost:8000", false).await?;
let wf = Webfinger::fetch(
&client,
Some("acct:"),
"asonix@localhost:8000",
"localhost:8000",
false,
)
.await?;
println!("asonix's webfinger:\n{:#?}", wf);
Ok(())

View file

@ -9,16 +9,19 @@ pub struct MyState {
pub struct MyResolver;
type LocalBoxFuture<'a, Output> = Pin<Box<dyn Future<Output = Output> + 'a>>;
impl Resolver for MyResolver {
type State = Data<MyState>;
type Error = actix_web::error::JsonPayloadError;
fn find(
scheme: Option<&str>,
account: &str,
domain: &str,
state: Data<MyState>,
) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
let w = if domain == state.domain {
) -> LocalBoxFuture<'static, Result<Option<Webfinger>, Self::Error>> {
let w = if scheme == Some("acct:") && domain == state.domain {
Some(Webfinger::new(&format!("{}@{}", account, domain)))
} else {
None
@ -34,9 +37,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
pretty_env_logger::init();
HttpServer::new(|| {
App::new()
.data(MyState {
.app_data(Data::new(MyState {
domain: "localhost:8000".to_owned(),
})
}))
.wrap(Logger::default())
.service(actix_webfinger::resource::<MyResolver>())
})

61
flake.lock Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1692174805,
"narHash": "sha256-xmNPFDi/AUMIxwgOH/IVom55Dks34u1g7sFKKebxUm0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "caac0eb6bdcad0b32cb2522e03e4002c8975c62e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

25
flake.nix Normal file
View file

@ -0,0 +1,25 @@
{
description = "actix-webfinger";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
in
{
packages.default = pkgs.hello;
devShell = with pkgs; mkShell {
nativeBuildInputs = [ cargo cargo-outdated cargo-zigbuild clippy gcc protobuf rust-analyzer rustc rustfmt ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
};
});
}

View file

@ -13,23 +13,30 @@
//!
//! ```toml
//! [dependencies]
//! actix = "0.10.0-alpha.1"
//! actix-web = "3.0.0-alpha.1"
//! actix-webfinger = "0.3.0-alpha.2"
//! actix-rt = "2.6.0"
//! actix-web = "4.0.0"
//! actix-webfinger = "0.4.0"
//! ```
//!
//! Then use it in your application
//!
//! #### Client Example
//! ```rust,ignore
//! use actix_web::client::Client;
//! use actix_webfinger::Webfinger;
//! use awc::Client;
//! use std::error::Error;
//!
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//! let client = Client::default();
//! let wf = Webfinger::fetch(&client, "asonix@asonix.dog", "localhost:8000", false).await?;
//! let wf = Webfinger::fetch(
//! &client,
//! Some("acct:"),
//! "asonix@localhost:8000",
//! "localhost:8000",
//! false,
//! )
//! .await?;
//!
//! println!("asonix's webfinger:\n{:#?}", wf);
//! Ok(())
@ -38,7 +45,7 @@
//!
//! #### Server Example
//! ```rust,ignore
//! use actix_web::{web::Data, App, HttpServer};
//! use actix_web::{middleware::Logger, web::Data, App, HttpServer};
//! use actix_webfinger::{Resolver, Webfinger};
//! use std::{error::Error, future::Future, pin::Pin};
//!
@ -49,16 +56,19 @@
//!
//! pub struct MyResolver;
//!
//! type LocalBoxFuture<'a, Output> = Pin<Box<dyn Future<Output = Output> + 'a>>;
//!
//! impl Resolver for MyResolver {
//! type State = Data<MyState>;
//! type Error = actix_web::error::JsonPayloadError;
//!
//! fn find(
//! scheme: Option<&str>,
//! account: &str,
//! domain: &str,
//! state: S,
//! ) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
//! let w = if domain == state.domain {
//! state: Data<MyState>,
//! ) -> LocalBoxFuture<'static, Result<Option<Webfinger>, Self::Error>> {
//! let w = if scheme == Some("acct:") && domain == state.domain {
//! Some(Webfinger::new(&format!("{}@{}", account, domain)))
//! } else {
//! None
@ -70,11 +80,14 @@
//!
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//! std::env::set_var("RUST_LOG", "info");
//! pretty_env_logger::init();
//! HttpServer::new(|| {
//! App::new()
//! .data(MyState {
//! domain: "asonix.dog".to_owned(),
//! })
//! .app_data(Data::new(MyState {
//! domain: "localhost:8000".to_owned(),
//! }))
//! .wrap(Logger::default())
//! .service(actix_webfinger::resource::<MyResolver>())
//! })
//! .bind("127.0.0.1:8000")?
@ -98,14 +111,14 @@
//!
//! You should have received a copy of the GNU General Public License along with Actix Webfinger. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
use actix_web::{
client::Client,
dev::RequestHead,
error::ResponseError,
guard::Guard,
guard::{Guard, GuardContext},
http::Method,
web::{get, Query},
FromRequest, HttpResponse, Resource,
};
#[cfg(feature = "client")]
use awc::Client;
use serde_derive::{Deserialize, Serialize};
use std::{future::Future, pin::Pin};
@ -135,8 +148,8 @@ use std::{future::Future, pin::Pin};
pub struct WebfingerGuard;
impl Guard for WebfingerGuard {
fn check(&self, request: &RequestHead) -> bool {
let valid_accept = if let Some(val) = request.headers().get("Accept") {
fn check(&self, ctx: &GuardContext<'_>) -> bool {
let valid_accept = if let Some(val) = ctx.head().headers().get("Accept") {
if let Ok(s) = val.to_str() {
s.split(',').any(|v| {
let v = if let Some(index) = v.find(';') {
@ -161,7 +174,7 @@ impl Guard for WebfingerGuard {
true
};
valid_accept && request.method == Method::GET
valid_accept && ctx.head().method == Method::GET
}
}
@ -249,6 +262,7 @@ pub struct InvalidResource(String);
/// formatting before the request reaches the route handler.
#[derive(Clone, Debug)]
pub struct WebfingerResource {
pub scheme: Option<String>,
pub account: String,
pub domain: String,
}
@ -257,12 +271,24 @@ impl std::str::FromStr for WebfingerResource {
type Err = InvalidResource;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let trimmed = s.trim_start_matches("acct:").trim_start_matches('@');
let (scheme, trimmed) = s
.find(':')
.map(|index| {
let (scheme, trimmed) = s.split_at(index);
(
Some(scheme.to_owned() + ":"),
trimmed.trim_start_matches(':'),
)
})
.unwrap_or((None, s));
let trimmed = trimmed.trim_start_matches('@');
if let Some(index) = trimmed.find('@') {
let (account, domain) = trimmed.split_at(index);
Ok(WebfingerResource {
scheme,
account: account.to_owned(),
domain: domain.trim_start_matches('@').to_owned(),
})
@ -353,6 +379,7 @@ pub trait Resolver {
type Error: ResponseError + 'static;
fn find(
scheme: Option<&str>,
account: &str,
domain: &str,
state: Self::State,
@ -366,10 +393,14 @@ pub fn endpoint<R>(
where
R: Resolver,
{
let WebfingerResource { account, domain } = query.into_inner().resource;
let WebfingerResource {
scheme,
account,
domain,
} = query.into_inner().resource;
Box::pin(async move {
match R::find(&account, &domain, state).await? {
match R::find(scheme.as_deref(), &account, &domain, state).await? {
Some(w) => Ok(w.respond()),
None => Ok(HttpResponse::NotFound().finish()),
}
@ -605,20 +636,23 @@ impl Webfinger {
.json(&self)
}
#[cfg(feature = "client")]
/// Fetch a webfinger with subject `user` from a given `domain`
///
/// This method takes a `Client` so derivative works can provide their own configured clients
/// rather this library generating it's own http clients.
pub async fn fetch(
client: &Client,
scheme: Option<&str>,
user: &str,
domain: &str,
https: bool,
) -> Result<Self, FetchError> {
let url = format!(
"{}://{}/.well-known/webfinger?resource=acct:{}",
"{}://{}/.well-known/webfinger?resource={}{}",
if https { "https" } else { "http" },
domain,
scheme.unwrap_or("acct:"),
user
);
@ -645,9 +679,9 @@ pub enum FetchError {
mod tests {
use crate::{Webfinger, WebfingerQuery};
const SIR_BOOPS: &'static str = r#"{"subject":"acct:Sir_Boops@sergal.org","aliases":["https://mastodon.sergal.org/@Sir_Boops","https://mastodon.sergal.org/users/Sir_Boops"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://mastodon.sergal.org/@Sir_Boops"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://mastodon.sergal.org/users/Sir_Boops.atom"},{"rel":"self","type":"application/activity+json","href":"https://mastodon.sergal.org/users/Sir_Boops"},{"rel":"salmon","href":"https://mastodon.sergal.org/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.vwDujxmxoYHs64MyVB3LG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ-3Zb6CI8zOO-nM-Q2llrVRYjZa4ZFnOLvMTq_Kf-Zf5wy2aCRer88gX-MsJOAtItSi412y0a_rKOuFaDYLOLeTkRvmGLgZWbsrZJOp-YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBOfOHggSt1-eAIKGIsCmINEMzs1mG9D75xKtC_sM8GfbvBclQcBstGkHAEj1VHPW0ch6Bok5_QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Qaw==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://mastodon.sergal.org/authorize_interaction?uri={uri}"}]}"#;
const SIR_BOOPS: &str = r#"{"subject":"acct:Sir_Boops@sergal.org","aliases":["https://mastodon.sergal.org/@Sir_Boops","https://mastodon.sergal.org/users/Sir_Boops"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://mastodon.sergal.org/@Sir_Boops"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://mastodon.sergal.org/users/Sir_Boops.atom"},{"rel":"self","type":"application/activity+json","href":"https://mastodon.sergal.org/users/Sir_Boops"},{"rel":"salmon","href":"https://mastodon.sergal.org/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.vwDujxmxoYHs64MyVB3LG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ-3Zb6CI8zOO-nM-Q2llrVRYjZa4ZFnOLvMTq_Kf-Zf5wy2aCRer88gX-MsJOAtItSi412y0a_rKOuFaDYLOLeTkRvmGLgZWbsrZJOp-YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBOfOHggSt1-eAIKGIsCmINEMzs1mG9D75xKtC_sM8GfbvBclQcBstGkHAEj1VHPW0ch6Bok5_QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Qaw==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://mastodon.sergal.org/authorize_interaction?uri={uri}"}]}"#;
const QUERIES: [&'static str; 4] = [
const QUERIES: [&str; 4] = [
r#"{"resource":"acct:asonix@asonix.dog"}"#,
r#"{"resource":"asonix@asonix.dog"}"#,
r#"{"resource":"acct:@asonix@asonix.dog"}"#,