Extracting markdown parsing out of the chat request handler and putting it in its own class so it can be used by other processes
This commit is contained in:
@@ -4,10 +4,7 @@ import com.gempukku.lotro.PrivateInformationException;
|
||||
import com.gempukku.lotro.SubscriptionExpiredException;
|
||||
import com.gempukku.lotro.async.HttpProcessingException;
|
||||
import com.gempukku.lotro.async.ResponseWriter;
|
||||
import com.gempukku.lotro.chat.ChatCommandErrorException;
|
||||
import com.gempukku.lotro.chat.ChatMessage;
|
||||
import com.gempukku.lotro.chat.ChatRoomMediator;
|
||||
import com.gempukku.lotro.chat.ChatServer;
|
||||
import com.gempukku.lotro.chat.*;
|
||||
import com.gempukku.lotro.game.ChatCommunicationChannel;
|
||||
import com.gempukku.lotro.game.Player;
|
||||
import com.gempukku.polling.LongPollingResource;
|
||||
@@ -17,18 +14,7 @@ import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;import org.commonmark.Extension;
|
||||
import org.commonmark.ext.autolink.AutolinkExtension;
|
||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||
import org.commonmark.node.Image;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.NodeRenderer;
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererContext;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.commonmark.renderer.html.HtmlWriter;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
@@ -38,32 +24,19 @@ import java.lang.reflect.Type;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ChatRequestHandler extends LotroServerRequestHandler implements UriRequestHandler {
|
||||
private final ChatServer _chatServer;
|
||||
private final LongPollingSystem longPollingSystem;
|
||||
private final Parser _markdownParser;
|
||||
private final HtmlRenderer _markdownRenderer;
|
||||
private final LongPollingSystem _longPollingSystem;
|
||||
private final MarkdownParser _markdownParser;
|
||||
|
||||
private static final Logger _log = LogManager.getLogger(ChatRequestHandler.class);
|
||||
|
||||
public ChatRequestHandler(Map<Type, Object> context, LongPollingSystem longPollingSystem) {
|
||||
super(context);
|
||||
_chatServer = extractObject(context, ChatServer.class);
|
||||
this.longPollingSystem = longPollingSystem;
|
||||
|
||||
List<Extension> adminExt = Arrays.asList(StrikethroughExtension.create(), AutolinkExtension.create());
|
||||
_markdownParser = Parser.builder()
|
||||
.extensions(adminExt)
|
||||
.build();
|
||||
_markdownRenderer = HtmlRenderer.builder()
|
||||
.nodeRendererFactory(htmlContext -> new LinkShredder(htmlContext))
|
||||
.extensions(adminExt)
|
||||
.escapeHtml(true)
|
||||
.sanitizeUrls(true)
|
||||
.softbreak("<br />")
|
||||
.build();
|
||||
_markdownParser = extractObject(context, MarkdownParser.class);
|
||||
_longPollingSystem = longPollingSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,8 +50,6 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern QuoteExtender = Pattern.compile("^([ \t]*>[ \t]*.+)(?=\n[ \t]*[^>])", Pattern.MULTILINE);
|
||||
|
||||
private void postMessages(HttpRequest request, String room, ResponseWriter responseWriter) throws Exception {
|
||||
HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request);
|
||||
try {
|
||||
@@ -94,29 +65,14 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
|
||||
try {
|
||||
final boolean admin = resourceOwner.hasType(Player.Type.ADMIN);
|
||||
final boolean leagueAdmin = resourceOwner.hasType(Player.Type.LEAGUE_ADMIN);
|
||||
if (message != null && message.trim().length() > 0) {
|
||||
String newMsg;
|
||||
newMsg = message.trim().replaceAll("\n\n\n+", "\n\n\n");
|
||||
newMsg = QuoteExtender.matcher(newMsg).replaceAll("$1\n");
|
||||
//Escaping underscores so that URLs with lots of underscores (i.e. wiki links) aren't mangled
|
||||
// Besides, who uses _this_ instead of *this*?
|
||||
newMsg = newMsg.replace("_", "\\_");
|
||||
|
||||
//Need to preserve any commands being made
|
||||
if(!newMsg.startsWith("/")) {
|
||||
newMsg = _markdownRenderer.render(_markdownParser.parse(newMsg));
|
||||
// Prevent quotes with newlines from displaying side-by-side
|
||||
newMsg = newMsg.replaceAll("</blockquote>[\n \t]*<blockquote>", "</blockquote><br /><blockquote>");
|
||||
//Make all links open in a new tab
|
||||
newMsg = newMsg.replaceAll("<(a href=\".*?\")>", "<$1 target=\"blank\">");
|
||||
}
|
||||
|
||||
if (message != null && !message.trim().isEmpty()) {
|
||||
String newMsg = _markdownParser.renderMarkdown(message);
|
||||
chatRoom.sendMessage(resourceOwner.getName(), newMsg, admin);
|
||||
responseWriter.writeXmlResponse(null);
|
||||
} else {
|
||||
ChatCommunicationChannel pollableResource = chatRoom.getChatRoomListener(resourceOwner.getName());
|
||||
ChatUpdateLongPollingResource polledResource = new ChatUpdateLongPollingResource(chatRoom, room, resourceOwner.getName(), admin, responseWriter);
|
||||
longPollingSystem.processLongPollingResource(polledResource, pollableResource);
|
||||
_longPollingSystem.processLongPollingResource(polledResource, pollableResource);
|
||||
}
|
||||
} catch (SubscriptionExpiredException exp) {
|
||||
logHttpError(_log, 410, request.uri(), exp);
|
||||
@@ -133,56 +89,6 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
|
||||
}
|
||||
}
|
||||
|
||||
//Processing to implement:
|
||||
// + quotes restricted to one line
|
||||
// - triple quote to avoid this??
|
||||
// + remove url text processing
|
||||
// + remove image processing
|
||||
// - re-enable bare url linking
|
||||
|
||||
private class LinkShredder implements NodeRenderer {
|
||||
|
||||
private final HtmlWriter html;
|
||||
|
||||
LinkShredder(HtmlNodeRendererContext context) {
|
||||
this.html = context.getWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends Node>> getNodeTypes() {
|
||||
// Return the node types we want to use this renderer for.
|
||||
return new HashSet<>(Arrays.asList(
|
||||
Link.class,
|
||||
Image.class
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Node node) {
|
||||
if(node instanceof Link link) {
|
||||
if(link.getTitle() != null) {
|
||||
html.text(link.getTitle() + ": " + link.getDestination());
|
||||
}
|
||||
else {
|
||||
if(link.getFirstChild() != null
|
||||
&& link.getFirstChild() instanceof Text text
|
||||
&& !text.getLiteral().equals(link.getDestination()))
|
||||
{
|
||||
html.text(text.getLiteral() + ": " + link.getDestination());
|
||||
}
|
||||
else {
|
||||
html.tag("a", Collections.singletonMap("href", link.getDestination()));
|
||||
html.text(link.getDestination());
|
||||
html.tag("/a");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if(node instanceof Image image){
|
||||
html.text(image.getTitle() + ": " + image.getDestination());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ChatUpdateLongPollingResource implements LongPollingResource {
|
||||
private final ChatRoomMediator chatRoom;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gempukku.lotro.builder;
|
||||
|
||||
import com.gempukku.lotro.cache.CacheManager;
|
||||
import com.gempukku.lotro.chat.MarkdownParser;
|
||||
import com.gempukku.lotro.collection.CachedCollectionDAO;
|
||||
import com.gempukku.lotro.collection.CachedTransferDAO;
|
||||
import com.gempukku.lotro.collection.CollectionSerializer;
|
||||
@@ -8,7 +9,6 @@ import com.gempukku.lotro.collection.TransferDAO;
|
||||
import com.gempukku.lotro.db.*;
|
||||
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
|
||||
import com.gempukku.lotro.log.LoggingProxy;
|
||||
import com.gempukku.lotro.packs.ProductLibrary;
|
||||
import com.gempukku.lotro.tournament.TournamentDAO;
|
||||
import com.gempukku.lotro.tournament.TournamentMatchDAO;
|
||||
import com.gempukku.lotro.tournament.TournamentPlayerDAO;
|
||||
@@ -50,7 +50,7 @@ public class DaoBuilder {
|
||||
objectMap.put(PlayerDAO.class, playerDao);
|
||||
|
||||
TransferDAO dbTransferDao = LoggingProxy.createLoggingProxy(TransferDAO.class, new DbTransferDAO(dbAccess));
|
||||
CachedTransferDAO transferDao = new CachedTransferDAO(dbTransferDao);
|
||||
CachedTransferDAO transferDao = new CachedTransferDAO(dbTransferDao, extract(objectMap, MarkdownParser.class));
|
||||
objectMap.put(TransferDAO.class, transferDao);
|
||||
|
||||
IpBanDAO dbIpBanDao = LoggingProxy.createLoggingProxy(IpBanDAO.class, new DbIpBanDAO(dbAccess));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gempukku.lotro.builder;
|
||||
|
||||
import com.gempukku.lotro.chat.ChatServer;
|
||||
import com.gempukku.lotro.chat.MarkdownParser;
|
||||
import com.gempukku.lotro.collection.CollectionSerializer;
|
||||
import com.gempukku.lotro.collection.CollectionsManager;
|
||||
import com.gempukku.lotro.collection.TransferDAO;
|
||||
@@ -32,6 +33,9 @@ public class ServerBuilder {
|
||||
|
||||
CollectionSerializer collectionSerializer = new CollectionSerializer();
|
||||
objectMap.put(CollectionSerializer.class, collectionSerializer);
|
||||
|
||||
MarkdownParser parser = new MarkdownParser();
|
||||
objectMap.put(MarkdownParser.class, parser);
|
||||
}
|
||||
|
||||
public static void CreateServices(Map<Type, Object> objectMap) {
|
||||
|
||||
@@ -10,18 +10,18 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ChatServer extends AbstractServer {
|
||||
private final IgnoreDAO ignoreDAO;
|
||||
private final PlayerDAO playerDAO;
|
||||
private final IgnoreDAO _ignoreDAO;
|
||||
private final PlayerDAO _playerDAO;
|
||||
private final Map<String, ChatRoomMediator> _chatRooms = new ConcurrentHashMap<>();
|
||||
|
||||
public ChatServer(IgnoreDAO ignoreDAO, PlayerDAO playerDAO) {
|
||||
this.ignoreDAO = ignoreDAO;
|
||||
this.playerDAO = playerDAO;
|
||||
_ignoreDAO = ignoreDAO;
|
||||
_playerDAO = playerDAO;
|
||||
}
|
||||
|
||||
public ChatRoomMediator createChatRoom(String name, boolean muteJoinPartMessages, int secondsTimeoutPeriod,
|
||||
boolean allowIncognito, String welcomeMessage) {
|
||||
ChatRoomMediator chatRoom = new ChatRoomMediator(ignoreDAO, playerDAO, name, muteJoinPartMessages, secondsTimeoutPeriod, allowIncognito, welcomeMessage);
|
||||
ChatRoomMediator chatRoom = new ChatRoomMediator(_ignoreDAO, _playerDAO, name, muteJoinPartMessages, secondsTimeoutPeriod, allowIncognito, welcomeMessage);
|
||||
try {
|
||||
chatRoom.sendMessage("System", "Welcome to room: " + name, true);
|
||||
} catch (PrivateInformationException exp) {
|
||||
@@ -34,7 +34,7 @@ public class ChatServer extends AbstractServer {
|
||||
}
|
||||
|
||||
public ChatRoomMediator createPrivateChatRoom(String name, boolean muteJoinPartMessages, Set<String> allowedUsers, int secondsTimeoutPeriod) {
|
||||
ChatRoomMediator chatRoom = new ChatRoomMediator(ignoreDAO, playerDAO, name, muteJoinPartMessages, secondsTimeoutPeriod, allowedUsers, false);
|
||||
ChatRoomMediator chatRoom = new ChatRoomMediator(_ignoreDAO, _playerDAO, name, muteJoinPartMessages, secondsTimeoutPeriod, allowedUsers, false);
|
||||
try {
|
||||
chatRoom.sendMessage("System", "Welcome to private room: " + name, true);
|
||||
} catch (PrivateInformationException exp) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gempukku.lotro.chat;
|
||||
|
||||
import org.commonmark.Extension;
|
||||
import org.commonmark.ext.autolink.AutolinkExtension;
|
||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||
import org.commonmark.node.Image;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.NodeRenderer;
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererContext;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.commonmark.renderer.html.HtmlWriter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MarkdownParser {
|
||||
|
||||
private final Parser _markdownParser;
|
||||
private final HtmlRenderer _markdownRenderer;
|
||||
|
||||
private static Pattern QuoteExtender = Pattern.compile("^([ \t]*>[ \t]*.+)(?=\n[ \t]*[^>])", Pattern.MULTILINE);
|
||||
|
||||
public MarkdownParser() {
|
||||
List<Extension> adminExt = Arrays.asList(StrikethroughExtension.create(), AutolinkExtension.create());
|
||||
_markdownParser = Parser.builder()
|
||||
.extensions(adminExt)
|
||||
.build();
|
||||
_markdownRenderer = HtmlRenderer.builder()
|
||||
.nodeRendererFactory(htmlContext -> new LinkShredder(htmlContext))
|
||||
.extensions(adminExt)
|
||||
.escapeHtml(true)
|
||||
.sanitizeUrls(true)
|
||||
.softbreak("<br />")
|
||||
.build();
|
||||
}
|
||||
|
||||
public String renderMarkdown(String markdown) {
|
||||
String newMsg = markdown.trim().replaceAll("\n\n\n+", "\n\n\n");
|
||||
newMsg = QuoteExtender.matcher(newMsg).replaceAll("$1\n");
|
||||
//Escaping underscores so that URLs with lots of underscores (i.e. wiki links) aren't mangled
|
||||
// Besides, who uses _this_ instead of *this*?
|
||||
newMsg = newMsg.replace("_", "\\_");
|
||||
|
||||
//Need to preserve any commands being made
|
||||
if(!newMsg.startsWith("/")) {
|
||||
newMsg = _markdownRenderer.render(_markdownParser.parse(newMsg));
|
||||
// Prevent quotes with newlines from displaying side-by-side
|
||||
newMsg = newMsg.replaceAll("</blockquote>[\n \t]*<blockquote>", "</blockquote><br /><blockquote>");
|
||||
//Make all links open in a new tab
|
||||
newMsg = newMsg.replaceAll("<(a href=\".*?\")>", "<$1 target=\"_blank\">");
|
||||
}
|
||||
|
||||
return newMsg;
|
||||
}
|
||||
|
||||
|
||||
//This class is used to ensure that markdown links [like this](https://somewebsite.com) are rendered literally.
|
||||
// This is to ensure that people are unable to falsely represent links as other links.
|
||||
private class LinkShredder implements NodeRenderer {
|
||||
|
||||
private final HtmlWriter html;
|
||||
|
||||
LinkShredder(HtmlNodeRendererContext context) {
|
||||
this.html = context.getWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends Node>> getNodeTypes() {
|
||||
// Return the node types we want to use this renderer for.
|
||||
return new HashSet<>(Arrays.asList(
|
||||
Link.class,
|
||||
Image.class
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Node node) {
|
||||
if(node instanceof Link link) {
|
||||
if(link.getTitle() != null) {
|
||||
html.text(link.getTitle() + ": " + link.getDestination());
|
||||
}
|
||||
else {
|
||||
if(link.getFirstChild() != null
|
||||
&& link.getFirstChild() instanceof Text text
|
||||
&& !text.getLiteral().equals(link.getDestination()))
|
||||
{
|
||||
html.text(text.getLiteral() + ": " + link.getDestination());
|
||||
}
|
||||
else {
|
||||
html.tag("a", Collections.singletonMap("href", link.getDestination()));
|
||||
html.text(link.getDestination());
|
||||
html.tag("/a");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if(node instanceof Image image){
|
||||
html.text(image.getTitle() + ": " + image.getDestination());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user