1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2024-10-17 09:42:44 +02:00
Discord-History-Tracker/bld/track.js
2024-03-03 14:16:57 +01:00

1 line
24 KiB
JavaScript

javascript:(function(){const url=window.location.href;if(!url.includes("discord.com/")&&!url.includes("discordapp.com/")&&!confirm("Could not detect Discord in the URL, do you want to run the script anyway?"))return;if(window.DHT_LOADED)return void alert("Discord History Tracker is already loaded.");window.DHT_LOADED=!0,window.DHT_ON_UNLOAD=[];class DISCORD{static CHANNEL_TYPE={DM:1,GROUP_DM:3,ANNOUNCEMENT_THREAD:10,PUBLIC_THREAD:11,PRIVATE_THREAD:12};static MESSAGE_TYPE={DEFAULT:0,REPLY:19,THREAD_STARTER:21};static getMessageOuterElement(){return DOM.queryReactClass("messagesWrapper")}static getMessageScrollerElement(){return DOM.queryReactClass("scroller",this.getMessageOuterElement())}static getMessageElements(){return this.getMessageOuterElement().querySelectorAll("[class*='message_']")}static hasMoreMessages(){return null===document.querySelector("#messagesNavigationDescription + [class^=container]")}static loadOlderMessages(){const view=this.getMessageScrollerElement();0<view.scrollTop&&(view.scrollTop=0)}static setupMessageCallback(callback){const previousMessages=new Set,onMessageElementsChanged=function(){const messages=DISCORD.getMessages();if(messages.some(message=>!previousMessages.has(message.id))||!DISCORD.hasMoreMessages()){previousMessages.clear();for(const message of messages)previousMessages.add(message.id);callback(messages)}};let debounceTimer;const onMessageElementsChangedLater=function(){window.clearTimeout(debounceTimer),debounceTimer=window.setTimeout(onMessageElementsChanged,100)},observer=new MutationObserver(function(){onMessageElementsChangedLater()});let skipsLeft=0,observedElement=null;const observerTimer=window.setInterval(()=>{if(0<skipsLeft)--skipsLeft;else{const view=this.getMessageOuterElement();view?null!==observedElement&&observedElement.isConnected||(observedElement=view.querySelector("[data-list-id='chat-messages']"),observedElement&&(console.debug("[DHT] Observed message container."),observer.observe(observedElement,{childList:!0}),onMessageElementsChangedLater())):skipsLeft=1}},400);window.DHT_ON_UNLOAD.push(()=>{observer.disconnect(),observedElement=null,window.clearInterval(observerTimer)})}static getMessageFromElement(props){props=DOM.getReactProps(props);if(props&&Array.isArray(props.children))for(const child of props.children)if(child instanceof Object){var childProps=child.props;if(childProps instanceof Object&&"message"in childProps)return childProps.message}return null}static getMessages(){try{const messages=[];for(const ele of this.getMessageElements())try{var message=this.getMessageFromElement(ele);null!=message&&messages.push(message)}catch(e){console.error("[DHT] Error extracing message data, skipping it.",e,ele,DOM.tryGetReactProps(ele))}return messages}catch(e){return console.error("[DHT] Error retrieving messages.",e),[]}}static getSelectedChannel(){try{let obj=null;try{for(const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children)if(child&&child.props&&child.props.channel){obj=child.props.channel;break}}catch(e){console.error("[DHT] Error retrieving selected channel from 'chatContent' element.",e)}if(!obj||"string"!=typeof obj.id)return null;const dms=DOM.queryReactClass("privateChannels");if(dms){let name;for(const ele of dms.querySelectorAll("[class*='channel_'] [class*='selected_'] [class^='name_'] *")){var node=Array.prototype.find.call(ele.childNodes,node=>node.nodeType===Node.TEXT_NODE);if(node){name=node.nodeValue;break}}if(!name)return null;let type;switch(obj.type){case DISCORD.CHANNEL_TYPE.DM:type="DM";break;case DISCORD.CHANNEL_TYPE.GROUP_DM:type="GROUP";break;default:return null}var id=obj.id;return{server:{id:id,name:name,type:type},channel:{id:id,name:name}}}if(obj.guild_id){let guild;for(const child of DOM.getReactProps(document.querySelector("nav header [class*='headerContent_']")).children)if(child&&child.props&&child.props.guild){guild=child.props.guild;break}if(!guild||"string"!=typeof guild.name||obj.guild_id!==guild.id)return null;const server={id:guild.id,name:guild.name,type:"SERVER"},channel={id:obj.id,name:obj.name,extra:{nsfw:obj.nsfw}};return 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.position=obj.position,channel.extra.topic=obj.topic),{server:server,channel:channel}}return null}catch(e){return console.error("[DHT] Error retrieving selected channel.",e),null}}static selectNextTextChannel(){var currentChannelContainer=DOM.queryReactClass("privateChannels");if(currentChannelContainer){const currentChannel=DOM.queryReactClass("selected",currentChannelContainer);currentChannelContainer=currentChannel&&currentChannel.closest("[class*='channel_']");const nextChannel=currentChannelContainer&&currentChannelContainer.nextElementSibling;if(!nextChannel||!nextChannel.getAttribute("class").includes("channel_"))return!1;const nextChannelLink=nextChannel.querySelector("a[href*='/@me/']");return nextChannelLink?(nextChannelLink.click(),nextChannelLink.scrollIntoView(!0),!0):!1}{const channelListEle=document.getElementById("channels");if(!channelListEle)return!1;function getLinkElement(channel){return channel.querySelector("a[href^='/channels/'][role='link']")}const allTextChannels=Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"),ele=>null!==getLinkElement(ele));let nextChannel=null;for(let index=0;index<allTextChannels.length-1;index++)if(allTextChannels[index].className.includes("selected_")){nextChannel=allTextChannels[index+1];break}if(null===nextChannel)return!1;const nextChannelLink=getLinkElement(nextChannel);return nextChannelLink?(nextChannelLink.click(),nextChannel.scrollIntoView(!0),!0):!1}}}class DOM{static id(id,parent){return(parent||document).getElementById(id)}static queryReactClass(cls,parent){return(parent||document).querySelector(`[class*="${cls}_"]`)}static createElement(tag,parent,id,html){const ele=document.createElement(tag);return ele.id=id||"",ele.innerHTML=html||"",parent.appendChild(ele),ele}static removeElement(ele){return ele.parentNode.removeChild(ele)}static createStyle(styles){return this.createElement("style",document.head,"",styles)}static saveToCookie(name,obj,expires){expires=new Date(Date.now()+1e3*expires).toUTCString();document.cookie=name+"="+encodeURIComponent(JSON.stringify(obj))+";path=/;expires="+expires}static loadFromCookie(value){value=document.cookie.replace(new RegExp("(?:(?:^|.*;\\s*)"+value+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1");return value.length?JSON.parse(decodeURIComponent(value)):null}static getReactProps(ele){const keys=Object.keys(ele||{});let key=keys.find(key=>key.startsWith("__reactInternalInstance"));return key?ele[key].memoizedProps:(key=keys.find(key=>key.startsWith("__reactProps$")),key?ele[key]:null)}static tryGetReactProps(ele){try{return this.getReactProps(ele)}catch(e){return null}}}var GUI=function(){function setupStateChanged(detail){registeredEvent||(STATE.onStateChanged(stateChangedEvent),SETTINGS.onSettingsChanged(property=>stateChangedEvent("setting",property)),registeredEvent=!0),stateChangedEvent("gui",detail)}var controller,settings,updateButtonState=()=>{STATE.isTracking()?(controller.ui.btnUpload.disabled=!0,controller.ui.btnSettings.disabled=!0,controller.ui.btnReset.disabled=!0):(controller.ui.btnUpload.disabled=!1,controller.ui.btnSettings.disabled=!1,controller.ui.btnDownload.disabled=controller.ui.btnReset.disabled=!STATE.hasSavedData())},stateChangedEvent=(type,detail)=>{var messageCount,channelCount,force,autoscrollRev;controller&&(force="gui"===type&&"controller"===detail,"data"!==type&&!force||updateButtonState(),"tracking"!==type&&!force||(updateButtonState(),controller.ui.btnToggleTracking.innerHTML=STATE.isTracking()?"Pause Tracking":"Start Tracking"),"data"!==type&&!force||(channelCount=messageCount=0,STATE.hasSavedData()&&(messageCount=STATE.getSavefile().countMessages(),channelCount=STATE.getSavefile().countChannels()),controller.ui.textStatus.innerHTML=[messageCount," message",1===messageCount?"":"s"," from ",channelCount," channel",1===channelCount?"":"s"].join(""))),settings&&((force="gui"===type&&"settings"===detail)&&(settings.ui.cbAutoscroll.checked=SETTINGS.autoscroll,settings.ui.optsAfterFirstMsg[SETTINGS.afterFirstMsg].checked=!0,settings.ui.optsAfterSavedMsg[SETTINGS.afterSavedMsg].checked=!0),"setting"!==type&&!force||(autoscrollRev=!SETTINGS.autoscroll,Object.values(settings.ui.optsAfterFirstMsg).forEach(ele=>ele.disabled=autoscrollRev),Object.values(settings.ui.optsAfterSavedMsg).forEach(ele=>ele.disabled=autoscrollRev)))},registeredEvent=!1,root={showController:function(){(controller={}).styles=DOM.createStyle(`#app-mount { height: calc(100% - 48px) !important; } #dht-ctrl { position: absolute; bottom: 0; width: 100%; height: 48px; background-color: #fff; z-index: 1000000; } #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 p { display: inline-block; margin: 14px 12px; } #dht-ctrl-close { margin: 8px 8px 8px 0 !important; float: right; } `);var btn=(id,title)=>"<button id='dht-ctrl-"+id+"'>"+title+"</button>";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 style="display: none"> ${btn("close","X")}`),controller.ui={btnUpload:DOM.id("dht-ctrl-upload"),btnSettings:DOM.id("dht-ctrl-settings"),btnToggleTracking:DOM.id("dht-ctrl-track"),btnDownload:DOM.id("dht-ctrl-download"),btnReset:DOM.id("dht-ctrl-reset"),btnClose:DOM.id("dht-ctrl-close"),textStatus:DOM.id("dht-ctrl-status"),inputUpload:DOM.id("dht-ctrl-upload-input")},controller.ui.btnUpload.addEventListener("click",()=>{controller.ui.inputUpload.click()}),controller.ui.btnSettings.addEventListener("click",()=>{root.showSettings()}),controller.ui.btnToggleTracking.addEventListener("click",()=>{STATE.setIsTracking(!STATE.isTracking())}),controller.ui.btnDownload.addEventListener("click",()=>{STATE.downloadSavefile()}),controller.ui.btnReset.addEventListener("click",()=>{STATE.resetState()}),controller.ui.btnClose.addEventListener("click",()=>{root.hideController(),window.DHT_ON_UNLOAD.forEach(f=>f()),window.DHT_LOADED=!1}),controller.ui.inputUpload.addEventListener("change",()=>{Array.prototype.forEach.call(controller.ui.inputUpload.files,file=>{var reader=new FileReader;reader.onload=function(){var obj={};try{obj=JSON.parse(reader.result)}catch(e){return alert("Could not parse '"+file.name+"', see console for details."),void console.error(e)}SAVEFILE.isValid(obj)?STATE.uploadSavefile(file.name,new SAVEFILE(obj)):alert("File '"+file.name+"' has an invalid format.")},reader.readAsText(file,"UTF-8")}),controller.ui.inputUpload.value=null}),setupStateChanged("controller")},hideController:function(){controller&&(DOM.removeElement(controller.ele),DOM.removeElement(controller.styles),controller=null)},showSettings:function(){(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: 1000001; } #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: 1000002; } #dht-cfg-note { margin-top: 22px; } #dht-cfg sub { color: #666; font-size: 13px; } `),settings.overlay=DOM.createElement("div",document.body,"dht-cfg-overlay"),settings.overlay.addEventListener("click",()=>{root.hideSettings()});var radio=(type,id,label)=>"<label><input id='dht-cfg-"+type+"-"+id+"' name='dht-"+type+"' type='radio'> "+label+"</label><br>";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","Continue Tracking")} ${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","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>v.31h, released 03 March 2024</sub> </p>`),settings.ui={cbAutoscroll:DOM.id("dht-cfg-autoscroll"),optsAfterFirstMsg:{},optsAfterSavedMsg:{}},settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING]=DOM.id("dht-cfg-afm-nothing"),settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE]=DOM.id("dht-cfg-afm-pause"),settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH]=DOM.id("dht-cfg-afm-switch"),settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING]=DOM.id("dht-cfg-asm-nothing"),settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE]=DOM.id("dht-cfg-asm-pause"),settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH]=DOM.id("dht-cfg-asm-switch"),settings.ui.cbAutoscroll.addEventListener("change",()=>{SETTINGS.autoscroll=settings.ui.cbAutoscroll.checked}),Object.keys(settings.ui.optsAfterFirstMsg).forEach(key=>{settings.ui.optsAfterFirstMsg[key].addEventListener("click",()=>{SETTINGS.afterFirstMsg=key})}),Object.keys(settings.ui.optsAfterSavedMsg).forEach(key=>{settings.ui.optsAfterSavedMsg[key].addEventListener("click",()=>{SETTINGS.afterSavedMsg=key})}),setupStateChanged("settings")},hideSettings:function(){settings&&(DOM.removeElement(settings.overlay),DOM.removeElement(settings.ele),DOM.removeElement(settings.styles),settings=null)},setStatus:function(status){}};return root}();class SAVEFILE{constructor(parsedObj){var me=this;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.tmp={userlookup:{},channelkeys:new Set,messagekeys:new Set}}static isValid(parsedObj){return parsedObj&&"object"==typeof parsedObj.meta&&"object"==typeof parsedObj.data}static getDate(date){return date instanceof Date?date:date.toDate()}findOrRegisterUser(userId,userName,userDiscriminator,userAvatar){var wasPresent=userId in this.meta.users,userObj=wasPresent?this.meta.users[userId]:{};return userObj.name=userName,userDiscriminator&&(userObj.tag=userDiscriminator),userAvatar&&(userObj.avatar=userAvatar),wasPresent?userId in this.tmp.userlookup?this.tmp.userlookup[userId]:this.tmp.userlookup[userId]=this.meta.userindex.findIndex(id=>id==userId):(this.meta.users[userId]=userObj,this.meta.userindex.push(userId),this.tmp.userlookup[userId]=this.meta.userindex.length-1)}findOrRegisterServer(serverName,serverType){var index=this.meta.servers.findIndex(server=>server.name===serverName&&server.type===serverType);return-1===index?(this.meta.servers.push({name:serverName,type:serverType}),this.meta.servers.length-1):index}tryRegisterChannel(channelObj,channelId,channelName,extraInfo){if(this.meta.servers[channelObj]){var wasPresent=channelId in this.meta.channels,channelObj=wasPresent?this.meta.channels[channelId]:{server:channelObj};return channelObj.name=channelName,extraInfo.position&&(channelObj.position=extraInfo.position),extraInfo.topic&&(channelObj.topic=extraInfo.topic),extraInfo.nsfw&&(channelObj.nsfw=extraInfo.nsfw),!wasPresent&&(this.meta.channels[channelId]=channelObj,this.tmp.channelkeys.add(channelId),!0)}}addMessage(wasPresent,messageId,messageObject){var container=this.data[wasPresent]||(this.data[wasPresent]={}),wasPresent=messageId in container;return container[messageId]=messageObject,this.tmp.messagekeys.add(messageId),!wasPresent}convertToMessageObject(discordMessage){var obj=discordMessage.author,obj={u:this.findOrRegisterUser(obj.id,obj.username,obj.bot?null:obj.discriminator,obj.avatar),t:SAVEFILE.getDate(discordMessage.timestamp).getTime()};return 0<discordMessage.content.length&&(obj.m=discordMessage.content),null!==discordMessage.editedTimestamp&&(obj.te=SAVEFILE.getDate(discordMessage.editedTimestamp).getTime()),0<discordMessage.embeds.length&&(obj.e=discordMessage.embeds.map(embed=>{let conv={url:embed.url,type:embed.type};return"rich"===embed.type&&Array.isArray(embed.title)&&1===embed.title.length&&"string"==typeof embed.title[0]&&(conv.t=embed.title[0],Array.isArray(embed.description)&&1===embed.description.length&&"string"==typeof embed.description[0]&&(conv.d=embed.description[0])),conv})),0<discordMessage.attachments.length&&(obj.a=discordMessage.attachments.map(attachment=>({url:attachment.url}))),null!==discordMessage.messageReference&&(obj.r=discordMessage.messageReference.message_id),0<discordMessage.reactions.length&&(obj.re=discordMessage.reactions.map(reaction=>{let conv={c:reaction.count,n:reaction.emoji.name};return null!==reaction.emoji.id&&(conv.id=reaction.emoji.id),reaction.emoji.animated&&(conv.an=!0),conv})),obj}addMessagesFromDiscord(discordMessageArray){var discordMessage,hasNewMessages=!1;for(discordMessage of discordMessageArray)this.addMessage(discordMessage.channel_id,discordMessage.id,this.convertToMessageObject(discordMessage))&&(hasNewMessages=!0);return hasNewMessages}countChannels(){return this.tmp.channelkeys.size}countMessages(){return this.tmp.messagekeys.size}combineWith(obj){var userId,channelId,messageId,userMap={},shownError=!1;for(userId in obj.meta.users){var oldUser=obj.meta.users[userId];userMap[obj.meta.userindex.findIndex(id=>id==userId)]=this.findOrRegisterUser(userId,oldUser.name,oldUser.tag,oldUser.avatar)}for(channelId in obj.meta.channels){var oldServer=obj.meta.servers[obj.meta.channels[channelId].server],oldChannel=obj.meta.channels[channelId];this.tryRegisterChannel(this.findOrRegisterServer(oldServer.name,oldServer.type),channelId,oldChannel.name,oldChannel)}for(channelId in obj.data)for(messageId in oldChannel=obj.data[channelId]){var oldMessage=oldChannel[messageId];(oldUser=oldMessage.u)in userMap?(oldMessage.u=userMap[oldUser],this.addMessage(channelId,messageId,oldMessage)):(shownError||(shownError=!0,alert("The uploaded archive appears to be corrupted, some messages will be skipped. See console for details."),console.error("User list:",obj.meta.users),console.error("User index:",obj.meta.userindex),console.error("Generated mapping:",userMap),console.error("Missing user for the following messages:")),console.error(oldMessage))}}toJson(){return JSON.stringify({meta:this.meta,data:this.data})}}const CONSTANTS={AUTOSCROLL_ACTION_NOTHING:"optNothing",AUTOSCROLL_ACTION_PAUSE:"optPause",AUTOSCROLL_ACTION_SWITCH:"optSwitch"};let IS_FIRST_RUN=!1;const SETTINGS=function(){const settingsChangedEvents=[],saveSettings=function(){DOM.saveToCookie("DHT_SETTINGS",root,15768e4)};function defineTriggeringProperty(obj,property,value){const name="_"+property;Object.defineProperty(obj,property,{get:()=>obj[name],set:value=>{obj[name]=value,function(property){for(const callback of settingsChangedEvents)callback(property);saveSettings()}(property)}}),obj[name]=value}let loaded=DOM.loadFromCookie("DHT_SETTINGS");loaded||(loaded={_autoscroll:!0,_afterFirstMsg:CONSTANTS.AUTOSCROLL_ACTION_PAUSE,_afterSavedMsg:CONSTANTS.AUTOSCROLL_ACTION_PAUSE},IS_FIRST_RUN=!0);const root={onSettingsChanged(callback){settingsChangedEvents.push(callback)}};return defineTriggeringProperty(root,"autoscroll",loaded._autoscroll),defineTriggeringProperty(root,"afterFirstMsg",loaded._afterFirstMsg),defineTriggeringProperty(root,"afterSavedMsg",loaded._afterSavedMsg),IS_FIRST_RUN&&saveSettings(),root}(),STATE=new class{constructor(){this._stateChangedEvents=[],this._trackingStateChangedListeners=[],this.resetState()}_triggerStateChanged(changeType,changeDetail){for(var callback of this._stateChangedEvents)callback(changeType,changeDetail);if("tracking"===changeType)for(let callback of this._trackingStateChangedListeners)callback(this._isTracking)}resetState(){this._savefile=null,this._isTracking=!1,this._lastFileName=null,this._triggerStateChanged("data","reset")}getSavefile(){return this._savefile||(this._savefile=new SAVEFILE),this._savefile}hasSavedData(){return null!=this._savefile}isTracking(){return this._isTracking}setIsTracking(state){this._isTracking=state,this._triggerStateChanged("tracking",state)}uploadSavefile(fileName,fileObject){this._lastFileName=fileName,this.getSavefile().combineWith(fileObject),this._triggerStateChanged("data","upload")}downloadTextFile(fileName,url){var ele=new Blob([url],{type:"octet/stream"});if("msSaveBlob"in window.navigator)return window.navigator.msSaveBlob(ele,fileName);url=window.URL.createObjectURL(ele),ele=DOM.createElement("a",document.body);ele.href=url,ele.download=fileName,ele.style.display="none",ele.click(),document.body.removeChild(ele),window.URL.revokeObjectURL(url)}downloadSavefile(){this.hasSavedData()&&this.downloadTextFile(this._lastFileName||"dht.txt",this._savefile.toJson())}addDiscordChannel(channelName,extraInfo){var serverName=channelName.name,serverIndex=channelName.type,channelId=extraInfo.id,channelName=extraInfo.name,extraInfo=extraInfo.extra||{},serverIndex=this.getSavefile().findOrRegisterServer(serverName,serverIndex);!0===this.getSavefile().tryRegisterChannel(serverIndex,channelId,channelName,extraInfo)&&this._triggerStateChanged("data","channel")}addDiscordMessages(discordMessageArray){return discordMessageArray=discordMessageArray.filter(msg=>(msg.type===DISCORD.MESSAGE_TYPE.DEFAULT||msg.type===DISCORD.MESSAGE_TYPE.REPLY||msg.type===DISCORD.MESSAGE_TYPE.THREAD_STARTER)&&"SENT"===msg.state),!!this.getSavefile().addMessagesFromDiscord(discordMessageArray)&&(this._triggerStateChanged("data","messages"),!0)}onStateChanged(callback){this._stateChangedEvents.push(callback)}onTrackingStateChanged(callback){this._trackingStateChangedListeners.push(callback),callback(this._isTracking)}};let delayedStopRequests=0;const stopTrackingDelayed=function(callback){delayedStopRequests++,window.setTimeout(()=>{STATE.setIsTracking(!1),delayedStopRequests--,callback&&callback()},200)};let hasJustStarted=!1,isSending=!1;const onError=function(e){console.log(e),GUI.setStatus("DISCONNECTED"===e.status?"Disconnected":"Error"),stopTrackingDelayed(()=>isSending=!1)},isNoAction=function(action){return null===action||action===CONSTANTS.AUTOSCROLL_ACTION_NOTHING},onTrackingContinued=function(anyNewMessages){if(STATE.isTracking()&&(GUI.setStatus("Tracking"),hasJustStarted&&(anyNewMessages=!0,hasJustStarted=!1),isSending=!1,SETTINGS.autoscroll)){let action=null;DISCORD.hasMoreMessages()||(console.debug("[DHT] Reached first message."),action=SETTINGS.afterFirstMsg),isNoAction(action)&&!anyNewMessages&&(console.debug("[DHT] No new messages."),action=SETTINGS.afterSavedMsg),isNoAction(action)?DISCORD.loadOlderMessages():action!==CONSTANTS.AUTOSCROLL_ACTION_PAUSE&&(action!==CONSTANTS.AUTOSCROLL_ACTION_SWITCH||DISCORD.selectNextTextChannel())||(GUI.setStatus("Reached End"),STATE.setIsTracking(!1))}};let waitUntilSendingFinishedTimer=null;const onMessagesUpdated=async messages=>{if(STATE.isTracking()&&!(0<delayedStopRequests)){if(isSending)return window.clearTimeout(waitUntilSendingFinishedTimer),void(waitUntilSendingFinishedTimer=window.setTimeout(()=>{waitUntilSendingFinishedTimer=null,onMessagesUpdated(messages)},100));var anyNewMessages,info=DISCORD.getSelectedChannel();if(!info)return GUI.setStatus("Error (Unknown Channel)"),void stopTrackingDelayed();isSending=!0;try{STATE.addDiscordChannel(info.server,info.channel)}catch(e){return void onError(e)}try{messages.length?(anyNewMessages=STATE.addDiscordMessages(messages),onTrackingContinued(anyNewMessages)):(isSending=!1,onTrackingContinued(!1))}catch(e){onError(e)}}};DISCORD.setupMessageCallback(onMessagesUpdated),STATE.onTrackingStateChanged(messages=>{messages?0!==(messages=DISCORD.getMessages()).length?(GUI.setStatus("Starting"),hasJustStarted=!0,onMessagesUpdated(messages)):stopTrackingDelayed(()=>alert("Cannot see any messages.")):isSending=!1}),GUI.showController(),IS_FIRST_RUN&&GUI.showSettings();})()