1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2025-04-10 17:15:43 +02:00

Refactor tracker (template literals, classes, minor refactoring)

This commit is contained in:
chylex 2018-08-05 12:12:51 +02:00
parent add5163820
commit 9a239d7e99
4 changed files with 316 additions and 336 deletions

View File

@ -1,6 +1,8 @@
var DOM = (function(){
var createElement = (tag, parent) => {
var createElement = (tag, parent, id, html) => {
var ele = document.createElement(tag);
ele.id = id || "";
ele.innerHTML = html || "";
parent.appendChild(ele);
return ele;
};
@ -29,7 +31,7 @@ var DOM = (function(){
/*
* Creates an element, adds it to the DOM, and returns it.
*/
createElement: (tag, parent) => createElement(tag, parent),
createElement: (tag, parent, id, html) => createElement(tag, parent, id, html),
/*
* Removes an element from the DOM.
@ -37,13 +39,9 @@ var DOM = (function(){
removeElement: (ele) => ele.parentNode.removeChild(ele),
/*
* Creates a new style element with the specified CSS contents and returns it.
* Creates a new style element with the specified CSS and returns it.
*/
createStyle: (styles) => {
var ele = createElement("style", document.head);
styles.forEach(rule => ele.sheet.insertRule(rule, 0));
return ele;
},
createStyle: (styles) => createElement("style", document.head, "", styles),
/*
* Convenience setTimeout function to save space after minification.

View File

@ -82,33 +82,28 @@ var GUI = (function(){
// styles
controller.styles = DOM.createStyle([
".app, .connecting { bottom: 48px !important; }",
"#dht-ctrl { position: absolute; bottom: 0; width: 100%; height: 48px; background-color: #FFF; }",
"#dht-ctrl button { height: 32px; margin: 8px 0 8px 8px; font-size: 16px; padding: 0 12px; background-color: #7289DA; color: #FFF; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75); }",
"#dht-ctrl button:disabled { background-color: #7A7A7A; cursor: default; }",
"#dht-ctrl-close { margin: 8px 8px 8px 0 !important; float: right; }",
"#dht-ctrl p { display: inline-block; margin: 14px 12px; }",
"#dht-ctrl input { display: none; }"
]);
controller.styles = DOM.createStyle(`
.app, .connecting { bottom: 48px !important; }
#dht-ctrl { position: absolute; bottom: 0; width: 100%; height: 48px; background-color: #FFF; }
#dht-ctrl button { height: 32px; margin: 8px 0 8px 8px; font-size: 16px; padding: 0 12px; background-color: #7289DA; color: #FFF; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75); }
#dht-ctrl button:disabled { background-color: #7A7A7A; cursor: default; }
#dht-ctrl-close { margin: 8px 8px 8px 0 !important; float: right; }
#dht-ctrl p { display: inline-block; margin: 14px 12px; }
#dht-ctrl input { display: none; }`);
// main
var btn = (id, title) => "<button id='dht-ctrl-"+id+"'>"+title+"</button>";
controller.ele = DOM.createElement("div", document.body);
controller.ele.id = "dht-ctrl";
controller.ele.innerHTML = [
btn("upload", "Upload &amp; Combine"),
btn("settings", "Settings"),
btn("track", ""),
btn("download", "Download"),
btn("reset", "Reset"),
"<p id='dht-ctrl-status'></p>",
"<input id='dht-ctrl-upload-input' type='file' multiple>",
btn("close", "X")
].join("");
controller.ele = DOM.createElement("div", document.body, "dht-ctrl", `
${btn("upload", "Upload &amp; Combine")}
${btn("settings", "Settings")}
${btn("track", "")}
${btn("download", "Download")}
${btn("reset", "Reset")}
<p id='dht-ctrl-status'></p>
<input id='dht-ctrl-upload-input' type='file' multiple>
${btn("close", "X")}`);
// elements
@ -196,17 +191,15 @@ var GUI = (function(){
// styles
settings.styles = DOM.createStyle([
"#dht-cfg-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.5; display: block; z-index: 1000; }",
"#dht-cfg { position: absolute; left: 50%; top: 50%; width: 800px; height: 262px; margin-left: -400px; margin-top: -131px; padding: 8px; background-color: #fff; z-index: 1001; }",
"#dht-cfg-note { margin-top: 22px; }",
"#dht-cfg sub { color: #666; font-size: 13px; }"
]);
settings.styles = DOM.createStyle(`
#dht-cfg-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.5; display: block; z-index: 1000; }
#dht-cfg { position: absolute; left: 50%; top: 50%; width: 800px; height: 262px; margin-left: -400px; margin-top: -131px; padding: 8px; background-color: #fff; z-index: 1001; }
#dht-cfg-note { margin-top: 22px; }
#dht-cfg sub { color: #666; font-size: 13px; }`);
// overlay
settings.overlay = DOM.createElement("div", document.body);
settings.overlay.id = "dht-cfg-overlay";
settings.overlay = DOM.createElement("div", document.body, "dht-cfg-overlay");
DOM.listen(settings.overlay, "click", () => {
root.hideSettings();
@ -214,28 +207,24 @@ var GUI = (function(){
// main
settings.ele = DOM.createElement("div", document.body);
settings.ele.id = "dht-cfg";
var radio = (type, id, label) => "<label><input id='dht-cfg-"+type+"-"+id+"' name='dht-"+type+"' type='radio'> "+label+"</label><br>";
settings.ele.innerHTML = [
"<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>",
"<br>",
"<label>After reaching the first message in channel...</label><br>",
radio("afm", "nothing", "Do Nothing"),
radio("afm", "pause", "Pause Tracking"),
radio("afm", "switch", "Switch to Next Channel"),
"<br>",
"<label>After reaching a previously saved message...</label><br>",
radio("asm", "nothing", "Do Nothing"),
radio("asm", "pause", "Pause Tracking"),
radio("asm", "switch", "Switch to Next Channel"),
"<p id='dht-cfg-note'>",
"It is recommended to disable link and image previews to avoid putting unnecessary strain on your browser.<br><br>",
"<sub>BETA v.7, released 16 Feb 2018</sub>",
"</p>"
].join("");
settings.ele = DOM.createElement("div", document.body, "dht-cfg", `
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
<br>
<label>After reaching the first message in channel...</label><br>
${radio("afm", "nothing", "Do Nothing")}
${radio("afm", "pause", "Pause Tracking")}
${radio("afm", "switch", "Switch to Next Channel")}
<br>
<label>After reaching a previously saved message...</label><br>
${radio("asm", "nothing", "Do Nothing")}
${radio("asm", "pause", "Pause Tracking")}
${radio("asm", "switch", "Switch to Next Channel")}
<p id='dht-cfg-note'>
It is recommended to disable link and image previews to avoid putting unnecessary strain on your browser.<br><br>
<sub>BETA v.7, released 16 Feb 2018</sub>
</p>`);
// elements

View File

@ -65,196 +65,195 @@
* }
*/
var SAVEFILE = function(parsedObj){
var me = this;
if (SAVEFILE.isValid(parsedObj)){
class SAVEFILE{
constructor(parsedObj){
var me = this;
if (!SAVEFILE.isValid(parsedObj)){
parsedObj = {
meta: {},
data: {}
};
}
me.meta = parsedObj.meta;
me.data = parsedObj.data;
me.meta.users = me.meta.users || {};
me.meta.userindex = me.meta.userindex || [];
me.meta.servers = me.meta.servers || [];
me.meta.channels = me.meta.channels || {};
me.data = parsedObj.data;
}
else{
me.meta = {};
me.meta.users = {};
me.meta.userindex = [];
me.meta.servers = [];
me.meta.channels = {};
me.data = {};
me.tmp = {
userlookup: {},
channelkeys: new Set(),
messagekeys: new Set(),
freshmsgs: new Set()
}
}
me.tmp = {};
me.tmp.userlookup = {};
me.tmp.channelkeys = new Set();
me.tmp.messagekeys = new Set();
me.tmp.freshmsgs = new Set();
};
SAVEFILE.isValid = function(parsedObj){
return parsedObj && typeof parsedObj.meta === "object" && typeof parsedObj.data === "object";
};
SAVEFILE.prototype.findOrRegisterUser = function(userId, userName){
if (!(userId in this.meta.users)){
this.meta.users[userId] = {
"name": userName
};
this.meta.userindex.push(userId);
return this.tmp.userlookup[userId] = this.meta.userindex.length-1;
}
else if (!(userId in this.tmp.userlookup)){
return this.tmp.userlookup[userId] = this.meta.userindex.findIndex(id => id == userId);
}
else{
return this.tmp.userlookup[userId];
}
};
SAVEFILE.prototype.findOrRegisterServer = function(serverName, serverType){
var index = this.meta.servers.findIndex(server => server.name === serverName && server.type === serverType);
if (index === -1){
this.meta.servers.push({
"name": serverName,
"type": serverType
});
return this.meta.servers.length-1;
}
else{
return index;
}
};
SAVEFILE.prototype.tryRegisterChannel = function(serverIndex, channelId, channelName){
if (!this.meta.servers[serverIndex]){
return undefined;
}
else if (channelId in this.meta.channels){
return false;
}
else{
this.meta.channels[channelId] = {
"server": serverIndex,
"name": channelName
};
this.tmp.channelkeys.add(channelId);
return true;
}
};
SAVEFILE.prototype.addMessage = function(channelId, messageId, messageObject){
var container = this.data[channelId] || (this.data[channelId] = {});
var wasPresent = messageId in container;
container[messageId] = messageObject;
this.tmp.messagekeys.add(messageId);
return !wasPresent;
};
SAVEFILE.prototype.convertToMessageObject = function(discordMessage){
var obj = {
u: this.findOrRegisterUser(discordMessage.author.id, discordMessage.author.username),
t: +discordMessage.timestamp.toDate(),
m: discordMessage.content
};
if (discordMessage.editedTimestamp !== null){
obj.f = 1; // rewrite as bit flag if needed later
static isValid(parsedObj){
return parsedObj && typeof parsedObj.meta === "object" && typeof parsedObj.data === "object";
}
if (discordMessage.embeds.length > 0){
obj.e = discordMessage.embeds.map(embed => {
let conv = {
url: embed.url,
type: embed.type
findOrRegisterUser(userId, userName){
if (!(userId in this.meta.users)){
this.meta.users[userId] = {
"name": userName
};
if (embed.type === "rich"){
if (Array.isArray(embed.title) && embed.title.length === 1){
conv.t = embed.title[0];
if (Array.isArray(embed.description) && embed.description.length === 1){
conv.d = embed.description[0];
this.meta.userindex.push(userId);
return this.tmp.userlookup[userId] = this.meta.userindex.length-1;
}
else if (!(userId in this.tmp.userlookup)){
return this.tmp.userlookup[userId] = this.meta.userindex.findIndex(id => id == userId);
}
else{
return this.tmp.userlookup[userId];
}
}
findOrRegisterServer(serverName, serverType){
var index = this.meta.servers.findIndex(server => server.name === serverName && server.type === serverType);
if (index === -1){
this.meta.servers.push({
"name": serverName,
"type": serverType
});
return this.meta.servers.length-1;
}
else{
return index;
}
}
tryRegisterChannel(serverIndex, channelId, channelName){
if (!this.meta.servers[serverIndex]){
return undefined;
}
else if (channelId in this.meta.channels){
return false;
}
else{
this.meta.channels[channelId] = {
"server": serverIndex,
"name": channelName
};
this.tmp.channelkeys.add(channelId);
return true;
}
}
addMessage(channelId, messageId, messageObject){
var container = this.data[channelId] || (this.data[channelId] = {});
var wasPresent = messageId in container;
container[messageId] = messageObject;
this.tmp.messagekeys.add(messageId);
return !wasPresent;
}
convertToMessageObject(discordMessage){
var obj = {
u: this.findOrRegisterUser(discordMessage.author.id, discordMessage.author.username),
t: +discordMessage.timestamp.toDate(),
m: discordMessage.content
};
if (discordMessage.editedTimestamp !== null){
obj.f = 1; // rewrite as bit flag if needed later
}
if (discordMessage.embeds.length > 0){
obj.e = discordMessage.embeds.map(embed => {
let conv = {
url: embed.url,
type: embed.type
};
if (embed.type === "rich"){
if (Array.isArray(embed.title) && embed.title.length === 1){
conv.t = embed.title[0];
if (Array.isArray(embed.description) && embed.description.length === 1){
conv.d = embed.description[0];
}
}
else{
conv.t = "";
}
}
else{
conv.t = "";
}
return conv;
});
}
if (discordMessage.attachments.length > 0){
obj.a = discordMessage.attachments.map(attachment => ({
url: attachment.url
}));
}
return obj;
}
isMessageFresh(id){
return this.tmp.freshmsgs.has(id);
}
addMessagesFromDiscord(channelId, discordMessageArray){
var hasNewMessages = false;
for(var discordMessage of discordMessageArray){
if (this.addMessage(channelId, discordMessage.id, this.convertToMessageObject(discordMessage))){
this.tmp.freshmsgs.add(discordMessage.id);
hasNewMessages = true;
}
}
return hasNewMessages;
}
countChannels(){
return this.tmp.channelkeys.size;
}
countMessages(){
return this.tmp.messagekeys.size;
}
combineWith(obj){
var userMap = {};
for(var userId in obj.meta.users){
userMap[obj.meta.userindex.findIndex(id => id == userId)] = this.findOrRegisterUser(userId, obj.meta.users[userId].name);
}
for(var channelId in obj.meta.channels){
var oldServer = obj.meta.servers[obj.meta.channels[channelId].server];
this.tryRegisterChannel(this.findOrRegisterServer(oldServer.name, oldServer.type), channelId, obj.meta.channels[channelId].name);
}
for(var channelId in obj.data){
var oldChannel = obj.data[channelId];
return conv;
for(var messageId in oldChannel){
var oldMessage = oldChannel[messageId];
var oldUser = oldMessage.u;
oldMessage.u = userMap[oldUser] || oldUser;
this.addMessage(channelId, messageId, oldMessage);
}
}
}
toJson(){
return JSON.stringify({
"meta": this.meta,
"data": this.data
});
}
if (discordMessage.attachments.length > 0){
obj.a = discordMessage.attachments.map(attachment => ({
url: attachment.url
}));
}
return obj;
};
SAVEFILE.prototype.isMessageFresh = function(id){
return this.tmp.freshmsgs.has(id);
};
SAVEFILE.prototype.addMessagesFromDiscord = function(channelId, discordMessageArray){
var hasNewMessages = false;
for(var discordMessage of discordMessageArray){
if (this.addMessage(channelId, discordMessage.id, this.convertToMessageObject(discordMessage))){
this.tmp.freshmsgs.add(discordMessage.id);
hasNewMessages = true;
}
}
return hasNewMessages;
};
SAVEFILE.prototype.countChannels = function(){
return this.tmp.channelkeys.size;
};
SAVEFILE.prototype.countMessages = function(){
return this.tmp.messagekeys.size;
};
SAVEFILE.prototype.combineWith = function(obj){
var userMap = {};
for(var userId in obj.meta.users){
userMap[obj.meta.userindex.findIndex(id => id == userId)] = this.findOrRegisterUser(userId, obj.meta.users[userId].name);
}
for(var channelId in obj.meta.channels){
var oldServer = obj.meta.servers[obj.meta.channels[channelId].server];
this.tryRegisterChannel(this.findOrRegisterServer(oldServer.name, oldServer.type), channelId, obj.meta.channels[channelId].name);
}
for(var channelId in obj.data){
var oldChannel = obj.data[channelId];
for(var messageId in oldChannel){
var oldMessage = oldChannel[messageId];
var oldUser = oldMessage.u;
oldMessage.u = userMap[oldUser] || oldUser;
this.addMessage(channelId, messageId, oldMessage);
}
}
};
SAVEFILE.prototype.toJson = function(){
return JSON.stringify({
"meta": this.meta,
"data": this.data
});
};
}

View File

@ -10,116 +10,110 @@ var STATE = (function(){
/*
* Internal class constructor.
*/
var CLS = function(){
this.resetState();
};
/*
* Resets the state to default values.
*/
CLS.prototype.resetState = function(){
this._savefile = null;
this._isTracking = false;
this._lastFileName = null;
triggerStateChanged("data", "reset");
};
/*
* Returns the savefile object, creates a new one if needed.
*/
CLS.prototype.getSavefile = function(){
if (!this._savefile){
this._savefile = new SAVEFILE();
class CLS{
constructor(){
this.resetState();
};
/*
* Resets the state to default values.
*/
resetState(){
this._savefile = null;
this._isTracking = false;
this._lastFileName = null;
triggerStateChanged("data", "reset");
}
return this._savefile;
};
/*
* Returns true if the database file contains any data.
*/
CLS.prototype.hasSavedData = function(){
return this._savefile != null;
};
/*
* Returns true if currently tracking message.
*/
CLS.prototype.isTracking = function(){
return this._isTracking;
};
/*
* Toggles the tracking state.
*/
CLS.prototype.toggleTracking = function(){
this._isTracking = !this._isTracking;
triggerStateChanged("tracking", this._isTracking);
};
/*
* Toggles the tracking state.
*/
CLS.prototype.toggleTracking = function(){
this._isTracking = !this._isTracking;
triggerStateChanged("tracking", this._isTracking);
};
/*
* Combines current savefile with the provided one.
*/
CLS.prototype.uploadSavefile = function(fileName, fileObject){
this._lastFileName = fileName;
this.getSavefile().combineWith(fileObject);
triggerStateChanged("data", "upload");
};
/*
* Triggers a savefile download, if available.
*/
CLS.prototype.downloadSavefile = function(){
if (this.hasSavedData()){
DOM.downloadTextFile(this._lastFileName || "dht.txt", this._savefile.toJson());
/*
* Returns the savefile object, creates a new one if needed.
*/
getSavefile(){
if (!this._savefile){
this._savefile = new SAVEFILE();
}
return this._savefile;
}
};
/*
* Registers a Discord server and channel.
*/
CLS.prototype.addDiscordChannel = function(serverName, serverType, channelId, channelName){
var serverIndex = this.getSavefile().findOrRegisterServer(serverName, serverType);
if (this.getSavefile().tryRegisterChannel(serverIndex, channelId, channelName) === true){
triggerStateChanged("data", "channel");
/*
* Returns true if the database file contains any data.
*/
hasSavedData(){
return this._savefile != null;
}
};
/*
* Adds all messages from the array to the specified channel. Returns true if the savefile was updated.
*/
CLS.prototype.addDiscordMessages = function(channelId, discordMessageArray){
if (this.getSavefile().addMessagesFromDiscord(channelId, discordMessageArray)){
triggerStateChanged("data", "messages");
return true;
/*
* Returns true if currently tracking message.
*/
isTracking(){
return this._isTracking;
}
else{
return false;
/*
* Toggles the tracking state.
*/
toggleTracking(){
this._isTracking = !this._isTracking;
triggerStateChanged("tracking", this._isTracking);
}
};
/*
* Returns true if the message was added during this session.
*/
CLS.prototype.isMessageFresh = function(id){
return this.getSavefile().isMessageFresh(id);
};
/*
* Adds a listener that is called whenever the state changes. The callback is a function that takes subject (generic type) and detail (specific type or data).
*/
CLS.prototype.onStateChanged = function(callback){
stateChangedEvents.push(callback);
};
/*
* Combines current savefile with the provided one.
*/
uploadSavefile(fileName, fileObject){
this._lastFileName = fileName;
this.getSavefile().combineWith(fileObject);
triggerStateChanged("data", "upload");
}
/*
* Triggers a savefile download, if available.
*/
downloadSavefile(){
if (this.hasSavedData()){
DOM.downloadTextFile(this._lastFileName || "dht.txt", this._savefile.toJson());
}
}
/*
* Registers a Discord server and channel.
*/
addDiscordChannel(serverName, serverType, channelId, channelName){
var serverIndex = this.getSavefile().findOrRegisterServer(serverName, serverType);
if (this.getSavefile().tryRegisterChannel(serverIndex, channelId, channelName) === true){
triggerStateChanged("data", "channel");
}
}
/*
* Adds all messages from the array to the specified channel. Returns true if the savefile was updated.
*/
addDiscordMessages(channelId, discordMessageArray){
if (this.getSavefile().addMessagesFromDiscord(channelId, discordMessageArray)){
triggerStateChanged("data", "messages");
return true;
}
else{
return false;
}
}
/*
* Returns true if the message was added during this session.
*/
isMessageFresh(id){
return this.getSavefile().isMessageFresh(id);
}
/*
* Adds a listener that is called whenever the state changes. The callback is a function that takes subject (generic type) and detail (specific type or data).
*/
onStateChanged(callback){
stateChangedEvents.push(callback);
}
}
return new CLS();
})();