Skip to content
This repository was archived by the owner on Oct 11, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
cacc107
Create ReadState.js
jsoncitron Mar 3, 2025
4caccc3
Update ReadState.js
jsoncitron Mar 5, 2025
668ae8a
Create ReadStateManager.js
jsoncitron Mar 5, 2025
ef6799c
Add read state type
jsoncitron Mar 5, 2025
5816efe
Super props bc 403
jsoncitron Mar 5, 2025
a0e9203
Read state manager on client
jsoncitron Mar 5, 2025
b18178e
Update ReadState.js
jsoncitron Mar 6, 2025
78bd160
Create GUILD_FEATURE_ACK.js
jsoncitron Mar 6, 2025
b37282e
Create USER_NON_CHANNEL_ACK.js
jsoncitron Mar 6, 2025
8939710
Create MESSAGE_ACK.js
jsoncitron Mar 6, 2025
1d4bac4
Update index.js
jsoncitron Mar 6, 2025
995275f
Update index.js
jsoncitron Mar 6, 2025
8928ee7
Create CHANNEL_PINS_ACK.js
jsoncitron Mar 6, 2025
d0bbba1
Update CHANNEL_PINS_ACK.js
jsoncitron Mar 6, 2025
1bdd26b
Update MESSAGE_ACK.js
jsoncitron Mar 6, 2025
5a50c42
Update Options.js
jsoncitron Mar 6, 2025
a91c26d
Update Constants.js
jsoncitron Mar 6, 2025
67bf85a
Update USER_NON_CHANNEL_ACK.js
jsoncitron Mar 6, 2025
702af2e
Update ReadStateManager.js
jsoncitron Mar 6, 2025
207235a
Update READY.js
jsoncitron Mar 6, 2025
98a8647
Update ReadStateManager.js
jsoncitron Mar 6, 2025
01dc43b
Update ReadStateManager.js
jsoncitron Mar 6, 2025
055bce9
Update Message.js
jsoncitron Mar 6, 2025
4707d24
Update ReadStateManager.js
jsoncitron Mar 6, 2025
1a80393
Update ReadState.js
jsoncitron Mar 6, 2025
e0397aa
Update ReadState.js
jsoncitron Mar 6, 2025
80afecf
Inc mention counter
jsoncitron Mar 6, 2025
04f93db
Update Guild.js
jsoncitron Mar 6, 2025
77e9299
Update CHANNEL_PINS_ACK.js
jsoncitron Mar 6, 2025
1d23fb1
Update CHANNEL_PINS_ACK.js
jsoncitron Mar 6, 2025
07d76a0
Update GUILD_FEATURE_ACK.js
jsoncitron Mar 6, 2025
d1bcede
Update MESSAGE_ACK.js
jsoncitron Mar 6, 2025
d7aefa6
Update USER_NON_CHANNEL_ACK.js
jsoncitron Mar 6, 2025
a2eb23c
Update READY.js
jsoncitron Mar 6, 2025
847153e
Update GuildSettingManager.js
jsoncitron Mar 6, 2025
57f3162
Update Guild.js
jsoncitron Mar 6, 2025
ddb98fa
Update Client.js
jsoncitron Mar 6, 2025
742d2cf
Update MessageCreate.js
jsoncitron Mar 6, 2025
9eb58fd
Update READY.js
jsoncitron Mar 6, 2025
e56c1ad
Update USER_GUILD_SETTINGS_UPDATE.js
jsoncitron Mar 6, 2025
3454465
Update Constants.js
jsoncitron Mar 6, 2025
9b35aa6
build 375018
jsoncitron Mar 6, 2025
a60634e
Update ReadStateManager.js
jsoncitron Mar 6, 2025
6271223
Update ReadState.js
jsoncitron Mar 6, 2025
f9cf065
Update ReadStateManager.js
jsoncitron Mar 6, 2025
6b82ac5
Update ReadState.js
jsoncitron Mar 6, 2025
745b2c6
Update TextBasedChannel.js
jsoncitron Mar 6, 2025
dc25b6f
Update ClientUser.js
jsoncitron Mar 6, 2025
4b9153a
Update Guild.js
jsoncitron Mar 6, 2025
c1d8c6f
Update ClientUser.js
jsoncitron Mar 6, 2025
d6918d8
Update Guild.js
jsoncitron Mar 6, 2025
5c80c00
Update Constants.js
jsoncitron Mar 7, 2025
76b330f
Update ReadState.js
jsoncitron Mar 7, 2025
ec1eaf6
Create ReadStateFlags.js
jsoncitron Mar 7, 2025
4dbce7d
Update ReadState.js
jsoncitron Mar 7, 2025
a2d258b
Update ReadStateManager.js
jsoncitron Mar 7, 2025
c4476d0
Update ReadStateManager.js
jsoncitron Mar 7, 2025
76549a1
Update GuildSettingManager.js
jsoncitron Mar 7, 2025
15c56a3
Update GuildSettingManager.js
jsoncitron Mar 7, 2025
9b9a239
Update Client.js
jsoncitron Mar 7, 2025
0ef2515
Update ReadStateManager.js
jsoncitron Mar 7, 2025
b51b899
fix crash manager
jsoncitron Mar 7, 2025
b459d2f
Update ReadState.js
jsoncitron Mar 7, 2025
b17f0f1
Update ReadStateManager.js
jsoncitron Mar 7, 2025
9836abd
Update MESSAGE_ACK.js
jsoncitron Mar 7, 2025
42d26e7
Update GUILD_FEATURE_ACK.js
jsoncitron Mar 7, 2025
9d4ad05
Update CHANNEL_PINS_ACK.js
jsoncitron Mar 7, 2025
cd48792
Update MessageCreate.js
jsoncitron Mar 7, 2025
d8b0275
Update MESSAGE_ACK.js
jsoncitron Mar 7, 2025
77bc436
Update GUILD_FEATURE_ACK.js
jsoncitron Mar 7, 2025
94135c6
Update CHANNEL_PINS_ACK.js
jsoncitron Mar 7, 2025
7abdde0
Update MessageCreate.js
jsoncitron Mar 7, 2025
5348f21
Update USER_NON_CHANNEL_ACK.js
jsoncitron Mar 7, 2025
26f04c5
Update enums.d.ts
jsoncitron Mar 9, 2025
9d303e1
Update enums.d.ts
jsoncitron Mar 9, 2025
c2b138d
Update index.d.ts
jsoncitron Mar 9, 2025
3443255
Update index.d.ts
jsoncitron Mar 9, 2025
3e3f9cb
Client Build number
jsoncitron Mar 9, 2025
290f363
Update rawDataTypes.d.ts
jsoncitron Mar 9, 2025
215ce31
Merge branch 'main' into read-state
jsoncitron Mar 14, 2025
c7248a6
377993
jsoncitron Mar 15, 2025
7a81785
380213
jsoncitron Mar 21, 2025
812cc6b
Update Options.js
jsoncitron Mar 28, 2025
f0c024e
Update MessageCreate.js
jsoncitron Mar 28, 2025
b6ce2e3
Update Constants.js
jsoncitron Apr 5, 2025
f52f6f4
Update Options.js
jsoncitron Apr 5, 2025
d02d7fc
Update Options.js
jsoncitron Apr 7, 2025
18b3280
Update Constants.js
jsoncitron Apr 7, 2025
113ed08
Update APIRequest.js
jsoncitron Apr 7, 2025
6709453
Update Constants.js
jsoncitron Apr 7, 2025
81a7e3e
Update ReadStateManager.js
jsoncitron Apr 13, 2025
fde39a0
Update ReadState.js
jsoncitron Apr 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const BillingManager = require('../managers/BillingManager');
const ChannelManager = require('../managers/ChannelManager');
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
const GuildManager = require('../managers/GuildManager');
const GuildSettingManager = require('../managers/GuildSettingManager');
const PresenceManager = require('../managers/PresenceManager');
const ReadStateManager = require('../managers/ReadStateManager');
const RelationshipManager = require('../managers/RelationshipManager');
const SessionManager = require('../managers/SessionManager');
const UserManager = require('../managers/UserManager');
Expand Down Expand Up @@ -145,6 +147,12 @@ class Client extends BaseClient {
*/
this.notes = new UserNoteManager(this);

/**
* All of the read read states
* @type {ReadStateManager}
*/
this.readStates = new ReadStateManager(this);

/**
* All of the relationships {@link User}
* @type {RelationshipManager}
Expand All @@ -169,6 +177,12 @@ class Client extends BaseClient {
*/
this.settings = new ClientUserSettingManager(this);

/**
* All of the guild settings {@link Object}
* @type {GuildSettingManager}
*/
this.guildSettings = new GuildSettingManager(this);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use GuildSettingManager (intended for Guild) with Client?

Copy link
Author

@jsoncitron jsoncitron Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Object.defineProperty(this, 'token', { writable: true });
if (!this.token && 'DISCORD_TOKEN' in process.env) {
/**
Expand Down
50 changes: 50 additions & 0 deletions src/client/actions/MessageCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const process = require('node:process');
const Action = require('./Action');
const { Collection } = require('@discordjs/collection');
const ReadState = require('../../structures/ReadState');
const { Events } = require('../../util/Constants');

let deprecationEmitted = false;
Expand All @@ -22,6 +24,54 @@ class MessageCreateAction extends Action {
const message = existing ?? channel.messages._add(data);
channel.lastMessageId = data.id;

let settings = (message.guild?.settings ?? this.client.guildSettings) ?? {
muted: false,
suppressEveryone: false,
suppressRoles: false,
};
let implicitAck = message.author?.id == this.client.user.id && message.type !== 'POLL_RESULT';
let mentioned = (
message.author?.relationship !== 'BLOCKED'
&& !(channel.type === 'GROUP_DM' && message.type === 'RECIPIENT_REMOVE')
|| (['DM', 'GROUP_DM'].includes(channel.type) && (settings.muteConfig?.endTime?.getTime() ?? 0) <= Date.now() && (settings.channelOverrides?.find(override => override.channel_id == channel.id)?.muteConfig?.endTime?.getTime() ?? 0) <= Date.now())
|| message.mentions.users.has(this.client.user.id)
|| (message.mentions.everyone && !settings.suppressEveryone)
|| (message.mentions.roles?.hasAny(message.guild?.members?.me?._roles ?? []) && !settings.suppressRoles)
);

if (implicitAck || mentioned) {
let readStates = client.readStates.cache.get('CHANNEL');
if (readStates) {
let readState = readStates.get(channel.id);
if (readState) {
if (implicitAck) readState.lastAckedId = message.id;
if (mentioned) readState.badgeCount++;
} else {
readState = new ReadState(this.client, {
id: channel.id,
read_state_type: 0,
badge_count: +mentioned,
last_viewed: null,
last_pin_timestamp: null,
last_acked_id: implicitAck ? message.id : '0',
});
readStates.set(readState.id, readState);
}
} else {
readStates = new Collection();
let readState = new ReadState(this.client, {
id: channel.id,
read_state_type: 0,
badge_count: +mentioned,
last_viewed: null,
last_pin_timestamp: null,
last_acked_id: implicitAck ? message.id : '0',
});
readStates.set(readState.id, readState);
client.readStates.cache.set('CHANNEL', readStates);
}
}

/**
* Emitted whenever a message is created.
* @event Client#messageCreate
Expand Down
47 changes: 47 additions & 0 deletions src/client/websocket/handlers/CHANNEL_PINS_ACK.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Events } = require('../../../util/Constants');
const ReadState = require('../../../structures/ReadState');

module.exports = (client, { d: data }) => {
let readStates = client.readStates.cache.get('CHANNEL');

let before = null, after = null;
if (readStates) {
after = readStates.get(data.channel_id) ?? null;
if (after) {
before = after._copy();
after.lastAckedId = data.channel_id;
} else {
after = new ReadState(client, {
id: data.channel_id,
last_acked_id: null,
last_pin_timestamp: data.timestamp,
badge_count: 0,
read_state_type: 0,
});
readStates.set(after.id, after);
}
} else {
after = new ReadState(client, {
id: data.channel_id,
last_acked_id: null,
last_pin_timestamp: data.timestamp,
badge_count: 0,
read_state_type: 0,
});

readStates = new Collection();
readStates.set(after.id, after);
client.readStates.cache.set('CHANNEL', readStates);
}

/**
* Emitted when pins were acked in a channel.
* @event Client#channelPinsAck
* @param {?ReadState} before Old read state
* @param {ReadState} after New read state
*/
client.emit(Events.CHANNEL_PINS_ACK, before, after);
};
48 changes: 48 additions & 0 deletions src/client/websocket/handlers/GUILD_FEATURE_ACK.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Events, ReadStateTypes } = require('../../../util/Constants');
const ReadState = require('../../../structures/ReadState');

module.exports = (client, { d: data }) => {
const readStateType = ReadStateTypes[data.ack_type];
if (readStateType !== 0 && !readStateType) return;

let readStates = client.readStates.cache.get(readStateType);

let before = null, after = null;
if (readStates) {
after = readStates.get(data.resource_id) ?? null;
if (after) {
before = after._copy();
after.lastAckedId = data.entity_id;
} else {
after = new ReadState(client, {
id: data.resource_id,
last_acked_id: data.entity_id,
badge_count: 0,
read_state_type: data.ack_type,
});
readStates.set(after.id, after);
}
} else {
after = new ReadState(client, {
id: data.resource_id,
last_acked_id: data.entity_id,
badge_count: 0,
read_state_type: data.ack_type,
});

readStates = new Collection();
readStates.set(after.id, after);
client.readStates.set(readStateType, readStates);
}

/**
* Emitted when a guild feature is acked.
* @event Client#guildFeatureAck
* @param {?ReadState} before Old read state
* @param {ReadState} after New read state
*/
client.emit(Events.GUILD_FEATURE_ACK, before, after);
};
51 changes: 51 additions & 0 deletions src/client/websocket/handlers/MESSAGE_ACK.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Events } = require('../../../util/Constants');
const ReadState = require('../../../structures/ReadState');

module.exports = (client, { d: data }) => {
let readStates = client.readStates.cache.get('CHANNEL');

const lastViewed = data.last_viewed === 0 || data.last_viewed ? data.last_viewed : undefined;

let before = null, after = null;
if (readStates) {
after = readStates.get(data.channel_id) ?? null;
if (after) {
before = after._copy();
after.lastAckedId = data.channel_id;
if (lastViewed !== undefined) after.lastViewed = lastViewed;
if (data.mention_count !== undefined) after.mentionCount = data.mentionCount;
} else {
after = new ReadState(client, {
id: data.channel_id,
last_acked_id: data.message_id,
mention_count: data.mention_count,
last_viewed: lastViewed,
read_state_type: 0,
});
readStates.set(after.id, after);
}
} else {
after = new ReadState(client, {
id: data.channel_id,
last_acked_id: data.message_id,
mention_count: data.mention_count,
last_viewed: lastViewed,
read_state_type: 0,
});

readStates = new Collection();
readStates.set(after.id, after);
client.readStates.cache.set('CHANNEL', readStates);
}

/**
* Emitted when a message is acked.
* @event Client#messageAck
* @param {?ReadState} before Old read state
* @param {ReadState} after New read state
*/
client.emit(Events.MESSAGE_ACK, before, after);
};
12 changes: 10 additions & 2 deletions src/client/websocket/handlers/READY.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@ module.exports = (client, { d: data }, shard) => {

// GuildSetting
for (const gSetting of Array.isArray(data.user_guild_settings) ? data.user_guild_settings : []) {
const guild = client.guilds.cache.get(gSetting.guild_id);
if (guild) guild.settings._patch(gSetting);
if (gSetting.guild_id === null) {
client.guildSettings._patch(gSetting);
} else {
const guild = client.guilds.cache.get(gSetting.guild_id);
if (guild) guild.settings._patch(gSetting);
}
}

// Read States
client.readStates._setup(data.read_state);

// Todo: data.auth_session_id_hash

if (data.guilds.length) {
Expand Down
24 changes: 22 additions & 2 deletions src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
'use strict';

const { Events } = require('../../../util/Constants');

module.exports = (client, { d: data }) => {
const guild = client.guilds.cache.get(data.guild_id);
if (guild) guild?.settings._patch(data);
if (data.guild_id === null) {
const before = client.guildSettings._copy();
client.guildSettings._patch(data);

/**
* Emitted when guild settings are updated.
* @event Client#guildSettingsUpdate
* @param {GuildSettingManager} before The guild setting
* @param {GuildSettingManager} after The guild setting
*/
client.emit(Events.USER_GUILD_SETTINGS_UPDATE, before, client.guildSettings);
} else {
let guild = client.guilds.cache.get(data.guild_id);
if (!guild) return;

const before = client.guildSettings._copy();
guild.settings._patch(data);

client.emit(Events.USER_GUILD_SETTINGS_UPDATE, before, guild.settings);
}
};
48 changes: 48 additions & 0 deletions src/client/websocket/handlers/USER_NON_CHANNEL_ACK.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Events, ReadStateTypes } = require('../../../util/Constants');
const ReadState = require('../../../structures/ReadState');

module.exports = (client, { d: data }) => {
const readStateType = ReadStateTypes[data.ack_type];
if (readStateType !== 0 && !readStateType) return;

let readStates = client.readStates.cache.get(readStateType);

let before = null, after = null;
if (readStates) {
after = readStates.get(data.resource_id) ?? null;
if (after) {
before = after._copy();
after.lastAckedId = data.entity_id;
} else {
after = new ReadState(client, {
id: data.resource_id,
last_acked_id: data.entity_id,
badge_count: 0,
read_state_type: data.ack_type,
});
readStates.set(after.id, after);
}
} else {
after = new ReadState(client, {
id: data.resource_id,
last_acked_id: data.entity_id,
badge_count: 0,
read_state_type: data.ack_type,
});

readStates = new Collection();
readStates.set(after.id, after);
client.readStates.cache.set(readStateType, readStates);
}

/**
* Emitted when a user feature is acked.
* @event Client#userFeatureAck
* @param {?ReadState} before Old read state
* @param {ReadState} after New read state
*/
client.emit(Events.USER_FEATURE_ACK, before, after);
};
4 changes: 4 additions & 0 deletions src/client/websocket/handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const handlers = Object.fromEntries([
['VOICE_CHANNEL_STATUS_UPDATE', require('./VOICE_CHANNEL_STATUS_UPDATE')],
['MESSAGE_POLL_VOTE_ADD', require('./MESSAGE_POLL_VOTE_ADD')],
['MESSAGE_POLL_VOTE_REMOVE', require('./MESSAGE_POLL_VOTE_REMOVE')],
['MESSAGE_ACK', require('./MESSAGE_ACK')],
['CHANNEL_PINS_ACK', require('./CHANNEL_PINS_ACK')],
['GUILD_FEATURE_ACK', require('./GUILD_FEATURE_ACK')],
['USER_NON_CHANNEL_ACK', require('./USER_NON_CHANNEL_ACK')],
]);

module.exports = handlers;
Loading