diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index e23eabf8b..48c2dd3fa 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -35,6 +35,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.PRESENCE_UPDATE, 'PresenceUpdate'); this.register(Constants.WSEvents.USER_UPDATE, 'UserUpdate'); this.register(Constants.WSEvents.VOICE_STATE_UPDATE, 'VoiceStateUpdate'); + this.register(Constants.WSEvents.TYPING_START, 'TypingStart'); } get client() { diff --git a/src/client/websocket/packets/handlers/TypingStart.js b/src/client/websocket/packets/handlers/TypingStart.js new file mode 100644 index 000000000..5fdbf598f --- /dev/null +++ b/src/client/websocket/packets/handlers/TypingStart.js @@ -0,0 +1,62 @@ +'use strict'; + +const AbstractHandler = require('./AbstractHandler'); +const Structure = name => require(`../../../../structures/${name}`); +const Constants = require('../../../../util/Constants'); +const CloneObject = require('../../../../util/CloneObject'); + +const Role = Structure('User'); + +class TypingData { + constructor(since, lastTimestamp, _timeout) { + this.since = since; + this.lastTimestamp = lastTimestamp; + this._timeout = _timeout; + } + + resetTimeout(_timeout) { + clearTimeout(this._timeout); + this._timeout = _timeout; + } + + get elapsedTime() { + return Date.now() - this.since; + } +} + +class TypingStartHandler extends AbstractHandler { + + constructor(packetManager) { + super(packetManager); + } + + handle(packet) { + let data = packet.d; + let client = this.packetManager.client; + let channel = client.store.get('channels', data.channel_id); + let user = client.store.get('users', data.user_id); + let timestamp = new Date(data.timestamp * 1000); + + if (channel && user) { + if (channel.typingMap[user.id]) { + // already typing, renew + let mapping = channel.typingMap[user.id]; + mapping.lastTimestamp = timestamp; + mapping.resetTimeout(tooLate()); + } else { + channel.typingMap[user.id] = new TypingData(timestamp, timestamp, tooLate()); + client.emit(Constants.Events.TYPING_START, channel, user); + } + } + + function tooLate() { + return setTimeout(() => { + client.emit(Constants.Events.TYPING_STOP, channel, user, channel.typingMap[user.id]); + delete channel.typingMap[user.id]; + }, 6000); + } + } + +}; + +module.exports = TypingStartHandler; diff --git a/src/structures/Channel.js b/src/structures/Channel.js index ae9950c33..245a17a09 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -3,6 +3,8 @@ class Channel { constructor(client, data) { this.client = client; + this.typingMap = {}; + this.typingTimeouts = []; if (data) { this.setup(data); } diff --git a/src/util/Constants.js b/src/util/Constants.js index 86fe01556..1449147fe 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -94,6 +94,8 @@ const Events = exports.Events = { PRESENCE_UPDATE: 'presenceUpdate', USER_UPDATE: 'userUpdate', VOICE_STATE_UPDATE: 'voiceStateUpdate', + TYPING_START: 'typingStart', + TYPING_STOP: 'typingStop', WARN: 'warn', }; @@ -118,7 +120,7 @@ const WSEvents = exports.WSEvents = { GUILD_ROLE_DELETE: 'GUILD_ROLE_DELETE', GUILD_ROLE_UPDATE: 'GUILD_ROLE_UPDATE', GUILD_UPDATE: 'GUILD_UPDATE', - TYPING: 'TYPING_START', + TYPING_START: 'TYPING_START', USER_UPDATE: 'USER_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', FRIEND_ADD: 'RELATIONSHIP_ADD', diff --git a/test/random.js b/test/random.js index ec4003d72..d47034116 100644 --- a/test/random.js +++ b/test/random.js @@ -56,3 +56,13 @@ client.on('presenceUpdate', (oldUser, newUser) => { client.on('voiceStateUpdate', (oldMember, newMember) => { console.log('voiceState', oldMember.user.username, oldMember.voiceChannel + '', newMember.voiceChannel + ''); }); + +client.on('typingStart', (channel, user) => { + if (user.username === 'hydrabolt') + console.log(user.username, 'started typing in', channel.name); +}); + +client.on('typingStop', (channel, user, data) => { + if (user.username === 'hydrabolt') + console.log(user.username, 'stopped typing in', channel.name, 'after', data.elapsedTime + 'ms'); +});