Added Discord integration

Overhauled the DOM for chat so that the actual definitions are in html and css, and javascript is reduced to only functionality (where possible).
Added a button that switches between Discord and Legacy chat.
Added an API for retrieving player info (to set the username properly in Discord)

(cherry picked from commit e3b790fa2b6f25267abd06d76083e9a691522d89)
This commit is contained in:
Christian 'ketura' McCarty
2021-08-09 00:53:39 -05:00
parent 07e2978720
commit e6bfbaef25
8 changed files with 325 additions and 64 deletions

View File

@@ -0,0 +1,51 @@
package com.gempukku.lotro.async.handler;
import com.gempukku.lotro.async.HttpProcessingException;
import com.gempukku.lotro.async.ResponseWriter;
import com.gempukku.lotro.db.PlayerStatistic;
import com.gempukku.lotro.db.vo.GameHistoryEntry;
import com.gempukku.lotro.game.GameHistoryService;
import com.gempukku.lotro.game.Player;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.lang.reflect.Type;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
public class PlayerInfoRequestHandler extends LotroServerRequestHandler implements UriRequestHandler {
private Gson JsonConvert = new Gson();
public PlayerInfoRequestHandler(Map<Type, Object> context) {
super(context);
}
@Override
public void handleRequest(String uri, HttpRequest request, Map<Type, Object> context, ResponseWriter responseWriter, String remoteIp) throws Exception {
if (uri.equals("") && request.method() == HttpMethod.GET) {
QueryStringDecoder queryDecoder = new QueryStringDecoder(request.getUri());
String participantId = getQueryParameterSafely(queryDecoder, "participantId");
Player resourceOwner = getResourceOwnerSafely(request, participantId);
String playerName = resourceOwner.getName();
responseWriter.writeJsonResponse(JsonConvert.toJson(playerName));
} else {
throw new HttpProcessingException(404);
}
}
}

View File

@@ -34,6 +34,7 @@ public class RootUriRequestHandler implements UriRequestHandler {
private TournamentRequestHandler _tournamentRequestHandler;
private SoloDraftRequestHandler _soloDraftRequestHandler;
private PlaytestRequestHandler _playtestRequestHandler;
private PlayerInfoRequestHandler _playerInfoRequestHandler;
private Pattern originPattern;
@@ -60,6 +61,7 @@ public class RootUriRequestHandler implements UriRequestHandler {
_tournamentRequestHandler = new TournamentRequestHandler(context);
_soloDraftRequestHandler = new SoloDraftRequestHandler(context);
_playtestRequestHandler = new PlaytestRequestHandler(context);
_playerInfoRequestHandler = new PlayerInfoRequestHandler(context);
}
@Override
@@ -116,6 +118,9 @@ public class RootUriRequestHandler implements UriRequestHandler {
} else if (uri.startsWith(_serverContextPath + "playtesting")) {
_playtestRequestHandler.handleRequest(uri.substring(_serverContextPath.length() + 11), request, context, responseWriter, remoteIp);
} else if (uri.startsWith(_serverContextPath + "player")) {
_playerInfoRequestHandler.handleRequest(uri.substring(_serverContextPath.length() + 6), request, context, responseWriter, remoteIp);
} else {
throw new HttpProcessingException(404);
}

View File

@@ -172,4 +172,74 @@ table.tables td, table.tables th {
tr.played {
background-color: #990000;
}
}
#chat {
display: grid;
grid-template-columns: auto 150px;
position:relative;
width:100%;
height:100%;
grid-gap: 3px;
overflow: hidden;
}
#chatarea {
position:relative;
width: 100%;
height: 100%;
overflow: hidden;
}
#chatMessages {
height: 100%;
max-height: 100%;
overflow-y: scroll;
display: flex;
flex-direction: column;
}
#userListColumn {
display: flex;
flex-direction: column;
}
#userList {
flex-grow: 1;
overflow: auto;
}
#toggleChatButt {
flex: none;
height: 25px;
}
#legacyChat {
display: grid;
width: 100%;
height: 100%;
max-height: 100%;
grid-template-columns: auto;
grid-template-rows: auto 25px;
}
#legacyChatInput {
display: flex;
}
#chatTalk {
flex-grow: 1;
}
#discordChat {
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
#widgetbot {
position: relative;
width: 100%;
height: 100%;
}

View File

@@ -63,7 +63,7 @@
$("#latestNews").append(
"If you find any problems with specific card or rules in general, please report it on TLHH forum - <a target='_blank' href='http://lotrtcgwiki.com/forums/index.php/topic,7592.0.html'>Report a bug</a>, please include game replay link.");
chat = new ChatBoxUI("Game Hall", $("#chat"), "/gemp-lotr-server", true, null, false, true, null);
chat = new ChatBoxUI("Game Hall", $("#chat"), "/gemp-lotr-server", true, null, false, true, null, true);
chat.setBounds(2, 2, 780 - 4, 200 - 4);
chat.showTimestamps = true;
@@ -188,8 +188,35 @@
<div id="motd" style="width:780px;height:30px;"></div>
<div id="hall" class="ui-widget-content"
style="width:780px;height:260px;"></div>
<div id="chat" class="ui-widget-content"
style="position:relative;width:780px;height:200px;"></div>
<div id="chat" class="ui-widget-content grid-container" >
<div id="chatarea" class="grid-item">
<div id="legacyChat" class="grid-container">
<div id="chatMessages" class="grid-item"></div>
<div id="legacyChatInput" class="grid-item">
<input type="text" id="chatTalk" class=""/>
<button id="showSystemButton" class="ui-icon-zoomin"></button>
<button id="lockChatButton" class="ui-icon-locked">Toggle lock chat</button>
</div>
</div>
<div id="discordChat">
<!-- This is now generated on-demand to prevent any potential performance issues if users elect not to use it-->
<!--<script src='https://cdn.jsdelivr.net/npm/@widgetbot/html-embed'></script>
<widgetbot id="widgetbot" server='699957633121255515' channel='873065954609881140' shard='https://e.widgetbot.co' username=""/> -->
</div>
</div>
<div id="userListColumn" class="grid-item">
<div id="userList" class="grid-item"></div>
<button id='toggleChatButt' class="grid-item">Switch to Discord</button>
</div>
</div>
</div>
</div>
<div id="disclaimer">

View File

@@ -1,5 +1,13 @@
<pre style="font-size:80%">
<b>Most recent update</b>
<b>09 Aug. 2021</b>
- Added Discord integration. Click the "Switch to Discord" button in the lower right for integration with the Discord server of the Player's Council.
- (If it says "Discord is currently unavailable", try switching channels in the left navigation bar or refreshing the page.)
- (If you see any bugs or have any issues, please report it to @ketura <a href="https://lotrtcgpc.net/discord">on the PC Discord</a>.)
- If you don't like it or it's being too buggy, feel free to switch back to the old chat by clicking the "Switch to Legacy" button in the lower right.
- Check out the #gemp-announcements channel for a list of things you can do, or <a href="https://support.discord.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-">check out the Markdown tutorial here</a> for examples of how to use some of the new functionality.
<b>22 Aug. 2019</b>
- Added "The Clouds Burst" Expansion for The Hobbit Draft game
- "Final Cry" should now work if The Balrog is discarded or killed in a skirmish
- "Anything goes" format now allows for sites from Fellowship or King blocks in addition to Towers block

View File

@@ -1,7 +1,8 @@
var ChatBoxUI = Class.extend({
name:null,
userName:null,
div:null,
communication:null,
comm:null,
chatMessagesDiv:null,
chatTalkDiv:null,
@@ -23,29 +24,46 @@ var ChatBoxUI = Class.extend({
stopUpdates: false,
dialogListener: null,
enableDiscord: false,
discordDiv:null,
discordWidget:null,
chatEmbed:null,
displayDiscord:true,
toggleChatButton:null,
init:function (name, div, url, showList, playerListener, showHideSystemButton, showLockButton, displayChatListener) {
init:function (name, div, url, showList, playerListener, showHideSystemButton, showLockButton, displayChatListener, allowDiscord=false) {
var that = this;
this.hiddenClasses = new Array();
this.playerListener = playerListener;
this.dialogListener = displayChatListener,
this.dialogListener = displayChatListener;
this.name = name;
this.div = div;
this.communication = new GempLotrCommunication(url, function (xhr, ajaxOptions, thrownError) {
that.appendMessage("Unkown chat problem occured (error=" + xhr.status + ")", "warningMessage");
this.comm = new GempLotrCommunication(url, function (xhr, ajaxOptions, thrownError) {
that.appendMessage("Unknown chat problem occured (error=" + xhr.status + ")", "warningMessage");
});
this.enableDiscord = allowDiscord;
this.chatMessagesDiv = $("<div class='chatMessages'></div>");
this.div.append(this.chatMessagesDiv);
this.chatMessagesDiv = $("#chatMessages");
if (this.name != null) {
this.chatTalkDiv = $("<input type='text' class='chatTalk'>");
this.discordDiv = $("#discordChat");
this.comm.getPlayerInfo(function(json)
{
that.userName = json;
}, this.chatErrorMap());
this.chatTalkDiv = $("#chatTalk");
this.hideSystemButton = $("#showSystemButton");
if (showHideSystemButton) {
this.hideSystemButton = $("<button id='showSystemMessages'>Toggle system messages</button>").button(
{icons:{
primary:"ui-icon-zoomin"
}, text:false});
hideSystemButton.button({icons:{
primary:"ui-icon-zoomin"
}, text:false});
this.hideSystemButton.click(
function () {
if (that.isShowingMessageClass("systemMessage")) {
@@ -58,35 +76,36 @@ var ChatBoxUI = Class.extend({
});
this.hideMessageClass("systemMessage");
}
else
{
this.hideSystemButton.hide();
this.hideSystemButton = null;
}
this.lockButton = $("#lockChatButton");
if (showLockButton) {
this.lockButton = $("<button id='lockChatButton'>Toggle lock chat</button>").button(
{icons:{
primary:"ui-icon-locked"
}, text:false});
this.lockButton.button({icons:{
primary:"ui-icon-locked"
}, text:false});
this.lockButton.click(
function () {
if (that.lockChat) {
$('#lockChatButton').button("option", "icons", {primary:'ui-icon-locked'});
that.lockChat = false;
} else {
$('#lockChatButton').button("option", "icons", {primary:'ui-icon-unlocked'});
that.lockChat = true;
}
});
function () {
if (that.lockChat) {
$('#lockChatButton').button("option", "icons", {primary:'ui-icon-locked'});
that.lockChat = false;
} else {
$('#lockChatButton').button("option", "icons", {primary:'ui-icon-unlocked'});
that.lockChat = true;
}
});
}
else
{
this.lockButton.hide();
this.lockButton = null;
}
if (showList) {
this.chatListDiv = $("<div class='userList'></div>");
this.div.append(this.chatListDiv);
}
if (this.hideSystemButton != null)
this.div.append(this.hideSystemButton);
if (this.lockButton != null)
this.div.append(this.lockButton);
this.div.append(this.chatTalkDiv);
this.communication.startChat(this.name,
this.comm.startChat(this.name,
function (xml) {
that.processMessages(xml, true);
}, this.chatErrorMap());
@@ -100,11 +119,26 @@ var ChatBoxUI = Class.extend({
$(this).val("");
}
});
if (showList) {
this.chatListDiv = $("#userList");
this.toggleChatButton = $("#toggleChatButt");
this.toggleChatButton.button();
this.toggleChatButton.click( function() {
that.toggleChat();
});
}
this.setDiscordVisible(false);
} else {
this.talkBoxHeight = 0;
}
},
hideMessageClass:function (msgClass) {
this.hiddenClasses.push(msgClass);
$("div.message." + msgClass, this.chatMessagesDiv).hide();
@@ -128,29 +162,74 @@ var ChatBoxUI = Class.extend({
},
setBounds:function (x, y, width, height) {
var talkBoxPadding = 3;
var userListWidth = 150;
if (this.chatListDiv == null)
userListWidth = 0;
if (this.chatListDiv != null)
this.chatListDiv.css({ position:"absolute", left:x + width - userListWidth + "px", top:y + "px", width:userListWidth, height:height - this.talkBoxHeight - 3 * talkBoxPadding, overflow:"auto" });
this.chatMessagesDiv.css({ position:"absolute", left:x + "px", top:y + "px", width:width - userListWidth, height:height - this.talkBoxHeight - 3 * talkBoxPadding, overflow:"auto" });
if (this.chatTalkDiv != null) {
var leftTextBoxPadding = 0;
if (this.hideSystemButton != null) {
this.hideSystemButton.css({position:"absolute", left:x + width - talkBoxPadding - this.talkBoxHeight + "px", top:y - 2 * talkBoxPadding + (height - this.talkBoxHeight) + "px", width:this.talkBoxHeight, height:this.talkBoxHeight});
leftTextBoxPadding += this.talkBoxHeight + talkBoxPadding;
this.handleChatVisibility();
},
handleChatVisibility:function() {
if(this.enableDiscord)
{
if(this.displayDiscord)
{
this.toggleChatButton.text("Switch to Legacy");
if(this.chatEmbed == null)
{
this.discordDiv.show();
this.chatEmbed = $("<widgetbot server='699957633121255515' channel='873065954609881140' shard='https://e.widgetbot.co' width='100%' height='100%' username='" + this.userName + "'></widgetbot>");
var script = $("<script src='https://cdn.jsdelivr.net/npm/@widgetbot/html-embed'></script>");
this.discordDiv.append(script);
this.discordDiv.append(this.chatEmbed);
}
}
if (this.lockButton != null) {
this.lockButton.css({position:"absolute", left:x + width - talkBoxPadding - this.talkBoxHeight - leftTextBoxPadding + "px", top:y - 2 * talkBoxPadding + (height - this.talkBoxHeight) + "px", width:this.talkBoxHeight, height:this.talkBoxHeight});
leftTextBoxPadding += this.talkBoxHeight + talkBoxPadding;
}
this.chatTalkDiv.css({ position:"absolute", left:x + talkBoxPadding + "px", top:y - 2 * talkBoxPadding + (height - this.talkBoxHeight) + "px", width:width - 3 * talkBoxPadding - leftTextBoxPadding, height:this.talkBoxHeight });
else
{
this.toggleChatButton.text("Switch to Discord");
}
}
if(this.enableDiscord && this.displayDiscord)
{
this.discordDiv.show();
if(this.chatMessagesDiv != null)
this.chatMessagesDiv.hide();
if(this.chatTalkDiv != null)
this.chatTalkDiv.hide();
if(this.hideSystemButton != null)
this.hideSystemButton.hide();
if(this.lockButton != null)
this.lockButton.hide();
}
else
{
this.discordDiv.hide();
if(this.chatMessagesDiv != null)
this.chatMessagesDiv.show();
if(this.chatTalkDiv != null)
this.chatTalkDiv.show();
if(this.hideSystemButton != null)
this.hideSystemButton.show();
if(this.lockButton != null)
this.lockButton.show();
}
},
toggleChat:function() {
this.setDiscordVisible(!this.displayDiscord);
},
setDiscordVisible:function(visible)
{
this.displayDiscord = visible;
this.handleChatVisibility();
},
checkForEnd:function (message, msgClass) {
@@ -249,20 +328,25 @@ var ChatBoxUI = Class.extend({
updateChatMessages:function () {
var that = this;
this.communication.updateChat(this.name, function (xml) {
this.comm.updateChat(this.name, function (xml) {
that.processMessages(xml, true);
}, this.chatErrorMap());
},
sendMessage:function (message) {
var that = this;
this.communication.sendChatMessage(this.name, message, this.chatErrorMap());
this.comm.sendChatMessage(this.name, message, this.chatErrorMap());
//this.chatEmbed.emit("sendMessage", message);
},
chatMalfunction: function() {
this.stopUpdates = true;
this.chatTalkDiv.prop('disabled', true);
this.chatTalkDiv.css({"background-color": "#ff9999"});
this.discordDiv.prop('disabled', true);
this.discordDiv.css({"background-color": "#ff9999"});
},
chatErrorMap:function() {
@@ -289,5 +373,7 @@ var ChatBoxUI = Class.extend({
that.appendMessage("You have been inactive for too long and were removed from the chat room. Refresh the page if you wish to re-enter.", "warningMessage");
}
};
}
},
});

View File

@@ -657,6 +657,20 @@ var GempLotrCommunication = Class.extend({
});
},
getPlayerInfo:function (callback, errorMap) {
$.ajax({
type:"GET",
url:this.url + "/player",
cache:false,
data:{
participantId:getUrlParam("participantId")
},
success:this.deliveryCheck(callback),
error:this.errorCheck(errorMap),
dataType:"json"
});
},
getStatus:function (callback, errorMap) {
$.ajax({
type:"GET",

View File

@@ -91,7 +91,7 @@ public class HallServer extends AbstractServer {
tableHolder = new TableHolder(leagueService, ignoreDAO);
_hallChat = _chatServer.createChatRoom("Game Hall", true, 15, true,
"You're now in the Game Hall, use /help to get a list of available commands");
"You're now in the Game Hall, use /help to get a list of available commands.<br>Don't forget to check out the new Discord chat integration! Click the 'Switch to Discord' button in the lower right ---->");
_hallChat.addChatCommandCallback("ban",
new ChatCommandCallback() {
@Override