diff options
Diffstat (limited to 'src/modules/web.rs')
| -rw-r--r-- | src/modules/web.rs | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/modules/web.rs b/src/modules/web.rs new file mode 100644 index 0000000..09c6ac9 --- /dev/null +++ b/src/modules/web.rs @@ -0,0 +1,187 @@ +// This file is part of Locus <https://github.com/gemrest/locus>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2023 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{response::success, route::track_mount}; + +fn error( + message: &str, + context: &windmark::context::RouteContext, +) -> windmark::Response { + success( + &format!( + "# World Wide Web to Gemini Gateway\n\n{message}\n\n=> /web Go back" + ), + context, + ) +} + +pub fn module(router: &mut windmark::Router) { + track_mount( + router, + "/web", + "World Wide Web to Gemini Gateway", + |context| { + success( + &r#"# World Wide Web to Gemini Gateway + +To use this gateway, simply append the address of your target resource to the end of the current route, minus the protocol. + +To visit the web version of this exact page, <https://fuwn.me/web>, you would visit <gemini://fuwn.me/web/fuwn.me/web>. + +=> /web/fuwn.me/web Try it!"#, + &context, + ) + }, + ); + track_mount( + router, + "/web/*url", + "World Wide Web to Gemini Gateway Visitor", + async move |context| { + windmark::Response::success({ + let Ok(url) = url::Url::parse(&format!( + "https://{}", + if let Some(url_choice) = context.parameters.get("url") { + url_choice + } else { + return error( + "Looks like you didn't provide a URL!", + &context, + ); + } + )) else { + return error( + "The URL you provided is invalid.", + &context, + ); + }; + let mut contents = vec![]; + let website = if let Ok(website) = reqwest::get(url.clone()).await { + if let Ok(text) = website.text().await { + text + } else { + return error( + "The website you provided could not be reached.", + &context, + ); + } + } else { + return error( + "The website you provided could not be reached.", + &context, + ); + }; + let Ok(dom) = tl::parse(&website, tl::ParserOptions::default()) else { + return error( + "The website you provided could not be properly parsed.", + &context, + ); + }; + let parser = dom.parser(); + let mut nodes = dom.nodes().iter().peekable(); + + while let Some(element) = nodes.next() { + let mut text = String::new(); + + element.as_tag().map_or((), |tag| { + match tag.name().as_utf8_str().to_string().as_str() { + "p" => { + contents.push(format!( + "{}\n\n", + tag + .inner_text(parser) + .lines() + .collect::<Vec<_>>() + .first() + .unwrap_or(&"A parse error occurred in this location.") + )); + } + "a" => { + contents.pop(); + contents.push(format!( + "=> {} {}\n{}", + tag.attributes().get("href").flatten().map_or( + "A parse error occurred in this location.".to_string(), + |href| href.as_utf8_str().to_string() + ), + tag.inner_text(parser), + nodes.peek().map_or("", |next_node| { + next_node.as_tag().map_or("", |next_tag| { + if next_tag.name().as_utf8_str().to_string().as_str() + == "a" + { + "" + } else { + "\n" + } + }) + }) + )); + } + "h1" => { + contents.push(format!("# {}\n\n", tag.inner_text(parser))); + } + "h2" => { + contents.push(format!("## {}\n\n", tag.inner_text(parser))); + } + "h3" => { + contents.push(format!("### {}\n\n", tag.inner_text(parser))); + } + "pre" => { + contents.push(format!("```\n{}```\n", tag.inner_text(parser))); + } + "blockquote" => { + contents.push(format!("> {}\n\n", tag.inner_text(parser))); + } + "li" => { + contents.push(format!("* {}\n", tag.inner_text(parser))); + } + "html" | "head" | "script" | "link" | "title" | "body" | "ul" => { + } + _ => { + text = tag.inner_text(parser).to_string(); + + if text.contains("Proxy information") + || text.contains("Proxied content from") + { + return; + } + + println!( + "{}: {}", + tag.name().as_utf8_str().to_string(), + tag.inner_text(parser) + ); + + contents.push(format!("{}\n\n", tag.inner_text(parser))); + } + } + }); + + // Covers September and Kineto + if text == "Proxy information" + || text.contains("Proxied content from") + { + break; + } + } + + contents.join("") + }) + }, + ); +} |