Its alive :)
This commit is contained in:
commit
2d19f6f21a
139
.gitignore
vendored
Normal file
139
.gitignore
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Sveltekit cache directory
|
||||
.svelte-kit/
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Firebase cache directory
|
||||
.firebase/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Vite logs files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
BIN
database.db
Normal file
BIN
database.db
Normal file
Binary file not shown.
58
index.js
Normal file
58
index.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
require("dotenv").config(); // import environment variables
|
||||
const colors = require("colors");
|
||||
const Discord = require("discord.js");
|
||||
const client = new Discord.Client({
|
||||
intents: [
|
||||
"DirectMessages"
|
||||
]});
|
||||
const sqlite3 = require("sqlite3").verbose();
|
||||
const db = new sqlite3.Database(process.env.DB_PATH || "./database.db", (err) => {
|
||||
if (err) {
|
||||
console.error(`${colors.red(`[ERROR]`)} ${colors.yellow(`Failed to connect to the database:`)} ${err.message}`);
|
||||
} else {
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`Connected to the database successfully.`)}`);
|
||||
db.run(`CREATE TABLE IF NOT EXISTS glucose_log (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
reading INTEGER NOT NULL,
|
||||
notes TEXT DEFAULT '',
|
||||
msg_id TEXT DEFAULT NULL
|
||||
)`, (err) => {
|
||||
if (err) {
|
||||
console.error(`${colors.red(`[ERROR]`)} ${colors.yellow(`Failed to create glucose_log table:`)} ${err.message}`);
|
||||
} else {
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`glucose_log table is ready.`)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
client.once("ready", () => {
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`Bot is online! Logged in as ${client.user.displayName} (${client.user.id})`)}!`);
|
||||
require("./modules/registerCommands.js")(client);
|
||||
});
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
const commandName = interaction.commandName;
|
||||
const command = require(`./modules/commands/${commandName}.js`);
|
||||
|
||||
if (command && command.execute) {
|
||||
try {
|
||||
await command.execute(interaction, client, db);
|
||||
} catch (error) {
|
||||
console.error(`${colors.red(`[ERROR]`)} ${colors.yellow(`Error executing command ${commandName}:`)} ${error}`);
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
} else {
|
||||
console.warn(`${colors.yellow(`[WARN]`)} ${colors.red(`Command ${commandName} not found or not executable.`)}`);
|
||||
}
|
||||
});
|
||||
|
||||
client.login(process.env.TOKEN).then(() => {
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`Logged in successfully!`)} Bot is ready to receive commands.`);
|
||||
}).catch(err => {
|
||||
console.error(`${colors.red(`[ERROR]`)} ${colors.yellow(`Failed to log in:`)} ${err}`);
|
||||
});
|
58
modules/commands/delete.js
Normal file
58
modules/commands/delete.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
module.exports = {}
|
||||
const Discord = require("discord.js");
|
||||
module.exports.definition = {
|
||||
name: 'delete',
|
||||
description: 'Delete a glucose reading',
|
||||
integration_types: [0, 1],
|
||||
contexts: [0, 1, 2],
|
||||
options: [
|
||||
{
|
||||
name: 'id',
|
||||
description: 'ID of the glucose reading to delete',
|
||||
type: 3, // STRING
|
||||
required: true
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
module.exports.execute = async (interaction, client, db) => {
|
||||
const id = interaction.options.getString('id');
|
||||
|
||||
if (!id) {
|
||||
return interaction.reply({ content: 'Please provide a valid glucose reading ID.', ephemeral: true });
|
||||
}
|
||||
|
||||
db.get(`SELECT * FROM glucose_log WHERE id = ? AND user_id = ?`, [id, interaction.user.id], async (err, row) => {
|
||||
if (err) {
|
||||
console.error('Database error:', err);
|
||||
return interaction.reply({ content: 'An error occurred while accessing the database.', ephemeral: true });
|
||||
}
|
||||
if (!row) {
|
||||
return interaction.reply({ content: 'No glucose reading found with the provided ID.', ephemeral: true });
|
||||
}
|
||||
const [ channel_id, message_id ] = row.msg_id.split('/'); //Get channel and msg id for update
|
||||
const ogMessage = await async function() {
|
||||
const chan = await client.channels.fetch(channel_id);
|
||||
if (!chan) {
|
||||
return null;
|
||||
}
|
||||
return await chan.messages.fetch(message_id).catch(() => null);
|
||||
}();
|
||||
if (!ogMessage) {
|
||||
return interaction.reply({ content: 'Original message not found. Cannot delete reading.', ephemeral: true });
|
||||
}
|
||||
try {
|
||||
await ogMessage.delete();
|
||||
} catch (error) {
|
||||
console.error('Error deleting message:', error);
|
||||
return interaction.reply({ content: 'Failed to delete the original message. It may have already been deleted.', ephemeral: true });
|
||||
}
|
||||
db.run(`DELETE FROM glucose_log WHERE id = ? AND user_id = ?`, [id, interaction.user.id], function(err) {
|
||||
if (err) {
|
||||
console.error('Database error:', err);
|
||||
return interaction.reply({ content: 'An error occurred while deleting the glucose reading from the database.', ephemeral: true });
|
||||
}
|
||||
return interaction.reply({ content: `Glucose reading with ID ${id} has been successfully deleted.`, ephemeral: true });
|
||||
});
|
||||
});
|
||||
}
|
94
modules/commands/edit.js
Normal file
94
modules/commands/edit.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
module.exports = {}
|
||||
|
||||
module.exports.definition = {
|
||||
name: 'edit',
|
||||
description: 'Edit a glucose reading',
|
||||
integration_types: [0, 1],
|
||||
contexts: [0, 1, 2],
|
||||
options: [
|
||||
{
|
||||
name: 'id',
|
||||
description: 'ID of the glucose reading to edit',
|
||||
type: 3, // STRING
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
description: 'New glucose reading value',
|
||||
type: 4, // INTEGER
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'datetime',
|
||||
description: 'New date/time in format mm/dd/yyyy hh:MM',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'notes',
|
||||
description: 'New notes for the reading',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
module.exports.execute = async (interaction, client, db) => {
|
||||
const id = interaction.options.getString('id');
|
||||
|
||||
if (!id) {
|
||||
return interaction.reply({ content: 'Please provide a valid glucose reading ID.', ephemeral: true });
|
||||
}
|
||||
|
||||
db.get(`SELECT * FROM glucose_log WHERE id = ? AND user_id = ?`, [id, interaction.user.id], async (err, row) => {
|
||||
if (err) {
|
||||
console.error('Database error:', err);
|
||||
return interaction.reply({ content: 'An error occurred while accessing the database.', ephemeral: true });
|
||||
}
|
||||
if (!row) {
|
||||
return interaction.reply({ content: 'No glucose reading found with the provided ID.', ephemeral: true });
|
||||
}
|
||||
const [ channel_id, message_id ] = row.msg_id.split('/'); //Get channel and msg id for update
|
||||
const ogMessage = await async function() {
|
||||
const chan = await client.channels.fetch(channel_id);
|
||||
if (!chan) {
|
||||
return null;
|
||||
}
|
||||
return await chan.messages.fetch(message_id).catch(() => null);
|
||||
}();
|
||||
if (!ogMessage) {
|
||||
return interaction.reply({ content: 'Original message not found. Cannot edit reading.', ephemeral: true });
|
||||
}
|
||||
const datetime = interaction.options.getString('datetime');
|
||||
let dateTimeObj;
|
||||
if (datetime) {
|
||||
dateTimeObj = new Date(datetime);
|
||||
if (isNaN(dateTimeObj.getTime())) {
|
||||
return interaction.reply({ content: 'Invalid date/time format. Use mm/dd/yyyy hh:MM.', ephemeral: true });
|
||||
}
|
||||
} else {
|
||||
dateTimeObj = new Date();
|
||||
}
|
||||
const formattedDate = dateTimeObj.toISOString().slice(0, 19).replace('T', ' ') || row.timestamp;
|
||||
const value = interaction.options.getInteger('value') || row.reading;
|
||||
const notes = interaction.options.getString('notes') || row.notes || '';
|
||||
|
||||
if (!db) {
|
||||
return interaction.reply({ content: 'Database connection is not available.', ephemeral: true });
|
||||
}
|
||||
|
||||
try {
|
||||
await db.run(
|
||||
`UPDATE glucose_log SET reading = ?, timestamp = ?, notes = ? WHERE id = ? AND user_id = ?`,
|
||||
[value, formattedDate, notes, id, interaction.user.id]
|
||||
);
|
||||
|
||||
await ogMessage.edit({ content: `[${id}] ${formattedDate}; ${value}; ${notes ? notes : ''}` });
|
||||
|
||||
return interaction.reply({ content: `Glucose reading with ID \`${id}\` has been updated successfully.`, ephemeral: true });
|
||||
} catch (updateErr) {
|
||||
console.error('Update error:', updateErr);
|
||||
return interaction.reply({ content: 'An error occurred while updating the glucose reading.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
64
modules/commands/export.js
Normal file
64
modules/commands/export.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
module.exports = {}
|
||||
const Discord = require('discord.js');
|
||||
module.exports.definition = {
|
||||
name: 'export',
|
||||
description: 'Export glucose readings to a csv file',
|
||||
integration_types: [0, 1],
|
||||
contexts: [0, 1, 2],
|
||||
options: [
|
||||
{
|
||||
name: 'start_date',
|
||||
description: 'Start date for the export in format mm/dd/yyyy',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'end_date',
|
||||
description: 'End date for the export in format mm/dd/yyyy',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
module.exports.execute = async (interaction, client, db) => {
|
||||
const startDate = interaction.options.getString('start_date');
|
||||
const endDate = interaction.options.getString('end_date');
|
||||
|
||||
if (!db) {
|
||||
return interaction.reply({ content: 'Database connection is not available.', ephemeral: true });
|
||||
}
|
||||
|
||||
try {
|
||||
let query = `SELECT * FROM glucose_log WHERE user_id = ?`;
|
||||
const params = [interaction.user.id];
|
||||
|
||||
if (startDate) {
|
||||
query += ` AND timestamp >= ?`;
|
||||
params.push(new Date(startDate).toISOString().slice(0, 19).replace('T', ' '));
|
||||
}
|
||||
if (endDate) {
|
||||
query += ` AND timestamp <= ?`;
|
||||
params.push(new Date(endDate).toISOString().slice(0, 19).replace('T', ' '));
|
||||
}
|
||||
db.all(query, params, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('Database error:', err);
|
||||
return interaction.reply({ content: 'An error occurred while accessing the database.', ephemeral: true });
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
return interaction.reply({ content: 'No glucose readings found for the specified date range.', ephemeral: true });
|
||||
}
|
||||
|
||||
// Convert rows to CSV format
|
||||
const csvRows = rows.map(row => `${row.timestamp},${row.reading},${row.notes}`);
|
||||
const csvContent = `Timestamp,Reading,Notes\n${csvRows.join('\n')}`;
|
||||
const csvBuffer = Buffer.from(csvContent, 'utf8');
|
||||
const attachment = new Discord.AttachmentBuilder(csvBuffer, { name: `glucose_readings.csv` });
|
||||
return interaction.reply({ content: 'Here are your glucose readings:', files: [attachment] });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error exporting glucose readings:', error);
|
||||
return interaction.reply({ content: 'An error occurred while exporting your glucose readings.', ephemeral: true });
|
||||
}
|
||||
}
|
71
modules/commands/log.js
Normal file
71
modules/commands/log.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
module.exports = {}
|
||||
|
||||
module.exports.definition = {
|
||||
name: 'log',
|
||||
description: 'Log a glucose reading',
|
||||
integration_types: [0,1],
|
||||
contexts: [0, 1, 2],
|
||||
options: [
|
||||
{
|
||||
name: 'value',
|
||||
description: 'Glucose reading value',
|
||||
type: 4, // INTEGER
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'datetime',
|
||||
description: 'Optional date/time in format mm/dd/yyyy hh:MM',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'notes',
|
||||
description: 'Optional notes for the reading',
|
||||
type: 3, // STRING
|
||||
required: false
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
module.exports.execute = async (interaction, client, db) => {
|
||||
const value = interaction.options.getInteger('value');
|
||||
const datetime = interaction.options.getString('datetime');
|
||||
const notes = interaction.options.getString('notes') || '';
|
||||
|
||||
if (isNaN(value) || value < 0) {
|
||||
return interaction.reply({ content: 'Please provide a valid glucose reading value.', ephemeral: true });
|
||||
}
|
||||
|
||||
let dateTimeObj;
|
||||
if (datetime) {
|
||||
dateTimeObj = new Date(datetime);
|
||||
if (isNaN(dateTimeObj.getTime())) {
|
||||
return interaction.reply({ content: 'Invalid date/time format. Use mm/dd/yyyy hh:MM.', ephemeral: true });
|
||||
}
|
||||
} else {
|
||||
dateTimeObj = new Date();
|
||||
}
|
||||
|
||||
const formattedDate = dateTimeObj.toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
if (!db) {
|
||||
return interaction.reply({ content: 'Database connection is not available.', ephemeral: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const id = uuidv4();
|
||||
await db.run(
|
||||
`INSERT INTO glucose_log (id, user_id, timestamp, reading, notes) VALUES (?, ?, ?, ?, ?)`,
|
||||
[id, interaction.user.id, formattedDate, value, notes]
|
||||
);
|
||||
await interaction.reply({ content: `Glucose reading logged: ${value} at ${formattedDate}. Notes: ${notes}`, ephemeral: true });
|
||||
await interaction.user.send({ content: `[${id}] ${formattedDate}; ${value}; ${notes ? notes : ''}`, ephemeral: false }).then((msg) => {
|
||||
db.run(`UPDATE glucose_log SET msg_id = ? WHERE id = ?`, [`${msg.channel.id}/${msg.id}`, id]);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Database error:', err);
|
||||
await interaction.reply({ content: 'Failed to log glucose reading. Please try again later.', ephemeral: true });
|
||||
}
|
||||
}
|
26
modules/registerCommands.js
Normal file
26
modules/registerCommands.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Discord = require("discord.js");
|
||||
const rest = new Discord.REST({ version: '9' }).setToken(process.env.TOKEN)
|
||||
const colors = require("colors");
|
||||
|
||||
module.exports = async (client) => {
|
||||
const commandsPath = path.join(__dirname, 'commands');
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
const commands = [];
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if (command.definition) commands.push(command.definition);
|
||||
}
|
||||
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`Found ${commands.length} commands.`)}`);
|
||||
try {
|
||||
//Global
|
||||
console.log(`${colors.cyan(`[INFO]`)} ${colors.green(`Registering commands...`)}`);
|
||||
await rest.put(Discord.Routes.applicationCommands(client.user.id), { body: commands })
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
1806
package-lock.json
generated
Normal file
1806
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "glucose-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"colors": "^1.4.0",
|
||||
"discord.js": "^14.21.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"uuid": "^11.1.0"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue