forked from XianhaiC/Voice-Bot
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
377 lines (339 loc) · 11 KB
/
index.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
const Discord = require("discord.js");
const ytdl = require("ytdl-core");
const request = require("request");
const getYoutubeID = require("get-youtube-id");
const fetchVideoInfo = require("youtube-info");
const ffmpeg = require('fluent-ffmpeg');
const WitSpeech = require('node-witai-speech');
const decode = require('./decodeOpus.js');
const fs = require('fs');
const path = require('path');
const opus = require('node-opus');
var config = JSON.parse(fs.readFileSync("./settings.json", "utf-8")); //get settings
const WIT_API_KEY = config.wit_api_key; //declaring api constants
const yt_api_key = config.yt_api_key;
const bot_controller = config.bot_controller;
const prefix = config.prefix;
const discord_token = config.discord_token;
const content_type = config.content_type;
const client = new Discord.Client(); //discord bot setup
const recordingsPath = makeDir('./recordings');
var isPlaying = false; //music vars
var dispatcher = null;
var voiceChannel = null;
var textChannel = null;
var listenConnection = null;
var listenReceiver = null;
var listenStreams = new Map();
var listening = false;
var listenChannel = "";
client.login(discord_token);
client.on('ready', handleReady.bind(this));
client.on('message', handleMessage.bind(this));
client.on('guildMemberSpeaking', handleSpeaking.bind(this)); //event management
function handleReady() {
console.log("I'm ready!");
client.user.setActivity('Commands', { type: 'LISTENING' }); //set bot status
}
function handleMessage(message) { //handling message commands
if (!message.content.startsWith(prefix)) {
return;
}
//interpereting text commands and cleaning data
var command = message.content.toLowerCase().slice(2).split(' '); //convert message to list of lowercase words
command = command[0];
var messageParams = message.content
messageParams = messageParams.substring(5)
console.log(messageParams)
switch (command) { //choosing function according to command
case 'help':
message.channel.send('\n**Commands:** \n - //leave: leave channel and stop listening \n - //help: list of commands \n - //play: play song from youtube \n - //skip: skip song \n - //listen: join channel to listen to voice commands\n\n **Voice Commands:** \n - "Silver, play": play song from youtube \n - "Silver, skip": skip song\n - "Silver, leave": leave channel and stop listening');
break;
case 'leave':
commandLeave();
break;
case 'play':
isPlaying = true;
getID(messageParams, function(id) {
commandPlay(id, message.member);
fetchVideoInfo(id, function (err, videoInfo) {
if (err) throw new Error(err);
if (id != "Vbks4abvLEw") {
message.channel.send("Playing: **" + videoInfo.title + "**");
} else {
message.channel.send("**No Video Found**");
}
});
});
break;
case 'skip':
case 'next':
commandSkip(message);
break;
case 'listen':
listenChannel = message.channel;
textChannel = message.channel;
commandListen(message);
break;
default:
message.channel.send("Command not recognized! Type '!help' for a list of commands.");
}
}
function handleSpeech(member, speech) { //handling voice commands
var command = speech.toLowerCase().split(' ');
if (command[0] == 'Silver' || command[0] == 'Silva' || command[0] == 'silver' || command[0] == 'silva') { //making sure voice commands start with silver
console.log("command recognized");
command = command[1];
var messageParams = speech
messageParams = messageParams.substring(10)
console.log(messageParams)
switch (command) { //choosing function according to voice data
case 'listen':
speechListen();
break;
case 'leave':
commandLeave();
break;
case 'play':
case 'hehe':
case 'apply':
isPlaying = true;
getID(messageParams, function(id) {
commandPlay(id, 'speechText');
fetchVideoInfo(id, function (err, videoInfo) {
if (err) throw new Error(err);
if (id != "Vbks4abvLEw") {
listenChannel.send(" Playing: **" + videoInfo.title + "**");
} else {
listenChannel.send("**No Video Found**");
}
});
});
break;
case 'skip':
case 'skip track':
case 'next':
case 'keep':
case 'ip':
case 'it':
commandSkip('speechText');
break;
default:
listenChannel.send("Command not recognized: **" + speech + "**");
break;
}
}
}
function handleSpeaking(member, speaking) { //turn audio into text
// Close the writeStream when a member stops speaking
if (!speaking && member.voiceChannel) {
let stream = listenStreams.get(member.id);
if (stream) {
listenStreams.delete(member.id);
stream.end(err => {
if (err) {
console.error(err);
}
let basename = path.basename(stream.path, '.opus_string');
let text = "default";
// decode file into pcm
decode.convertOpusStringToRawPCM(stream.path,
basename,
(function() {
processRawToWav( //convert raw audio to wav
path.join('./recordings', basename + '.raw_pcm'),
path.join('./recordings', basename + '.wav'),
(function(data) {
if (data != null) {
handleSpeech(member, data._text);
}
}).bind(this))
}).bind(this));
var totalName = './recordings/'+ basename + '.opus_string';
deleteFile(totalName); //delete file after it has been used
});
}
}
}
function commandSkip(message) {
if (message == "speechText") { //check if skip command was issued by voice or message
if (isPlaying == true) {
listenChannel.send("**Skipping!**"); //respond in text
dispatcher.end(); //end song
} else {
listenChannel.send("**Skip failed** - nothing is playing!");
}
} else {
if (isPlaying == true) {
message.channel.send("**Skipping!**");
dispatcher.end();
} else {
message.channel.send("**Skip failed** - nothing is playing!");
}
}
}
function commandPlay(id, member) {
if (member != 'speechText') { //check if command was issued via voice
voiceChannel = member.voiceChannel;
}
isPlaying = true;
voiceChannel.join().then(function (connection) {
YTstreamT = ytdl("https://youtube.com/watch?v=" + id, { //set stream from appropriate youtube video
filter: 'audioonly'
});
try {
console.log("attempt playing");
dispatcher = connection.playStream(YTstreamT); //play the previously defined stream
console.log("playing");
} catch (err) {
console.log(err.message);
}
dispatcher.on('end', function () {
isPlaying = false;
dispatcher = null;
console.log("stopped playing")
}); //change variables when audio is finished
});
}
function getID(str, cb) { //find id of youtube video to be played
if (isYoutube(str)) {
cb(getYoutubeID(str));
} else {
search_video(str, function(id) {
cb(id);
console.log(id);
});
}
}
function search_video(query, callback) { //search for youtube video according to search terms
request("https://www.googleapis.com/youtube/v3/search?part=id&type=video&q=" + encodeURIComponent(query) + "&key=" + yt_api_key, function(error, response, body) {
var json = JSON.parse(body);
try {
callback(json.items[0].id.videoId);
} catch(err) {
callback("Vbks4abvLEw");
}
});
}
function isYoutube(str) { //check if play command is a url or a search query
return str.toLowerCase().indexOf('youtube.com') > -1;
}
function commandListen(message) {
member = message.member;
if (!member) {
return; //checks if bot or human
}
if (!member.voiceChannel) {
message.reply(" you need to be in a voice channel first.") //check if user is in a voice channel
return;
}
if (listening) {
message.reply(" a voice channel is already being listened to!"); //check if voice channel is already being listened to (durr)
return;
}
//else:
listening = true; //log that the bot is listening currently
voiceChannel = member.voiceChannel;
textChannel.send('Listening in to **' + member.voiceChannel.name + '**!'); //respond
var recordingsPath = path.join('.', 'recordings');
makeDir(recordingsPath);
voiceChannel.join().then((connection) => {
listenConnection = connection;
let receiver = connection.createReceiver();
receiver.on('opus', function(user, data) { //start recieving voice data
let hexString = data.toString('hex');
let stream = listenStreams.get(user.id);
if (!stream) {
if (hexString === 'f8fffe') {
return;
}
let outputPath = path.join(recordingsPath, `${user.id}-${Date.now()}.opus_string`); //record audio
stream = fs.createWriteStream(outputPath);
listenStreams.set(user.id, stream);
}
stream.write(`,${hexString}`);
});
listenReceiver = receiver;
}).catch(console.error); //catch errors
commandPlay('6485oNnwum8','speechText');
}
function commandLeave() {
console.log('leaving');
listening = false;
queue = []
if (dispatcher) {
dispatcher.end();
}
dispatcher = null;
commandStop();
if (listenReceiver) {
listenReceiver.destroy();
listenReceiver = null;
}
if (listenConnection) {
listenConnection.disconnect();
listenConnection = null;
}
if (voiceChannel) {
voiceChannel.leave();
voiceChannel = null; //leave channel (simple)
}
}
function processRawToWav(filepath, outputpath, cb) {
fs.closeSync(fs.openSync(outputpath, 'w'));
var command = ffmpeg(filepath)
.addInputOptions([
'-f s32le',
'-ar 48k',
'-ac 1'
])
.on('end', function() {
// Stream the file to be sent to the wit.ai
var stream = fs.createReadStream(outputpath);
// Its best to return a promise
var parseSpeech = new Promise((ressolve, reject) => {
// call the wit.ai api with the created stream
WitSpeech.extractSpeechIntent(WIT_API_KEY, stream, content_type,
(err, res) => {
if (err) return reject(err);
ressolve(res);
});
});
// check in the promise for the completion of call to witai
parseSpeech.then((data) => {
console.log("you said: " + data._text);
cb(data);
//return data;
})
.catch((err) => {
console.log(err);
cb(null);
//return null;
}).then(deleteFile(filepath));
})
.on('error', function(err) {
console.log('an error happened: ' + err.message);
})
.addOutput(outputpath)
.run();
}
function deleteFile(dir) {
fs.unlink(dir, (err) => {
if (err) {
console.log(err);
}
});
}
function makeDir(dir) {
try {
fs.mkdirSync(dir);
} catch (err) {}
}
function commandStop() {
if (listenReceiver) {
listening = false;
listenReceiver.destroy();
listenReceiver = null;
textChannel.send("Stopped listening!");
}
}