mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-09-15 19:32:09 +02:00
Compare commits
11 Commits
6ca386b741
...
master-bro
Author | SHA1 | Date | |
---|---|---|---|
83603eceb8
|
|||
5277f28318
|
|||
![]() |
515825f7a4 | ||
af48bf60ce
|
|||
3ac968aa38
|
|||
5debfa9ec6
|
|||
92b8450c80
|
|||
ff6e21186c
|
|||
f1bbe6d13c
|
|||
4eb78def90
|
|||
4e8df28dc2
|
File diff suppressed because one or more lines are too long
2602
bld/track.user.js
2602
bld/track.user.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
4
build.py
4
build.py
@@ -8,8 +8,8 @@ import os
|
|||||||
import re
|
import re
|
||||||
import distutils.dir_util
|
import distutils.dir_util
|
||||||
|
|
||||||
VERSION_SHORT = "v.31f"
|
VERSION_SHORT = "v.31h"
|
||||||
VERSION_FULL = VERSION_SHORT + ", released 20 November 2023"
|
VERSION_FULL = VERSION_SHORT + ", released 03 March 2024"
|
||||||
|
|
||||||
EXEC_UGLIFYJS_WIN = "{2}/lib/uglifyjs.cmd --parse bare_returns --compress --output \"{1}\" \"{0}\""
|
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}\""
|
EXEC_UGLIFYJS_AUTO = "uglifyjs --parse bare_returns --compress --output \"{1}\" \"{0}\""
|
||||||
|
4
src/tracker/bootstrap.js
vendored
4
src/tracker/bootstrap.js
vendored
@@ -61,9 +61,11 @@ const onTrackingContinued = function(anyNewMessages) {
|
|||||||
let action = null;
|
let action = null;
|
||||||
|
|
||||||
if (!DISCORD.hasMoreMessages()) {
|
if (!DISCORD.hasMoreMessages()) {
|
||||||
|
console.debug("[DHT] Reached first message.");
|
||||||
action = SETTINGS.afterFirstMsg;
|
action = SETTINGS.afterFirstMsg;
|
||||||
}
|
}
|
||||||
if (isNoAction(action) && !anyNewMessages) {
|
if (isNoAction(action) && !anyNewMessages) {
|
||||||
|
console.debug("[DHT] No new messages.");
|
||||||
action = SETTINGS.afterSavedMsg;
|
action = SETTINGS.afterSavedMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ const onMessagesUpdated = async messages => {
|
|||||||
isSending = true;
|
isSending = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await STATE.addDiscordChannel(info.server, info.channel);
|
STATE.addDiscordChannel(info.server, info.channel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onError(e);
|
onError(e);
|
||||||
return;
|
return;
|
||||||
|
@@ -1,5 +1,23 @@
|
|||||||
// noinspection JSUnresolvedVariable
|
// noinspection JSUnresolvedVariable
|
||||||
|
// noinspection LocalVariableNamingConventionJS
|
||||||
class DISCORD {
|
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() {
|
static getMessageOuterElement() {
|
||||||
return DOM.queryReactClass("messagesWrapper");
|
return DOM.queryReactClass("messagesWrapper");
|
||||||
}
|
}
|
||||||
@@ -28,46 +46,11 @@ class DISCORD {
|
|||||||
* Calls the provided function with a list of messages whenever the currently loaded messages change.
|
* Calls the provided function with a list of messages whenever the currently loaded messages change.
|
||||||
*/
|
*/
|
||||||
static setupMessageCallback(callback) {
|
static setupMessageCallback(callback) {
|
||||||
let skipsLeft = 0;
|
|
||||||
let waitForCleanup = false;
|
|
||||||
const previousMessages = new Set();
|
const previousMessages = new Set();
|
||||||
|
|
||||||
const timer = window.setInterval(() => {
|
const onMessageElementsChanged = function() {
|
||||||
if (skipsLeft > 0) {
|
const messages = DISCORD.getMessages();
|
||||||
--skipsLeft;
|
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !DISCORD.hasMoreMessages();
|
||||||
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) {
|
if (!hasChanged) {
|
||||||
return;
|
return;
|
||||||
@@ -79,24 +62,74 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callback(messages);
|
callback(messages);
|
||||||
}, 200);
|
};
|
||||||
|
|
||||||
window.DHT_ON_UNLOAD.push(() => window.clearInterval(timer));
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the property object of a message element.
|
* Returns the message from a message element.
|
||||||
* @returns { null | { message: DiscordMessage, channel: Object } }
|
* @returns { null | DiscordMessage } }
|
||||||
*/
|
*/
|
||||||
static getMessageElementProps(ele) {
|
static getMessageFromElement(ele) {
|
||||||
const props = DOM.getReactProps(ele);
|
const props = DOM.getReactProps(ele);
|
||||||
|
|
||||||
if (props.children && props.children.length) {
|
if (props && Array.isArray(props.children)) {
|
||||||
for (let i = 3; i < props.children.length; i++) {
|
for (const child of props.children) {
|
||||||
const childProps = props.children[i].props;
|
if (!(child instanceof Object)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (childProps && "message" in childProps && "channel" in childProps) {
|
const childProps = child.props;
|
||||||
return childProps;
|
if (childProps instanceof Object && "message" in childProps) {
|
||||||
|
return childProps.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,10 +146,10 @@ class DISCORD {
|
|||||||
|
|
||||||
for (const ele of this.getMessageElements()) {
|
for (const ele of this.getMessageElements()) {
|
||||||
try {
|
try {
|
||||||
const props = this.getMessageElementProps(ele);
|
const message = this.getMessageFromElement(ele);
|
||||||
|
|
||||||
if (props != null) {
|
if (message != null) {
|
||||||
messages.push(props.message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[DHT] Error extracing message data, skipping it.", e, ele, DOM.tryGetReactProps(ele));
|
console.error("[DHT] Error extracing message data, skipping it.", e, ele, DOM.tryGetReactProps(ele));
|
||||||
@@ -137,7 +170,7 @@ class DISCORD {
|
|||||||
*/
|
*/
|
||||||
static getSelectedChannel() {
|
static getSelectedChannel() {
|
||||||
try {
|
try {
|
||||||
let obj;
|
let obj = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children) {
|
for (const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children) {
|
||||||
@@ -148,15 +181,6 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[DHT] Error retrieving selected channel from 'chatContent' element.", 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") {
|
if (!obj || typeof obj.id !== "string") {
|
||||||
@@ -185,8 +209,8 @@ class DISCORD {
|
|||||||
|
|
||||||
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case 1: type = "DM"; break;
|
case DISCORD.CHANNEL_TYPE.DM: type = "DM"; break;
|
||||||
case 3: type = "GROUP"; break;
|
case DISCORD.CHANNEL_TYPE.GROUP_DM: type = "GROUP"; break;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +248,7 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (obj.parent_id) {
|
if (obj.type === DISCORD.CHANNEL_TYPE.ANNOUNCEMENT_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PUBLIC_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PRIVATE_THREAD) {
|
||||||
channel["extra"]["parent"] = obj.parent_id;
|
channel["extra"]["parent"] = obj.parent_id;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@@ -204,12 +204,12 @@ ${btn("close", "X")}`);
|
|||||||
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
|
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
|
||||||
<br>
|
<br>
|
||||||
<label>After reaching the first message in channel...</label><br>
|
<label>After reaching the first message in channel...</label><br>
|
||||||
${radio("afm", "nothing", "Do Nothing")}
|
${radio("afm", "nothing", "Continue Tracking")}
|
||||||
${radio("afm", "pause", "Pause Tracking")}
|
${radio("afm", "pause", "Pause Tracking")}
|
||||||
${radio("afm", "switch", "Switch to Next Channel")}
|
${radio("afm", "switch", "Switch to Next Channel")}
|
||||||
<br>
|
<br>
|
||||||
<label>After reaching a previously saved message...</label><br>
|
<label>After reaching a previously saved message...</label><br>
|
||||||
${radio("asm", "nothing", "Do Nothing")}
|
${radio("asm", "nothing", "Continue Tracking")}
|
||||||
${radio("asm", "pause", "Pause Tracking")}
|
${radio("asm", "pause", "Pause Tracking")}
|
||||||
${radio("asm", "switch", "Switch to Next Channel")}
|
${radio("asm", "switch", "Switch to Next Channel")}
|
||||||
<p id='dht-cfg-note'>
|
<p id='dht-cfg-note'>
|
||||||
|
@@ -113,6 +113,16 @@ class SAVEFILE{
|
|||||||
static isValid(parsedObj){
|
static isValid(parsedObj){
|
||||||
return parsedObj && typeof parsedObj.meta === "object" && typeof parsedObj.data === "object";
|
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){
|
findOrRegisterUser(userId, userName, userDiscriminator, userAvatar){
|
||||||
var wasPresent = userId in this.meta.users;
|
var wasPresent = userId in this.meta.users;
|
||||||
@@ -203,7 +213,7 @@ class SAVEFILE{
|
|||||||
|
|
||||||
var obj = {
|
var obj = {
|
||||||
u: this.findOrRegisterUser(author.id, author.username, author.bot ? null : author.discriminator, author.avatar),
|
u: this.findOrRegisterUser(author.id, author.username, author.bot ? null : author.discriminator, author.avatar),
|
||||||
t: discordMessage.timestamp.toDate().getTime()
|
t: SAVEFILE.getDate(discordMessage.timestamp).getTime()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (discordMessage.content.length > 0){
|
if (discordMessage.content.length > 0){
|
||||||
@@ -211,7 +221,7 @@ class SAVEFILE{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (discordMessage.editedTimestamp !== null){
|
if (discordMessage.editedTimestamp !== null){
|
||||||
obj.te = discordMessage.editedTimestamp.toDate().getTime();
|
obj.te = SAVEFILE.getDate(discordMessage.editedTimestamp).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discordMessage.embeds.length > 0){
|
if (discordMessage.embeds.length > 0){
|
||||||
@@ -269,11 +279,9 @@ class SAVEFILE{
|
|||||||
|
|
||||||
addMessagesFromDiscord(discordMessageArray){
|
addMessagesFromDiscord(discordMessageArray){
|
||||||
var hasNewMessages = false;
|
var hasNewMessages = false;
|
||||||
|
|
||||||
for(var discordMessage of discordMessageArray){
|
for(var discordMessage of discordMessageArray){
|
||||||
var type = discordMessage.type;
|
if (this.addMessage(discordMessage.channel_id, discordMessage.id, this.convertToMessageObject(discordMessage))){
|
||||||
|
|
||||||
// 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;
|
hasNewMessages = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -122,12 +122,13 @@ const STATE = (function() {
|
|||||||
this._triggerStateChanged("data", "channel");
|
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.
|
* Adds all messages from the array to the specified channel. Returns true if the savefile was updated.
|
||||||
*/
|
*/
|
||||||
addDiscordMessages(discordMessageArray){
|
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)){
|
if (this.getSavefile().addMessagesFromDiscord(discordMessageArray)){
|
||||||
this._triggerStateChanged("data", "messages");
|
this._triggerStateChanged("data", "messages");
|
||||||
return true;
|
return true;
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
var DISCORD = (function(){
|
var DISCORD = (function(){
|
||||||
var REGEX = {
|
var REGEX = {
|
||||||
formatBold: /\*\*([\s\S]+?)\*\*(?!\*)/g,
|
formatBold: /\*\*([\s\S]+?)\*\*(?!\*)/g,
|
||||||
formatItalic: /(.)?\*([\s\S]+?)\*(?!\*)/g,
|
formatItalic1: /\*([\s\S]+?)\*(?!\*)/g,
|
||||||
|
formatItalic2: /_([\s\S]+?)_(?!_)\b/g,
|
||||||
formatUnderline: /__([\s\S]+?)__(?!_)/g,
|
formatUnderline: /__([\s\S]+?)__(?!_)/g,
|
||||||
formatStrike: /~~([\s\S]+?)~~(?!~)/g,
|
formatStrike: /~~([\s\S]+?)~~(?!~)/g,
|
||||||
formatCodeInline: /(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/g,
|
formatCodeInline: /(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/g,
|
||||||
@@ -54,7 +55,8 @@ var DISCORD = (function(){
|
|||||||
.replace(REGEX.specialEscapedSingle, escapeHtmlMatch)
|
.replace(REGEX.specialEscapedSingle, escapeHtmlMatch)
|
||||||
.replace(REGEX.specialEscapedDouble, full => full.replace(/\\/g, "").replace(/(.)/g, escapeHtmlMatch))
|
.replace(REGEX.specialEscapedDouble, full => full.replace(/\\/g, "").replace(/(.)/g, escapeHtmlMatch))
|
||||||
.replace(REGEX.formatBold, "<b>$1</b>")
|
.replace(REGEX.formatBold, "<b>$1</b>")
|
||||||
.replace(REGEX.formatItalic, (full, pre, match) => pre === '\\' ? full : (pre || "")+"<i>"+match+"</i>")
|
.replace(REGEX.formatItalic1, "<i>$1</i>")
|
||||||
|
.replace(REGEX.formatItalic2, "<i>$1</i>")
|
||||||
.replace(REGEX.formatUnderline, "<u>$1</u>")
|
.replace(REGEX.formatUnderline, "<u>$1</u>")
|
||||||
.replace(REGEX.formatStrike, "<s>$1</s>");
|
.replace(REGEX.formatStrike, "<s>$1</s>");
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
106
web/index.php
106
web/index.php
@@ -13,110 +13,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<h1>Discord History Tracker <span class="version">{{{version:web}}}</span> <span class="bar">|</span> <span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Release-Notes">Release Notes</a></span></h1>
|
<h1>Discord History Tracker <span class="bar">|</span> <span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Release-Notes">Release Notes</a></span> <span class="bar">|</span> <span class="notes"><a href="https://github.com/chylex/Discord-History-Tracker/wiki/Old-Versions">Old Versions</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">
|
<img src="img/tracker.png" width="851" class="dht bordered">
|
||||||
<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>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>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>
|
<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>
|
||||||
|
|
||||||
<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 «Bookmark This Link» 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 & 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 & Suggestions</a> —
|
|
||||||
<a href="https://github.com/chylex/Discord-History-Tracker/tree/master-browser-only">Source Code</a> —
|
|
||||||
<a href="https://twitter.com/chylexmc">Follow Dev on Twitter</a> —
|
|
||||||
<a href="https://www.patreon.com/chylex">Support via Patreon</a> —
|
|
||||||
<a href="https://ko-fi.com/chylex">Support via Ko-fi</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user