mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-08-17 10:31:41 +02:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
52998f7d7a
|
|||
c2e302f8bb
|
|||
9f506aceb2
|
|||
bbb50dc50c
|
|||
3f763bbf6b
|
|||
be3a7d6d80
|
|||
bd78051a9d
|
|||
6a24befca1
|
|||
09dce7b062
|
|||
396c622d9c
|
|||
73bf16a21e
|
|||
25071d4323
|
23
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Minify.xml
generated
Normal file
23
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Minify.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Minify" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="rider.module" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="false" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="false" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/minify.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
@@ -12,7 +12,7 @@
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
||||
<Version>31.0.0.0</Version>
|
||||
<Version>32.1.0.0</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<PackageVersion>$(Version)</PackageVersion>
|
||||
|
@@ -3,46 +3,56 @@ using System;
|
||||
namespace DHT.Desktop.Dialogs {
|
||||
public static class DialogResult {
|
||||
public enum All {
|
||||
Ok, Yes, No, Cancel
|
||||
Ok,
|
||||
Yes,
|
||||
No,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public enum OkCancel {
|
||||
Closed, Ok, Cancel
|
||||
Closed,
|
||||
Ok,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public enum YesNo {
|
||||
Closed, Yes, No
|
||||
Closed,
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
public enum YesNoCancel {
|
||||
Closed, Yes, No, Cancel
|
||||
Closed,
|
||||
Yes,
|
||||
No,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public static OkCancel ToOkCancel(this All? result) {
|
||||
return result switch {
|
||||
null => OkCancel.Closed,
|
||||
All.Ok => OkCancel.Ok,
|
||||
All.Cancel => OkCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to ok/cancel.")
|
||||
null => OkCancel.Closed,
|
||||
All.Ok => OkCancel.Ok,
|
||||
All.Cancel => OkCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to ok/cancel.")
|
||||
};
|
||||
}
|
||||
|
||||
public static YesNo ToYesNo(this All? result) {
|
||||
return result switch {
|
||||
null => YesNo.Closed,
|
||||
All.Yes => YesNo.Yes,
|
||||
All.No => YesNo.No,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no.")
|
||||
null => YesNo.Closed,
|
||||
All.Yes => YesNo.Yes,
|
||||
All.No => YesNo.No,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no.")
|
||||
};
|
||||
}
|
||||
|
||||
public static YesNoCancel ToYesNoCancel(this All? result) {
|
||||
return result switch {
|
||||
null => YesNoCancel.Closed,
|
||||
All.Yes => YesNoCancel.Yes,
|
||||
All.No => YesNoCancel.No,
|
||||
All.Cancel => YesNoCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no/cancel.")
|
||||
null => YesNoCancel.Closed,
|
||||
All.Yes => YesNoCancel.Yes,
|
||||
All.No => YesNoCancel.No,
|
||||
All.Cancel => YesNoCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no/cancel.")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -16,4 +16,3 @@ namespace DHT.Desktop.Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,11 +19,11 @@ namespace DHT.Desktop.Main.Controls {
|
||||
public string StatusText {
|
||||
get {
|
||||
return CurrentStatus switch {
|
||||
Status.Starting => "STARTING",
|
||||
Status.Ready => "READY",
|
||||
Status.Stopping => "STOPPING",
|
||||
Status.Stopped => "STOPPED",
|
||||
_ => ""
|
||||
Status.Starting => "STARTING",
|
||||
Status.Ready => "READY",
|
||||
Status.Stopping => "STOPPING",
|
||||
Status.Stopped => "STOPPED",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
9
app/Resources/Tracker/bootstrap.js
vendored
9
app/Resources/Tracker/bootstrap.js
vendored
@@ -97,7 +97,7 @@
|
||||
const info = DISCORD.getSelectedChannel();
|
||||
|
||||
if (!info) {
|
||||
GUI.setStatus("Stopped");
|
||||
GUI.setStatus("Error (Unknown Channel)");
|
||||
stopTrackingDelayed();
|
||||
return;
|
||||
}
|
||||
@@ -130,14 +130,9 @@
|
||||
|
||||
STATE.onTrackingStateChanged(enabled => {
|
||||
if (enabled) {
|
||||
if (DISCORD.getSelectedChannel() == null) {
|
||||
stopTrackingDelayed(() => alert("The selected channel is not visible in the channel list."));
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = DISCORD.getMessages();
|
||||
|
||||
if (messages == null) {
|
||||
if (messages.length === 0) {
|
||||
stopTrackingDelayed(() => alert("Cannot see any messages."));
|
||||
return;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
const STATE=function(){let serverPort=-1;let serverToken="";const post=function(endpoint,json){const aborter=new AbortController;const timeout=window.setTimeout(()=>aborter.abort(),5e3);return new Promise(async(resolve,reject)=>{let r;try{r=await fetch("http://127.0.0.1:"+serverPort+endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-DHT-Token":serverToken},credentials:"omit",redirect:"error",body:JSON.stringify(json),signal:aborter.signal})}catch(e){if(e.name==="AbortError"){reject({status:"DISCONNECTED"});return}else{reject({status:"ERROR",message:e.message});return}}finally{window.clearTimeout(timeout)}if(r.status===200){resolve(r);return}try{const message=await r.text();reject({status:"ERROR",message:message})}catch(e){reject({status:"ERROR",message:e.message})}})};const trackingStateChangedListeners=[];let isTracking=false;const addedChannels=new Set;const addedUsers=new Set;return{setup(port,token){serverPort=port;serverToken=token},onTrackingStateChanged(callback){trackingStateChangedListeners.push(callback);callback(isTracking)},isTracking(){return isTracking},setIsTracking(state){if(isTracking!==state){isTracking=state;if(isTracking){addedChannels.clear();addedUsers.clear()}for(const callback of trackingStateChangedListeners){callback(isTracking)}}},async addDiscordChannel(serverInfo,channelInfo){if(addedChannels.has(channelInfo.id)){return}const server={id:serverInfo.id,name:serverInfo.name,type:serverInfo.type};const channel={id:channelInfo.id,name:channelInfo.name};if("extra"in channelInfo){channel.position=channelInfo.extra.position;channel.topic=channelInfo.extra.topic;channel.nsfw=channelInfo.extra.nsfw}await post("/track-channel",{server:server,channel:channel});addedChannels.add(channelInfo.id)},async addDiscordMessages(channelId,discordMessageArray){const userInfo={};let hasNewUsers=false;for(const msg of discordMessageArray){const user=msg.author;if(!addedUsers.has(user.id)){const obj={id:user.id,name:user.username};if(user.avatar){obj.avatar=user.avatar}if(!user.bot){obj.discriminator=user.discriminator}userInfo[user.id]=obj;hasNewUsers=true}}if(hasNewUsers){await post("/track-users",Object.values(userInfo));for(const id of Object.keys(userInfo)){addedUsers.add(id)}}const response=await post("/track-messages",discordMessageArray.map(msg=>{const obj={id:msg.id,sender:msg.author.id,channel:msg.channel_id,text:msg.content,timestamp:msg.timestamp.toDate().getTime()};if(msg.editedTimestamp!==null){obj.editTimestamp=msg.editedTimestamp.toDate().getTime()}if(msg.messageReference!==null){obj.repliedToId=msg.messageReference.message_id}if(msg.attachments.length>0){obj.attachments=msg.attachments.map(attachment=>{const mapped={id:attachment.id,name:attachment.filename,size:attachment.size,url:attachment.url};if(attachment.content_type){mapped.type=attachment.content_type}return mapped})}if(msg.embeds.length>0){obj.embeds=msg.embeds.map(embed=>{const mapped={};for(const key of Object.keys(embed)){if(key==="id"){continue}if(key==="rawTitle"){mapped["title"]=embed[key]}else if(key==="rawDescription"){mapped["description"]=embed[key]}else{mapped[key]=embed[key]}}return JSON.stringify(mapped)})}if(msg.reactions.length>0){obj.reactions=msg.reactions.map(reaction=>{const emoji=reaction.emoji;const mapped={count:reaction.count};if(emoji.id){mapped.id=emoji.id}if(emoji.name){mapped.name=emoji.name}if(emoji.animated){mapped.isAnimated=emoji.animated}return mapped})}return obj}));const anyNewMessages=await response.text();return anyNewMessages==="1"}}}();
|
||||
const STATE=function(){let serverPort=-1;let serverToken="";const post=function(endpoint,json){const aborter=new AbortController;const timeout=window.setTimeout(()=>aborter.abort(),5e3);return new Promise(async(resolve,reject)=>{let r;try{r=await fetch("http://127.0.0.1:"+serverPort+endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-DHT-Token":serverToken},credentials:"omit",redirect:"error",body:JSON.stringify(json),signal:aborter.signal})}catch(e){if(e.name==="AbortError"){reject({status:"DISCONNECTED"});return}else{reject({status:"ERROR",message:e.message});return}}finally{window.clearTimeout(timeout)}if(r.status===200){resolve(r);return}try{const message=await r.text();reject({status:"ERROR",message:message})}catch(e){reject({status:"ERROR",message:e.message})}})};const trackingStateChangedListeners=[];let isTracking=false;const addedChannels=new Set;const addedUsers=new Set;return{setup(port,token){serverPort=port;serverToken=token},onTrackingStateChanged(callback){trackingStateChangedListeners.push(callback);callback(isTracking)},isTracking(){return isTracking},setIsTracking(state){if(isTracking!==state){isTracking=state;if(isTracking){addedChannels.clear();addedUsers.clear()}for(const callback of trackingStateChangedListeners){callback(isTracking)}}},async addDiscordChannel(serverInfo,channelInfo){if(addedChannels.has(channelInfo.id)){return}const server={id:serverInfo.id,name:serverInfo.name,type:serverInfo.type};const channel={id:channelInfo.id,name:channelInfo.name};if("extra"in channelInfo){const extra=channelInfo.extra;if("parent"in extra){channel.parent=extra.parent}channel.position=extra.position;channel.topic=extra.topic;channel.nsfw=extra.nsfw}await post("/track-channel",{server:server,channel:channel});addedChannels.add(channelInfo.id)},async addDiscordMessages(channelId,discordMessageArray){discordMessageArray=discordMessageArray.filter(msg=>(msg.type===0||msg.type===19||msg.type===21)&&msg.state==="SENT");if(discordMessageArray.length===0){return false}const userInfo={};let hasNewUsers=false;for(const msg of discordMessageArray){const user=msg.author;if(!addedUsers.has(user.id)){const obj={id:user.id,name:user.username};if(user.avatar){obj.avatar=user.avatar}if(!user.bot){obj.discriminator=user.discriminator}userInfo[user.id]=obj;hasNewUsers=true}}if(hasNewUsers){await post("/track-users",Object.values(userInfo));for(const id of Object.keys(userInfo)){addedUsers.add(id)}}const response=await post("/track-messages",discordMessageArray.map(msg=>{const obj={id:msg.id,sender:msg.author.id,channel:msg.channel_id,text:msg.content,timestamp:msg.timestamp.toDate().getTime()};if(msg.editedTimestamp!==null){obj.editTimestamp=msg.editedTimestamp.toDate().getTime()}if(msg.messageReference!==null){obj.repliedToId=msg.messageReference.message_id}if(msg.attachments.length>0){obj.attachments=msg.attachments.map(attachment=>{const mapped={id:attachment.id,name:attachment.filename,size:attachment.size,url:attachment.url};if(attachment.content_type){mapped.type=attachment.content_type}return mapped})}if(msg.embeds.length>0){obj.embeds=msg.embeds.map(embed=>{const mapped={};for(const key of Object.keys(embed)){if(key==="id"){continue}if(key==="rawTitle"){mapped["title"]=embed[key]}else if(key==="rawDescription"){mapped["description"]=embed[key]}else{mapped[key]=embed[key]}}return JSON.stringify(mapped)})}if(msg.reactions.length>0){obj.reactions=msg.reactions.map(reaction=>{const emoji=reaction.emoji;const mapped={count:reaction.count};if(emoji.id){mapped.id=emoji.id}if(emoji.name){mapped.name=emoji.name}if(emoji.animated){mapped.isAnimated=emoji.animated}return mapped})}return obj}));const anyNewMessages=await response.text();return anyNewMessages==="1"}}}();
|
@@ -1,10 +1,15 @@
|
||||
// noinspection JSUnresolvedVariable
|
||||
class DISCORD {
|
||||
static getMessageOuterElement() {
|
||||
return DOM.queryReactClass("messagesWrapper");
|
||||
}
|
||||
|
||||
static getMessageScrollerElement() {
|
||||
return this.getMessageOuterElement().querySelector("[class*='scroller-']");
|
||||
return DOM.queryReactClass("scroller", this.getMessageOuterElement());
|
||||
}
|
||||
|
||||
static getMessageElements() {
|
||||
return this.getMessageOuterElement().querySelectorAll("[class*='message-']");
|
||||
}
|
||||
|
||||
static hasMoreMessages() {
|
||||
@@ -41,7 +46,7 @@ class DISCORD {
|
||||
return;
|
||||
}
|
||||
|
||||
const anyMessage = this.getMessageOuterElement().querySelector("[class*='message-']");
|
||||
const anyMessage = DOM.queryReactClass("message", this.getMessageOuterElement());
|
||||
const messageCount = anyMessage ? anyMessage.parentElement.children.length : 0;
|
||||
|
||||
if (messageCount > 300) {
|
||||
@@ -85,33 +90,74 @@ class DISCORD {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property object of a message element.
|
||||
* @returns { null | { message: DiscordMessage, channel: Object } }
|
||||
*/
|
||||
static getMessageElementProps(ele) {
|
||||
const props = DOM.getReactProps(ele);
|
||||
|
||||
if (props.children && props.children.length >= 4) {
|
||||
const childProps = props.children[3].props;
|
||||
|
||||
if ("message" in childProps && "channel" in childProps) {
|
||||
return childProps;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing currently loaded messages.
|
||||
*/
|
||||
static getMessages() {
|
||||
try {
|
||||
const messages = [];
|
||||
|
||||
for (const ele of this.getMessageElements()) {
|
||||
const props = this.getMessageElementProps(ele);
|
||||
|
||||
if (props != null) {
|
||||
messages.push(props.message);
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing the selected server and channel information.
|
||||
* For types DM and GROUP, the server and channel ids and names are identical.
|
||||
* @returns {{}|null}
|
||||
* @returns { {} | null }
|
||||
*/
|
||||
static getSelectedChannel() {
|
||||
try {
|
||||
let obj;
|
||||
let channelListEle = DOM.queryReactClass("privateChannels");
|
||||
|
||||
if (channelListEle) {
|
||||
const channel = DOM.queryReactClass("selected", channelListEle);
|
||||
for (const ele of this.getMessageElements()) {
|
||||
const props = this.getMessageElementProps(ele);
|
||||
|
||||
if (!channel || !("href" in channel) || !channel.href.includes("/@me/")) {
|
||||
return null;
|
||||
if (props != null) {
|
||||
obj = props.channel;
|
||||
break;
|
||||
}
|
||||
|
||||
const linkSplit = channel.href.split("/");
|
||||
const id = linkSplit[linkSplit.length - 1];
|
||||
|
||||
if (!(/^\d+$/.test(id))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dms = DOM.queryReactClass("privateChannels");
|
||||
|
||||
if (dms) {
|
||||
let name;
|
||||
|
||||
for (const ele of channel.querySelectorAll("[class^='name-'] *")) {
|
||||
for (const ele of dms.querySelectorAll("[class*='channel-'][class*='selected-'] [class^='name-'] *")) {
|
||||
const node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);
|
||||
|
||||
if (node) {
|
||||
@@ -124,87 +170,49 @@ class DISCORD {
|
||||
return null;
|
||||
}
|
||||
|
||||
const icon = channel.querySelector("img[class*='avatar']");
|
||||
const iconParent = icon && icon.closest("foreignObject");
|
||||
const iconMask = iconParent && iconParent.getAttribute("mask");
|
||||
let type;
|
||||
|
||||
obj = {
|
||||
"server": { id, name, type: (iconMask && iconMask.includes("#svg-mask-avatar-default")) ? "GROUP" : "DM" },
|
||||
"channel": { id, name }
|
||||
};
|
||||
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
||||
switch (obj.type) {
|
||||
case 1: type = "DM"; break;
|
||||
case 3: type = "GROUP"; break;
|
||||
default: return null;
|
||||
}
|
||||
|
||||
const id = obj.id;
|
||||
const server = { id, name, type };
|
||||
const channel = { id, name };
|
||||
|
||||
return { server, channel };
|
||||
}
|
||||
else {
|
||||
channelListEle = document.getElementById("channels");
|
||||
else if (obj.guild_id) {
|
||||
const server = {
|
||||
"id": obj.guild_id,
|
||||
"name": document.querySelector("nav header > h1").innerText,
|
||||
"type": "SERVER"
|
||||
};
|
||||
|
||||
const channel = channelListEle.querySelector("[class*='modeSelected']").parentElement;
|
||||
// noinspection JSUnresolvedVariable
|
||||
const props = DOM.getReactProps(channel).children.props;
|
||||
|
||||
if (!props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedVariable
|
||||
const channelObj = props.channel || props.children().props.channel;
|
||||
|
||||
if (!channelObj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedVariable
|
||||
obj = {
|
||||
"server": {
|
||||
"id": channelObj.guild_id,
|
||||
"name": document.querySelector("nav header > h1").innerText,
|
||||
"type": "SERVER"
|
||||
},
|
||||
"channel": {
|
||||
"id": channelObj.id,
|
||||
"name": channelObj.name,
|
||||
"extra": {
|
||||
"position": channelObj.position,
|
||||
"topic": channelObj.topic,
|
||||
"nsfw": channelObj.nsfw
|
||||
}
|
||||
const channel = {
|
||||
"id": obj.id,
|
||||
"name": obj.name,
|
||||
"extra": {
|
||||
"nsfw": obj.nsfw
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return obj.channel.length === 0 ? null : obj;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing currently loaded messages, or null if the messages cannot be retrieved.
|
||||
*/
|
||||
static getMessages() {
|
||||
try {
|
||||
const scroller = this.getMessageScrollerElement();
|
||||
const props = DOM.getReactProps(scroller);
|
||||
let wrappers;
|
||||
|
||||
try {
|
||||
// noinspection JSUnresolvedVariable
|
||||
wrappers = props.children.props.children.props.children.props.children.find(ele => Array.isArray(ele));
|
||||
} catch (e) { // old version compatibility
|
||||
wrappers = props.children.find(ele => Array.isArray(ele));
|
||||
}
|
||||
|
||||
const messages = [];
|
||||
|
||||
for (const obj of wrappers) {
|
||||
// noinspection JSUnresolvedVariable
|
||||
const nested = obj.props;
|
||||
|
||||
if (nested && nested.message) {
|
||||
messages.push(nested.message);
|
||||
if (obj.parent_id) {
|
||||
channel["extra"]["parent"] = obj.parent_id;
|
||||
}
|
||||
else {
|
||||
channel["extra"]["position"] = obj.position;
|
||||
channel["extra"]["topic"] = obj.topic;
|
||||
}
|
||||
|
||||
return { server, channel };
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return messages;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
@@ -231,11 +239,16 @@ class DISCORD {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const channelIconNormal = "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z";
|
||||
const channelIconSpecial = "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z";
|
||||
const channelIcons = [
|
||||
/* normal */ "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z",
|
||||
/* normal + thread */ "M5.43309 21C5.35842 21 5.30189 20.9325 5.31494 20.859L5.99991 17H2.14274C2.06819 17 2.01168 16.9327 2.02453 16.8593L2.33253 15.0993C2.34258 15.0419 2.39244 15 2.45074 15H6.34991L7.40991 9H3.55274C3.47819 9 3.42168 8.93274 3.43453 8.85931L3.74253 7.09931C3.75258 7.04189 3.80244 7 3.86074 7H7.75991L8.45234 3.09903C8.46251 3.04174 8.51231 3 8.57049 3H10.3267C10.4014 3 10.4579 3.06746 10.4449 3.14097L9.75991 7H15.7599L16.4523 3.09903C16.4625 3.04174 16.5123 3 16.5705 3H18.3267C18.4014 3 18.4579 3.06746 18.4449 3.14097L17.7599 7H21.6171C21.6916 7 21.7481 7.06725 21.7353 7.14069L21.4273 8.90069C21.4172 8.95811 21.3674 9 21.3091 9H17.4099L17.0495 11.04H15.05L15.4104 9H9.41035L8.35035 15H10.5599V17H7.99991L7.30749 20.901C7.29732 20.9583 7.24752 21 7.18934 21H5.43309Z",
|
||||
/* nsfw or private */ "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z",
|
||||
/* nsfw + thread */ "M14.4 7C14.5326 7 14.64 7.10745 14.64 7.24V8.76C14.64 8.89255 14.5326 9 14.4 9H9.41045L8.35045 15H10.56V17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H14.4Z",
|
||||
/* private + thread */ "M15.44 6.99992C15.5725 6.99992 15.68 7.10737 15.68 7.23992V8.75992C15.68 8.89247 15.5725 8.99992 15.44 8.99992H9.41045L8.35045 14.9999H10.56V16.9999H8.00001L7.36325 20.5873C7.32088 20.826 7.11337 20.9999 6.87094 20.9999H5.88657C5.57547 20.9999 5.3399 20.7189 5.39427 20.4125L6.00001 16.9999H2.59511C2.28449 16.9999 2.04905 16.7197 2.10259 16.4137L2.27759 15.4137C2.31946 15.1745 2.52722 14.9999 2.77011 14.9999H6.35001L7.41001 8.99992H4.00511C3.69449 8.99992 3.45905 8.71969 3.51259 8.41373L3.68759 7.41373C3.72946 7.17448 3.93722 6.99992 4.18011 6.99992H7.76001L8.39677 3.41254C8.43914 3.17384 8.64664 2.99992 8.88907 2.99992H9.87344C10.1845 2.99992 10.4201 3.28099 10.3657 3.58731L9.76001 6.99992H15.44Z"
|
||||
];
|
||||
|
||||
const isValidChannelClass = cls => cls.includes("wrapper-") && !cls.includes("clickable-");
|
||||
const isValidChannelType = ele => !!ele.querySelector("path[d=\"" + channelIconNormal + "\"]") || !!ele.querySelector("path[d=\"" + channelIconSpecial + "\"]");
|
||||
const isValidChannelType = ele => channelIcons.some(icon => !!ele.querySelector("path[d=\"" + icon + "\"]"));
|
||||
const isValidChannel = ele => ele.childElementCount > 0 && isValidChannelClass(ele.children[0].className) && isValidChannelType(ele);
|
||||
|
||||
const channelListEle = document.querySelector("div[class*='sidebar'] > nav[class*='container'] > div[class*='scroller']");
|
||||
|
@@ -75,6 +75,8 @@ const STATE = (function() {
|
||||
* @property {Object[]} embeds
|
||||
* @property {DiscordMessageReaction[]} [reactions]
|
||||
* @property {DiscordMessageReference} [messageReference]
|
||||
* @property {Number} type
|
||||
* @property {String} state
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -156,9 +158,15 @@ const STATE = (function() {
|
||||
};
|
||||
|
||||
if ("extra" in channelInfo) {
|
||||
channel.position = channelInfo.extra.position;
|
||||
channel.topic = channelInfo.extra.topic;
|
||||
channel.nsfw = channelInfo.extra.nsfw;
|
||||
const extra = channelInfo.extra;
|
||||
|
||||
if ("parent" in extra) {
|
||||
channel.parent = extra.parent;
|
||||
}
|
||||
|
||||
channel.position = extra.position;
|
||||
channel.topic = extra.topic;
|
||||
channel.nsfw = extra.nsfw;
|
||||
}
|
||||
|
||||
await post("/track-channel", { server, channel });
|
||||
@@ -170,6 +178,13 @@ const STATE = (function() {
|
||||
* @param {DiscordMessage[]} discordMessageArray
|
||||
*/
|
||||
async addDiscordMessages(channelId, discordMessageArray) {
|
||||
// https://discord.com/developers/docs/resources/channel#message-object-message-types
|
||||
discordMessageArray = discordMessageArray.filter(msg => (msg.type === 0 || msg.type === 19 || msg.type === 21) && msg.state === "SENT");
|
||||
|
||||
if (discordMessageArray.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userInfo = {};
|
||||
let hasNewUsers = false;
|
||||
|
||||
|
@@ -171,7 +171,7 @@ const DISCORD = (function() {
|
||||
return DOM.getHumanReadableTime(value);
|
||||
}
|
||||
else if (property === "contents") {
|
||||
return value == null || value.length === 0 ? "" : processMessageContents(value);
|
||||
return value && value.length > 0 ? processMessageContents(value) : "";
|
||||
}
|
||||
else if (property === "embeds") {
|
||||
if (!value) {
|
||||
@@ -225,8 +225,8 @@ const DISCORD = (function() {
|
||||
return STATE.hasActiveFilter ? "<span class='info jump' data-jump='" + value + "'>Jump to message</span>" : "";
|
||||
}
|
||||
else if (property === "reply") {
|
||||
if (value === null) {
|
||||
return "";
|
||||
if (!value) {
|
||||
return value === null ? "<span class='reply-contents reply-missing'>(replies to an unknown message)</span>" : "";
|
||||
}
|
||||
|
||||
const user = "<span class='reply-username' title='#" + (value.user.tag ? value.user.tag : "????") + "'>" + value.user.name + "</span>";
|
||||
@@ -236,7 +236,7 @@ const DISCORD = (function() {
|
||||
return "<span class='jump' data-jump='" + value.id + "'>Jump to reply</span><span class='user'>" + avatar + user + "</span>" + contents;
|
||||
}
|
||||
else if (property === "reactions"){
|
||||
if (value === null){
|
||||
if (!value){
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@@ -118,7 +118,13 @@ const GUI = (function() {
|
||||
resetActiveFilter();
|
||||
|
||||
const index = STATE.navigateToMessage(jump);
|
||||
DOM.id("messages").children[index].scrollIntoView();
|
||||
|
||||
if (index === -1) {
|
||||
alert("Message not found.");
|
||||
}
|
||||
else {
|
||||
DOM.id("messages").children[index].scrollIntoView();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -29,29 +29,132 @@ const STATE = (function() {
|
||||
return loadedFileMeta ? loadedFileMeta.users : [];
|
||||
};
|
||||
|
||||
const getServer = function(index) {
|
||||
return loadedFileMeta.servers[index] || { "name": "<unknown>", "type": "unknown" };
|
||||
};
|
||||
|
||||
const generateChannelHierarchy = function() {
|
||||
/**
|
||||
* @type {Map<string, Set>}
|
||||
*/
|
||||
const hierarchy = new Map();
|
||||
|
||||
if (!loadedFileMeta) {
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Set}
|
||||
*/
|
||||
function getChildren(parentId) {
|
||||
let children = hierarchy.get(parentId);
|
||||
|
||||
if (!children) {
|
||||
children = new Set();
|
||||
hierarchy.set(parentId, children);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
for (const [ id, channel ] of Object.entries(loadedFileMeta.channels)) {
|
||||
getChildren(channel.parent || "").add(id);
|
||||
}
|
||||
|
||||
const unreachableIds = new Set(hierarchy.keys());
|
||||
|
||||
function reachIds(parentId) {
|
||||
unreachableIds.delete(parentId);
|
||||
|
||||
const children = hierarchy.get(parentId);
|
||||
|
||||
if (children) {
|
||||
for (const id of children) {
|
||||
reachIds(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reachIds("");
|
||||
|
||||
const rootChildren = getChildren("");
|
||||
|
||||
for (const unreachableId of unreachableIds) {
|
||||
for (const id of hierarchy.get(unreachableId)) {
|
||||
rootChildren.add(id);
|
||||
}
|
||||
|
||||
hierarchy.delete(unreachableId);
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
};
|
||||
|
||||
const generateChannelOrder = function() {
|
||||
if (!loadedFileMeta) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const channels = loadedFileMeta.channels;
|
||||
const hierarchy = generateChannelHierarchy();
|
||||
|
||||
function getSortedSubTree(parentId) {
|
||||
const children = hierarchy.get(parentId);
|
||||
if (!children) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const sortedChildren = Array.from(children);
|
||||
|
||||
sortedChildren.sort((id1, id2) => {
|
||||
const c1 = channels[id1];
|
||||
const c2 = channels[id2];
|
||||
const s1 = getServer(c1.server);
|
||||
const s2 = getServer(c2.server);
|
||||
|
||||
return s1.type.localeCompare(s2.type, "en") ||
|
||||
s1.name.toLocaleLowerCase().localeCompare(s2.name.toLocaleLowerCase(), undefined, { numeric: true }) ||
|
||||
(c1.position || -1) - (c2.position || -1) ||
|
||||
c1.name.toLocaleLowerCase().localeCompare(c2.name.toLocaleLowerCase(), undefined, { numeric: true });
|
||||
});
|
||||
|
||||
const subTree = [];
|
||||
|
||||
for (const id of sortedChildren) {
|
||||
subTree.push(id);
|
||||
subTree.push(...getSortedSubTree(id));
|
||||
}
|
||||
|
||||
return subTree;
|
||||
}
|
||||
|
||||
const orderArray = getSortedSubTree("");
|
||||
const orderMap = {};
|
||||
|
||||
for (let i = 0; i < orderArray.length; i++) {
|
||||
orderMap[orderArray[i]] = i;
|
||||
}
|
||||
|
||||
return orderMap;
|
||||
};
|
||||
|
||||
const getChannelList = function() {
|
||||
if (!loadedFileMeta) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const channels = loadedFileMeta.channels;
|
||||
const channelOrder = generateChannelOrder();
|
||||
|
||||
return Object.keys(channels).map(key => ({
|
||||
"id": key,
|
||||
"name": channels[key].name,
|
||||
"server": loadedFileMeta.servers[channels[key].server] || { "name": "<unknown>", "type": "unknown" },
|
||||
"server": getServer(channels[key].server),
|
||||
"msgcount": getFilteredMessageKeys(key).length,
|
||||
"topic": channels[key].topic || "",
|
||||
"nsfw": channels[key].nsfw || false,
|
||||
"position": channels[key].position || -1
|
||||
})).sort((ac, bc) => {
|
||||
const as = ac.server;
|
||||
const bs = bc.server;
|
||||
|
||||
return as.type.localeCompare(bs.type, "en") ||
|
||||
as.name.toLocaleLowerCase().localeCompare(bs.name.toLocaleLowerCase(), undefined, { numeric: true }) ||
|
||||
ac.position - bc.position ||
|
||||
ac.name.toLocaleLowerCase().localeCompare(bc.name.toLocaleLowerCase(), undefined, { numeric: true });
|
||||
return channelOrder[ac.id] - channelOrder[bc.id];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -59,6 +162,26 @@ const STATE = (function() {
|
||||
return loadedFileData[channel] || {};
|
||||
};
|
||||
|
||||
const getMessageById = function(id) {
|
||||
for (const messages of Object.values(loadedFileData)) {
|
||||
if (id in messages) {
|
||||
return messages[id];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getMessageChannel = function(id) {
|
||||
for (const [ channel, messages ] of Object.entries(loadedFileData)) {
|
||||
if (id in messages) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getMessageList = function() {
|
||||
if (!loadedMessages) {
|
||||
return [];
|
||||
@@ -83,28 +206,47 @@ const STATE = (function() {
|
||||
const user = getUser(message.u);
|
||||
const avatar = user.avatar ? { id: getUserId(message.u), path: user.avatar } : null;
|
||||
|
||||
const reply = ("r" in message && message.r in messages) ? messages[message.r] : null;
|
||||
const replyUser = reply ? getUser(reply.u) : null;
|
||||
const replyAvatar = replyUser && replyUser.avatar ? { id: getUserId(reply.u), path: replyUser.avatar } : null;
|
||||
const replyObj = reply ? {
|
||||
"id": message.r,
|
||||
"user": replyUser,
|
||||
"avatar": replyAvatar,
|
||||
"contents": reply.m
|
||||
} : null;
|
||||
|
||||
return {
|
||||
const obj = {
|
||||
user,
|
||||
avatar,
|
||||
"timestamp": message.t,
|
||||
"contents": ("m" in message) ? message.m : null,
|
||||
"embeds": ("e" in message) ? message.e.map(embed => JSON.parse(embed)) : [],
|
||||
"attachments": ("a" in message) ? message.a : [],
|
||||
"edit": ("te" in message) ? message.te : null,
|
||||
"jump": key,
|
||||
"reply": replyObj,
|
||||
"reactions": ("re" in message) ? message.re : null
|
||||
};
|
||||
|
||||
if ("m" in message) {
|
||||
obj["contents"] = message.m;
|
||||
}
|
||||
|
||||
if ("e" in message) {
|
||||
obj["embeds"] = message.e.map(embed => JSON.parse(embed));
|
||||
}
|
||||
|
||||
if ("a" in message) {
|
||||
obj["attachments"] = message.a;
|
||||
}
|
||||
|
||||
if ("te" in message) {
|
||||
obj["edit"] = message.te;
|
||||
}
|
||||
|
||||
if ("r" in message) {
|
||||
const replyMessage = getMessageById(message.r);
|
||||
const replyUser = replyMessage ? getUser(replyMessage.u) : null;
|
||||
const replyAvatar = replyUser && replyUser.avatar ? { id: getUserId(replyMessage.u), path: replyUser.avatar } : null;
|
||||
|
||||
obj["reply"] = replyMessage ? {
|
||||
"id": message.r,
|
||||
"user": replyUser,
|
||||
"avatar": replyAvatar,
|
||||
"contents": replyMessage.m
|
||||
} : null;
|
||||
}
|
||||
|
||||
if ("re" in message) {
|
||||
obj["reactions"] = message.re;
|
||||
}
|
||||
|
||||
return obj;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -175,15 +317,18 @@ const STATE = (function() {
|
||||
},
|
||||
|
||||
getChannelName(channel) {
|
||||
return loadedFileMeta.channels[channel].name || channel;
|
||||
const channelObj = loadedFileMeta.channels[channel];
|
||||
return (channelObj && channelObj.name) || channel;
|
||||
},
|
||||
|
||||
getUserTag(user) {
|
||||
return loadedFileMeta.users[user].tag;
|
||||
const userObj = loadedFileMeta.users[user];
|
||||
return (userObj && userObj.tag) || "????";
|
||||
},
|
||||
|
||||
getUserName(user) {
|
||||
return loadedFileMeta.users[user].name || user;
|
||||
const userObj = loadedFileMeta.users[user];
|
||||
return (userObj && userObj.name) || user;
|
||||
},
|
||||
|
||||
selectChannel(channel) {
|
||||
@@ -247,13 +392,20 @@ const STATE = (function() {
|
||||
|
||||
navigateToMessage(id) {
|
||||
if (!loadedMessages) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const channel = getMessageChannel(id);
|
||||
|
||||
if (channel !== null && channel !== selectedChannel) {
|
||||
triggerChannelsRefreshed(channel);
|
||||
this.selectChannel(channel);
|
||||
}
|
||||
|
||||
const index = loadedMessages.indexOf(id);
|
||||
|
||||
if (index === -1) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
currentPage = Math.max(1, Math.min(this.getPageCount(), 1 + Math.floor(index / messagesPerPage)));
|
||||
|
@@ -221,6 +221,10 @@
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
.reply-missing {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.reactions {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ namespace DHT.Server.Data {
|
||||
public ulong Id { get; init; }
|
||||
public ulong Server { get; init; }
|
||||
public string Name { get; init; }
|
||||
public ulong? ParentId { get; init; }
|
||||
public int? Position { get; init; }
|
||||
public string? Topic { get; init; }
|
||||
public bool? Nsfw { get; init; }
|
||||
|
@@ -8,28 +8,28 @@ namespace DHT.Server.Data {
|
||||
public static class ServerTypes {
|
||||
public static ServerType? FromString(string? str) {
|
||||
return str switch {
|
||||
"SERVER" => ServerType.Server,
|
||||
"GROUP" => ServerType.Group,
|
||||
"DM" => ServerType.DirectMessage,
|
||||
_ => null
|
||||
"SERVER" => ServerType.Server,
|
||||
"GROUP" => ServerType.Group,
|
||||
"DM" => ServerType.DirectMessage,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToString(ServerType? type) {
|
||||
return type switch {
|
||||
ServerType.Server => "SERVER",
|
||||
ServerType.Group => "GROUP",
|
||||
ServerType.DirectMessage => "DM",
|
||||
_ => "UNKNOWN"
|
||||
ServerType.Server => "SERVER",
|
||||
ServerType.Group => "GROUP",
|
||||
ServerType.DirectMessage => "DM",
|
||||
_ => "UNKNOWN"
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToJsonViewerString(ServerType? type) {
|
||||
return type switch {
|
||||
ServerType.Server => "server",
|
||||
ServerType.Group => "group",
|
||||
ServerType.DirectMessage => "user",
|
||||
_ => "unknown"
|
||||
ServerType.Server => "server",
|
||||
ServerType.Group => "group",
|
||||
ServerType.DirectMessage => "user",
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -71,6 +71,10 @@ namespace DHT.Server.Database.Export {
|
||||
obj.server = serverIndices[channel.Server];
|
||||
obj.name = channel.Name;
|
||||
|
||||
if (channel.ParentId != null) {
|
||||
obj.parent = channel.ParentId;
|
||||
}
|
||||
|
||||
if (channel.Position != null) {
|
||||
obj.position = channel.Position;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace DHT.Server.Database.Sqlite {
|
||||
internal class Schema {
|
||||
internal const int Version = 1;
|
||||
internal const int Version = 2;
|
||||
|
||||
private readonly SqliteConnection conn;
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace DHT.Server.Database.Sqlite {
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
server INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
parent_id INTEGER,
|
||||
position INTEGER,
|
||||
topic TEXT,
|
||||
nsfw INTEGER)");
|
||||
@@ -105,6 +106,10 @@ namespace DHT.Server.Database.Sqlite {
|
||||
|
||||
private void UpgradeSchemas(int dbVersion) {
|
||||
Execute("UPDATE metadata SET value = " + Version + " WHERE key = 'version'");
|
||||
|
||||
if (dbVersion <= 1) {
|
||||
Execute("ALTER TABLE channels ADD parent_id INTEGER");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -72,13 +72,14 @@ namespace DHT.Server.Database.Sqlite {
|
||||
|
||||
public void AddChannel(Channel channel) {
|
||||
using var cmd = conn.Upsert("channels", new[] {
|
||||
"id", "server", "name", "position", "topic", "nsfw"
|
||||
"id", "server", "name", "parent_id", "position", "topic", "nsfw"
|
||||
});
|
||||
|
||||
var channelParams = cmd.Parameters;
|
||||
channelParams.AddAndSet(":id", channel.Id);
|
||||
channelParams.AddAndSet(":server", channel.Server);
|
||||
channelParams.AddAndSet(":name", channel.Name);
|
||||
channelParams.AddAndSet(":parent_id", channel.ParentId);
|
||||
channelParams.AddAndSet(":position", channel.Position);
|
||||
channelParams.AddAndSet(":topic", channel.Topic);
|
||||
channelParams.AddAndSet(":nsfw", channel.Nsfw);
|
||||
@@ -89,7 +90,7 @@ namespace DHT.Server.Database.Sqlite {
|
||||
public List<Channel> GetAllChannels() {
|
||||
var list = new List<Channel>();
|
||||
|
||||
using var cmd = conn.Command("SELECT id, server, name, position, topic, nsfw FROM channels");
|
||||
using var cmd = conn.Command("SELECT id, server, name, parent_id, position, topic, nsfw FROM channels");
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
while (reader.Read()) {
|
||||
@@ -97,9 +98,10 @@ namespace DHT.Server.Database.Sqlite {
|
||||
Id = (ulong) reader.GetInt64(0),
|
||||
Server = (ulong) reader.GetInt64(1),
|
||||
Name = reader.GetString(2),
|
||||
Position = reader.IsDBNull(3) ? null : reader.GetInt32(3),
|
||||
Topic = reader.IsDBNull(4) ? null : reader.GetString(4),
|
||||
Nsfw = reader.IsDBNull(5) ? null : reader.GetBoolean(5)
|
||||
ParentId = reader.IsDBNull(3) ? null : (ulong) reader.GetInt64(3),
|
||||
Position = reader.IsDBNull(4) ? null : reader.GetInt32(4),
|
||||
Topic = reader.IsDBNull(5) ? null : reader.GetString(5),
|
||||
Nsfw = reader.IsDBNull(6) ? null : reader.GetBoolean(6)
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -27,24 +27,24 @@ namespace DHT.Server.Endpoints {
|
||||
var requestToken = request.Headers["X-DHT-Token"];
|
||||
if (requestToken.Count != 1 || requestToken[0] != parameters.Token) {
|
||||
Log.Error("Token: " + (requestToken.Count == 1 ? requestToken[0] : "<missing>"));
|
||||
response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
response.StatusCode = (int) HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var (statusCode, output) = await Respond(ctx);
|
||||
response.StatusCode = (int)statusCode;
|
||||
response.StatusCode = (int) statusCode;
|
||||
|
||||
if (output != null) {
|
||||
await response.WriteAsJsonAsync(output);
|
||||
}
|
||||
} catch (HttpException e) {
|
||||
Log.Error(e);
|
||||
response.StatusCode = (int)e.StatusCode;
|
||||
response.StatusCode = (int) e.StatusCode;
|
||||
await response.WriteAsync(e.Message);
|
||||
} catch (Exception e) {
|
||||
Log.Error(e);
|
||||
response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
response.StatusCode = (int) HttpStatusCode.InternalServerError;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,7 @@ namespace DHT.Server.Endpoints {
|
||||
Id = json.RequireSnowflake("id", path),
|
||||
Server = serverId,
|
||||
Name = json.RequireString("name", path),
|
||||
ParentId = json.HasKey("parent") ? json.RequireSnowflake("parent", path) : null,
|
||||
Position = json.HasKey("position") ? json.RequireInt("position", path, min: 0) : null,
|
||||
Topic = json.HasKey("topic") ? json.RequireString("topic", path) : null,
|
||||
Nsfw = json.HasKey("nsfw") ? json.RequireBool("nsfw", path) : null
|
||||
|
@@ -56,7 +56,7 @@ namespace DHT.Server.Endpoints {
|
||||
Name = ele.RequireString("name", path),
|
||||
Type = ele.HasKey("type") ? ele.RequireString("type", path) : null,
|
||||
Url = ele.RequireString("url", path),
|
||||
Size = (ulong)ele.RequireLong("size", path)
|
||||
Size = (ulong) ele.RequireLong("size", path)
|
||||
});
|
||||
|
||||
private static IEnumerable<Embed> ReadEmbeds(JsonElement.ArrayEnumerator array, string path) => array.Select(ele => new Embed {
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<Authors>chylex</Authors>
|
||||
<Company>DiscordHistoryTracker</Company>
|
||||
<Product>DiscordHistoryTrackerServer</Product>
|
||||
<Version>31.0.0.0</Version>
|
||||
<Version>32.1.0.0</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<PackageVersion>$(Version)</PackageVersion>
|
||||
|
@@ -16,7 +16,7 @@ namespace DHT.Server.Service {
|
||||
|
||||
services.AddCors(cors => {
|
||||
cors.AddDefaultPolicy(builder => {
|
||||
builder.WithOrigins("https://discord.com").AllowCredentials().AllowAnyMethod().AllowAnyHeader();
|
||||
builder.WithOrigins("https://discord.com", "https://discordapp.com").AllowCredentials().AllowAnyMethod().AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
15
app/minify.py
Normal file
15
app/minify.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Python 3
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
uglifyjs = os.path.abspath("../lib/uglifyjs")
|
||||
input_dir = os.path.abspath("./Resources/Tracker/scripts")
|
||||
output_dir = os.path.abspath("./Resources/Tracker/scripts.min")
|
||||
|
||||
for file in glob.glob(input_dir + "/*.js"):
|
||||
name = os.path.basename(file)
|
||||
print("Minifying {0}...".format(name))
|
||||
os.system("{0} {1} -o {2}/{3}".format(uglifyjs, file, output_dir, name))
|
||||
|
||||
print("Done!")
|
Reference in New Issue
Block a user