Compare commits

..

108 Commits

Author SHA1 Message Date
81f91df8ac Adding a fix for when new calls are starting in voice channels that haven't been registered yet 2024-01-08 06:47:22 -05:00
6c6c2655f2 Adding another protection to try to avoid multiple recordings of the same audio happening 2024-01-07 16:23:42 -05:00
6012351b3e Attempting to protect against call echo AND fix new call registry which wasn't working correctly it seems... 2024-01-07 15:30:15 -05:00
934fa4d37f Setting auto commit on the db connection to true because apparently it defaults to false. Meaning nothing gets written to the DB 2024-01-06 15:48:08 -05:00
44982fd439 Pushing up an attempted fix for breadmixer not being able to submit transcriptions to the database 2024-01-06 15:35:12 -05:00
46fdebd503 Another minor fix for breadmixer, specifically the final ffmpeg call 2024-01-05 19:34:16 -05:00
c294489a60 Attempted fixes for breadmixers ffmpeg issues 2024-01-05 19:32:17 -05:00
2cf2ec7d88 Trying to figure out what is wrong with breadmixer running the FFMPEG command that I know for certain works 2024-01-05 19:06:24 -05:00
2f03a0de4c Fix for breadbot filter strings 2024-01-05 18:54:45 -05:00
3b33bb6ee4 Cleanup, database structure backup, and an additional log item for breadmixer to try to figure out where the ffmpeg command structure is wrong 2024-01-05 18:51:11 -05:00
f7e0c4b15a Some cleanup, and transitioning breadbot.js to use winston for logging 2024-01-04 20:27:31 -05:00
9fc901a2ef Pushing changes to sqlutil to add winston logging 2024-01-04 19:49:44 -05:00
b7283c17b6 A minor change to how servers are registered, bad logic 2024-01-03 21:06:50 -05:00
40f6ea7cd7 A minor modification to how edited messages are handled to deal with how discord changes links to gifs and other media to what I assume are embeds 2024-01-02 21:50:47 -05:00
cb94d94943 Drastically lowering the end time duration of a speaking session in voice calls to see if event emitter warnings go away 2024-01-02 21:32:45 -05:00
00b834c40b Adding another element to this to detect when someone stops speaking 2024-01-02 21:19:46 -05:00
a294b8c668 Minor fixes and adding a forgotten component of ending a call 2024-01-02 20:55:16 -05:00
263405141b A number of changes to address issues and missing pieces from the voice call monitoring components of Breadbot 2024-01-02 20:39:23 -05:00
089bb99119 Commands for breadbot seem to be missing, modifying deploy-command to fix this 2023-12-31 09:42:39 -05:00
2f51c18a69 Trying some upgrades to command loading 2023-12-31 09:39:25 -05:00
7afbb169e2 Another sql syntax fix 2023-12-30 18:53:32 -05:00
bdb9d4b807 Fixing a connection pool config to allow multiple statements per query 2023-12-30 18:37:28 -05:00
ed67193761 Fixing minor SQL syntax error 2023-12-30 18:34:36 -05:00
18c9be444d Fixing a minor issue when adding messages 2023-12-30 18:30:10 -05:00
40956e5c9e Adding message modify, delete, and attachment tracking (untested) 2023-12-30 17:32:44 -05:00
4de56c1af8 Sort returns none, which was deleting the file dictionary items 2023-12-25 20:09:44 -05:00
7b9a7fb339 Verbosity for breadmixer to try to figure out whats wrong 2023-12-25 19:23:20 -05:00
3039bf65bd Adding breadmixer changes 2023-12-16 12:51:35 -05:00
850e7288d1 A little more work on breadmixer 2023-11-29 20:59:50 -05:00
3bbba3217a More fixes for dragging milliseconds out of the time diff 2023-11-24 20:50:22 -05:00
7b0fde230d Really need milliseconds for seconds from starttime to be accurate 2023-11-24 20:46:42 -05:00
511f4bf69e Fixing datatype coersion problems 2023-11-24 20:43:24 -05:00
78f1232f5b Adding BreadMixer file indexing 2023-11-24 20:41:29 -05:00
41683fe098 Ok, trying s instead of d, even though that's technically not correct 2023-11-24 20:16:03 -05:00
61b617db04 I think we're back to typal pickiness 2023-11-24 20:13:42 -05:00
740213712a Ok, the parameterization for queries is SUPER picky 2023-11-24 20:12:23 -05:00
8cdd7d3974 It's not type coersion it's not seeing something as a tuple 2023-11-24 20:11:05 -05:00
e7681bf7cd Variable type coersion seems to be a problem with parameterized queries 2023-11-24 20:09:55 -05:00
7bee11e4f4 Initial work on BreadMixer 2023-11-24 19:56:03 -05:00
2954d1b304 Found something, might have been a node-crc dependency issue 2023-11-24 15:54:38 -05:00
4868f6930e Trying a PCM write 2023-11-24 15:17:40 -05:00
42f04a552a Attempting to write raw OPUS to file 2023-11-24 15:02:03 -05:00
00de13acd2 Switched back to the old version and turning CRC back on 2023-11-22 21:57:26 -05:00
9a5942facc Trying something because the files I'm getting are playable, but only under specify conditions and not with FFMEG 2023-11-22 21:40:34 -05:00
bdc0ed0fad Setting CRC false on the Ogg bitstream object to see if the problem with the CRC component goes away 2023-11-21 19:49:13 -05:00
55490c4b04 Adding node-crc dependency 2023-11-21 19:33:30 -05:00
66cec55bb6 Found a way to be more like the example code. Still not sure where the 2.0 alpha of prism comes from, but what the example uses I now have 2023-11-21 19:25:51 -05:00
16b41fad2b Modifying versions and restoring the PCM version of audio saving 2023-11-21 18:46:06 -05:00
ab3ef81df9 Trying to write just straight ogg rather than convert to PCM, which seems to be the issue 2023-11-21 18:06:32 -05:00
0f68356254 Possibly working version of joining voice call and collecting audio 2023-11-21 17:38:01 -05:00
855a91e340 Trying something I found on Stack Overflow 2023-11-19 19:49:28 -05:00
8ef2ede2c0 Trying another logging check to figure out this cache 2023-11-19 19:46:21 -05:00
32954a2536 Fixed the problem with registering a new call id 2023-11-19 19:42:21 -05:00
a689783b36 Another commit 2023-11-19 19:39:07 -05:00
9b0f381673 I DID PRINT AGAIN, JAVASCRIPT IS NOT PYTHON 2023-11-19 19:31:29 -05:00
1eb4da8729 More debugging 2023-11-19 19:29:57 -05:00
85fc4805dc Trying to fix the call registration return 2023-11-19 19:25:00 -05:00
f7bcbe348b Python is not Javascript, print doesn't exist 2023-11-19 19:22:00 -05:00
2307119ad7 More random prints 2023-11-19 19:20:32 -05:00
d3daa299c0 Trying another sqlutil fix for inserting call state 2023-11-19 19:14:32 -05:00
abf8f10668 Modifications to fix some bugs with inputting ID 2023-11-19 19:10:55 -05:00
acaedb434c Adding a bunch of random minor changes to sqlutil to fix a lambda parameter problem 2023-11-19 19:00:41 -05:00
e64811a179 More debug messages 2023-11-19 18:57:34 -05:00
113f6cbbd5 Adding a random debug message to see if what I think is happening is actually happening 2023-11-19 18:52:51 -05:00
7c4b5c52f1 Fixing a minor object reference issue 2023-11-19 18:46:52 -05:00
b5ded6acaf Adding a bunch of debug logging 2023-11-19 18:44:22 -05:00
08a9d0addd Another slight modification to make sure voice audio data is stored in a sensible location 2023-11-19 18:37:26 -05:00
aa1789da97 Adding a gitignore entry for media capture testing 2023-11-19 18:36:03 -05:00
9e0c78d939 Major modifications to try out actual audio recording 2023-11-19 18:32:16 -05:00
d82c569363 Added a large amount of untested voice management code, stopping just short of actually connecting to/disconnecting from calls 2023-11-14 21:50:42 -05:00
6effe2983e Some final updates for today, trying to keep track of how many people are in a particular voice channel 2023-11-12 14:54:42 -05:00
a729545e01 Fixing comparison states 2023-11-12 11:01:07 -05:00
7e79b2c3b4 Adding an actual detection that says what user joined where 2023-11-12 10:58:09 -05:00
87be8ca8db Doing to some state value checking now that the event works 2023-11-12 10:49:22 -05:00
5507fea98e I think some misplaced brackets were causing the issues 2023-11-12 10:16:41 -05:00
92df0cf4a7 Trying something simpler for VoiceStateChange notification just to see if the event works 2023-11-12 10:12:57 -05:00
944f8d4e72 I'm pretty sure I used the wrong intent 2023-11-12 10:07:52 -05:00
27e9fdb743 Missing an intent to capture the event that I want 2023-11-12 10:03:57 -05:00
b9a699c4c2 Adding preliminary test for Voice State Changed 2023-11-12 10:01:41 -05:00
734aa58e67 Adding a 'Database Backup', it's not perfect, the queries are out of order, but it'll do for now if something blows up 2023-11-11 22:31:59 -05:00
dc323ffeb1 Some final cleanup, and README adjustments 2023-11-11 22:18:54 -05:00
9fd8f3c289 Cleanup, and more logic fixes 2023-11-11 22:09:26 -05:00
c82fceeef1 Fixing some more logic problems 2023-11-11 22:02:31 -05:00
01a72ca544 Fixing a logic error 2023-11-11 21:58:56 -05:00
8befc32453 Trying a different way of handling the rows variable 2023-11-11 21:57:59 -05:00
4a3e470945 Adding some more console noise for debugging 2023-11-11 21:56:07 -05:00
7c6389d421 A significant amount of reimplementation 2023-11-11 21:51:34 -05:00
ac672e73d0 Pretty sure I have a working chain example 2023-11-11 21:29:05 -05:00
43546d93fe More changes to promise chain testing 2023-11-11 21:23:49 -05:00
6ea91cd1c9 Testing some stuff with promises and return values 2023-11-11 21:20:25 -05:00
0ad30722ae Added more modifications, await doesn't seem to be respected in the way I'm thinking, so more logging! 2023-11-11 20:35:30 -05:00
dfeb3d5148 Trying to fix issues again, this time with the truthiness of promise objects 2023-11-11 20:21:24 -05:00
6cbf6dc718 Trying to unwind some async/await mess 2023-11-11 20:17:59 -05:00
e24cc98fec Fixed some syncronization issues (I think) 2023-11-11 20:09:03 -05:00
a0e979e0bc The field for the message sender is author, not user 2023-11-11 19:52:27 -05:00
69a711a4a4 Added some fancy intents to try to fix BreadBots deafness to new messages 2023-11-11 19:48:08 -05:00
89e73e866b Adding greater console logging for event misfire 2023-11-11 19:43:23 -05:00
77b526ca21 Added preliminary BreadBot message surveillance 2023-11-11 19:39:07 -05:00
881b7f6359 I think I found a missing not directive 2023-11-11 18:37:24 -05:00
ece032ff5a Attempting to fix server already registered handling 2023-11-11 18:31:56 -05:00
dabb635b6b Trying another thing to fix escapement issues 2023-11-11 18:22:07 -05:00
2de2202187 Fixing escapement issues 2023-11-11 18:18:09 -05:00
3928b1f564 Fixed some insertion issue 2023-11-11 18:13:57 -05:00
8865015499 Promises in JavaScript are neat and also a pain 2023-11-11 18:08:42 -05:00
811478f486 More attempts to fix the situation with server registrations 2023-11-11 18:03:09 -05:00
808ca6c442 Fix for isServerRegistered 2023-11-11 17:46:37 -05:00
cb3466c6c6 Adding server to database registration 2023-11-11 17:38:45 -05:00
0d15e7931c Testing a new interaction 2023-11-11 15:33:57 -05:00
13 changed files with 2205 additions and 1730 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
.env
config.json
node_modules
keyfile.json
keyfile.json
media

View File

@ -14,7 +14,7 @@ I have a subset of features that I want it to have before I call it "production
- [ ] Add Autocomplete for common elements like Calendar Names, Event Names, Timezones, etc.
- [ ] Calendar Announcements for Upcoming Events
- [ ] Poll Creation and Results Announcements
- [ ] Conversation Archiving (May be Removed)
- [ ] Conversation Archiving (Partially working, need to work out edited messages, deleted messages, and attachments)
"Adventurous (Lots of Work)" Features
- [ ] BreadBot Voice Chat Hall Monitor

201
bin/breadmixer.py Normal file
View File

@ -0,0 +1,201 @@
import argparse
import mysql.connector
import json
import os
import sys
import datetime
import subprocess
import random
import string
from txtai.pipeline import Transcription
from pprint import pprint
argument_parser = argparse.ArgumentParser(description="BreadMixer is used to combine media from Discord Voice Calls")
argument_parser.add_argument("callid", help="The call id that needs to be mixed")
argument_parser.add_argument("config", help="The BreadBot config file location")
argument_parser.add_argument("-f", "--filespercycle", help="The number of files to combine per run of ffmpeg", default=50)
argument_parser.add_argument("-v", "--verbose", help="Make the script tell you more about what it's doing", action="store_true")
args = argument_parser.parse_args()
if args.verbose:
print("Checking config file")
if not os.path.exists(args.config):
print('The file path {path} does not exist'.format(path=args.config))
sys.exit(1)
with open(args.config) as config:
json_config = json.loads(config.read())
config_must_contain = [
"mysql_username",
"mysql_password",
"mysql_db_name",
"mysql_host",
"media_voice_folder"
]
if not all([element in json_config for element in config_must_contain]):
print('One or more of the following config items are missing')
for element in config_must_contain:
print('\t{item}'.format(item=element))
sys.exit(2)
if args.verbose:
print("Connecting to mysql db {dbname}".format(dbname=json_config["mysql_db_name"]))
mydb = mysql.connector.connect(
host=json_config["mysql_host"],
user=json_config["mysql_username"],
password=json_config["mysql_password"],
database=json_config["mysql_db_name"]
)
mydb.autocommit = True
cursor = mydb.cursor()
if args.verbose:
print("Checking to see if call ID {callid} exists".format(callid=args.callid))
cursor.execute("SELECT call_start_time FROM call_states WHERE call_id = %s", [args.callid])
call_start_time = cursor.fetchall()
cursor.close()
if len(call_start_time) == 0:
print('{call_id} does not exist in the database'.format(call_id=args.callid))
sys.exit(3)
if args.verbose:
print("Collecting files from {folder}".format(folder=os.path.join(json_config["media_voice_folder"], args.callid)))
file_dict = {}
for file in os.listdir(os.path.join(json_config["media_voice_folder"], args.callid)):
file_name_no_ext = file.split('.')[0]
timestamp = int(file_name_no_ext.split('-')[0])
user_snowflake = file_name_no_ext.split('-')[1]
file_stamp_as_datetime = datetime.datetime.fromtimestamp(timestamp / 1000)
time_diff = file_stamp_as_datetime - call_start_time[0][0]
file_dict[os.path.join(json_config["media_voice_folder"], args.callid, file)] = dict(
user=user_snowflake,
real_date=file_stamp_as_datetime,
milliseconds_from_starttime=int((time_diff.seconds * 1000) + (time_diff.microseconds / 1000))
)
file_dict_items = [(k, v) for k, v in file_dict.items()]
file_dict_items.sort(key=lambda a: a[1]["milliseconds_from_starttime"])
if args.verbose:
print("Collected files: ")
[print(element) for element in file_dict_items]
list_of_final_merges = []
for i in range(0, len(file_dict_items), args.filespercycle):
input_list = []
filter_list = []
next_endpoint = i + 50 if i + 50 <= len(file_dict_items) else len(file_dict_items)
for j in range(i, next_endpoint, 1):
input_list.append(file_dict_items[j][0])
filter_list.append("[{inputid}]adelay={delay}|{delay}[a{inputid}]".format(
inputid = j - i,
delay = file_dict_items[j][1]["milliseconds_from_starttime"]
))
command_list = ["ffmpeg"]
for input in input_list:
command_list.append("-i")
command_list.append(input)
command_list.append("-filter_complex")
filter_string = "\""
filter_string = filter_string + ';'.join(filter_list)
filter_string = filter_string + ";"
for j in range(i, next_endpoint, 1):
filter_string = filter_string + "[a{inputid}]".format(inputid=j - i)
filter_string = filter_string + "amix=inputs={input_count}:normalize=0[a]\"".format(input_count = next_endpoint - i)
command_list.append(filter_string)
command_list.append("-map")
command_list.append("\"[a]\"")
output_file_name = os.path.join(
json_config["media_voice_folder"],
args.callid,
"intermediate-" + "".join(random.choices(string.ascii_uppercase + string.digits, k=10)) + ".mp3"
)
list_of_final_merges.append(output_file_name)
command_list.append(output_file_name)
ffmpeg_process = subprocess.Popen(' '.join(command_list), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
print(ffmpeg_process.args)
stdout, stderr = ffmpeg_process.communicate()
if (ffmpeg_process.returncode != 0):
print("The ffmpeg process failed")
print(stdout)
print(stderr)
sys.exit(5)
final_command_list = ["ffmpeg"]
for file in list_of_final_merges:
final_command_list.append("-i")
final_command_list.append(file)
final_command_list.append("-filter_complex")
filter_string = "\""
for i in range(len(list_of_final_merges)):
filter_string = filter_string + "[{inputid}]".format(inputid=i)
filter_string = filter_string + "amix=inputs={input_count}:normalize=0[a];[a]volume=3[boosted]\"".format(input_count=len(list_of_final_merges))
final_command_list.append(filter_string)
final_command_list.append("-map")
final_command_list.append("\"[boosted]\"")
final_command_list.append(os.path.join(json_config["media_voice_folder"], args.callid, "output.mp3"))
final_command_process = subprocess.Popen(' '.join(final_command_list), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = final_command_process.communicate()
if (final_command_process.returncode != 0):
print("The final ffmpeg process failed")
print(stdout)
print(stderr)
sys.exit(6)
for file in os.listdir(os.path.join(json_config["media_voice_folder"], args.callid)):
if file.startswith("intermediate"):
os.remove(os.path.join(json_config["media_voice_folder"], args.callid, file))
transcribe = Transcription("openai/whisper-base")
for (k, v) in file_dict.items():
text = transcribe(k)
cursor = mydb.cursor()
cursor.execute("INSERT INTO call_transcriptions (call_id, user_snowflake, speaking_start_time, text) VALUES (%s, %s, %s, %s)", [
args.callid,
v["user"],
v["real_date"],
text
])
cursor.close()

View File

@ -1,7 +1,33 @@
const fs = require('node:fs');
const path = require('node:path');
const { Client, Events, GatewayIntentBits, Collection } = require('discord.js');
const { token } = require('./config.json');
const { joinVoiceChannel, getVoiceConnection, entersState, VoiceConnectionStatus, EndBehaviorType } = require('@discordjs/voice')
const { token, media_voice_folder, breadbot_logging_config } = require('./config.json');
const winston = require('winston')
const winston_mysql = require('winston-mysql')
const sqlutil = require('./utilities/sqlutil');
const { Console } = require('node:console');
const prism = require('prism-media')
const logger = winston.createLogger({
level: "silly",
transports: [
new winston.transports.Console({
format: winston.format.simple(),
level: breadbot_logging_config["console_log_level"]
}),
new winston_mysql({
level: breadbot_logging_config["sql_log_level"],
host: breadbot_logging_config["mysql_host"],
user: breadbot_logging_config["mysql_username"],
password: breadbot_logging_config["mysql_password"],
database: breadbot_logging_config["mysql_db_name"],
table: breadbot_logging_config["mysql_table_name"]
})
]
})
sqlutil.buildPool()
const getAllFiles = function(directoryPath, arrayOfFiles) {
const files = fs.readdirSync(directoryPath);
@ -20,26 +46,25 @@ const getAllFiles = function(directoryPath, arrayOfFiles) {
return arrayOfFiles;
};
const allFiles = [];
getAllFiles('.' + path.sep + 'commands', allFiles);
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMembers, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildVoiceStates] });
client.commands = new Collection();
const commandFiles = allFiles.filter(file => file.endsWith('.js'));
var activeCalls = []
for (const file of commandFiles) {
const command = require(file);
getAllFiles('.' + path.sep + 'commands', [])
.filter(file => file.endsWith('.js'))
.forEach(file => {
const command = require(file);
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
console.log(`[INFO] Loaded command at ${file}`);
}
else {
console.log(`[WARNING] The command at ${file} is missing a required "data" or "execute" property.`);
}
}
if ('enabled' in command && command.enabled && 'data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
logger.info(`Loaded command at ${file}`)
}
else {
logger.warn(`The command at ${file} is missing a required "data" or "execute" property or is not enabled`)
}
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
@ -47,7 +72,7 @@ client.on(Events.InteractionCreate, async interaction => {
const command = interaction.client.commands.get(interaction.commandName);
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
logger.error(`No command matching ${interaction.commandName} was found`)
return;
}
@ -55,13 +80,214 @@ client.on(Events.InteractionCreate, async interaction => {
await command.execute(interaction);
}
catch (error) {
console.error(error);
logger.error(error)
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
});
client.on(Events.GuildCreate, async guild => {
if (guild.available) {
logger.info(`Got into a server, ${guild.name}, ${guild.id}, ${guild.description}`)
sqlutil.registerServerIfMissing(guild.id, guild.name, guild.description).then(server_added => {
if(server_added) {
logger.info(`Server Added ${guild.name}`)
} else {
logger.error(`Server failed to add ${guild.name}`)
}
})
}
})
client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
logger.info("Voice State Update Fired")
if (oldState.channel == null && newState.channel != null) {
if (newState.member.id == client.user.id) {
return //If the user is breadbot, ignore and exit
}
logger.info(`Channel Join Detected ${newState.guild.id} - ${newState.channelId} - ${newState.member.id}`)
//TODO Nothing should happen after this if the channel fails to register
await sqlutil.registerChannelIfMissing(newState.channel.id, newState.guild.id, newState.channel.name)
var existingCallID = await sqlutil.inCall(newState.guild.id, newState.channelId)
logger.info(`Existing call ID ${existingCallID}`)
if (existingCallID == -1) {
logger.info("Joining a call")
var newCallID = await sqlutil.registerNewCall(newState.guild.id, newState.channelId, new Date())
existingCallID = newCallID // To ensure all the stuff that happens after call creation works
logger.info(`Next call ID ${newCallID}`)
fs.mkdirSync(media_voice_folder + path.sep + newCallID, {recursive: true})
const connection = joinVoiceChannel({
channelId: newState.channelId,
guildId: newState.guild.id,
selfDeaf: false,
selfMute: true,
adapterCreator: newState.guild.voiceAdapterCreator
})
try {
await entersState(connection, VoiceConnectionStatus.Ready, 20e3)
const receiver = connection.receiver
if (receiver.speaking.listenerCount("start") == 0) {
receiver.speaking.on("start", (user_id) => {
if(!receiver.subscriptions.has(user_id)) {
receiver.subscribe(user_id, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: 500
}
})
.pipe(new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
}))
.pipe(fs.createWriteStream(media_voice_folder + path.sep + newCallID + path.sep + `${Date.now()}-${user_id}.ogg`))
} else {
logger.warn(`Attempted to create new user subscriber for ${user_id} even though one already exists, receiver arm if statement protected against this`)
}
})
receiver.speaking.on("end", (user_id) => {
logger.info(`User ${user_id} stopped speaking`)
})
} else {
logger.warn("Attempted to create a new start and end listener for users who are speaking for some reason, receiver armor if statement protected against this")
}
} catch (error) {
logger.error(error)
}
}
var userRegistered = await sqlutil.registerUserIfMissing(newState.member.id, newState.member.username, newState.member.displayName)
if (userRegistered) {
var markedUserInCall = await sqlutil.registerUserInCall(existingCallID, newState.member.id)
if (!markedUserInCall) {
logger.error(`Something went wrong when marking user in voice call: ${newState.member.id} - ${newState.channelId}`)
}
} else {
logger.error(`Something went wrong when registering user for call: ${newState.member.id} - ${newState.member.username}`)
}
} else if (oldState.channel != null && newState.channel == null) {
if (oldState.member.id == client.user.id) {
return //If the user is breadbot, ignore and exit
}
logger.info(`Channel Exit Detected ${oldState.guild.id} - ${oldState.channelId} - ${oldState.member.id}`)
var existingCallID = await sqlutil.inCall(oldState.guild.id, oldState.channelId)
logger.info(`Existing call ID ${existingCallID}`)
if (existingCallID != -1) {
await sqlutil.deregisterUserInCall(existingCallID, oldState.member.id)
var usersInCall = await sqlutil.getNumberUsersInCall(existingCallID)
if (usersInCall == 0) {
const connection = getVoiceConnection(oldState.guild.id)
connection.disconnect()
var didUpdateEndTime = await sqlutil.updateCallEndTime(existingCallID, new Date())
if (!didUpdateEndTime) {
logger.error(`Failed to mark call ID ${existingCallID} as ended with an end date`)
}
}
} else {
logger.error("Couldn't find a call ID based on the guild and channel info, was Breadbot in the call?")
}
}
})
client.on(Events.MessageCreate, async message => {
console.info("Message Create Fired")
var channel_ok = await sqlutil.registerChannelIfMissing(message.channelId, message.channel.guild.id, message.channel.name)
var user_ok = await sqlutil.registerUserIfMissing(message.author.id, message.author.username, message.author.displayName)
logger.info(`Channel Ok? ${channel_ok} User OK? ${user_ok}`)
if (channel_ok && user_ok) {
await sqlutil.registerMessage(message.id, message.channelId, message.author.id, message.content, message.createdAt).then(async message_add => {
if(message_add) {
logger.info("Message Added")
if (message.attachments.size != 0) {
const all_attachments = message.attachments.map(attachment => sqlutil.registerAttachmentIfMissing(
attachment.id,
message.id,
attachment.name,
attachment.description,
message.createdAt,
attachment.contentType,
attachment.url
))
await Promise.all(all_attachments).catch((error) => {
logger.error(error)
})
}
} else {
logger.error("Failed to log message")
}
})
}
})
client.on(Events.MessageUpdate, async (oldMessage, newMessage) => {
logger.info("Message Update Fired")
logger.info(`Old Message Snowflake: ${oldMessage.id} New Message Snowflake: ${newMessage.id}`)
var editTime = newMessage.editedAt
if (editTime == null) {
editTime = newMessage.createdAt
}
await sqlutil.updateMessageContentIfPresent(newMessage.id, newMessage.content, editTime).then(async (updated) => {
if (updated) {
if (newMessage.attachments.size != 0) {
const all_attachments = newMessage.attachments.map(attachment => sqlutil.registerAttachmentIfMissing(
attachment.id,
newMessage.id,
attachment.name,
attachment.description,
editTime,
attachment.contentType,
attachment.url
))
await Promise.all(all_attachments).catch((error) => {
logger.error(error)
})
}
}
})
})
client.on(Events.MessageDelete, async deletedMessage => {
await sqlutil.markMessageDeletedIfPresent(deletedMessage.id)
})
client.once(Events.ClientReady, c => {
console.log(`Ready! Logged in as ${c.user.tag}`);
logger.info(`Ready! Logged in as ${c.user.tag} - ${c.user.id}`)
});
client.login(token);

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
enabled: true,
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
enabled: true,
data: new SlashCommandBuilder()
.setName('server')
.setDescription('Provides information about the server.'),

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
enabled: true,
data: new SlashCommandBuilder()
.setName('user')
.setDescription('Provides information about the user.'),

178
database_generator.sql Normal file
View File

@ -0,0 +1,178 @@
-- --------------------------------------------------------
-- Host: 192.168.1.196
-- Server version: 8.0.33 - MySQL Community Server - GPL
-- Server OS: Linux
-- HeidiSQL Version: 12.5.0.6677
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- Dumping database structure for breadbot_test
CREATE DATABASE IF NOT EXISTS `breadbot_test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `breadbot_test`;
-- Dumping structure for table breadbot_test.call_states
CREATE TABLE IF NOT EXISTS `call_states` (
`call_id` int NOT NULL AUTO_INCREMENT,
`server_snowflake` bigint unsigned NOT NULL,
`channel_snowflake` bigint unsigned NOT NULL,
`call_start_time` datetime NOT NULL,
`call_end_time` datetime DEFAULT NULL,
`call_consolidated` bit(1) NOT NULL DEFAULT (0),
`call_transcribed` bit(1) NOT NULL DEFAULT (0),
`call_data_cleaned_up` bit(1) NOT NULL DEFAULT (0),
PRIMARY KEY (`call_id`),
KEY `fk_snowflake_recording_to_server` (`server_snowflake`),
KEY `fk_snowflake_recording_to_channel` (`channel_snowflake`),
CONSTRAINT `fk_snowflake_recording_to_channel` FOREIGN KEY (`channel_snowflake`) REFERENCES `channels` (`channel_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_snowflake_recording_to_server` FOREIGN KEY (`server_snowflake`) REFERENCES `servers` (`server_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.call_transcriptions
CREATE TABLE IF NOT EXISTS `call_transcriptions` (
`transcription_id` int NOT NULL AUTO_INCREMENT,
`call_id` int NOT NULL,
`user_snowflake` bigint unsigned NOT NULL,
`speaking_start_time` datetime NOT NULL,
`text` longtext NOT NULL,
PRIMARY KEY (`transcription_id`),
KEY `fk_call_id_states_to_transcriptions` (`call_id`),
KEY `fk_snowflake_call_states_to_users` (`user_snowflake`),
CONSTRAINT `fk_call_id_states_to_transcriptions` FOREIGN KEY (`call_id`) REFERENCES `call_states` (`call_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_snowflake_call_states_to_users` FOREIGN KEY (`user_snowflake`) REFERENCES `users` (`user_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.call_users
CREATE TABLE IF NOT EXISTS `call_users` (
`call_users_id` int NOT NULL AUTO_INCREMENT,
`call_id` int NOT NULL,
`user_snowflake` bigint unsigned NOT NULL,
PRIMARY KEY (`call_users_id`) USING BTREE,
KEY `fk_call_id_call_user_to_state` (`call_id`),
KEY `fk_snowflake_call_user_to_users` (`user_snowflake`),
CONSTRAINT `fk_call_id_call_user_to_state` FOREIGN KEY (`call_id`) REFERENCES `call_states` (`call_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_snowflake_call_user_to_users` FOREIGN KEY (`user_snowflake`) REFERENCES `users` (`user_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.channels
CREATE TABLE IF NOT EXISTS `channels` (
`channel_snowflake` bigint unsigned NOT NULL,
`server_snowflake` bigint unsigned NOT NULL,
`channel_name` text,
PRIMARY KEY (`channel_snowflake`),
KEY `fk_snowflake_channel_to_server` (`server_snowflake`),
CONSTRAINT `fk_snowflake_channel_to_server` FOREIGN KEY (`server_snowflake`) REFERENCES `servers` (`server_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.messages
CREATE TABLE IF NOT EXISTS `messages` (
`message_snowflake` bigint unsigned NOT NULL,
`channel_snowflake` bigint unsigned NOT NULL,
`user_snowflake` bigint unsigned NOT NULL,
`message_content` text NOT NULL,
`message_timestamp` datetime NOT NULL,
`message_deleted` bit(1) NOT NULL DEFAULT (0),
PRIMARY KEY (`message_snowflake`),
KEY `fk_snowflake_message_to_channel` (`channel_snowflake`),
KEY `fk_snowflake_message_to_user` (`user_snowflake`),
CONSTRAINT `fk_snowflake_message_to_channel` FOREIGN KEY (`channel_snowflake`) REFERENCES `channels` (`channel_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_snowflake_message_to_user` FOREIGN KEY (`user_snowflake`) REFERENCES `users` (`user_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.message_attachments
CREATE TABLE IF NOT EXISTS `message_attachments` (
`attachment_snowflake` bigint unsigned NOT NULL,
`message_snowflake` bigint unsigned NOT NULL DEFAULT (0),
`attachment_name` text NOT NULL,
`attachment_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
`attachment_timestamp` datetime NOT NULL,
`attachment_mime_type` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
`attachment_url` text NOT NULL,
`attachment_downloaded` bit(1) NOT NULL DEFAULT (0),
PRIMARY KEY (`attachment_snowflake`) USING BTREE,
KEY `fk_snowflake_messages_to_attachments` (`message_snowflake`),
CONSTRAINT `fk_snowflake_messages_to_attachments` FOREIGN KEY (`message_snowflake`) REFERENCES `messages` (`message_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.message_content_changes
CREATE TABLE IF NOT EXISTS `message_content_changes` (
`message_change_id` int unsigned NOT NULL AUTO_INCREMENT,
`message_snowflake` bigint unsigned NOT NULL,
`message_change_old_timestamp` datetime NOT NULL,
`message_change_old_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`message_change_id`),
KEY `fk_snowflake_message_to_message_changes` (`message_snowflake`),
CONSTRAINT `fk_snowflake_message_to_message_changes` FOREIGN KEY (`message_snowflake`) REFERENCES `messages` (`message_snowflake`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.servers
CREATE TABLE IF NOT EXISTS `servers` (
`server_snowflake` bigint unsigned NOT NULL,
`server_name` text NOT NULL,
`server_description` mediumtext,
PRIMARY KEY (`server_snowflake`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.users
CREATE TABLE IF NOT EXISTS `users` (
`user_snowflake` bigint unsigned NOT NULL,
`user_name` text NOT NULL,
`user_displayname` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
PRIMARY KEY (`user_snowflake`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.winston_breadbot
CREATE TABLE IF NOT EXISTS `winston_breadbot` (
`id` int NOT NULL AUTO_INCREMENT,
`level` varchar(16) NOT NULL,
`message` varchar(2048) NOT NULL,
`meta` varchar(2048) NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
-- Dumping structure for table breadbot_test.winston_sqlutil
CREATE TABLE IF NOT EXISTS `winston_sqlutil` (
`id` int NOT NULL AUTO_INCREMENT,
`level` varchar(16) NOT NULL,
`message` varchar(2048) NOT NULL,
`meta` varchar(2048) NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- Data exporting was unselected.
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;

View File

@ -32,7 +32,7 @@ const commandFiles = allFiles.filter(file => file.endsWith('.js'));
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) {
const command = require(`${file}`);
if ('data' in command && 'execute' in command) {
if ('enabled' in command && command.enabled && 'data' in command && 'execute' in command) {
commands.push(command.data.toJSON());
}
else {

3012
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,18 @@
"author": "Bradley Bickford",
"license": "ISC",
"dependencies": {
"@discordjs/opus": "^0.9.0",
"@discordjs/voice": "^0.16.0",
"@vvo/tzdb": "^6.77.0",
"discord.js": "^14.6.0",
"googleapis": "^109.0.1"
"googleapis": "^109.0.1",
"libsodium-wrappers": "^0.7.13",
"mysql": "^2.18.1",
"mysql2": "^3.6.3",
"node-crc": "1.3.2",
"prism-media": "^2.0.0-alpha.0",
"winston": "^3.11.0",
"winston-mysql": "^1.1.1"
},
"devDependencies": {
"eslint": "^8.27.0"

18
promise_chain_test.js Normal file
View File

@ -0,0 +1,18 @@
const mysql = require('mysql2')
const { mysql_username, mysql_password } = require('./config.json')
var connection_pool = mysql.createPool({
host: "10.26.48.207",
user: mysql_username,
password: mysql_password,
database: 'breadbot_test',
connectionLimit: 10
}).promise()
connection_pool.query("SELECT * FROM servers").then(async ([rows, fields]) => {
console.log(rows)
return await connection_pool.query("SELECT * FROM channels").then(([rows, fields]) => {
console.log(rows)
return "SOME TEXT"
})
}).catch(() => {return "SOME FAIL TEXT"}).then(console.log)

241
utilities/sqlutil.js Normal file
View File

@ -0,0 +1,241 @@
const mysql = require('mysql2')
const winston = require('winston')
const winston_mysql = require('winston-mysql')
const {
mysql_username,
mysql_password,
mysql_host,
mysql_db_name,
sqlutil_logging_config
} = require('../config.json')
var connection_pool = null
var logger = null
async function buildPool() {
if (connection_pool == null) {
connection_pool = mysql.createPool({
host: mysql_host,
user: mysql_username,
password: mysql_password,
database: mysql_db_name,
connectionLimit: 10,
multipleStatements: true
}).promise()
}
if (logger == null) {
logger = winston.createLogger({
level: "silly",
transports: [
new winston.transports.Console({
format: winston.format.simple(),
level: sqlutil_logging_config["console_log_level"]
}),
new winston_mysql({
level: sqlutil_logging_config["sql_log_level"],
host: sqlutil_logging_config["mysql_host"],
user: sqlutil_logging_config["mysql_username"],
password: sqlutil_logging_config["mysql_password"],
database: sqlutil_logging_config["mysql_db_name"],
table: sqlutil_logging_config["mysql_table_name"]
})
]
})
}
}
async function registerServerIfMissing(server_snowflake, server_name, server_description) {
return connection_pool.query("SELECT * FROM servers WHERE server_snowflake = ?;", [server_snowflake]).then(async ([rows, fields]) => {
if (rows.length != 0) {
return true
} else {
return await connection_pool.query("INSERT INTO servers VALUES (?, ?, ?)", [server_snowflake, server_name, server_description]).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function registerChannelIfMissing(channel_snowflake, server_snowflake, channel_name) {
return connection_pool.query("SELECT * FROM channels WHERE channel_snowflake = ?;", [channel_snowflake]).then(async ([rows, fields]) => {
if (rows.length != 0) {+
logger.info("Channel already registered")
return true
} else {
logger.info("Channel Not registered, registering")
return await connection_pool.query("INSERT INTO channels VALUES (?, ?, ?)", [channel_snowflake, server_snowflake, channel_name]).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function updateMessageContentIfPresent(message_snowflake, message_content, message_timestamp) {
return connection_pool.query("SELECT message_snowflake FROM messages WHERE message_snowflake = ?", [message_snowflake]).then(async ([rows, fields]) => {
if (rows.length == 0) {
logger.info("Message specified doesn't exist, probably created before breadbot was here")
return false
} else {
return await connection_pool.query(
"INSERT INTO message_content_changes (message_snowflake, message_change_old_timestamp, message_change_old_content) " +
"SELECT message_snowflake, message_timestamp, message_content FROM messages WHERE message_snowflake = ?;" +
"UPDATE messages SET message_timestamp = ?, message_content = ? WHERE message_snowflake = ?;",
[message_snowflake, message_timestamp, message_content, message_snowflake]
).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function markMessageDeletedIfPresent(message_snowflake) {
return connection_pool.query("SELECT message_snowflake FROM messages WHERE message_snowflake = ?", [message_snowflake]).then(async ([rows, fields]) => {
if (rows.length == 0) {
logger.info("Message specified doesn't exists, probably created before breadbot was here")
return false
} else {
return await connection_pool.query(
"UPDATE messages SET message_deleted = 1 WHERE message_snowflake = ?", [message_snowflake]
).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function registerAttachmentIfMissing(attachment_snowflake, message_snowflake, attachment_name, attachment_description, attachment_timestamp, attachment_mime_type, attachment_url) {
return connection_pool.query("SELECT attachment_snowflake FROM message_attachments WHERE attachment_snowflake = ?", [attachment_snowflake]).then(async ([rows, fields]) => {
if (rows.length != 0) {
logger.info("Attachment alreaedy exists")
return true
} else {
return await connection_pool.query(
"INSERT INTO message_attachments (attachment_snowflake, message_snowflake, attachment_name, attachment_description, attachment_timestamp, attachment_mime_type, attachment_url) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)",
[attachment_snowflake, message_snowflake, attachment_name, attachment_description, attachment_timestamp, attachment_mime_type, attachment_url]
).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function registerUserIfMissing(user_snowflake, user_name, user_displayname) {
return connection_pool.query("SELECT * FROM users WHERE user_snowflake = ?;", [user_snowflake]).then(async ([rows, fields]) => {
if (rows.length != 0) {
return true
} else {
return await connection_pool.query("INSERT INTO users VALUES (?, ?, ?)", [user_snowflake, user_name, user_displayname]).then(([rows, fields]) => {
return true
})
}
}).catch((error) => {
logger.error(error)
return false
})
}
async function registerMessage(message_snowflake, channel_snowflake, user_snowflake, message_content, message_timestamp) {
return connection_pool.query("INSERT INTO messages VALUES (?, ?, ?, ?, ?, 0)", [message_snowflake, channel_snowflake, user_snowflake, message_content, message_timestamp]).then(([rows, fields]) => {
return true
}).catch((error) => {
logger.error(error)
return false
})
}
async function inCall(server_snowflake, channel_snowflake) {
return connection_pool.query("SELECT call_id FROM call_states WHERE server_snowflake = ? AND channel_snowflake = ? AND call_end_time IS NULL", [server_snowflake, channel_snowflake]).then(async ([rows, fields]) => {
if (rows.length == 0) {
return -1;
} else {
return rows[0].call_id
}
}).catch((error) => {
logger.error(error)
return -1;
})
}
async function registerNewCall(server_snowflake, channel_snowflake, call_start_time) {
return connection_pool.query("INSERT INTO call_states (server_snowflake, channel_snowflake, call_start_time, call_end_time, call_consolidated, call_transcribed, call_data_cleaned_up) VALUES (?, ?, ?, NULL, 0, 0, 0)", [server_snowflake, channel_snowflake, call_start_time]).then(async ([rows, fields]) => {
if (typeof rows.insertId === 'undefined') {
return -1;
} else {
return rows.insertId
}
}).catch((error) => {
logger.error(error)
return -1;
})
}
async function registerUserInCall(call_id, user_snowflake) {
return connection_pool.query("INSERT INTO call_users (call_id, user_snowflake) VALUES (?, ?)", [call_id, user_snowflake]).then(([rows, fields]) => {
return true
}).catch((error) => {
logger.error(error)
return false
})
}
async function deregisterUserInCall(call_id, user_snowflake) {
return connection_pool.query("DELETE FROM call_users WHERE call_id = ? AND user_snowflake = ?", [call_id, user_snowflake]).then(([rows, field]) => {
return true
}).catch((error) => {
logger.error(error)
return false
})
}
async function getNumberUsersInCall(call_id) {
return connection_pool.query("SELECT COUNT(call_users_id) AS users_in_call FROM call_users WHERE call_id = ?", [call_id]).then(([rows, fields]) => {
return rows[0].users_in_call
}).catch((error) => {
logger.error(error)
return -1
})
}
async function updateCallEndTime(call_id, call_end_time) {
return await connection_pool.query("UPDATE call_states SET call_end_time = ? WHERE call_id = ?", [call_end_time, call_id]).then(async ([rows, fields]) => {
return true
}).catch((error) => {
logger.error(error)
return false;
})
}
module.exports = {
buildPool,
registerMessage,
registerServerIfMissing,
registerChannelIfMissing,
registerUserIfMissing,
registerNewCall,
updateCallEndTime,
inCall,
updateMessageContentIfPresent,
markMessageDeletedIfPresent,
registerAttachmentIfMissing,
registerUserInCall,
deregisterUserInCall,
getNumberUsersInCall
}