الجواب المختصر. أضف mysql2 إلى بوت discord.js الخاص بك، وأنشئ مجمّع اتصالات واحدًا من متغيرات البيئة عند بدء البوت، ونفّذ كل استعلام من خلاله باستخدام العناصر النائبة ?. في PlayNHost تتضمن كل باقة بوت قاعدة بيانات MySQL واحدة: أنشئها في صفحة قواعد البيانات، والصق بياناتها في لوحة البيئة، فتعمل الشيفرة أدناه كما هي. تقبل القاعدة الاتصالات من خوادم PlayNHost الخاصة بك ومن phpMyAdmin، ولا تقبلها من حاسوبك الشخصي. اختبرنا هذه الشيفرة في 6 نوفمبر 2026 مع discord.js 14.28.1 وmysql2 3.24.6 وNode.js 22، على MariaDB 10.11 بحساب قاعدة بيانات مضبوط مثل حسابنا (20 اتصالًا، وحد 120 ثانية للاستعلام): التشغيل، والجدول، ومعالج /warn، لكن دون تسجيل دخول فعلي إلى Discord.
افتح خادم البوت الخاص بك في اللوحة وانقر على قواعد البيانات.
انقر على قاعدة بيانات جديدة. حقل التسمية اختياري؛ ويعرض الحقل ستُنشأ باسم الاسم الكامل. انقر على إنشاء قاعدة بيانات.
تُفتح صفحة قاعدة البيانات الجديدة (وفي وقت لاحق انقر عليها ضمن قواعد بياناتك). تعرض بطاقة الاتصال كلاً من المضيف والمنفذ وقاعدة البيانات واسم المستخدم وكلمة المرور. وتحت إعداد جاهز يتضمن إعداد لـ مقطع Node.js (mysql2) بالقيم نفسها معبّأة مسبقًا.
2. ضع البيانات في البيئة
افتح صفحة بوت، ثم لوحة البيئة، وانقر على لصق ملف .env.
الصق هذه الأسطر الخمسة بالقيم المأخوذة من بطاقة الاتصال:
يحصل DB_PASSWORD على شارة سر: يُحفظ للكتابة فقط ولا يُعرض مرة أخرى. انقر على استيراد 5 متغيرات.
استخدم المنفذ الحقيقي من بطاقة الاتصال. إبقاء كلمة المرور خارج شيفرتك يعني ألا تصل أبدًا إلى ملف أو إلى مستودع Git. المزيد عن هذه اللوحة: كيف تضبط متغيرات البيئة والأسرار لبوت Discord.
const { Client, Events, GatewayIntentBits, InteractionContextType, MessageFlags, REST, Routes, SlashCommandBuilder } = require('discord.js');
const mysql = require('mysql2/promise');
// One pool for the whole bot. The values come from the Environment pane.
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 5,
});
const commands = [
new SlashCommandBuilder()
.setName('warn')
.setDescription('Warn a member and count their warnings')
.setContexts(InteractionContextType.Guild)
.addUserOption((o) => o.setName('user').setDescription('Who to warn').setRequired(true))
.addStringOption((o) => o.setName('reason').setDescription('Why').setRequired(true).setMaxLength(200))
.toJSON(),
];
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, async () => {
await new REST().setToken(process.env.DISCORD_TOKEN).put(Routes.applicationCommands(client.user.id), { body: commands });
console.log(`Logged in as ${client.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand() || interaction.commandName !== 'warn' || !interaction.guildId) return;
const user = interaction.options.getUser('user', true);
const reason = interaction.options.getString('reason', true);
try {
await pool.execute('INSERT INTO warnings (guild_id, user_id, reason) VALUES (?, ?, ?)', [interaction.guildId, user.id, reason]);
const [rows] = await pool.execute('SELECT COUNT(*) AS total FROM warnings WHERE guild_id = ? AND user_id = ?', [interaction.guildId, user.id]);
await interaction.reply(`${user} has ${rows[0].total} warning(s).`);
} catch (err) {
console.error(err);
if (!interaction.replied) await interaction.reply({ content: 'Could not save the warning. Try again later.', flags: MessageFlags.Ephemeral }).catch(console.error);
}
});
async function main() {
await pool.query(`CREATE TABLE IF NOT EXISTS warnings (
id INT AUTO_INCREMENT PRIMARY KEY,
guild_id VARCHAR(20) NOT NULL,
user_id VARCHAR(20) NOT NULL,
reason VARCHAR(200) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY guild_user (guild_id, user_id)
)`);
console.log('Database ready');
await client.login(process.env.DISCORD_TOKEN);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
ماذا تفعل الشيفرة: عند بدء البوت تنشئ الجدول warnings إذا كان مفقودًا، وتطبع Database ready، ثم تسجّل الدخول. يحفظ /warn تحذيرًا ويردّ بإجمالي تحذيرات العضو. يعطي discord.js معرّفات Discord كسلاسل نصية لأنها أكبر من أن يحملها رقم JavaScript، ولذلك تُحفظ كنص. ترسل العناصر النائبة ? نص العضو إلى قاعدة البيانات منفصلًا عن SQL، فيُحفظ سبب مثل '); DROP TABLE warnings; -- كنص عادي.
سطران يُبقيان البوت حيًا. يمنع setContexts(InteractionContextType.Guild) والتحقق !interaction.guildId استخدام /warn في رسالة خاصة، حيث لا يوجد خادم يُحفظ التحذير له. ويردّ الأمر try/catch برسالة خاصة قصيرة عند فشل استعلام. وبدونه يمرّر discord.js الخطأ إلى حدثه error، وإذا لم يكن هناك من يستمع إليه يوقف Node.js البوت. في اختبارنا حذفنا الجدول ونفّذنا /warn: سجّل البوت ER_NO_SUCH_TABLE، وردّ بـ Could not save the warning. Try again later.، واستمر في العمل.
4. شغّل البوت
ارفع index.js وpackage.json في الملفات. لا ترفع node_modules.
اضغط تشغيل وافتح وحدة التحكم. يثبّت التشغيل الحزم، ثم يعرض [PlayNHost] Starting your bot وDatabase ready وLogged in as ....
في Discord اكتب /warn. لعرض الصفوف افتح صفحة قاعدة البيانات وانقر على فتح في phpMyAdmin.
حدود تهم المجمّع الخاص بك
20 اتصالًا في الوقت نفسه لكل مستخدم قاعدة بيانات. تُحسب كل عملية تستخدم بيانات الدخول نفسها: بوتك، ونسخة ثانية منه، ولوحة تحكم. يُرفض الاتصال 21 بالخطأ ER_USER_LIMIT_REACHED. في اختبارنا فشل مجمّع بإعداد connectionLimit: 30 بهذا الخطأ بمجرد تشغيل 30 استعلامًا معًا. أما مجمّع من 5 فلا يفتح أكثر من 5: تنتظر الاستعلامات الزائدة اتصالًا حرًا.
يُوقَف أي استعلام يعمل أكثر من 120 ثانية. ينهيه MariaDB بالخطأ 1969، Query execution was interrupted (max_statement_time exceeded). في اختبارنا حدث ذلك بعد 120.03 ثانية. تستغرق استعلامات البوت عادةً أجزاء من الثانية، لذا لا يتأثر بهذا إلا استعلام خرج عن السيطرة.
500 MB لكل قاعدة بيانات. تعرض صفحة قواعد البيانات المساحة المستخدمة. عند 500 MB تتوقف عمليات الكتابة إلى أن تحذف صفوفًا.
النسخ الاحتياطية للخادم لا تتضمن قاعدة البيانات. صدّرها من phpMyAdmin من وقت لآخر.
الأخطاء ومعناها
هذه هي الرسائل الدقيقة التي أعطاها mysql2 في اختبارنا:
ER_ACCESS_DENIED_ERROR ... Access denied for user: قيمة DB_USER أو DB_PASSWORD خاطئة. بعد إعادة تعيين كلمة المرور في بطاقة الاتصال تتوقف كلمة المرور القديمة عن العمل فورًا: استبدل DB_PASSWORD وأعد تشغيل البوت.
ECONNREFUSED: لم يقبل أي شيء الاتصال بهذا المضيف وهذا المنفذ. تحقق من DB_HOST وDB_PORT. وغياب DB_HOST ينتهي هنا أيضًا، لأن mysql2 يتصل حينها بـ localhost حيث لا تعمل أي قاعدة بيانات: المتغير مفقود من البيئة، أو لم يُعاد تشغيل البوت بعد إضافته.
ENOTFOUND: اسم DB_HOST مكتوب بشكل خاطئ، فالاسم غير موجود.
ETIMEDOUT: لم يرد شيء خلال 10 ثوانٍ، وهي مهلة الانتظار الافتراضية في mysql2. أيًّا كانت الرسالة، فالاتصال من حاسوبك الشخصي يفشل: لا تقبل القاعدة إلا الاتصالات من خوادم PlayNHost ومن phpMyAdmin.
ER_USER_LIMIT_REACHED ... has exceeded the 'max_user_connections' resource (current value: 20): عدد الاتصالات المفتوحة كبير جدًا، راجع الحدود أعلاه. وغالبًا يكون السبب اتصالًا جديدًا لكل أمر لا يُغلق أبدًا، أو نسخة ثانية من البوت.
بلغ تثبيت discord.js وmysql2 من الصفر ذروة قدرها 147 MB من الذاكرة (دون احتساب ذاكرة الملفات المؤقتة) في اختبارنا، داخل حاوية محدودة بـ 512 MB مثل أصغر باقة بوت لدينا. ومنذ بدء التشغيل حتى رفض Discord رمز الاختبار الخاص بنا، استخدم البوت 31 MB على الأكثر؛ فقد أنشأ الجدول لكنه لم يسجّل الدخول قط. أما البوت الذي سجّل الدخول وهو موجود في خوادم كثيرة فيستخدم أكثر: كم RAM يحتاج بوت Discord؟
نستخدم ملفات تعريف الارتباط لإبقاء الموقع يعمل، ولفهم كيف يُستخدم، ولقياس حملاتنا التسويقية. يمكنك قبول الكل أو قبول ملفات تعريف الارتباط الأساسية فقط أو الاختيار لكل فئة.نستخدم ملفات تعريف الارتباط لإبقاء الموقع يعمل، ولفهم كيف يُستخدم، ولقياس حملاتنا التسويقية. وهي مفعّلة هنا افتراضيًا. يمكنك إيقاف أي منها لا تريده.اقرأ سياسة الخصوصية