Added Payment Authorization Middleware

- Default options for commands
- Verify user has enough balance to create the transaction
- Need to create methods to create transactions
This commit is contained in:
Logan Cusano
2023-02-26 00:51:56 -05:00
parent 8ed30ad171
commit f3bae7e223
5 changed files with 206 additions and 34 deletions

View File

@@ -14,34 +14,34 @@ module.exports = {
.setRequired(true))
.addNumberOption(option =>
option.setName('temperature')
.setDescription('Set the temperature, 0 = repetitive, 1 = random')
.setDescription('Set the temperature, 0 = repetitive, 1 = random; Defaults to 0')
.setRequired(false))
.addNumberOption(option =>
option.setName('tokens')
.setDescription('The max amount of tokens to be spent')
.setRequired(false)),
example: "chat [tell me a story] [0.07] []", // Need to figure out the tokens
isPrivileged: true,
isPrivileged: false,
requiresTokens: true,
defaultTokenUsage: 100,
deferInitialReply: true,
async execute(interaction) {
// Needs middleware for payment
try {
var params = {};
var promptText = interaction.options.getString('prompt');
var temperature = interaction.options.getNumber('temperature');
var maxTokens = interaction.options.getNumber('tokens') ?? this.defaultTokenUsage;
try {
var params = {};
var prompt = encodeURIComponent(interaction.options.getString('prompt').join(" "));
var temperature = interaction.options.getNumber('temperature');
var maxTokens = interaction.options.getNumber('tokens');
if (temperature) params._temperature = temperature;
if (maxTokens) params._max_tokens = maxTokens;
var gptResponse = await libCore.getChat(promptText, params);
await interaction.reply(`${interaction.member.user} ${gptResponse}`);
if (temperature) params._temperature = temperature;
if (maxTokens) params._max_tokens = maxTokens;
var gptResponse = await prompt.libCore.getChat(prompt, params);
await interaction.reply(`${interaction.author.username} ${gptResponse}`);
// Needs reply code to reply to the generation
}catch(err){
log.ERROR(err)
//await interaction.reply(err.toString());
}
// Needs reply code to reply to the generation
}catch(err){
log.ERROR(err)
//await interaction.reply(err.toString());
}
}
};

View File

@@ -1,5 +1,6 @@
const { Events } = require('discord.js');
const discordAuth = require('../middleware/discordAuthorization');
const { authorizeCommand } = require('../middleware/discordAuthorization');
const { authorizeTokenUsage } = require('../middleware/balanceAuthorization');
const { DebugBuilder } = require("../utilities/debugBuilder");
const log = new DebugBuilder("server", "interactionCreate");
@@ -18,17 +19,19 @@ module.exports = {
log.DEBUG(`${interaction.member.user} is running '${interaction.commandName}'`);
await discordAuth.authorizeCommand(interaction, command, () => {
try {
command.execute(interaction);
} catch (error) {
log.ERROR(error);
if (interaction.replied || interaction.deferred) {
interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
} else {
interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
await authorizeCommand(interaction, command, async () => {
await authorizeTokenUsage(interaction, command, async () => {
try {
command.execute(interaction);
} catch (error) {
log.ERROR(error);
if (interaction.replied || interaction.deferred) {
interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
} else {
interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
}
}
})
});
},
};

View File

@@ -5,9 +5,9 @@ let parser = new Parser();
const { FeedStorage, PostStorage } = require("./libStorage");
const libUtils = require("./libUtils");
const { createHash } = require("crypto");
const { all } = require('axios');
const { DebugBuilder } = require("./utilities/debugBuilder");
const { all } = require('axios');
const log = new DebugBuilder("server", "libCore");
/* OpenAI config
@@ -267,7 +267,10 @@ exports.getSlang = async function (question) {
* @param {*} param1 Default parameters can be modified
* @returns
*/
exports.getChat = async function (_prompt, { _model = "text-davinci-003", _temperature = 0, _max_tokens = 100 }) {
exports.getChat = async (_prompt, { _model = "text-davinci-003", _temperature = 0, _max_tokens = 100 }) => {
log.DEBUG("Getting chat with these properties: ", _prompt, _model, _temperature, _max_tokens)
return
/*
const response = await openai.createCompletion({
model: _model,
prompt: _prompt,
@@ -275,9 +278,9 @@ exports.getChat = async function (_prompt, { _model = "text-davinci-003", _tempe
max_tokens: _max_tokens
});
var responseData = response.data.choices[0].text;
return responseData;
*/
}
/**

View File

@@ -12,6 +12,9 @@ const mysql = require("mysql");
const rssFeedsTable = process.env.DB_RSS_FEEDS_TABLE;
const rssPostsTable = process.env.DB_RSS_POSTS_TABLE;
const accountsTable = process.env.DB_ACCOUNTS_TABLE;
const transactionsTable = process.env.DB_TRANSACTIONS_TABLE;
const pricingTable = process.env.DB_PRICING_TABLE;
// Helper Functions
// Function to run and handle SQL errors
@@ -30,6 +33,15 @@ function runSQL(sqlQuery, connection, callback = (err, rows) => {
})
}
/**
* Return a formatted date time string from now for MySQL
*
* @returns Date string for now formatted for MySQL
*/
function returnMysqlTime(){
return new Date().toISOString().slice(0, 19).replace('T', ' ');
}
class Storage {
constructor(_dbTable) {
this.connection = mysql.createPool({
@@ -142,6 +154,63 @@ class Storage {
}
}
exports.UserStorage = class UserStorage extends Storage {
constructor() {
super(accountsTable);
}
/**
* Save a new account to the database
* @param {*} _discordAccountId The Discord ID the the user
* @param {*} callback The callback to be sent
* @callback Error|Array|*
*/
saveAccount(_discordAccountId, callback){
const sqlQuery = `INSERT INTO ${this.dbTable} (discord_account_id, balance) VALUES ('${_discordAccountId}', ${0});`;
log.DEBUG(`Adding new entry with SQL query: '${sqlQuery}'`)
runSQL(sqlQuery, this.connection, (err, rows) => {
if (err) return callback(err, undefined);
if (rows?.affectedRows > 0) return callback(undefined, rows);
return callback(undefined, undefined);
})
}
checkBalance(_tokensToBeUsed, _account_id, callback) {
this.getRecordBy('account_id', _account_id, (err, record) => {
if (err) return callback(err, undefined);
if (record?.balance && record.balance > _tokensToBeUsed) return callback(undefined, true);
else{
return callback(undefined, false);
}
})
}
updateBalance(){
}
}
/*
exports.TransactionStorage = class TransactionStorage extends Storage {
constructor() {
super(transactionsTable);
}
createTransaction(transaction, callback){
const sqlQuery = `INSERT INTO ${this.dbTable} () VALUES ('${}');`;
log.DEBUG(`Adding new entry with SQL query: '${sqlQuery}'`)
runSQL(sqlQuery, this.connection, (err, rows) => {
if (err) return callback(err, undefined);
if (rows[0]?.id) return callback(undefined, rows);
return callback(undefined, undefined);
})
}
}
*/
exports.FeedStorage = class FeedStorage extends Storage {
constructor() {
super(rssFeedsTable);
@@ -329,7 +398,7 @@ exports.PostStorage = class PostStorage extends Storage {
}
savePost(_postObject, callback){
const tempCreationDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
const tempCreationDate = returnMysqlTime();
log.DEBUG("Saving Post Object:", _postObject);
if (!_postObject?.guid || !_postObject?.link) {
return callback(new Error("Post object malformed, check the object before saving it"), undefined)

View File

@@ -0,0 +1,97 @@
// To ensure the command caller has, 1. an account and 2. has enough balance for the request
// The pricing table needs to be in the DB so it can be updated via API
const { DebugBuilder } = require("../utilities/debugBuilder");
const log = new DebugBuilder("server", "balanceAuthorizations");
const { UserStorage } = require("../libStorage");
const userStorage = new UserStorage();
/**
* Check to see if the discord ID has an account associated with it
* @param {*} _discordAccountId The discord account to look for
* @param {*} callback The callback function to be called
* @callback false|*
*/
function checkForAccount(_discordAccountId, callback){
userStorage.getRecordBy("discord_account_id", _discordAccountId, (err, results) => {
if (err) return callback(err, undefined);
if (!results) return callback(undefined, false);
return callback(undefined, results);
})
}
/**
* Create an account from a discord ID
* @param {*} _discordAccountId
* @param {*} callback
*/
function createAccount(_discordAccountId, callback){
if (!_discordAccountId) return callback(new Error("No discord account specified before creation"));
userStorage.saveAccount(_discordAccountId, (err, results) => {
if (err) return callback(err, undefined);
if (!results) return callback(new Error("No results from creating account"), undefined);
return callback(undefined, results);
})
}
function verifyBalance(_tokensToBeUsed, _accountId, callback){
userStorage.checkBalance(_tokensToBeUsed, _accountId, (err, results) => {
if (err) return callback(err, undefined);
if(!results) return callback(undefined, false);
return callback(undefined, true);
})
}
function insufficientTokensResponse(interaction){
log.DEBUG("INSUFFICIENT TOKENS RESPONSE")
return interaction.reply("Sorry, not enough tokens for this request.");
}
function welcomeResponse(interaction){
log.DEBUG("WELCOME RESPONSE")
return interaction.reply("Hey there, you have an account now.");
}
exports.authorizeTokenUsage = async (interaction, command, next) => {
log.DEBUG("Command requires tokens? ", command.isPrivileged)
if(!command.requiresTokens) return next(true);
if(!interaction.member && (!interaction.options.getNumber("tokens") || !command.defaultTokenUsage)) throw new Error("No member or tokens specified before attempting to authorize");
const memberId = interaction.member.id;
const tokensToBeUsed = interaction.options.getNumber("tokens") ?? command.defaultTokenUsage
log.DEBUG(`Authorizing ${memberId} for a purchase worth ${tokensToBeUsed} tokens`)
log.DEBUG("Checking for account associated with discord ID: ", memberId);
await checkForAccount(memberId, async (err, results) => {
if (err) throw err;
log.DEBUG("Results from checking for account: ", results);
// First time user is attempting transaction
if(!results){
log.DEBUG("No account for discord ID: ", memberId);
await createAccount(memberId, (err, results) => {
if (err) throw err;
if (results) return welcomeResponse(interaction);
})
} else{
// User has an account
log.DEBUG(`Account ID: ${results.account_id} found for discord ID: ${memberId}`);
await verifyBalance(tokensToBeUsed, results.account_id, async (err, isVerified) => {
if (err) throw err;
if(!isVerified) return insufficientTokensResponse(interaction);
return next(isVerified);
})
}
})
}