1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2025-09-15 19:32:09 +02:00

2 Commits

Author SHA1 Message Date
will-ca
da72649247 Merge 21490dd69e into ce87901088 2023-11-23 14:51:45 +01:00
will-ca
21490dd69e Refactor to match code from the app version (PR #237) 2023-11-23 14:48:09 +01:00
12 changed files with 1438 additions and 1490 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@ import os
import re
import distutils.dir_util
VERSION_SHORT = "v.31h"
VERSION_FULL = VERSION_SHORT + ", released 03 March 2024"
VERSION_SHORT = "v.31f"
VERSION_FULL = VERSION_SHORT + ", released 20 November 2023"
EXEC_UGLIFYJS_WIN = "{2}/lib/uglifyjs.cmd --parse bare_returns --compress --output \"{1}\" \"{0}\""
EXEC_UGLIFYJS_AUTO = "uglifyjs --parse bare_returns --compress --output \"{1}\" \"{0}\""
@@ -109,15 +109,10 @@ def build_tracker():
combined_tracker_js = combined_tracker_js.replace("/*[CSS-SETTINGS]*/", settings_css)
full_tracker_js = bootstrap_js.replace("/*[IMPORTS]*/", combined_tracker_js)
minified_tracker_js = full_tracker_js
with open(output_file_raw, "w") as out:
if not USE_UGLIFYJS:
out.write("(function(){\n")
out.write(full_tracker_js)
if not USE_UGLIFYJS:
out.write("})()")
if USE_UGLIFYJS:
output_file_tmp = "bld/track.tmp.js"
@@ -133,9 +128,9 @@ def build_tracker():
out.write("})()")
os.remove(output_file_tmp)
with open(output_file_raw, "r") as raw:
minified_tracker_js = raw.read()
with open(output_file_raw, "r") as raw:
minified_tracker_js = raw.read()
write_tracker_html(output_file_html, minified_tracker_js)
write_tracker_userscript(output_file_userscript, full_tracker_js)

View File

@@ -61,11 +61,9 @@ const onTrackingContinued = function(anyNewMessages) {
let action = null;
if (!DISCORD.hasMoreMessages()) {
console.debug("[DHT] Reached first message.");
action = SETTINGS.afterFirstMsg;
}
if (isNoAction(action) && !anyNewMessages) {
console.debug("[DHT] No new messages.");
action = SETTINGS.afterSavedMsg;
}
@@ -108,7 +106,7 @@ const onMessagesUpdated = async messages => {
isSending = true;
try {
STATE.addDiscordChannel(info.server, info.channel);
await STATE.addDiscordChannel(info.server, info.channel);
} catch (e) {
onError(e);
return;

View File

@@ -1,23 +1,5 @@
// noinspection JSUnresolvedVariable
// noinspection LocalVariableNamingConventionJS
class DISCORD {
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
static CHANNEL_TYPE = {
DM: 1,
GROUP_DM: 3,
ANNOUNCEMENT_THREAD: 10,
PUBLIC_THREAD: 11,
PRIVATE_THREAD: 12
};
// https://discord.com/developers/docs/resources/channel#message-object-message-types
static MESSAGE_TYPE = {
DEFAULT: 0,
REPLY: 19,
THREAD_STARTER: 21
};
static getMessageOuterElement() {
return DOM.queryReactClass("messagesWrapper");
}
@@ -46,11 +28,46 @@ class DISCORD {
* Calls the provided function with a list of messages whenever the currently loaded messages change.
*/
static setupMessageCallback(callback) {
let skipsLeft = 0;
let waitForCleanup = false;
const previousMessages = new Set();
const onMessageElementsChanged = function() {
const messages = DISCORD.getMessages();
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !DISCORD.hasMoreMessages();
const timer = window.setInterval(() => {
if (skipsLeft > 0) {
--skipsLeft;
return;
}
const view = this.getMessageOuterElement();
if (!view) {
skipsLeft = 2;
return;
}
const anyMessage = DOM.queryReactClass("message", this.getMessageOuterElement());
const messageCount = anyMessage ? anyMessage.parentElement.children.length : 0;
if (messageCount > 300) {
if (waitForCleanup) {
return;
}
skipsLeft = 3;
waitForCleanup = true;
window.setTimeout(() => {
const view = this.getMessageScrollerElement();
// noinspection JSUnusedGlobalSymbols
view.scrollTop = view.scrollHeight / 2;
}, 1);
}
else {
waitForCleanup = false;
}
const messages = this.getMessages();
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !this.hasMoreMessages();
if (!hasChanged) {
return;
@@ -62,74 +79,24 @@ class DISCORD {
}
callback(messages);
};
}, 200);
let debounceTimer;
/**
* Do not trigger the callback too often due to autoscrolling.
*/
const onMessageElementsChangedLater = function() {
window.clearTimeout(debounceTimer);
debounceTimer = window.setTimeout(onMessageElementsChanged, 100);
};
const observer = new MutationObserver(function () {
onMessageElementsChangedLater();
});
let skipsLeft = 0;
let observedElement = null;
const observerTimer = window.setInterval(() => {
if (skipsLeft > 0) {
--skipsLeft;
return;
}
const view = this.getMessageOuterElement();
if (!view) {
skipsLeft = 1;
return;
}
if (observedElement !== null && observedElement.isConnected) {
return;
}
observedElement = view.querySelector("[data-list-id='chat-messages']");
if (observedElement) {
console.debug("[DHT] Observed message container.");
observer.observe(observedElement, { childList: true });
onMessageElementsChangedLater();
}
}, 400);
window.DHT_ON_UNLOAD.push(() => {
observer.disconnect();
observedElement = null;
window.clearInterval(observerTimer);
});
window.DHT_ON_UNLOAD.push(() => window.clearInterval(timer));
}
/**
* Returns the message from a message element.
* @returns { null | DiscordMessage } }
* Returns the property object of a message element.
* @returns { null | { message: DiscordMessage, channel: Object } }
*/
static getMessageFromElement(ele) {
static getMessageElementProps(ele) {
const props = DOM.getReactProps(ele);
if (props && Array.isArray(props.children)) {
for (const child of props.children) {
if (!(child instanceof Object)) {
continue;
}
if (props.children && props.children.length) {
for (let i = 3; i < props.children.length; i++) {
const childProps = props.children[i].props;
const childProps = child.props;
if (childProps instanceof Object && "message" in childProps) {
return childProps.message;
if (childProps && "message" in childProps && "channel" in childProps) {
return childProps;
}
}
}
@@ -146,10 +113,10 @@ class DISCORD {
for (const ele of this.getMessageElements()) {
try {
const message = this.getMessageFromElement(ele);
const props = this.getMessageElementProps(ele);
if (message != null) {
messages.push(message);
if (props != null) {
messages.push(props.message);
}
} catch (e) {
console.error("[DHT] Error extracing message data, skipping it.", e, ele, DOM.tryGetReactProps(ele));
@@ -170,7 +137,7 @@ class DISCORD {
*/
static getSelectedChannel() {
try {
let obj = null;
let obj;
try {
for (const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children) {
@@ -181,6 +148,15 @@ class DISCORD {
}
} catch (e) {
console.error("[DHT] Error retrieving selected channel from 'chatContent' element.", e);
for (const ele of this.getMessageElements()) {
const props = this.getMessageElementProps(ele);
if (props != null) {
obj = props.channel;
break;
}
}
}
if (!obj || typeof obj.id !== "string") {
@@ -209,8 +185,8 @@ class DISCORD {
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
switch (obj.type) {
case DISCORD.CHANNEL_TYPE.DM: type = "DM"; break;
case DISCORD.CHANNEL_TYPE.GROUP_DM: type = "GROUP"; break;
case 1: type = "DM"; break;
case 3: type = "GROUP"; break;
default: return null;
}
@@ -248,7 +224,7 @@ class DISCORD {
}
};
if (obj.type === DISCORD.CHANNEL_TYPE.ANNOUNCEMENT_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PUBLIC_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PRIVATE_THREAD) {
if (obj.parent_id) {
channel["extra"]["parent"] = obj.parent_id;
}
else {

View File

@@ -204,12 +204,12 @@ ${btn("close", "X")}`);
<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", "Continue Tracking")}
${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", "Continue Tracking")}
${radio("asm", "nothing", "Do Nothing")}
${radio("asm", "pause", "Pause Tracking")}
${radio("asm", "switch", "Switch to Next Channel")}
<p id='dht-cfg-note'>

View File

@@ -113,16 +113,6 @@ class SAVEFILE{
static isValid(parsedObj){
return parsedObj && typeof parsedObj.meta === "object" && typeof parsedObj.data === "object";
}
static getDate(date){
if (date instanceof Date) {
return date;
}
else {
// noinspection JSUnresolvedReference
return date.toDate();
}
};
findOrRegisterUser(userId, userName, userDiscriminator, userAvatar){
var wasPresent = userId in this.meta.users;
@@ -213,7 +203,7 @@ class SAVEFILE{
var obj = {
u: this.findOrRegisterUser(author.id, author.username, author.bot ? null : author.discriminator, author.avatar),
t: SAVEFILE.getDate(discordMessage.timestamp).getTime()
t: discordMessage.timestamp.toDate().getTime()
};
if (discordMessage.content.length > 0){
@@ -221,7 +211,7 @@ class SAVEFILE{
}
if (discordMessage.editedTimestamp !== null){
obj.te = SAVEFILE.getDate(discordMessage.editedTimestamp).getTime();
obj.te = discordMessage.editedTimestamp.toDate().getTime();
}
if (discordMessage.embeds.length > 0){
@@ -279,9 +269,11 @@ class SAVEFILE{
addMessagesFromDiscord(discordMessageArray){
var hasNewMessages = false;
for(var discordMessage of discordMessageArray){
if (this.addMessage(discordMessage.channel_id, discordMessage.id, this.convertToMessageObject(discordMessage))){
var type = discordMessage.type;
// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure
if ((type === 0 || type === 19) && discordMessage.state === "SENT" && this.addMessage(discordMessage.channel_id, discordMessage.id, this.convertToMessageObject(discordMessage))){
hasNewMessages = true;
}
}

View File

@@ -122,13 +122,12 @@ const STATE = (function() {
this._triggerStateChanged("data", "channel");
}
}
// Right. Upstream desktop `bootstrap.js` expects an `async` here. I think it's fine.
/*
* Adds all messages from the array to the specified channel. Returns true if the savefile was updated.
*/
addDiscordMessages(discordMessageArray){
discordMessageArray = discordMessageArray.filter(msg => (msg.type === DISCORD.MESSAGE_TYPE.DEFAULT || msg.type === DISCORD.MESSAGE_TYPE.REPLY || msg.type === DISCORD.MESSAGE_TYPE.THREAD_STARTER) && msg.state === "SENT");
if (this.getSavefile().addMessagesFromDiscord(discordMessageArray)){
this._triggerStateChanged("data", "messages");
return true;

View File

@@ -1,8 +1,7 @@
var DISCORD = (function(){
var REGEX = {
formatBold: /\*\*([\s\S]+?)\*\*(?!\*)/g,
formatItalic1: /\*([\s\S]+?)\*(?!\*)/g,
formatItalic2: /_([\s\S]+?)_(?!_)\b/g,
formatItalic: /(.)?\*([\s\S]+?)\*(?!\*)/g,
formatUnderline: /__([\s\S]+?)__(?!_)/g,
formatStrike: /~~([\s\S]+?)~~(?!~)/g,
formatCodeInline: /(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/g,
@@ -55,8 +54,7 @@ var DISCORD = (function(){
.replace(REGEX.specialEscapedSingle, escapeHtmlMatch)
.replace(REGEX.specialEscapedDouble, full => full.replace(/\\/g, "").replace(/(.)/g, escapeHtmlMatch))
.replace(REGEX.formatBold, "<b>$1</b>")
.replace(REGEX.formatItalic1, "<i>$1</i>")
.replace(REGEX.formatItalic2, "<i>$1</i>")
.replace(REGEX.formatItalic, (full, pre, match) => pre === '\\' ? full : (pre || "")+"<i>"+match+"</i>")
.replace(REGEX.formatUnderline, "<u>$1</u>")
.replace(REGEX.formatStrike, "<s>$1</s>");
}

BIN
web/img/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -13,10 +13,110 @@
</head>
<body>
<div class="inner">
<h1>Discord History Tracker&nbsp;<span class="bar">|</span>&nbsp;<span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Release-Notes">Release&nbsp;Notes</a></span>&nbsp;<span class="bar">|</span>&nbsp;<span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Old-Versions">Old&nbsp;Versions</a></span></h1>
<h1>Discord History Tracker <span class="version">{{{version:web}}}</span>&nbsp;<span class="bar">|</span>&nbsp;<span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Release-Notes">Release&nbsp;Notes</a></span></h1>
<p>Discord History Tracker lets you save chat history in your servers, groups, and private conversations, and view it offline.</p>
<img src="img/tracker.png" width="851" class="dht bordered">
<p>The browser-only version of Discord History Tracker has been superseded by a <a href="https://dht.chylex.com">desktop app version</a>, and is no longer supported.</p>
<p>You can import archives from the browser-only version into the desktop app, or view them by downloading the <a href="build/viewer.html">Viewer</a>.</p>
<p>This page explains how to use Discord History Tracker entirely in your browser. While this method gets you started quicker and works on any device that has a modern web browser, it has <strong>significant limitations and fewer features</strong> than the <a href="https://dht.chylex.com">desktop app</a>.</p>
<p>Because everything happens in your browser, if the browser tab is closed, or your browser or computer crashes, you will lose all progress. Your browser may also crash or freeze if you have too many messages. If this is a concern, <a href="https://dht.chylex.com">use the desktop app</a> instead.</p>
<h2>How to Use</h2>
<p>A tracking script will load messages according to your settings, and temporarily save them in your browser. Once you finish tracking, the browser will create an archive file you can save to your disk, and open in an offline viewer later.</p>
<h3>Setup the Tracking Script</h3>
<h4>Option 1: Userscript</h4>
<div class="quote">
<p><strong>Preferred option.</strong> Requires a browser addon, but DHT will stay up-to-date and be easily accessible on the Discord website.</p>
<ol>
<li>Install a userscript manager addon:
<ul>
<li><a href="https://violentmonkey.github.io/get-it/">Violentmonkey</a> (Chrome)</li>
<li><a href="https://tampermonkey.net/">Tampermonkey</a> (Firefox, Edge, Chrome, Opera)</li>
<li>Due to browser bugs / limitations, DHT will not work in <strong>Firefox</strong> with Greasemonkey / Violentmonkey, and in <strong>Safari</strong> at all</li>
</ul>
</li>
<li>Click <a href="build/track.user.js">Install Userscript</a> to prompt an installation into the userscript manager</li>
<li>Open <a href="https://discord.com/channels/@me" rel="noreferrer">Discord</a>, and view any server, group, or private conversation (it will not appear in Friends list)</li>
<li>Click <strong>DHT</strong> in the top right corner:<br><img src="img/button.png" class="bordered"></li>
</ol>
</div>
<h4>Option 2: Browser / Discord Console</h4>
<div class="quote">
<p>The console is the only way to use DHT directly in the desktop app.</p>
<ol>
<li>Click <a href="javascript:" id="tracker-copy-button" onauxclick="return false;">Copy to Clipboard</a> to copy the tracking script
<noscript> (requires JavaScript)</noscript>
</li>
<li>Press <strong>Ctrl</strong>+<strong>Shift</strong>+<strong>I</strong> in your browser or the Discord app, and select the <strong>Console</strong> tab</li>
<li>Paste the script into the console, and press <strong>Enter</strong> to run it</li>
<li>Press <strong>Ctrl</strong>+<strong>Shift</strong>+<strong>I</strong> again to close the console</li>
</ol>
<p id="tracker-copy-issue">Your browser may not support copying to clipboard, please try copying the script manually:</p>
<textarea id="tracker-copy-contents"><?php include './build/track.html'; ?></textarea>
</div>
<h4>Option 3: Bookmarklet</h4>
<div class="quote">
<p>Requires Firefox 69 or newer.</p>
<ol>
<li>Right-click <a href="<?php include './build/track.html'; ?>" onclick="return false;" onauxclick="return false;">Discord History Tracker</a></li>
<li>Select &laquo;Bookmark This Link&raquo; and save the bookmark</li>
<li>Open <a href="https://discord.com/channels/@me" rel="noreferrer">Discord</a> and click the bookmark to run the script</li>
</ol>
</div>
<h4>Old Versions</h4>
<p>Whenever DHT is updated to work with a new version of Discord, it may no longer work with the previous version of Discord.</p>
<p>If you haven't received that Discord update yet, see <a href="https://github.com/chylex/Discord-History-Tracker/wiki/Release-Notes">Release Notes</a> for information about recent updates, and <a href="https://github.com/chylex/Discord-History-Tracker/wiki/Old-Versions">Old Versions</a> if you need to use an older version of DHT.</p>
<h3>How to Track Messages</h3>
<p>When using the script for the first time, you will see a <strong>Settings</strong> dialog where you can configure the script. These settings will be remembered as long as you don't delete cookies in your browser.</p>
<p>By default, Discord History Tracker is set to automatically scroll up to load the channel history, and pause tracking if it reaches a previously saved message to avoid unnecessary history loading.</p>
<p>Before you <strong>Start Tracking</strong>, you may use <strong>Upload &amp; Combine</strong> to load messages from a previously saved archive file into the browser.</p>
<p>When you click <strong>Download</strong>, the browser will generate an archive file from saved messages, and lets you save it to your computer.</p>
<h3>How to View History</h3>
<p>First, save the <a href="build/viewer.html">Viewer</a> file to your computer. Then you can open the downloaded viewer in your browser, click <strong>Load File</strong>, and select the archive to view.</p>
<h2>External Links</h2>
<p class="links">
<a href="https://github.com/chylex/Discord-History-Tracker/issues">Issues&nbsp;&amp;&nbsp;Suggestions</a>&nbsp;&nbsp;&mdash;&nbsp;
<a href="https://github.com/chylex/Discord-History-Tracker/tree/master-browser-only">Source&nbsp;Code</a>&nbsp;&nbsp;&mdash;&nbsp;
<a href="https://twitter.com/chylexmc">Follow&nbsp;Dev&nbsp;on&nbsp;Twitter</a>&nbsp;&nbsp;&mdash;&nbsp;
<a href="https://www.patreon.com/chylex">Support&nbsp;via&nbsp;Patreon</a>&nbsp;&nbsp;&mdash;&nbsp;
<a href="https://ko-fi.com/chylex">Support&nbsp;via&nbsp;Ko-fi</a>
</p>
</div>
<script type="text/javascript">
var contents = document.getElementById("tracker-copy-contents");
var issue = document.getElementById("tracker-copy-issue");
var button = document.getElementById("tracker-copy-button");
if (document.queryCommandSupported("copy")) {
contents.style.display = "none";
issue.style.display = "none";
}
button.addEventListener("click", function() {
contents.style.display = "block";
issue.style.display = "block";
contents.select();
document.execCommand("copy");
button.innerHTML = "Copied to Clipboard";
contents.style.display = "none";
issue.style.display = "none";
});
contents.addEventListener("click", function() {
contents.select();
});
</script>
</body>
</html>