diff --git a/src/client/websocket/handlers/TYPING_START.js b/src/client/websocket/handlers/TYPING_START.js index 426c96f14..86fb26b1a 100644 --- a/src/client/websocket/handlers/TYPING_START.js +++ b/src/client/websocket/handlers/TYPING_START.js @@ -5,14 +5,45 @@ const { Events } = require('../../../util/Constants'); module.exports = (client, { d: data }) => { const channel = client.channels.cache.get(data.channel_id); const user = client.users.cache.get(data.user_id); + const timestamp = new Date(data.timestamp * 1000); if (channel && user) { - /** - * Emitted whenever a user starts typing in a channel. - * @event Client#typingStart - * @param {Channel} channel The channel the user started typing in - * @param {User} user The user that started typing - */ - client.emit(Events.TYPING_START, channel, user); + if (channel.type === 'voice') { + client.emit(Events.WARN, `Discord sent a typing packet to a voice channel ${channel.id}`); + return; + } + + if (channel._typing.has(user.id)) { + const typing = channel._typing.get(user.id); + + typing.lastTimestamp = timestamp; + typing.elapsedTime = Date.now() - typing.since; + client.clearTimeout(typing.timeout); + typing.timeout = tooLate(channel, user); + } else { + const since = new Date(); + const lastTimestamp = new Date(); + channel._typing.set(user.id, { + user, + since, + lastTimestamp, + elapsedTime: Date.now() - since, + timeout: tooLate(channel, user), + }); + + /** + * Emitted whenever a user starts typing in a channel. + * @event Client#typingStart + * @param {Channel} channel The channel the user started typing in + * @param {User} user The user that started typing + */ + client.emit(Events.TYPING_START, channel, user); + } } }; + +function tooLate(channel, user) { + return channel.client.setTimeout(() => { + channel._typing.delete(user.id); + }, 10000); +} diff --git a/typings/index.d.ts b/typings/index.d.ts index 4cc9bb698..c0c61ef08 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1952,6 +1952,7 @@ declare module 'discord.js' { } interface TextBasedChannelFields extends PartialTextBasedChannelFields { + _typing: Map; lastPinTimestamp: number | null; readonly lastPinAt: Date; typing: boolean; @@ -2780,15 +2781,11 @@ declare module 'discord.js' { partial: true; fetch(): Promise; } & { - [K in keyof Omit - // tslint:disable-next-line:ban-types - ]: T[K] extends Function ? T[K] : T[K] | null; + [K in keyof Omit< + T, + 'client' | 'createdAt' | 'createdTimestamp' | 'id' | 'partial' | 'fetch' + >]: // tslint:disable-next-line:ban-types + T[K] extends Function ? T[K] : T[K] | null; }; interface PartialDMChannel extends Partialize