-
-
Notifications
You must be signed in to change notification settings - Fork 987
/
Copy pathchat.js
220 lines (193 loc) · 7.38 KB
/
chat.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
const { onceWithCleanup } = require('../promise_utils')
const USERNAME_REGEX = '(?:\\(.{1,15}\\)|\\[.{1,15}\\]|.){0,5}?(\\w+)'
const LEGACY_VANILLA_CHAT_REGEX = new RegExp(`^${USERNAME_REGEX}\\s?[>:\\-»\\]\\)~]+\\s(.*)$`)
module.exports = inject
function inject (bot, options) {
const CHAT_LENGTH_LIMIT = options.chatLengthLimit ?? (bot.supportFeature('lessCharsInChat') ? 100 : 256)
const defaultChatPatterns = options.defaultChatPatterns ?? true
const ChatMessage = require('prismarine-chat')(bot.registry)
// chat.pattern.type will emit an event for bot.on() of the same type, eg chatType = whisper will trigger bot.on('whisper')
const _patterns = {}
let _length = 0
// deprecated
bot.chatAddPattern = (patternValue, typeValue) => {
return bot.addChatPattern(typeValue, patternValue, { deprecated: true })
}
bot.addChatPatternSet = (name, patterns, opts = {}) => {
if (!patterns.every(p => p instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
const { repeat = true, parse = false } = opts
_patterns[_length++] = {
name,
patterns,
position: 0,
matches: [],
messages: [],
repeat,
parse
}
return _length
}
bot.addChatPattern = (name, pattern, opts = {}) => {
if (!(pattern instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
const { repeat = true, deprecated = false, parse = false } = opts
_patterns[_length] = {
name,
patterns: [pattern],
position: 0,
matches: [],
messages: [],
deprecated,
repeat,
parse
}
return _length++ // increment length after we give it back to the user
}
bot.removeChatPattern = name => {
if (typeof name === 'number') {
_patterns[name] = undefined
} else {
const matchingPatterns = Object.entries(_patterns).filter(pattern => pattern[1]?.name === name)
matchingPatterns.forEach(([indexString]) => {
_patterns[+indexString] = undefined
})
}
}
function findMatchingPatterns (msg) {
const found = []
for (const [indexString, pattern] of Object.entries(_patterns)) {
if (!pattern) continue
const { position, patterns } = pattern
if (patterns[position].test(msg)) {
found.push(+indexString)
}
}
return found
}
bot.on('messagestr', (msg, _, originalMsg) => {
const foundPatterns = findMatchingPatterns(msg)
for (const ix of foundPatterns) {
_patterns[ix].matches.push(msg)
_patterns[ix].messages.push(originalMsg)
_patterns[ix].position++
if (_patterns[ix].deprecated) {
const [, ...matches] = _patterns[ix].matches[0].match(_patterns[ix].patterns[0])
bot.emit(_patterns[ix].name, ...matches, _patterns[ix].messages[0].translate, ..._patterns[ix].messages)
_patterns[ix].messages = [] // clear out old messages
} else { // regular parsing
if (_patterns[ix].patterns.length > _patterns[ix].matches.length) return // we have all the matches, so we can emit the done event
if (_patterns[ix].parse) {
const matches = _patterns[ix].patterns.map((pattern, i) => {
const [, ...matches] = _patterns[ix].matches[i].match(pattern) // delete full message match
return matches
})
bot.emit(`chat:${_patterns[ix].name}`, matches)
} else {
bot.emit(`chat:${_patterns[ix].name}`, _patterns[ix].matches)
}
// these are possibly null-ish if the user deletes them as soon as the event for the match is emitted
}
if (_patterns[ix]?.repeat) {
_patterns[ix].position = 0
_patterns[ix].matches = []
} else {
_patterns[ix] = undefined
}
}
})
addDefaultPatterns()
bot._client.on('playerChat', (data) => {
const message = data.formattedMessage
const verified = data.verified
let msg
if (bot.supportFeature('clientsideChatFormatting')) {
const parameters = {
sender: data.senderName ? JSON.parse(data.senderName) : undefined,
target: data.targetName ? JSON.parse(data.targetName) : undefined,
content: message ? JSON.parse(message) : { text: data.plainMessage }
}
msg = ChatMessage.fromNetwork(data.type, parameters)
if (data.unsignedContent) {
msg.unsigned = ChatMessage.fromNetwork(data.type, { sender: parameters.sender, target: parameters.target, content: JSON.parse(data.unsignedContent) })
}
} else {
msg = ChatMessage.fromNotch(message)
}
bot.emit('message', msg, 'chat', data.sender, verified)
bot.emit('messagestr', msg.toString(), 'chat', msg, data.sender, verified)
})
bot._client.on('systemChat', (data) => {
const msg = ChatMessage.fromNotch(data.formattedMessage)
const chatPositions = {
1: 'system',
2: 'game_info'
}
bot.emit('message', msg, chatPositions[data.positionId], null)
bot.emit('messagestr', msg.toString(), chatPositions[data.positionId], msg, null)
if (data.positionId === 2) bot.emit('actionBar', msg, null)
})
function chatWithHeader (header, message) {
if (typeof message === 'number') message = message.toString()
if (typeof message !== 'string') {
throw new Error('Chat message type must be a string or number: ' + typeof message)
}
if (!header && message.startsWith('/')) {
// Do not try and split a command without a header
bot._client.chat(message)
return
}
const lengthLimit = CHAT_LENGTH_LIMIT - header.length
message.split('\n').forEach((subMessage) => {
if (!subMessage) return
let i
let smallMsg
for (i = 0; i < subMessage.length; i += lengthLimit) {
smallMsg = header + subMessage.substring(i, i + lengthLimit)
bot._client.chat(smallMsg)
}
})
}
async function tabComplete (text, assumeCommand = false, sendBlockInSight = true, timeout = 5000) {
let position
if (sendBlockInSight) {
const block = bot.blockAtCursor()
if (block) {
position = block.position
}
}
bot._client.write('tab_complete', {
text,
assumeCommand,
lookedAtBlock: position
})
const [packet] = await onceWithCleanup(bot._client, 'tab_complete', { timeout })
return packet.matches
}
bot.whisper = (username, message) => {
chatWithHeader(`/tell ${username} `, message)
}
bot.chat = (message) => {
chatWithHeader('', message)
}
bot.tabComplete = tabComplete
function addDefaultPatterns () {
// 1.19 changes the chat format to move <sender> prefix from message contents to a seperate field.
// TODO: new chat lister to handle this
if (!defaultChatPatterns) return
bot.addChatPattern('whisper', new RegExp(`^${USERNAME_REGEX} whispers(?: to you)?:? (.*)$`), { deprecated: true })
bot.addChatPattern('whisper', new RegExp(`^\\[${USERNAME_REGEX} -> \\w+\\s?\\] (.*)$`), { deprecated: true })
bot.addChatPattern('chat', LEGACY_VANILLA_CHAT_REGEX, { deprecated: true })
}
function awaitMessage (...args) {
return new Promise((resolve, reject) => {
const resolveMessages = args.flatMap(x => x)
function messageListener (msg) {
if (resolveMessages.some(x => x instanceof RegExp ? x.test(msg) : msg === x)) {
resolve(msg)
bot.off('messagestr', messageListener)
}
}
bot.on('messagestr', messageListener)
})
}
bot.awaitMessage = awaitMessage
}