Skip to content
This repository was archived by the owner on Jun 21, 2024. It is now read-only.

Commit ccc3212

Browse files
author
Mark Nefedov
authored
Merge pull request #2 from markusgod/instantReplay
Instant replay
2 parents 66af122 + f1aeedd commit ccc3212

File tree

6 files changed

+134
-4
lines changed

6 files changed

+134
-4
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ gradle-app.setting
1515

1616
# IDE
1717
.idea/
18-
*.iml
18+
*.iml
19+
20+
#created by bot
21+
temp/
22+
records/

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# Simple Discord bot in Kotlin
22
A simple bot for recording voice chats.
33

4-
Usage:
4+
For simple recording:
55
```
66
::record time
77
::stop
88
```
9+
"Instant replay" functions much like Nvidia's ShadowPlay instant replays (that is where name come from). Bot is constantly recording audio from a channel to a buffer and will send current buffer content in a file when you request it.
10+
```
11+
:!record
12+
:!replay
13+
```
914

1015
After recording bot will send an audio file to the chat it was started from.
1116

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ dependencies {
1313
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
1414
compile "net.dv8tion:JDA:4.1.1_101"
1515
compile group: 'ws.schild', name: 'jave-all-deps', version: '2.7.3'
16+
compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'
17+
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
1618
}
1719

1820
compileKotlin {

src/main/kotlin/FileEncoder.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import net.dv8tion.jda.api.audio.AudioReceiveHandler
2+
import ws.schild.jave.AudioAttributes
3+
import ws.schild.jave.Encoder
4+
import ws.schild.jave.EncodingAttributes
5+
import ws.schild.jave.MultimediaObject
6+
import java.io.ByteArrayInputStream
7+
import java.io.File
8+
import javax.sound.sampled.AudioFileFormat
9+
import javax.sound.sampled.AudioInputStream
10+
import javax.sound.sampled.AudioSystem
11+
12+
const val tempFolder = "temp"
13+
14+
fun createWAVFile(buffer: Array<Byte>, filename: String): File {
15+
val directory = File(tempFolder)
16+
if (!directory.exists())
17+
directory.mkdir()
18+
19+
val audioStream = AudioInputStream(
20+
ByteArrayInputStream(buffer.toByteArray()),
21+
AudioReceiveHandler.OUTPUT_FORMAT,
22+
buffer.size.toLong()
23+
)
24+
val wav = File("$tempFolder/$filename.wav")
25+
AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, wav)
26+
return wav
27+
}
28+
29+
fun convertToMP3(file: File): File {
30+
val encoder = Encoder()
31+
val audioAttributes = AudioAttributes().apply {
32+
setCodec("libmp3lame")
33+
setBitRate(48000)
34+
setChannels(1)
35+
setSamplingRate(44100)
36+
}
37+
val attrs = EncodingAttributes().apply {
38+
format = "mp3"
39+
setAudioAttributes(audioAttributes)
40+
}
41+
val mp3 = File("$tempFolder/${file.name}.mp3")
42+
encoder.encode(MultimediaObject(file), mp3, attrs)
43+
return mp3
44+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import net.dv8tion.jda.api.audio.AudioReceiveHandler
2+
import net.dv8tion.jda.api.audio.CombinedAudio
3+
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent
4+
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent
5+
import net.dv8tion.jda.api.hooks.ListenerAdapter
6+
import org.apache.commons.collections4.queue.CircularFifoQueue
7+
import java.time.LocalDateTime
8+
9+
const val bufferSize = 50000000
10+
11+
class InstantReplayCommandHandler : ListenerAdapter() {
12+
override fun onGuildMessageReceived(event: GuildMessageReceivedEvent) {
13+
super.onGuildMessageReceived(event)
14+
if (event.message.contentRaw.startsWith(":!record") && event.member?.voiceState?.inVoiceChannel() == true) {
15+
if (event.guild.audioManager.receivingHandler == null) {
16+
event.guild.audioManager.receivingHandler = InstantReplayAudioHandler()
17+
}
18+
event.guild.audioManager.openAudioConnection(event.member?.voiceState?.channel)
19+
}
20+
21+
if (event.message.contentRaw.startsWith(":!replay")) {
22+
val replayAudioHandler = event.guild.audioManager.receivingHandler as? InstantReplayAudioHandler ?: return
23+
val wavRecord = createWAVFile(
24+
replayAudioHandler.getRecordBytes().toTypedArray().copyOf(),
25+
"${LocalDateTime.now()}${event.guild.id}"
26+
)
27+
val mp3Compressed = convertToMP3(wavRecord)
28+
wavRecord.delete()
29+
event.message.textChannel.sendFile(mp3Compressed).submit().thenRunAsync { mp3Compressed.delete() }
30+
}
31+
}
32+
33+
override fun onGuildVoiceLeave(event: GuildVoiceLeaveEvent) {
34+
super.onGuildVoiceLeave(event)
35+
if (event.member.id == clientId)
36+
{
37+
println("Bot leave from: ${event.channelLeft.name}")
38+
event.guild.audioManager.receivingHandler = null
39+
}
40+
}
41+
}
42+
43+
class InstantReplayAudioHandler : AudioReceiveHandler {
44+
private var buffer = CircularFifoQueue<Byte>(bufferSize)
45+
override fun canReceiveCombined(): Boolean {
46+
return true
47+
}
48+
49+
override fun handleCombinedAudio(combinedAudio: CombinedAudio) {
50+
super.handleCombinedAudio(combinedAudio)
51+
val decodedData = combinedAudio.getAudioData(1.0)
52+
buffer.addAll(decodedData.toTypedArray())
53+
}
54+
55+
fun getRecordBytes(): Collection<Byte> {
56+
return buffer
57+
}
58+
}

src/main/kotlin/main.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
11
import net.dv8tion.jda.api.JDABuilder
22

3+
lateinit var clientId : String
4+
35
fun main(args : Array<String>) {
46
val token = when {
57
args.isNotEmpty() -> args[0]
68
System.getenv("BOT_TOKEN") != null -> System.getenv("BOT_TOKEN")
79
else -> {
8-
print("Bot token:")
10+
println("Bot token:")
911
(readLine() ?: return)
1012
}
1113
}
14+
15+
clientId = when {
16+
args.size > 1 -> args[1]
17+
System.getenv("CLIENT_ID") != null -> System.getenv("CLIENT_ID")
18+
else -> {
19+
println("Client id: ")
20+
(readLine() ?: return)
21+
}
22+
}
23+
24+
println("Starting bot")
1225
JDABuilder(token).apply {
13-
addEventListeners(RecordCommandHandler())
26+
addEventListeners(
27+
RecordCommandHandler(),
28+
InstantReplayCommandHandler()
29+
)
30+
setAutoReconnect(true)
1431
build()
1532
}
1633
}

0 commit comments

Comments
 (0)