Skip to content

Commit

Permalink
Add a Markdown parser to convert Discord formatting to Minecraft (#4)
Browse files Browse the repository at this point in the history
Add a Markdown parser to convert Discord formatting to Minecraft

Signed-off-by: Evan Maddock <[email protected]>
  • Loading branch information
EbonJaeger authored Jan 27, 2021
1 parent e62f01a commit 7839de1
Show file tree
Hide file tree
Showing 14 changed files with 799 additions and 34 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Add a converter to show Discord formatting in Minecraft

## [v2.2.0] - 2021-01-23

### Changed

- Update dependencies
- Tokio runtime updated to 1.0
- Serenity updated to 0.10
Expand All @@ -20,40 +26,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [v2.1.1] - 2020-12-11

### Fixed

- Fix full username mentions with discriminator not being parsed correctly

## [v2.1.0] - 2020-12-03

### Added

- Add ability to mention roles and channels from Minecraft
- Add an optional webserver implementation to listen for messages from other machines

### Fixed

- Log files being moved (such as maybe during log rotation) should no longer break the bot, if that was happening
- Fix mentions from Minecraft with spaces not creating a mention

### Changed

- Print nicer-looking error messages

## [v2.0.1] - 2020-11-22

### Fixed

- Fix a bad value in the default configuration

### Changed

- Eliminated a call to the Discord REST API when messages are received from Minecraft
- Replace ugly Discord mentions with names in messages to Minecraft
- Escape double quote characters in messages to Minecraft

## [v2.0.0] - 2020-11-8

### Added

- Add more customization options for chat formatting in Minecraft

### Changed

- Improve experience when a user sends an attachment in Discord

[Unreleased]: https://github.com/EbonJaeger/dolphin-rs/compare/v2.2.0...master
[unreleased]: https://github.com/EbonJaeger/dolphin-rs/compare/v2.2.0...master
[v2.2.0]: https://github.com/EbonJaeger/dolphin-rs/compare/v2.1.1...v2.2.0
[v2.1.1]: https://github.com/EbonJaeger/dolphin-rs/compare/v2.1.0...v2.1.1
[v2.1.0]: https://github.com/EbonJaeger/dolphin-rs/compare/v2.0.1...v2.1.0
Expand Down
34 changes: 33 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ confy = "0.4.0"
err-derive = "0.3"
lazy_static = "1.4.0"
linemux = {git = "https://github.com/jmagnuson/linemux"}
pipeline = "0.5"
rcon = "0.4"
regex = "1.4.2"
fancy-regex = "0.4"
serde_json = "1.0"
tracing = "0.1.21"
tracing-subscriber = "0.2.15"
Expand Down
65 changes: 34 additions & 31 deletions src/discord.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::config::RootConfig;
use crate::errors::DolphinError;
use crate::listener::{Listener, LogTailer, Webserver};
use crate::markdown;
use crate::minecraft::{MinecraftMessage, Source};
use fancy_regex::Regex;
use rcon::Connection;
use regex::Regex;
use serenity::{
async_trait,
model::{
Expand All @@ -15,12 +16,9 @@ use serenity::{
prelude::*,
utils::parse_channel,
};
use std::{
str::Split,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
},
use std::sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
};
use tokio::sync::mpsc;
use tracing::{debug, error, info, warn};
Expand Down Expand Up @@ -191,8 +189,17 @@ impl EventHandler for Handler {
let content = self.sanitize_message(&ctx, &msg).await;

// Send a separate message for each line
let lines = content.split("\n");
let lines = truncate_lines(lines);
let lines = content.split('\n');

// Parse and convert any Markdown
let mut marked = Vec::new();
lines.for_each(|line| {
let blocks = markdown::parse(&line);
debug!("event_handler:message: parsed plocks: {:?}", blocks);
marked.push(markdown::to_minecraft_format(&blocks));
});

let lines = truncate_lines(marked);
let mut lines = self.apply_line_template(lines);

// Add attachement message if an attachment is present
Expand Down Expand Up @@ -286,7 +293,7 @@ impl EventHandler for Handler {
/// by default 100. If a line is over the limit, it will be split at that
/// number of chacacters, and a new line inserted into the line Vector.
///
fn truncate_lines<'a>(lines: Split<'a, &'a str>) -> Vec<String> {
fn truncate_lines(lines: Vec<String>) -> Vec<String> {
let mut truncated: Vec<String> = Vec::new();

for mut line in lines {
Expand All @@ -303,8 +310,8 @@ fn truncate_lines<'a>(lines: Split<'a, &'a str>) -> Vec<String> {

// Shorten the line for the next iteration
line = match line.get(MAX_LINE_LENGTH..) {
Some(sub) => sub,
None => "",
Some(sub) => sub.to_string(),
None => String::new(),
};
}
}
Expand Down Expand Up @@ -482,22 +489,20 @@ fn split_webhook_url(url: &str) -> Option<(u64, &str)> {
Regex::new(r"^https://discord.com/api/webhooks/(?P<id>.*)/(?P<token>.*)$").unwrap();
}

let captures = match WEBHOOK_REGEX.captures(&url) {
Some(captures) => captures,
None => return None,
};
let mut ret = None;

if captures.len() != 3 {
return None;
}
if let Ok(Some(captures)) = WEBHOOK_REGEX.captures(&url) {
if captures.len() != 3 {
return None;
}

let id = captures.name("id").unwrap().as_str();
let id = match id.parse::<u64>() {
Ok(num) => num,
Err(_) => return None,
};
let id = captures.name("id").unwrap().as_str();
if let Ok(id) = id.parse::<u64>() {
ret = Some((id, captures.name("token").unwrap().as_str()));
}
}

Some((id, captures.name("token").unwrap().as_str()))
ret
}

#[cfg(test)]
Expand All @@ -508,12 +513,11 @@ mod tests {
#[test]
fn split_long_line() {
// Given
let input = String::from("01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
let split = input.split("\n");
let input = vec!(String::from("01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"));
let expected = vec!("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "0123456789");

// When
let result = truncate_lines(split);
let result = truncate_lines(input);

// Then
assert_eq!(result, expected);
Expand All @@ -522,12 +526,11 @@ mod tests {
#[test]
fn no_split_line() {
// Given
let input = String::from("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
let split = input.split("\n");
let input = vec!(String::from("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"));
let expected = vec!("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");

// When
let result = truncate_lines(split);
let result = truncate_lines(input);

// Then
assert_eq!(result, expected);
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ mod config;
mod discord;
mod errors;
mod listener;
mod markdown;
mod minecraft;

#[macro_use]
extern crate clap;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate pipeline;

use clap::{App, Arg};
use commands::{general::*, hooks::after, minecraft::*};
Expand Down
19 changes: 19 additions & 0 deletions src/markdown/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
This is a stripped down version of the Rust crate `markdown` written by Johann Hofmann, licensed under the Apache 2.0 license. You can find the original work [here](https://github.com/johannhof/markdown.rs). The vast majority of the credit here goes to him.

# Differences

Since this application only deals with Discord's flavor of Markdown and Minecraft formatting, there is a lot of the original crate that isn't needed here. On top of that, there are a couple of formatting types that Discord uses that aren't present in the upstream library.

Only the following elements are implemented:

- Blockquotes
- Emphasis
- Strikethrough
- Strong
- Underline

# License

All work **except** the strikethrough and underline parsers, and the Minecraft format conversion code is &copy; Johann Hofmann.

The license for the original work can be found [here](https://github.com/johannhof/markdown.rs/blob/master/LICENSE-APACHE). I really make no claims on top of that.
41 changes: 41 additions & 0 deletions src/markdown/block/blockquote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use super::Block;
use super::Block::Blockquote;
use crate::markdown::block::parse_blocks;

pub fn parse_blockquote(line: &str) -> Option<Block> {
if line.is_empty() || !line.starts_with("> ") {
return None;
}

let mut content = String::new();

// Push the content of the quote after the opening `>`
content.push_str(&line[2..line.len()]);

Some(Blockquote(parse_blocks(&content)))
}

#[cfg(test)]
mod tests {
use super::parse_blockquote;
use super::Block::Blockquote;

#[test]
fn finds_blockquote() {
match parse_blockquote("> quote") {
Some(Blockquote(_)) => (),
_ => panic!(),
}
}

#[test]
fn no_false_positives() {
assert_eq!(parse_blockquote(">shouldn't parse"), None);
assert_eq!(parse_blockquote("shouldn't > parse"), None);
}

#[test]
fn no_early_matching() {
assert_eq!(parse_blockquote("first > quote > another blah"), None);
}
}
Loading

0 comments on commit 7839de1

Please sign in to comment.