|
|
@ -5,6 +5,7 @@ use crate::{
|
|
|
|
pdu::PduBuilder,
|
|
|
|
pdu::PduBuilder,
|
|
|
|
server_server, Database, PduEvent,
|
|
|
|
server_server, Database, PduEvent,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
use clap::Parser;
|
|
|
|
use regex::Regex;
|
|
|
|
use regex::Regex;
|
|
|
|
use rocket::{
|
|
|
|
use rocket::{
|
|
|
|
futures::{channel::mpsc, stream::StreamExt},
|
|
|
|
futures::{channel::mpsc, stream::StreamExt},
|
|
|
@ -15,7 +16,6 @@ use ruma::{
|
|
|
|
EventId, RoomId, RoomVersionId, UserId,
|
|
|
|
EventId, RoomId, RoomVersionId, UserId,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
use serde_json::value::to_raw_value;
|
|
|
|
use serde_json::value::to_raw_value;
|
|
|
|
use structopt::StructOpt;
|
|
|
|
|
|
|
|
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
|
|
|
|
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
|
|
|
|
use tracing::warn;
|
|
|
|
use tracing::warn;
|
|
|
|
|
|
|
|
|
|
|
@ -155,7 +155,7 @@ pub fn parse_admin_command(db: &Database, command_line: &str, body: Vec<&str>) -
|
|
|
|
Some(command) => *command,
|
|
|
|
Some(command) => *command,
|
|
|
|
None => {
|
|
|
|
None => {
|
|
|
|
let markdown_message = "No command given. Use `help` for a list of commands.";
|
|
|
|
let markdown_message = "No command given. Use `help` for a list of commands.";
|
|
|
|
let html_message = markdown_to_html(&markdown_message);
|
|
|
|
let html_message = "No command given. Use <code>help</code> for a list of commands.";
|
|
|
|
|
|
|
|
|
|
|
|
return AdminCommand::SendMessage(RoomMessageEventContent::text_html(
|
|
|
|
return AdminCommand::SendMessage(RoomMessageEventContent::text_html(
|
|
|
|
markdown_message,
|
|
|
|
markdown_message,
|
|
|
@ -164,10 +164,17 @@ pub fn parse_admin_command(db: &Database, command_line: &str, body: Vec<&str>) -
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Replace `help command` with `command --help`
|
|
|
|
|
|
|
|
// Clap has a help subcommand, but it omits the long help description.
|
|
|
|
|
|
|
|
if argv[0] == "help" {
|
|
|
|
|
|
|
|
argv.remove(0);
|
|
|
|
|
|
|
|
argv.push("--help");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Backwards compatibility with `register_appservice`-style commands
|
|
|
|
// Backwards compatibility with `register_appservice`-style commands
|
|
|
|
let command_with_dashes;
|
|
|
|
let command_with_dashes;
|
|
|
|
if command_line.contains("_") {
|
|
|
|
if argv[0].contains("_") {
|
|
|
|
command_with_dashes = command_name.replace("_", "-");
|
|
|
|
command_with_dashes = argv[0].replace("_", "-");
|
|
|
|
argv[0] = &command_with_dashes;
|
|
|
|
argv[0] = &command_with_dashes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -179,7 +186,11 @@ pub fn parse_admin_command(db: &Database, command_line: &str, body: Vec<&str>) -
|
|
|
|
```\n{}\n```",
|
|
|
|
```\n{}\n```",
|
|
|
|
command_name, error,
|
|
|
|
command_name, error,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
let html_message = markdown_to_html(&markdown_message);
|
|
|
|
let html_message = format!(
|
|
|
|
|
|
|
|
"Encountered an error while handling the <code>{}</code> command:\n\
|
|
|
|
|
|
|
|
<pre>\n{}\n</pre>",
|
|
|
|
|
|
|
|
command_name, error,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
AdminCommand::SendMessage(RoomMessageEventContent::text_html(
|
|
|
|
AdminCommand::SendMessage(RoomMessageEventContent::text_html(
|
|
|
|
markdown_message,
|
|
|
|
markdown_message,
|
|
|
@ -189,9 +200,10 @@ pub fn parse_admin_command(db: &Database, command_line: &str, body: Vec<&str>) -
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
|
|
|
#[derive(Parser)]
|
|
|
|
|
|
|
|
#[clap(name = "@conduit:example.com", version = env!("CARGO_PKG_VERSION"))]
|
|
|
|
enum AdminCommands {
|
|
|
|
enum AdminCommands {
|
|
|
|
#[structopt(verbatim_doc_comment)]
|
|
|
|
#[clap(verbatim_doc_comment)]
|
|
|
|
/// Register an appservice using its registration YAML
|
|
|
|
/// Register an appservice using its registration YAML
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// This command needs a YAML generated by an appservice (such as a bridge),
|
|
|
|
/// This command needs a YAML generated by an appservice (such as a bridge),
|
|
|
@ -200,25 +212,25 @@ enum AdminCommands {
|
|
|
|
/// Registering a new bridge using the ID of an existing bridge will replace
|
|
|
|
/// Registering a new bridge using the ID of an existing bridge will replace
|
|
|
|
/// the old one.
|
|
|
|
/// the old one.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
/// [add-yaml-block-to-usage]
|
|
|
|
/// ````
|
|
|
|
|
|
|
|
/// @conduit:example.com: register-appservice
|
|
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
|
|
/// yaml content here
|
|
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
|
|
/// ````
|
|
|
|
|
|
|
|
RegisterAppservice,
|
|
|
|
RegisterAppservice,
|
|
|
|
|
|
|
|
|
|
|
|
/// Unregister an appservice using its ID
|
|
|
|
/// Unregister an appservice using its ID
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// You can find the ID using the `list-appservices` command.
|
|
|
|
/// You can find the ID using the `list-appservices` command.
|
|
|
|
UnregisterAppservice { appservice_identifier: String },
|
|
|
|
UnregisterAppservice {
|
|
|
|
|
|
|
|
/// The appservice to unregister
|
|
|
|
|
|
|
|
appservice_identifier: String,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// List all the currently registered appservices
|
|
|
|
/// List all the currently registered appservices
|
|
|
|
ListAppservices,
|
|
|
|
ListAppservices,
|
|
|
|
|
|
|
|
|
|
|
|
/// Get the auth_chain of a PDU
|
|
|
|
/// Get the auth_chain of a PDU
|
|
|
|
GetAuthChain { event_id: Box<EventId> },
|
|
|
|
GetAuthChain {
|
|
|
|
|
|
|
|
/// An event ID (the $ character followed by the base64 reference hash)
|
|
|
|
|
|
|
|
event_id: Box<EventId>,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// Parse and print a PDU from a JSON
|
|
|
|
/// Parse and print a PDU from a JSON
|
|
|
|
///
|
|
|
|
///
|
|
|
@ -227,7 +239,10 @@ enum AdminCommands {
|
|
|
|
ParsePdu,
|
|
|
|
ParsePdu,
|
|
|
|
|
|
|
|
|
|
|
|
/// Retrieve and print a PDU by ID from the Conduit database
|
|
|
|
/// Retrieve and print a PDU by ID from the Conduit database
|
|
|
|
GetPdu { event_id: Box<EventId> },
|
|
|
|
GetPdu {
|
|
|
|
|
|
|
|
/// An event ID (a $ followed by the base64 reference hash)
|
|
|
|
|
|
|
|
event_id: Box<EventId>,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// Print database memory usage statistics
|
|
|
|
/// Print database memory usage statistics
|
|
|
|
DatabaseMemoryUsage,
|
|
|
|
DatabaseMemoryUsage,
|
|
|
@ -239,16 +254,16 @@ pub fn try_parse_admin_command(
|
|
|
|
body: Vec<&str>,
|
|
|
|
body: Vec<&str>,
|
|
|
|
) -> Result<AdminCommand> {
|
|
|
|
) -> Result<AdminCommand> {
|
|
|
|
argv.insert(0, "@conduit:example.com:");
|
|
|
|
argv.insert(0, "@conduit:example.com:");
|
|
|
|
let command = match AdminCommands::from_iter_safe(argv) {
|
|
|
|
let command = match AdminCommands::try_parse_from(argv) {
|
|
|
|
Ok(command) => command,
|
|
|
|
Ok(command) => command,
|
|
|
|
Err(error) => {
|
|
|
|
Err(error) => {
|
|
|
|
println!("Before:\n{}\n", error.to_string());
|
|
|
|
let message = error
|
|
|
|
let markdown_message = usage_to_markdown(&error.to_string())
|
|
|
|
.to_string()
|
|
|
|
.replace("example.com", db.globals.server_name().as_str());
|
|
|
|
.replace("example.com", db.globals.server_name().as_str());
|
|
|
|
let html_message = markdown_to_html(&markdown_message);
|
|
|
|
let html_message = usage_to_html(&message);
|
|
|
|
|
|
|
|
|
|
|
|
return Ok(AdminCommand::SendMessage(
|
|
|
|
return Ok(AdminCommand::SendMessage(
|
|
|
|
RoomMessageEventContent::text_html(markdown_message, html_message),
|
|
|
|
RoomMessageEventContent::text_html(message, html_message),
|
|
|
|
));
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -380,42 +395,58 @@ pub fn try_parse_admin_command(
|
|
|
|
Ok(admin_command)
|
|
|
|
Ok(admin_command)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Utility to turn structopt's `--help` text to markdown.
|
|
|
|
// Utility to turn clap's `--help` text to HTML.
|
|
|
|
fn usage_to_markdown(text: &str) -> String {
|
|
|
|
fn usage_to_html(text: &str) -> String {
|
|
|
|
// For the conduit admin room, subcommands become main commands
|
|
|
|
// For the conduit admin room, subcommands become main commands
|
|
|
|
let text = text.replace("SUBCOMMAND", "COMMAND");
|
|
|
|
let text = text.replace("SUBCOMMAND", "COMMAND");
|
|
|
|
let text = text.replace("subcommand", "command");
|
|
|
|
let text = text.replace("subcommand", "command");
|
|
|
|
|
|
|
|
|
|
|
|
// Put the first line (command name and version text) on its own paragraph
|
|
|
|
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
|
|
|
|
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
|
|
|
|
let text = text.replace("<", "<").replace(">", ">");
|
|
|
|
let text = re.replace_all(&text, "*$1*\n\n");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wrap command names in backticks
|
|
|
|
// Italicize the first line (command name and version text)
|
|
|
|
// (?m) enables multi-line mode for ^ and $
|
|
|
|
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
|
|
|
|
let re = Regex::new("(?m)^ ([a-z-]+) +(.*)$").expect("Regex compilation should not fail");
|
|
|
|
let text = re.replace_all(&text, "<em>$1</em>\n");
|
|
|
|
let text = re.replace_all(&text, " `$1`: $2");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add * to list items
|
|
|
|
// Unmerge wrapped lines
|
|
|
|
let re = Regex::new("(?m)^ (.*)$").expect("Regex compilation should not fail");
|
|
|
|
let text = text.replace("\n ", " ");
|
|
|
|
let text = re.replace_all(&text, "* $1");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Turn section names to headings
|
|
|
|
// Wrap option names in backticks. The lines look like:
|
|
|
|
let re = Regex::new("(?m)^([A-Z-]+):$").expect("Regex compilation should not fail");
|
|
|
|
// -V, --version Prints version information
|
|
|
|
let text = re.replace_all(&text, "#### $1");
|
|
|
|
// And are converted to:
|
|
|
|
|
|
|
|
// <code>-V, --version</code>: Prints version information
|
|
|
|
|
|
|
|
// (?m) enables multi-line mode for ^ and $
|
|
|
|
|
|
|
|
let re = Regex::new("(?m)^ (([a-zA-Z_&;-]+(, )?)+) +(.*)$")
|
|
|
|
|
|
|
|
.expect("Regex compilation should not fail");
|
|
|
|
|
|
|
|
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // Enclose examples in code blocks
|
|
|
|
|
|
|
|
// // (?ms) enables multi-line mode and dot-matches-all
|
|
|
|
|
|
|
|
// let re =
|
|
|
|
|
|
|
|
// Regex::new("(?ms)^Example:\n(.*?)\nUSAGE:$").expect("Regex compilation should not fail");
|
|
|
|
|
|
|
|
// let text = re.replace_all(&text, "EXAMPLE:\n<pre>$1</pre>\nUSAGE:");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let has_yaml_block_marker = text.contains("\n[add-yaml-block-to-usage]\n");
|
|
|
|
|
|
|
|
let text = text.replace("\n[add-yaml-block-to-usage]\n", "");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add HTML line-breaks
|
|
|
|
|
|
|
|
let text = text.replace("\n", "<br>\n");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let text = if !has_yaml_block_marker {
|
|
|
|
|
|
|
|
// Wrap the usage line in code tags
|
|
|
|
|
|
|
|
let re = Regex::new("(?m)^USAGE:<br>\n (@conduit:.*)<br>$")
|
|
|
|
|
|
|
|
.expect("Regex compilation should not fail");
|
|
|
|
|
|
|
|
re.replace_all(&text, "USAGE:<br>\n<code>$1</code><br>")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Wrap the usage line in a code block, and add a yaml block example
|
|
|
|
|
|
|
|
// This makes the usage of e.g. `register-appservice` more accurate
|
|
|
|
|
|
|
|
let re = Regex::new("(?m)^USAGE:<br>\n (.*?)<br>\n<br>\n")
|
|
|
|
|
|
|
|
.expect("Regex compilation should not fail");
|
|
|
|
|
|
|
|
re.replace_all(
|
|
|
|
|
|
|
|
&text,
|
|
|
|
|
|
|
|
"USAGE:<br>\n<pre>$1\n```\nyaml content here\n```</pre>",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
text.to_string()
|
|
|
|
text.to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert markdown to HTML using the CommonMark flavor
|
|
|
|
|
|
|
|
fn markdown_to_html(text: &str) -> String {
|
|
|
|
|
|
|
|
// CommonMark's spec allows HTML tags; however, CLI required arguments look
|
|
|
|
|
|
|
|
// very much like tags so escape them.
|
|
|
|
|
|
|
|
let text = text.replace("<", "<").replace(">", ">");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut html_output = String::new();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let parser = pulldown_cmark::Parser::new(&text);
|
|
|
|
|
|
|
|
pulldown_cmark::html::push_html(&mut html_output, parser);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
html_output
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|