git-utils #3
77
_opt/gitUtils.js
Normal file
77
_opt/gitUtils.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git-related slash commands: /gitstatus and /gitpull
|
||||||
|
*/
|
||||||
|
export const commands = [
|
||||||
|
// Show current branch and commit status
|
||||||
|
{
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('gitstatus')
|
||||||
|
.setDescription('Show current git branch and remote status'),
|
||||||
|
async execute(interaction, client) {
|
||||||
|
try {
|
||||||
|
// Get current branch and commit
|
||||||
|
const { stdout: branchOut } = await execAsync('git rev-parse --abbrev-ref HEAD');
|
||||||
|
const branch = branchOut.trim();
|
||||||
|
const { stdout: localOut } = await execAsync('git rev-parse HEAD');
|
||||||
|
const local = localOut.trim().slice(0, 7);
|
||||||
|
// Fetch updates and get remote commit
|
||||||
|
await execAsync('git fetch --quiet');
|
||||||
|
const { stdout: remoteOut } = await execAsync(`git rev-parse origin/${branch}`);
|
||||||
|
const remote = remoteOut.trim().slice(0, 7);
|
||||||
|
// Determine ahead/behind counts
|
||||||
|
const { stdout: behindOut } = await execAsync(`git rev-list --count HEAD..origin/${branch}`);
|
||||||
|
const behind = parseInt(behindOut.trim(), 10);
|
||||||
|
const { stdout: aheadOut } = await execAsync(`git rev-list --count origin/${branch}..HEAD`);
|
||||||
|
const ahead = parseInt(aheadOut.trim(), 10);
|
||||||
|
const status = (ahead === 0 && behind === 0)
|
||||||
|
? 'Up-to-date' : `${ahead} ahead, ${behind} behind`;
|
||||||
|
// Reply with status
|
||||||
|
await interaction.reply({
|
||||||
|
content: `Branch: \`${branch}\`\nLocal: \`${local}\`\nRemote: \`${remote}\`\nStatus: ${status}`,
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
client.logger.error(`Error in gitstatus: ${err.message}`);
|
||||||
|
await interaction.reply({ content: `Failed to get git status: ${err.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Pull latest changes and restart bot (owner only)
|
||||||
|
{
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('gitpull')
|
||||||
|
.setDescription('Pull latest changes and restart bot (Owner only)')
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||||
|
async execute(interaction, client) {
|
||||||
|
const ownerId = client.config.owner;
|
||||||
|
if (interaction.user.id !== ownerId) {
|
||||||
|
return interaction.reply({ content: 'Only the bot owner can perform this action.', ephemeral: true });
|
||||||
|
}
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
try {
|
||||||
|
// Pull with fast-forward only
|
||||||
|
const { stdout, stderr } = await execAsync('git pull --ff-only');
|
||||||
|
const output = stdout.trim() || stderr.trim();
|
||||||
|
await interaction.editReply({
|
||||||
|
content: `Git pull output:\n\`\`\`\n${output}\n\`\`\`\nRestarting...`
|
||||||
|
});
|
||||||
|
setTimeout(() => process.exit(0), 1000);
|
||||||
|
} catch (err) {
|
||||||
|
client.logger.error(`Error in gitpull: ${err.message}`);
|
||||||
|
await interaction.editReply({ content: `Git pull failed: ${err.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No special init logic for git utilities
|
||||||
|
*/
|
||||||
|
export async function init(client, config) {
|
||||||
|
client.logger.info('Git utilities module loaded');
|
||||||
|
}
|
||||||
28
_opt/messageQueue-example.js
Normal file
28
_opt/messageQueue-example.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// _opt/messageQueue-example.js
|
||||||
|
import { onMessageQueueEvent } from './pbUtils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example module that listens for 'test' messages in the message_queue collection.
|
||||||
|
*/
|
||||||
|
export const init = async (client, config) => {
|
||||||
|
client.logger.info('Initializing Message Queue Example module');
|
||||||
|
onMessageQueueEvent(client, async (action, record) => {
|
||||||
|
// Only process newly created records
|
||||||
|
if (action !== 'create') return;
|
||||||
|
// Only process messages meant for this client
|
||||||
|
if (record.destination !== client.config.id) return;
|
||||||
|
// Only handle test dataType
|
||||||
|
if (record.dataType !== 'test') return;
|
||||||
|
|
||||||
|
// At this point we have a test message for us
|
||||||
|
client.logger.info('test received');
|
||||||
|
|
||||||
|
// Delete the processed message from the queue
|
||||||
|
try {
|
||||||
|
await client.pb.deleteMessageQueue(record.id);
|
||||||
|
client.logger.debug(`Deleted message_queue record ${record.id}`);
|
||||||
|
} catch (err) {
|
||||||
|
client.logger.error(`Failed to delete message_queue record ${record.id}: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -19,12 +19,15 @@ export const loadModules = async (clientConfig, client) => {
|
|||||||
// Load each module
|
// Load each module
|
||||||
for (const moduleName of modules) {
|
for (const moduleName of modules) {
|
||||||
try {
|
try {
|
||||||
const modulePath = path.join(modulesDir, `${moduleName}.js`);
|
// Try _opt first, then fallback to core _src modules
|
||||||
|
let modulePath = path.join(modulesDir, `${moduleName}.js`);
|
||||||
// Check if module exists
|
|
||||||
if (!fs.existsSync(modulePath)) {
|
if (!fs.existsSync(modulePath)) {
|
||||||
client.logger.warn(`Module not found: ${modulePath}`);
|
// Fallback to core source directory
|
||||||
|
modulePath = path.join(rootDir, '_src', `${moduleName}.js`);
|
||||||
|
if (!fs.existsSync(modulePath)) {
|
||||||
|
client.logger.warn(`Module not found in _opt or _src: ${moduleName}.js`);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import module (using dynamic import for ES modules)
|
// Import module (using dynamic import for ES modules)
|
||||||
|
|||||||
@ -70,6 +70,7 @@ export default {
|
|||||||
'pbUtils',
|
'pbUtils',
|
||||||
'responses',
|
'responses',
|
||||||
'responsesQuery',
|
'responsesQuery',
|
||||||
|
'gitUtils'
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
22
package-lock.json
generated
22
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"discord-api-types": "^0.37.120",
|
"discord-api-types": "^0.37.120",
|
||||||
"discord.js": "^14.18.0",
|
"discord.js": "^14.18.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
|
"eventsource": "^3.0.6",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"openai": "^4.95.1",
|
"openai": "^4.95.1",
|
||||||
"pocketbase": "^0.25.2",
|
"pocketbase": "^0.25.2",
|
||||||
@ -519,6 +520,27 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventsource": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventsource-parser": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eventsource-parser": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"discord-api-types": "^0.37.120",
|
"discord-api-types": "^0.37.120",
|
||||||
"discord.js": "^14.18.0",
|
"discord.js": "^14.18.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
|
"eventsource": "^3.0.6",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"openai": "^4.95.1",
|
"openai": "^4.95.1",
|
||||||
"pocketbase": "^0.25.2",
|
"pocketbase": "^0.25.2",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user