Slackbots müssen nicht darauf warten, dass du Befehle eingibst. Mit der richtigen Einrichtung kann dein Bot dir bei der Verwaltung deiner WordPress-Websites helfen, indem er interaktive Schaltflächen, Dropdowns, geplante Aufgaben und intelligente Benachrichtigungen anbietet – und das alles direkt in Slack.
In diesem Artikel zeigen wir dir, wie du deinem Slack-Bot Interaktivität, Automatisierung und Monitoring hinzufügen kannst.
Voraussetzungen
Bevor du beginnst, stelle sicher, dass du Folgendes hast
- Eine Slack-Anwendung mit Bot-Berechtigungen und einem Slash-Befehl.
- Ein Kinsta-Konto mit API-Zugang und eine Website, mit der du testen kannst.
- Node.js und NPM sind lokal installiert.
- Grundlegende Vertrautheit mit JavaScript (oder zumindest die Fähigkeit, Code zu kopieren und zu verändern).
- API-Schlüssel für Slack und Kinsta.
Erste Schritte
Um diesen Slackbot zu bauen, werden Node.js und das Bolt-Framework von Slack verwendet, um Slash-Befehle zu verkabeln, die über die Kinsta-API Aktionen auslösen.
Wir werden in diesem Leitfaden nicht alle Schritte zur Erstellung einer Slack-Anwendung oder zum Zugriff auf die Kinsta-API wiederholen, da diese bereits in unserem früheren Leitfaden How to Build a Slackbot With Node.js and Kinsta API for Site Management behandelt wurden.
Wenn du diesen Leitfaden noch nicht kennst, solltest du ihn zuerst lesen. Darin erfährst du, wie du deine Slack-Anwendung erstellst, dein Bot-Token und dein Signiergeheimnis bekommst und deinen Kinsta-API-Schlüssel erhältst.
Füge deinem Slackbot Interaktivität hinzu
Slackbots müssen sich nicht nur auf Schrägstrich-Befehle verlassen. Mit interaktiven Komponenten wie Schaltflächen, Menüs und Modals kannst du deinen Bot in ein viel intuitiveres und benutzerfreundlicheres Tool verwandeln.
Anstatt /clear_cache environment_id
einzugeben, stell dir vor, du klickst auf eine Schaltfläche mit der Aufschrift Cache löschen, nachdem du den Status einer Website überprüft hast. Dafür brauchst du den Web-API-Client von Slack. Installiere ihn mit dem unten stehenden Befehl in deinem Projekt:
npm install @slack/web-api
Dann initialisiere ihn in deinem app.js
:
const { WebClient } = require('@slack/web-api');
const web = new WebClient(process.env.SLACK_BOT_TOKEN);
Stelle sicher, dass SLACK_BOT_TOKEN
in deiner .env
Datei gesetzt ist. Jetzt wollen wir den Befehl /site_status
aus dem vorherigen Artikel verbessern. Anstatt nur Text zu senden, fügen wir Schaltflächen für schnelle Aktionen wie Cache löschen, Backup erstellen oder detaillierten Status prüfen ein.
So sieht der aktualisierte Handler aus:
app.command('/site_status', async ({ command, ack, say }) => {
await ack();
const environmentId = command.text.trim();
if (!environmentId) {
await say('Please provide an environment ID. Usage: `/site_status [environment-id]`');
return;
}
try {
// Get environment status
const response = await kinstaRequest(`/sites/environments/${environmentId}`);
if (response && response.site && response.site.environments && response.site.environments.length > 0) {
const env = response.site.environments[0];
// Format the status message
let statusMessage = formatSiteStatus(env);
// Send message with interactive buttons
await web.chat.postMessage({
channel: command.channel_id,
text: statusMessage,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: statusMessage
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: '🧹 Clear Cache',
emoji: true
},
value: environmentId,
action_id: 'clear_cache_button'
},
{
type: 'button',
text: {
type: 'plain_text',
text: '📊 Detailed Status',
emoji: true
},
value: environmentId,
action_id: 'detailed_status_button'
},
{
type: 'button',
text: {
type: 'plain_text',
text: '💾 Create Backup',
emoji: true
},
value: environmentId,
action_id: 'create_backup_button'
}
]
}
]
});
} else {
await say(`⚠️ No environment found with ID: `${environmentId}``);
}
} catch (error) {
console.error('Error checking site status:', error);
await say(`❌ Error checking site status: ${error.message}`);
}
});
Jeder Klick auf eine Schaltfläche löst eine Aktion aus. Das beinhaltet die Schaltfläche Cache löschen:
// Add action handlers for the buttons
app.action('clear_cache_button', async ({ body, ack, respond }) => {
await ack();
const environmentId = body.actions[0].value;
await respond(`🔄 Clearing cache for environment `${environmentId}`...`);
try {
// Call Kinsta API to clear cache
const response = await kinstaRequest(
`/sites/environments/${environmentId}/clear-cache`,
'POST'
);
if (response && response.operation_id) {
await respond(`✅ Cache clearing operation started! Operation ID: `${response.operation_id}``);
} else {
await respond('⚠️ Cache clearing request was sent, but no operation ID was returned.');
}
} catch (error) {
console.error('Cache clearing error:', error);
await respond(`❌ Error clearing cache: ${error.message}`);
}
});
Du kannst das gleiche Muster für die Schaltflächen Backup und Status anwenden, indem du jede Schaltfläche mit dem entsprechenden API-Endpunkt oder der Befehlslogik verknüpfst.
// Handlers for other buttons
app.action('detailed_status_button', async ({ body, ack, respond }) => {
await ack();
const environmentId = body.actions[0].value;
// Implement detailed status check similar to the /detailed_status command
// ...
});
app.action('create_backup_button', async ({ body, ack, respond }) => {
await ack();
const environmentId = body.actions[0].value;
// Implement backup creation similar to the /create_backup command
// ...
});
Verwende eine Auswahlliste, um eine Website auszuwählen
Das Eingeben von Umgebungs-IDs macht keinen Spaß. Und du erwartest, dass sich jedes Teammitglied merken kann, welche ID zu welcher Umgebung gehört? Das ist unrealistisch.
Lass uns das Ganze intuitiver gestalten. Anstatt die Benutzer aufzufordern, /site_status [environment-id]
einzugeben, geben wir ihnen ein Slack-Dropdown, in dem sie eine Website aus einer Liste auswählen können. Sobald sie eine Auswahl getroffen haben, zeigt der Bot den Status an und fügt die gleichen Schaltflächen für Schnellaktionen hinzu, die wir bereits implementiert haben.
Um das zu tun, müssen wir
- Alle Websites von der Kinsta-API abrufen
- Die Umgebungen für jede Website abrufen
- Ein Dropdown-Menü mit diesen Optionen erstellen
- Die Auswahl des Nutzers verarbeiten und den Status der Website anzeigen
Hier ist der Befehl, der das Dropdown-Menü anzeigt:
app.command('/select_site', async ({ command, ack, say }) => {
await ack();
try {
// Get all sites
const response = await kinstaRequest('/sites');
if (response && response.company && response.company.sites) {
const sites = response.company.sites;
// Create options for each site
const options = [];
for (const site of sites) {
// Get environments for this site
const envResponse = await kinstaRequest(`/sites/${site.id}/environments`);
if (envResponse && envResponse.site && envResponse.site.environments) {
for (const env of envResponse.site.environments) {
options.push({
text: {
type: 'plain_text',
text: `${site.name} (${env.name})`
},
value: env.id
});
}
}
}
// Send message with dropdown
await web.chat.postMessage({
channel: command.channel_id,
text: 'Select a site to manage:',
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Select a site to manage:*'
},
accessory: {
type: 'static_select',
placeholder: {
type: 'plain_text',
text: 'Select a site'
},
options: options.slice(0, 100), // Slack has a limit of 100 options
action_id: 'site_selected'
}
}
]
});
} else {
await say('❌ Error retrieving sites. Please check your API credentials.');
}
} catch (error) {
console.error('Error:', error);
await say(`❌ Error retrieving sites: ${error.message}`);
}
});
Wenn ein Nutzer eine Website auswählt, verarbeiten wir das mit diesem Action-Handler:
// Handle the site selection
app.action('site_selected', async ({ body, ack, respond }) => {
await ack();
const environmentId = body.actions[0].selected_option.value;
const siteName = body.actions[0].selected_option.text.text;
// Get environment status
try {
const response = await kinstaRequest(`/sites/environments/${environmentId}`);
if (response && response.site && response.site.environments && response.site.environments.length > 0) {
const env = response.site.environments[0];
// Format the status message
let statusMessage = `*${siteName}* (ID: `${environmentId}`)nn${formatSiteStatus(env)}`;
// Send message with interactive buttons (similar to the site_status command)
// ...
} else {
await respond(`⚠️ No environment found with ID: `${environmentId}``);
}
} catch (error) {
console.error('Error:', error);
await respond(`❌ Error retrieving environment: ${error.message}`);
}
});
Jetzt, wo unser Bot mit einer Schaltfläche Aktionen auslösen und Websites aus einer Liste auswählen kann, müssen wir sicherstellen, dass wir nicht versehentlich riskante Aktionen ausführen.
Bestätigungsdialoge
Manche Aktionen sollten niemals versehentlich ausgeführt werden. Einen Cache zu löschen mag harmlos klingen, aber wenn du an einer Produktionsseite arbeitest, willst du das wahrscheinlich nicht mit einem einzigen Klick tun – vor allem, wenn du nur den Status der Seite überprüfen wolltest. Hier kommen die Slack-Modale (Dialoge) ins Spiel.
Anstatt den Cache sofort zu löschen, wenn du auf clear_cache_button
klickst, zeigen wir ein Bestätigungsmodal an. So geht’s:
app.action('clear_cache_button', async ({ body, ack, context }) => {
await ack();
const environmentId = body.actions[0].value;
// Open a confirmation dialog
try {
await web.views.open({
trigger_id: body.trigger_id,
view: {
type: 'modal',
callback_id: 'clear_cache_confirmation',
private_metadata: environmentId,
title: {
type: 'plain_text',
text: 'Confirm Cache Clearing'
},
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `Are you sure you want to clear the cache for environment `${environmentId}`?`
}
}
],
submit: {
type: 'plain_text',
text: 'Clear Cache'
},
close: {
type: 'plain_text',
text: 'Cancel'
}
}
});
} catch (error) {
console.error('Error opening confirmation dialog:', error);
}
});
Im obigen Code verwenden wir web.views.open()
, um ein Modal mit einem eindeutigen Titel, einer Warnmeldung und zwei Schaltflächen – Cache löschen und Abbrechen – zu starten, und speichern die environmentId
in private_metadata
, damit wir sie zur Verfügung haben, wenn der Nutzer auf Cache löschen klickt.
Sobald der Nutzer auf die Schaltfläche Cache löschen im Modal klickt, sendet Slack ein view_submission
Ereignis. So gehen wir damit um und fahren mit dem eigentlichen Vorgang fort:
// Handle the confirmation dialog submission
app.view('clear_cache_confirmation', async ({ ack, body, view }) => {
await ack();
const environmentId = view.private_metadata;
const userId = body.user.id;
// Find a DM channel with the user to respond to
const result = await web.conversations.open({
users: userId
});
const channel = result.channel.id;
await web.chat.postMessage({
channel,
text: `🔄 Clearing cache for environment `${environmentId}`...`
});
try {
// Call Kinsta API to clear cache
const response = await kinstaRequest(
`/sites/environments/${environmentId}/clear-cache`,
'POST'
);
if (response && response.operation_id) {
await web.chat.postMessage({
channel,
text: `✅ Cache clearing operation started! Operation ID: `${response.operation_id}``
});
} else {
await web.chat.postMessage({
channel,
text: '⚠️ Cache clearing request was sent, but no operation ID was returned.'
});
}
} catch (error) {
console.error('Cache clearing error:', error);
await web.chat.postMessage({
channel,
text: `❌ Error clearing cache: ${error.message}`
});
}
});
In diesem Code holen wir uns nach der Bestätigung des Nutzers die environmentId
von private_metadata
, öffnen eine private DM mit web.conversations.open()
, um die öffentlichen Kanäle nicht zu überladen, führen die API-Anfrage aus, um den Cache zu löschen, und geben je nach Ergebnis eine Erfolgs- oder Fehlermeldung aus.
Fortschrittsanzeigen
Einige Slack-Befehle lassen sich sofort ausführen, z. B. das Löschen eines Caches oder die Überprüfung eines Status. Aber andere? Nicht so sehr.
Das Erstellen eines Backups oder das Verteilen von Dateien kann mehrere Sekunden oder sogar Minuten dauern. Wenn dein Bot in dieser Zeit einfach nur stumm dasteht, könnten die Nutzer annehmen, dass etwas nicht funktioniert.
Slack bietet zwar keinen nativen Fortschrittsbalken, aber mit ein bisschen Kreativität können wir einen vortäuschen. Hier ist eine Hilfsfunktion, die eine Nachricht mit einem visuellen Fortschrittsbalken unter Verwendung von Block Kit aktualisiert:
async function updateProgress(channel, messageTs, text, percentage) {
// Create a progress bar
const barLength = 20;
const filledLength = Math.round(barLength * (percentage / 100));
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
await web.chat.update({
channel,
ts: messageTs,
text: `${text} [${percentage}%]`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `${text} [${percentage}%]n`${bar}``
}
}
]
});
}
Lass uns das in einen /create_backup
Befehl integrieren. Anstatt zu warten, bis der gesamte Vorgang abgeschlossen ist, bevor wir antworten, melden wir uns bei jedem Schritt beim Nutzer.
app.command('/create_backup', async ({ command, ack, say }) => {
await ack();
const args = command.text.split(' ');
const environmentId = args[0];
const tag = args.length > 1 ? args.slice(1).join(' ') : `Manual backup ${new Date().toISOString()}`;
if (!environmentId) {
await say('Please provide an environment ID. Usage: `/create_backup [environment-id] [optional-tag]`');
return;
}
// Post initial message and get its timestamp for updates
const initial = await say('🔄 Initiating backup...');
const messageTs = initial.ts;
try {
// Update progress to 10%
await updateProgress(command.channel_id, messageTs, '🔄 Creating backup...', 10);
// Call Kinsta API to create a backup
const response = await kinstaRequest(
`/sites/environments/${environmentId}/manual-backups`,
'POST',
{ tag }
);
if (response && response.operation_id) {
await updateProgress(command.channel_id, messageTs, '🔄 Backup in progress...', 30);
// Poll the operation status
let completed = false;
let percentage = 30;
while (!completed && percentage setTimeout(resolve, 3000));
// Check operation status
const statusResponse = await kinstaRequest(`/operations/${response.operation_id}`);
if (statusResponse && statusResponse.operation) {
const operation = statusResponse.operation;
if (operation.status === 'completed') {
completed = true;
percentage = 100;
} else if (operation.status === 'failed') {
await web.chat.update({
channel: command.channel_id,
ts: messageTs,
text: `❌ Backup failed! Error: ${operation.error || 'Unknown error'}`
});
return;
} else {
// Increment progress
percentage += 10;
if (percentage > 95) percentage = 95;
await updateProgress(
command.channel_id,
messageTs,
'🔄 Backup in progress...',
percentage
);
}
}
}
// Final update
await web.chat.update({
channel: command.channel_id,
ts: messageTs,
text: `✅ Backup completed successfully!`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `✅ Backup completed successfully!n*Tag:* ${tag}n*Operation ID:* `${response.operation_id}``
}
}
]
});
} else {
await web.chat.update({
channel: command.channel_id,
ts: messageTs,
text: '⚠️ Backup request was sent, but no operation ID was returned.'
});
}
} catch (error) {
console.error('Backup creation error:', error);
await web.chat.update({
channel: command.channel_id,
ts: messageTs,
text: `❌ Error creating backup: ${error.message}`
});
}
});
Erfolgs/Fehlschlags-Benachrichtigungen
Im Moment sendet dein Bot wahrscheinlich einfachen Text wie ✅ Erfolg oder ❌ Fehlgeschlagen zurück. Das funktioniert zwar, ist aber öde und hilft den Nutzern nicht dabei, zu verstehen, warum etwas erfolgreich war oder was sie tun sollen, wenn es fehlgeschlagen ist.
Das können wir ändern, indem wir Erfolgs- und Fehlermeldungen richtig formatieren und mit nützlichem Kontext, Vorschlägen und einer sauberen Formatierung versehen.
Füge diese Hilfsprogramme zu deinem utils.js
hinzu, damit du sie für alle Befehle wiederverwenden kannst:
function formatSuccessMessage(title, details = []) {
let message = `✅ *${title}*nn`;
if (details.length > 0) {
details.forEach(detail => {
message += `• ${detail.label}: ${detail.value}n`;
});
}
return message;
}
function formatErrorMessage(title, error, suggestions = []) {
let message = `❌ *${title}*nn`;
message += `*Error:* ${error}nn`;
if (suggestions.length > 0) {
message += '*Suggestions:*n';
suggestions.forEach(suggestion => {
message += `• ${suggestion}n`;
});
}
return message;
}
module.exports = {
connectToSite,
logCommand,
formatSuccessMessage,
formatErrorMessage
};
Diese Funktionen wandeln strukturierte Eingaben in Slack-freundliches Markdown mit Emoji, Labels und Zeilenumbrüchen um. Das ist viel einfacher, wenn du mitten in einem geschäftigen Slack-Thread scannst. Hier siehst du, wie das in einem echten Command Handler aussieht. Nehmen wir /clear_cache
als Beispiel:
app.command('/clear_cache', async ({ command, ack, say }) => {
await ack();
const environmentId = command.text.trim();
if (!environmentId) {
await say('Please provide an environment ID. Usage: `/clear_cache [environment-id]`');
return;
}
try {
await say('🔄 Processing...');
// Call Kinsta API to clear cache
const response = await kinstaRequest(
`/sites/environments/${environmentId}/clear-cache`,
'POST'
);
if (response && response.operation_id) {
const { formatSuccessMessage } = require('./utils');
await say(formatSuccessMessage('Cache Clearing Started', [
{ label: 'Environment ID', value: ``${environmentId}`` },
{ label: 'Operation ID', value: ``${response.operation_id}`` },
{ label: 'Status', value: 'In Progress' }
]));
} else {
const { formatErrorMessage } = require('./utils');
await say(formatErrorMessage(
'Cache Clearing Error',
'No operation ID returned',
[
'Check your environment ID',
'Verify your API credentials',
'Try again later'
]
));
}
} catch (error) {
console.error('Cache clearing error:', error);
const { formatErrorMessage } = require('./utils');
await say(formatErrorMessage(
'Cache Clearing Error',
error.message,
[
'Check your environment ID',
'Verify your API credentials',
'Try again later'
]
));
}
});
WordPress-Aufgaben mit geplanten Jobs automatisieren
Bis jetzt geschieht alles, was dein Slackbot tut, wenn jemand explizit einen Befehl auslöst. Aber nicht alles sollte davon abhängen, dass jemand daran denkt, es auszuführen.
Wie wäre es, wenn dein Bot jede Nacht automatisch ein Backup deiner Websites erstellen könnte? Oder wenn er jeden Morgen, bevor das Team aufwacht, überprüfen würde, ob eine Website ausgefallen ist.
Wir werden die Bibliothek node-schedule verwenden, um Aufgaben auf der Grundlage von Cron-Ausdrücken auszuführen. Installiere sie zuerst:
npm install node-schedule
Richte sie oben in deinem app.js
ein:
const schedule = require('node-schedule');
Wir brauchen auch eine Möglichkeit, aktive geplante Aufträge zu verfolgen, damit die Benutzer sie später auflisten oder abbrechen können:
const scheduledJobs = {};
Den Befehl „Aufgabe planen“ erstellen
Wir beginnen mit einem einfachen /schedule_task
Befehl, der einen Aufgabentyp (backup
, clear_cache
oder status_check
), die Umgebungs-ID und einen Cron-Ausdruck akzeptiert.
/schedule_task backup 12345 0 0 * * *
Damit wird ein tägliches Backup um Mitternacht geplant. Hier ist der vollständige Befehls-Handler:
app.command('/schedule_task', async ({ command, ack, say }) => {
await ack();
const args = command.text.split(' ');
if (args.length {
console.log(`Running scheduled ${taskType} for environment ${environmentId}`);
try {
switch (taskType) {
case 'backup':
await kinstaRequest(`/sites/environments/${environmentId}/manual-backups`, 'POST', {
tag: `Scheduled backup ${new Date().toISOString()}`
});
break;
case 'clear_cache':
await kinstaRequest(`/sites/environments/${environmentId}/clear-cache`, 'POST');
break;
case 'status_check':
const response = await kinstaRequest(`/sites/environments/${environmentId}`);
const env = response?.site?.environments?.[0];
if (env) {
console.log(`Status: ${env.display_name} is ${env.is_blocked ? 'blocked' : 'running'}`);
}
break;
}
} catch (err) {
console.error(`Scheduled ${taskType} failed for ${environmentId}:`, err.message);
}
});
scheduledJobs[jobId] = {
job,
taskType,
environmentId,
cronSchedule,
userId: command.user_id,
createdAt: new Date().toISOString()
};
await say(`✅ Scheduled task created!
*Task:* ${taskType}
*Environment:* `${environmentId}`
*Cron:* `${cronSchedule}`
*Job ID:* `${jobId}`
To cancel this task, run `/cancel_task ${jobId}``);
} catch (err) {
console.error('Error creating scheduled job:', err);
await say(`❌ Failed to create scheduled task: ${err.message}`);
}
});
Geplante Aufgaben stornieren
Wenn sich etwas ändert oder die Aufgabe nicht mehr benötigt wird, können Nutzer sie mit abbrechen:
/cancel_task
Hier ist die Umsetzung:
app.command('/cancel_task', async ({ command, ack, say }) => {
await ack();
const jobId = command.text.trim();
if (!scheduledJobs[jobId]) {
await say(`⚠️ No task found with ID: `${jobId}``);
return;
}
scheduledJobs[jobId].job.cancel();
delete scheduledJobs[jobId];
await say(`✅ Task `${jobId}` has been cancelled.`);
});
Auflistung aller geplanten Aufgaben
Wir können den Nutzern auch alle geplanten Aufgaben anzeigen:
app.command('/list_tasks', async ({ command, ack, say }) => {
await ack();
const tasks = Object.entries(scheduledJobs);
if (tasks.length === 0) {
await say('No scheduled tasks found.');
return;
}
let message = '*Scheduled Tasks:*nn';
for (const [jobId, job] of tasks) {
message += `• *Job ID:* `${jobId}`n`;
message += ` - Task: ${job.taskType}n`;
message += ` - Environment: `${job.environmentId}`n`;
message += ` - Cron: `${job.cronSchedule}`n`;
message += ` - Created by: nn`;
}
message += '_Use `/cancel_task [job_id]` to cancel a task._';
await say(message);
});
Das gibt deinem Slackbot ein ganz neues Maß an Autonomie. Backups, Cache-Löschungen und Statusüberprüfungen müssen nicht mehr von jemandem erledigt werden. Sie laufen einfach leise, zuverlässig und pünktlich ab.
Wiederkehrende Wartung
Manchmal möchtest du eine Gruppe von Wartungsaufgaben in regelmäßigen Abständen durchführen, z. B. wöchentliche Backups und Cache-Löschungen am Sonntagabend. Hier kommen die Wartungsfenster ins Spiel.
Ein Wartungsfenster ist ein geplanter Zeitblock, in dem der Bot automatisch vordefinierte Aufgaben ausführt, wie zum Beispiel:
- Erstellen eines Backups
- Den Cache leeren
- Start- und Abschlussbenachrichtigungen senden
Das Format ist einfach:
/maintenance_window [environment_id] [day_of_week] [hour] [duration_hours]
Zum Beispiel:
/maintenance_window 12345 Sunday 2 3
Das bedeutet, dass jeden Sonntag um 2 Uhr morgens 3 Stunden lang Wartungsaufgaben ausgeführt werden. Hier ist die vollständige Umsetzung:
// Add a command to create a maintenance window
app.command('/maintenance_window', async ({ command, ack, say }) => {
await ack();
// Expected format: environment_id day_of_week hour duration
// Example: /maintenance_window 12345 Sunday 2 3
const args = command.text.split(' ');
if (args.length < 4) {
await say('Please provide all required parameters. Usage: `/maintenance_window [environment_id] [day_of_week] [hour] [duration_hours]`');
return;
}
const [environmentId, dayOfWeek, hour, duration] = args;
// Validate inputs
const validDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
if (!validDays.includes(dayOfWeek)) {
await say(`Invalid day of week. Please choose from: ${validDays.join(', ')}`);
return;
}
const hourInt = parseInt(hour, 10);
if (isNaN(hourInt) || hourInt 23) {
await say('Hour must be a number between 0 and 23.');
return;
}
const durationInt = parseInt(duration, 10);
if (isNaN(durationInt) || durationInt 12) {
await say('Duration must be a number between 1 and 12 hours.');
return;
}
// Convert day of week to cron format
const dayMap = {
'Sunday': 0,
'Monday': 1,
'Tuesday': 2,
'Wednesday': 3,
'Thursday': 4,
'Friday': 5,
'Saturday': 6
};
const cronDay = dayMap[dayOfWeek];
// Create cron schedule for the start of the maintenance window
const cronSchedule = `0 ${hourInt} * * ${cronDay}`;
// Generate a unique job ID
const jobId = `maintenance_${environmentId}_${Date.now()}`;
// Schedule the job
try {
const job = schedule.scheduleJob(cronSchedule, async function() {
// Start of maintenance window
await web.chat.postMessage({
channel: command.channel_id,
text: `🔧 *Maintenance Window Started*n*Environment:* `${environmentId}`n*Duration:* ${durationInt} hoursnnAutomatic maintenance tasks are now running.`
});
// Perform maintenance tasks
try {
// 1. Create a backup
const backupResponse = await kinstaRequest(
`/sites/environments/${environmentId}/manual-backups`,
'POST',
{ tag: `Maintenance backup ${new Date().toISOString()}` }
);
if (backupResponse && backupResponse.operation_id) {
await web.chat.postMessage({
channel: command.channel_id,
text: `✅ Maintenance backup created. Operation ID: `${backupResponse.operation_id}``
});
}
// 2. Clear cache
const cacheResponse = await kinstaRequest(
`/sites/environments/${environmentId}/clear-cache`,
'POST'
);
if (cacheResponse && cacheResponse.operation_id) {
await web.chat.postMessage({
channel: command.channel_id,
text: `✅ Cache cleared. Operation ID: `${cacheResponse.operation_id}``
});
}
// 3. Schedule end of maintenance window notification
setTimeout(async () => {
await web.chat.postMessage({
channel: command.channel_id,
text: `✅ *Maintenance Window Completed*n*Environment:* `${environmentId}`nnAll maintenance tasks have been completed.`
});
}, durationInt * 60 * 60 * 1000); // Convert hours to milliseconds
} catch (error) {
console.error('Maintenance tasks error:', error);
await web.chat.postMessage({
channel: command.channel_id,
text: `❌ Error during maintenance: ${error.message}`
});
}
});
// Store the job for later cancellation
scheduledJobs[jobId] = {
job,
taskType: 'maintenance',
environmentId,
cronSchedule,
dayOfWeek,
hour: hourInt,
duration: durationInt,
userId: command.user_id,
createdAt: new Date().toISOString()
};
await say(`✅ Maintenance window scheduled!
*Environment:* `${environmentId}`
*Schedule:* Every ${dayOfWeek} at ${hourInt}:00 for ${durationInt} hours
*Job ID:* `${jobId}`
To cancel this maintenance window, use `/cancel_task ${jobId}``);
} catch (error) {
console.error('Error scheduling maintenance window:', error);
await say(`❌ Error scheduling maintenance window: ${error.message}`);
}
});
Automatisierte Berichte
Du willst nicht jeden Montag aufwachen und dich fragen, ob deine WordPress-Website gesichert wurde oder ob sie seit Stunden nicht erreichbar ist. Mit automatisierten Berichten kann dein Slack-Bot dir und deinem Team nach einem bestimmten Zeitplan einen schnellen Überblick über die Leistung geben.
Diese Art von Berichten ist ideal, um Dinge im Auge zu behalten wie:
- Den aktuellen Status der Website
- Backup-Aktivitäten in den letzten 7 Tagen
- PHP-Version und primäre Domain
- Alle roten Fahnen, wie blockierte Umgebungen oder fehlende Backups
Lass uns einen /schedule_report
Befehl erstellen, der dies automatisiert.
// Add a command to schedule weekly reporting
app.command('/schedule_report', async ({ command, ack, say }) => {
await ack();
// Expected format: environment_id day_of_week hour
// Example: /schedule_report 12345 Monday 9
const args = command.text.split(' ');
if (args.length < 3) {
await say('Please provide all required parameters. Usage: `/schedule_report [environment_id] [day_of_week] [hour]`');
return;
}
const [environmentId, dayOfWeek, hour] = args;
// Validate inputs
const validDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
if (!validDays.includes(dayOfWeek)) {
await say(`Invalid day of week. Please choose from: ${validDays.join(', ')}`);
return;
}
const hourInt = parseInt(hour, 10);
if (isNaN(hourInt) || hourInt 23) {
await say('Hour must be a number between 0 and 23.');
return;
}
// Convert day of week to cron format
const dayMap = {
'Sunday': 0,
'Monday': 1,
'Tuesday': 2,
'Wednesday': 3,
'Thursday': 4,
'Friday': 5,
'Saturday': 6
};
const cronDay = dayMap[dayOfWeek];
// Create cron schedule for the report
const cronSchedule = `0 ${hourInt} * * ${cronDay}`;
// Generate a unique job ID
const jobId = `report_${environmentId}_${Date.now()}`;
// Schedule the job
try {
const job = schedule.scheduleJob(cronSchedule, async function() {
// Generate and send the report
await generateWeeklyReport(environmentId, command.channel_id);
});
// Store the job for later cancellation
scheduledJobs[jobId] = {
job,
taskType: 'report',
environmentId,
cronSchedule,
dayOfWeek,
hour: hourInt,
userId: command.user_id,
createdAt: new Date().toISOString()
};
await say(`✅ Weekly report scheduled!
*Environment:* `${environmentId}`
*Schedule:* Every ${dayOfWeek} at ${hourInt}:00
*Job ID:* `${jobId}`
To cancel this report, use `/cancel_task ${jobId}``);
} catch (error) {
console.error('Error scheduling report:', error);
await say(`❌ Error scheduling report: ${error.message}`);
}
});
// Function to generate weekly report
async function generateWeeklyReport(environmentId, channelId) {
try {
// Get environment details
const response = await kinstaRequest(`/sites/environments/${environmentId}`);
if (!response || !response.site || !response.site.environments || !response.site.environments.length) {
await web.chat.postMessage({
channel: channelId,
text: `⚠️ Weekly Report Error: No environment found with ID: `${environmentId}``
});
return;
}
const env = response.site.environments[0];
// Get backups for the past week
const backupsResponse = await kinstaRequest(`/sites/environments/${environmentId}/backups`);
let backupsCount = 0;
let latestBackup = null;
if (backupsResponse && backupsResponse.environment && backupsResponse.environment.backups) {
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const recentBackups = backupsResponse.environment.backups.filter(backup => {
const backupDate = new Date(backup.created_at);
return backupDate >= oneWeekAgo;
});
backupsCount = recentBackups.length;
if (recentBackups.length > 0) {
latestBackup = recentBackups.sort((a, b) => b.created_at - a.created_at)[0];
}
}
// Get environment status
const statusEmoji = env.is_blocked ? '🔴' : '🟢';
const statusText = env.is_blocked ? 'Blocked' : 'Running';
// Create report message
const reportDate = new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
const reportMessage = `📊 *Weekly Report - ${reportDate}*
*Site:* ${env.display_name}
*Environment ID:* `${environmentId}`
*Status Summary:*
• Current Status: ${statusEmoji} ${statusText}
• PHP Version: ${env.container_info?.php_engine_version || 'Unknown'}
• Primary Domain: ${env.primaryDomain?.name || env.domains?.[0]?.name || 'N/A'}
*Backup Summary:*
• Total Backups (Last 7 Days): ${backupsCount}
• Latest Backup: ${latestBackup ? new Date(latestBackup.created_at).toLocaleString() : 'N/A'}
• Latest Backup Type: ${latestBackup ? latestBackup.type : 'N/A'}
*Recommendations:*
• ${backupsCount === 0 ? '⚠️ No recent backups found. Consider creating a manual backup.' : '✅ Regular backups are being created.'}
• ${env.is_blocked ? '⚠️ Site is currently blocked. Check for issues.' : '✅ Site is running normally.'}
_This is an automated report. For detailed information, use the `/site_status ${environmentId}` command._`;
await web.chat.postMessage({
channel: channelId,
text: reportMessage
});
} catch (error) {
console.error('Report generation error:', error);
await web.chat.postMessage({
channel: channelId,
text: `❌ Error generating weekly report: ${error.message}`
});
}
}
Fehlerbehandlung und Monitoring
Sobald dein Bot echte Operationen durchführt, z. B. Umgebungen ändert oder geplante Aufgaben auslöst, brauchst du mehr als console.log()
, um zu verfolgen, was hinter den Kulissen passiert.
Lass uns das in einwandfreie, wartbare Ebenen aufteilen:
Strukturiertes Logging mit Winston
Anstatt Protokolle auf der Konsole auszugeben, kannst du mit winston
strukturierte Logs zu Dateien und optional an Dienste wie Loggly oder Datadog senden. Installiere es mit dem unten stehenden Befehl:
npm install winston
Als nächstes richtest du logger.js
ein:
const winston = require('winston');
const fs = require('fs');
const path = require('path');
const logsDir = path.join(__dirname, '../logs');
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
defaultMeta: { service: 'wordpress-slack-bot' },
transports: [
new winston.transports.Console({ format: winston.format.simple() }),
new winston.transports.File({ filename: path.join(logsDir, 'error.log'), level: 'error' }),
new winston.transports.File({ filename: path.join(logsDir, 'combined.log') })
]
});
module.exports = logger;
Ersetze dann in deinem app.js
alle console.log
oder console.error
Aufrufe durch:
const logger = require('./logger');
logger.info('Cache clear initiated', { userId: command.user_id });
logger.error('API failure', { error: err.message });
Warnmeldungen an Administratoren über Slack senden
Du hast bereits die ADMIN_USERS
env-Variable. Nutze sie, um dein Team direkt in Slack zu benachrichtigen, wenn etwas Kritisches fehlschlägt:
async function alertAdmins(message, metadata = {}) {
for (const userId of ADMIN_USERS) {
const dm = await web.conversations.open({ users: userId });
const channel = dm.channel.id;
let alert = `🚨 *${message}*n`;
for (const [key, value] of Object.entries(metadata)) {
alert += `• *${key}:* ${value}n`;
}
await web.chat.postMessage({ channel, text: alert });
}
}
Verwende sie wie folgt:
await alertAdmins('Backup Failed', {
environmentId,
error: error.message,
user: ``
});
Verfolge die Leistung mit grundlegenden Metriken
Wenn du nur sehen willst, wie gut dein Bot funktioniert, musst du nicht gleich den ganzen Prometheus einsetzen. Behalte ein einfaches Leistungsobjekt:
const metrics = {
apiCalls: 0,
errors: 0,
commands: 0,
totalTime: 0,
get avgResponseTime() {
return this.apiCalls === 0 ? 0 : this.totalTime / this.apiCalls;
}
};
Aktualisiere es in deinem kinstaRequest()
helper:
const start = Date.now();
try {
metrics.apiCalls++;
const res = await fetch(...);
return await res.json();
} catch (err) {
metrics.errors++;
throw err;
} finally {
metrics.totalTime += Date.now() - start;
}
Ruf es mit einem Befehl wie /bot_performance
auf:
app.command('/bot_performance', async ({ command, ack, say }) => {
await ack();
if (!ADMIN_USERS.includes(command.user_id)) {
return await say('⛔ Not authorized.');
}
const msg = `📊 *Bot Metrics*
• API Calls: ${metrics.apiCalls}
• Errors: ${metrics.errors}
• Avg Response Time: ${metrics.avgResponseTime.toFixed(2)}ms
• Commands Run: ${metrics.commands}`;
await say(msg);
});
Optional: Definiere Wiederherstellungsschritte
Wenn du eine Wiederherstellungslogik implementieren willst (z.B. das erneute Löschen des Caches über SSH), erstelle einfach einen Helfer wie:
async function attemptRecovery(environmentId, issue) {
logger.warn('Attempting recovery', { environmentId, issue });
if (issue === 'cache_clear_failure') {
// fallback logic here
}
// Return a recovery status object
return { success: true, message: 'Fallback ran.' };
}
Halte sie aus deiner Hauptbefehlslogik heraus, es sei denn, es handelt sich um einen kritischen Pfad. In vielen Fällen ist es besser, den Fehler zu protokollieren, die Administratoren zu alarmieren und die Menschen entscheiden zu lassen, was zu tun ist.
Einsetzen und Verwalten deines Slackbots
Sobald dein Bot vollständig ist, solltest du ihn in einer Produktionsumgebung einsetzen, in der er rund um die Uhr laufen kann.
Sevalla von Kinsta ist ein hervorragender Ort, um Bots wie diesen zu hosten. Es unterstützt Node.js-Apps, Umgebungsvariablen, Logging und skalierbare Bereitstellungen von Haus aus.
Alternativ kannst du deinen Bot mit Docker containerisieren oder ihn auf einer beliebigen Cloud-Plattform bereitstellen, die Node.js und Hintergrunddienste unterstützt.
Hier sind ein paar Dinge, die du beachten solltest, bevor du in Betrieb gehst:
- Verwende Umgebungsvariablen für alle Geheimnisse (Slack-Tokens, Kinsta-API-Schlüssel, SSH-Schlüssel).
- Richte ein Logging und eine Überwachung der Betriebszeit ein, damit du weißt, wenn etwas nicht funktioniert.
- Lasse deinen Bot mit einem Prozessmanager wie PM2 oder der Docker-Richtlinie
restart: always
laufen, damit er auch nach Abstürzen oder Neustarts weiterläuft. - Bewahre deine SSH-Schlüssel sicher auf, besonders wenn du sie für die Automatisierung verwendest.
Zusammenfassung
Du hast deinen Slackbot jetzt von einem einfachen Befehls-Handler zu einem mächtigen Werkzeug mit echter Interaktivität, geplanter Automatisierung und solider Überwachung gemacht. Diese Funktionen machen deinen Bot nützlicher, zuverlässiger und viel angenehmer zu bedienen, besonders für Teams, die mehrere WordPress-Websites verwalten.
In Kombination mit der leistungsstarken Kinsta-API und dem stressfreien Hosting von Kinsta hast du ein skalierbares und zuverlässiges System.