@conflict/beta
Advanced tools
Comparing version 0.5.2 to 0.6.0
93
bot.js
@@ -15,3 +15,3 @@ import Discord, { | ||
import stump from './logger.js' | ||
import events, { _setClient, onInteractionCreate, onDebug, onReady } from './events.js' | ||
import events, { _setClient, onInteractionCreate, onDebug, onReady, onHotReload, onStartThinking } from './events.js' | ||
import Command, { InteractionResponse } from './commands.js' | ||
@@ -26,2 +26,14 @@ import View from './view.js' | ||
let thinking = false; | ||
const finishThinking = () => { | ||
return new Promise(resolve => { | ||
let id = setInterval(() => { | ||
if (!thinking) { | ||
clearInterval(id); | ||
resolve(); | ||
} | ||
}, 100); | ||
}) | ||
} | ||
global.__ConflictViewParser = View.createElement; | ||
@@ -46,3 +58,4 @@ | ||
} | ||
const { token, intents, errorHandler, plugins: [] } = config.default; | ||
let { token, intents, errorHandler, plugins } = config.default; | ||
if (!plugins) plugins = []; | ||
@@ -54,2 +67,14 @@ const rest = new REST({ version: '9' }).setToken(token); | ||
_setClient(client); | ||
if (client.shard) { | ||
setInterval(() => { | ||
client.shard.fetchClientValues('user') | ||
.then(() => {}) | ||
.catch((error) => { | ||
if (error.message == 'Channel closed') { | ||
if (__ConflictENV.verbose) stump.verbose('Disconnected from parent, killing shard'); | ||
process.exit(1); | ||
} | ||
}); | ||
}, 3000); | ||
} | ||
@@ -80,3 +105,4 @@ if (process.env.CONFLICT_VERBOSE === "TRUE") onDebug(message => { | ||
async function initCommands () { | ||
async function initCommands (hotReload) { | ||
if (hotReload) commands = {}; | ||
let previousGuilds = []; | ||
@@ -93,3 +119,3 @@ if (fs.existsSync(path.join(process.cwd(), '.conflict', '.guilds.commands.cache'))) { | ||
if (file.endsWith('.js') || file.endsWith('.cjs') || file.endsWith('.mjs')) { | ||
let fileData = await import(file); | ||
let fileData = await import(file + '?r=' + Math.random().toString(36).substring(3)); | ||
@@ -116,3 +142,5 @@ if (fileData.default && fileData.default instanceof Command) { | ||
description: commandData.description, | ||
options: commandData.options | ||
options: commandData.options, | ||
name_localizations: commandData.name_localizations, | ||
description_localizations: commandData.description_localizations | ||
}); | ||
@@ -123,3 +151,5 @@ } else { | ||
description: commandData.description, | ||
options: commandData.options | ||
options: commandData.options, | ||
name_localizations: commandData.name_localizations, | ||
description_localizations: commandData.description_localizations | ||
}); | ||
@@ -130,7 +160,4 @@ } | ||
previousGuilds = previousGuilds.filter(guild => !guilds.includes(guild)); | ||
for (const guild of previousGuilds) { | ||
stump.debug( | ||
'124', | ||
await rest.put(Routes.applicationGuildCommands(client.user.id, guild), { body: [] }) | ||
); | ||
if (!hotReload) for (const guild of previousGuilds) { | ||
await rest.put(Routes.applicationGuildCommands(client.user.id, guild), { body: [] }); | ||
} | ||
@@ -140,21 +167,9 @@ | ||
setTimeout(async () => { | ||
stump.debug( | ||
'133', | ||
await rest.put(Routes.applicationCommands(client.user.id), { body: publicCommands }) | ||
); | ||
// await client.api.applications(client.user.id).commands.put({ | ||
// data: publicCommands | ||
// }); | ||
if (!hotReload) setTimeout(async () => { | ||
await rest.put(Routes.applicationCommands(client.user.id), { body: publicCommands }); | ||
}, 30000); | ||
for (const guild in guildCommands) { | ||
if (!hotReload) for (const guild in guildCommands) { | ||
const commandsForGuild = guildCommands[guild]; | ||
stump.debug( | ||
'144', | ||
await rest.put(Routes.applicationGuildCommands(client.user.id, guild), { body: commandsForGuild }) | ||
); | ||
// await client.api.applications(client.user.id).guilds(guild).commands.put({ | ||
// data: commandsForGuild | ||
// }); | ||
await rest.put(Routes.applicationGuildCommands(client.user.id, guild), { body: commandsForGuild }) | ||
} | ||
@@ -164,3 +179,14 @@ | ||
onInteractionCreate(async interaction => { | ||
if (!hotReload) onInteractionCreate(async interaction => { | ||
if (thinking) { | ||
interaction.deferReply(); | ||
await finishThinking(); | ||
return interaction.editReply({ embeds: [ | ||
new MessageEmbed() | ||
.setColor('#ff4444') | ||
.setTitle('Build Finished') | ||
.setDescription('Run the command again to see the updated output.') | ||
.setTimestamp() | ||
] }) | ||
} | ||
if (interaction.isCommand()) { | ||
@@ -258,3 +284,3 @@ if (commands[interaction.commandName]) { | ||
}); | ||
client.ws.on('INTERACTION_CREATE', async (apiInteraction) => { | ||
if (!hotReload) client.ws.on('INTERACTION_CREATE', async (apiInteraction) => { | ||
if (apiInteraction.type !== 5) return; | ||
@@ -328,2 +354,11 @@ let interaction = new Discord.CommandInteraction(client, apiInteraction); | ||
init(); | ||
onHotReload(async () => { | ||
stump.info('Reloading...'); | ||
await initCommands(true); | ||
thinking = false; | ||
stump.success('Reloaded. Hot reload does not update command metadata with Discord, only the response code.'); | ||
}); | ||
onStartThinking(() => { | ||
thinking = true; | ||
}); | ||
})(); |
import View, { Component } from './view.js' | ||
class ValueLocalizations { | ||
constructor (default_, localizations) { | ||
this.default = default_; | ||
this.localizations = localizations; | ||
} | ||
getDefault () { | ||
return this.default; | ||
} | ||
getLocalizations () { | ||
return this.localizations; | ||
} | ||
} | ||
export function localize (localizations) { | ||
return new ValueLocalizations(localizations); | ||
} | ||
export default class Command { | ||
constructor ({ name, description, options, execute, meta, testing }) { | ||
constructor ({ name, description, options, execute, meta, testing, description_localizations = {}, name_localizations = {} }) { | ||
this.name = name; | ||
this.description = description; | ||
this.options = options; | ||
this.name_localizations = name_localizations; | ||
this.description_localizations = description_localizations; | ||
if (name instanceof ValueLocalizations) { | ||
this.name = name.getDefault(); | ||
this.name_localizations = name.getLocalizations(); | ||
} | ||
if (description instanceof ValueLocalizations) { | ||
this.description = description.getDefault(); | ||
this.description_localizations = description.getLocalizations(); | ||
} | ||
this.testing = testing; | ||
@@ -16,2 +43,13 @@ this.type = (testing && testing.guildId) ? 'guild' : 'global'; | ||
} | ||
localize (languageData) { | ||
for (const language in languageData) { | ||
const text = languageData[language]; | ||
this.name_localizations[language] = text.name; | ||
this.description_localizations[language] = text.description; | ||
} | ||
return this; | ||
} | ||
static localize (data) { | ||
return localize(data); | ||
} | ||
} | ||
@@ -103,3 +141,7 @@ | ||
if (ephemeral && options[0] && options[0].toString() == '[object Object]') options[0].ephemeral = true; | ||
return this.interaction.reply(...options); | ||
return ( | ||
this.interaction.conflictThunked ? | ||
this.interaction.editReply(...options) : | ||
this.interaction.reply(...options) | ||
); | ||
} | ||
@@ -106,0 +148,0 @@ respond (...options) { |
@@ -1,52 +0,83 @@ | ||
import { spawn, exec } from 'child_process'; | ||
import chalk from 'chalk'; | ||
import { stdin } from 'process'; | ||
let projectDirectory = process.cwd(); | ||
const build = exec('npx babel bot --out-dir .conflict', { cwd: projectDirectory }, () => { | ||
const babel = spawn('npx', 'babel bot --out-dir .conflict --watch'.split(' '), { cwd: projectDirectory }); | ||
babel.stdout.setEncoding('utf8'); | ||
babel.stderr.setEncoding('utf8'); | ||
babel.stdout.on('data', (chunk) => { | ||
console.log(chalk.yellow('Babel > ') + chunk); | ||
}); | ||
babel.stderr.on('data', (chunk) => { | ||
console.error(chalk.yellow('Babel > ') + chunk); | ||
}); | ||
babel.on('close', (code) => { | ||
console.log(`Babel exited with code ${code}`); | ||
}); | ||
function spawnRunner () { | ||
const runner = spawn('npx', 'conflict'.split(' '), { stdio: ['pipe', 'pipe', 'pipe'], env: process.env, cwd: projectDirectory }); | ||
runner.stdout.setEncoding('utf8'); | ||
runner.stderr.setEncoding('utf8'); | ||
runner.stdout.on('data', (chunk) => { | ||
console.log(chunk); | ||
}); | ||
process.stdin.pipe(runner.stdin); | ||
process.stdin.on('data', data => { | ||
if (data.toString().includes('r')) { | ||
runner.stdin.pause(); | ||
runner.kill(); | ||
console.log(chalk.blue('Conflict > ') + 'Refreshed'); | ||
spawnRunner(); | ||
} | ||
}) | ||
import { ShardingManager } from 'discord.js'; | ||
import { exec } from 'child_process'; | ||
import stump from '../logger.js'; | ||
import path from 'path'; | ||
import chokidar from 'chokidar'; | ||
import { dirname } from "esm-dirname"; | ||
import { detectFlag } from '../utils.js'; | ||
const __dirname = dirname(import.meta); | ||
const build = () => { | ||
return new Promise((resolve, reject) => { | ||
exec('npx babel bot --out-dir .conflict/build', { cwd: process.cwd() }, (error, stdout, stderr) => { | ||
if (error) return reject(error); | ||
resolve({ stdout, stderr }); | ||
}); | ||
}); | ||
}; | ||
runner.stderr.on('data', (chunk) => { | ||
console.error(chunk); | ||
}); | ||
runner.on('close', (code) => { | ||
console.log(`Process exited with code ${code}`); | ||
}); | ||
process.stdin.on('end', (chunk) => { | ||
runner.stdin.pause(); | ||
runner.kill(); | ||
babel.stdin.pause(); | ||
babel.kill(); | ||
}); | ||
} | ||
let running = false; | ||
let next; | ||
spawnRunner(); | ||
console.log(chalk.blue('Conflict > ') + 'Development server started. Press r to refresh. Please use only Ctrl+C to exit.'); | ||
build().then(async ({ stdout, stderr }) => { | ||
stump.info('Starting development environment...'); | ||
global.__ConflictENV = {}; | ||
global.__ConflictENV.verbose = detectFlag(process.argv, 'verbose') || detectFlag(process.argv, 'detailed'); | ||
if (global.__ConflictENV.verbose) stump.verbose('Running verbose'); | ||
if (global.__ConflictENV.verbose) process.env.CONFLICT_VERBOSE = "TRUE"; | ||
let config; | ||
try { | ||
config = await import(process.cwd() + '/conflict.config.js'); | ||
} catch (err) { | ||
stump.error('Missing conflict.config.js'); | ||
} | ||
const { token } = config.default; | ||
const manager = new ShardingManager(path.join(__dirname, '..', 'bot.js'), { token: token }); | ||
manager.on('shardCreate', shard => stump.success(`Launched shard ${shard.id}`)); | ||
manager.spawn().then(_ => { | ||
stump.success('All shards launched, waiting for file events...'); | ||
chokidar.watch('./bot', { | ||
persistent: true, | ||
ignoreInitial: true, | ||
awaitWriteFinish: true | ||
}).on('all', (event, path) => { | ||
onFileChange(); | ||
}); | ||
}); | ||
function refresh () { | ||
manager.broadcastEval(c => { | ||
c.emit('conflict.hotReload'); | ||
}); | ||
} | ||
async function buildAndRefresh () { | ||
manager.broadcastEval(c => { | ||
c.emit('conflict.startThinking'); | ||
}); | ||
stump.info('Change detected, rebuilding...'); | ||
if (global.__ConflictENV.verbose) stump.verbose('Starting rebuild...'); | ||
await build(); | ||
if (global.__ConflictENV.verbose) stump.verbose('Finished building, starting refresh...'); | ||
refresh(); | ||
if (global.__ConflictENV.verbose) stump.verbose('Refresh finished'); | ||
} | ||
async function onFileChange () { | ||
if (running) next = true; | ||
else { | ||
running = true; | ||
await buildAndRefresh(); | ||
running = false; | ||
while (next) { | ||
next = false; | ||
buildAndRefresh(); | ||
} | ||
} | ||
} | ||
}); |
@@ -361,2 +361,18 @@ let client; | ||
// Conflict events | ||
export function onHotReload (fn) { | ||
client.on('conflict.hotReload', (...args) => { | ||
fn(...args); | ||
}); | ||
} | ||
events.onHotReload = onHotReload; | ||
export function onStartThinking (fn) { | ||
client.on('conflict.startThinking', () => { | ||
fn(); | ||
}); | ||
} | ||
events.onStartThinking = onStartThinking; | ||
// Time-based events | ||
@@ -363,0 +379,0 @@ |
import Stump from 'stump.js' | ||
const logger = new Stump(['Timestamp', 'Debug']) | ||
const logger = new Stump(['Debug']) | ||
export { logger as default, logger as stump, logger as logger } |
{ | ||
"name": "@conflict/beta", | ||
"version": "0.5.2", | ||
"version": "0.6.0", | ||
"description": "The first and only UI framework for Discord bots", | ||
@@ -47,3 +47,4 @@ "main": "exports.js", | ||
"./components": "./components.js", | ||
"./Components": "./components.js" | ||
"./Components": "./components.js", | ||
"./babel-plugin": "./babel.js" | ||
}, | ||
@@ -50,0 +51,0 @@ "devDependencies": { |
@@ -76,3 +76,3 @@ import { uuid, queryString } from './utils.js' | ||
let id = crypto.createHmac("sha256", 'shhh').update(code.toString()).digest("base64"); | ||
let queryString = 'c?type=stateless&id=' + encodeURIComponent(id); | ||
let queryString = 'c?type=stateless&r=' + Math.floor(Math.random() * 10000) + (Date.now() + '').substring(5) + '&id=' + encodeURIComponent(id); | ||
if (this.get(id)) return queryString; | ||
@@ -79,0 +79,0 @@ this.set(id, code); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
68045
36
1873