ساخت ربات تلگرامی برای یادگیری زبان آلمانی: از ۴۰ نود به ۲۰۰ خط کد
چطور یک ربات یادگیری زبان ساختم که درسهای تولیدشده با هوش مصنوعی، فلشکارت با تکرار فاصلهدار، و تلفظ صوتی دارد — و چند درس سخت هم درباره معماری n8n یاد گرفتم.
رویا چی بود؟
میخواستم یه ربات تلگرامی بسازم که کمکم کنه آلمانی یاد بگیرم. ولی نه هر رباتی. یه ربات که هر روز مقالههای خبری متناسب با سطحم (از A1 تا C1) تولید کنه، بذاره روی کلمات ناآشنا بزنم و فوری توضیحش رو ببینم، خودکار از سوالهام فلشکارت بسازه، از سیستم لایتنر برای حفظ کردن کارآمد استفاده کنه، کلمات رو بلند بخونه که تلفظ درست رو بشنوم، و اگه تمرین نکرده بودم یادآوری هوشمند بفرسته.
خیلی ساده به نظر میرسید، نه؟
اولین تلاش: انفجار نودها
اولین رویکردم «روش n8n» بود — برای همه چیز یه نود بذار. یه نود برای چک کردن اینکه کاربر وجود داره. یکی دیگه برای چک کردن سطحش. یکی دیگه برای چک کردن اینکه جلسه فعال داره. یکی دیگه برای چک کردن کش خبرها. یکی دیگه برای...
متوجه شدی دیگه.
بعد از ساختن بیش از ۴۰ نود، یه نمودار اسپاگتی داشتم که هر سرآشپز ایتالیایی رو به گریه میانداخت. بوم ورکفلو شبیه نقشه مترو یه شهر بود که هیچوقت اسم برنامهریزی شهری رو نشنیده بود.
مشکلات واضح بودن:
- دیباگ کردن کابوس بود. کدوم شاخه داشت اجرا میشد؟ چرا اون نود رو رد کرد؟
- اضافه کردن قابلیت جدید یعنی سیمکشی مجدد همه چیز. میخوای دستور `/stats` اضافه کنی؟ موفق باشی پیدا کنی کجا وصلش کنی.
- پیامها همه جا پخش بودن. نصف نودهای ارسال تلگرام فرمتبندیهای کمی متفاوت داشتن.
نقطه عطف: معماری کد-محور
بعد یه بینشی بهم دست داد: n8n برای I/O عالیه، ولی برای منطق افتضاحه.
راهحل ساده بود — همه منطق مسیریابی رو بذار توی یه نود کد جاوااسکریپت. بذار n8n کاری رو بکنه که توش خوبه: وصل شدن به تلگرام، MySQL، OpenAI، و MinIO برای کش کردن صدا.
[تلگرام] → [دریافت کاربر] → [روتر JS] → [سوییچ] → [خروجیهای مختلف]
همین. یه روتر برای حکومت بر همه.
روتر جاوااسکریپت حدود ۲۰۰ خط کده. همه دستورات رو مدیریت میکنه (`/start`، `/learn`، `/practice`، `/next`، `/level`، `/stats`، `/reset`، `/help`)، دکمههای callback برای انتخاب سطح و جواب فلشکارت، جریان جستجوی کلمه، درخواستهای text-to-speech، مدیریت جلسه، و انتخاب زبان پیامها.
هر تصمیم مسیریابی توی یه جاست. میخوای بدونی وقتی کاربر روی ✅ فلشکارت کلیک میکنه چی میشه؟ `card_correct` رو توی کد سرچ کن. تمام.
مشکلاتی که باهاشون کلنجار رفتم
باگ «کاربر جدید»
سه ساعت، کاربرای جدید `/start` میفرستادن و... هیچی. ورکفلو همینجوری متوقف میشد.
مقصر کی بود؟ MySQL برای کاربرایی که هنوز وجود ندارن صفر ردیف برمیگردونه. و n8n به طور پیشفرض وقتی یه نود دادهای برنمیگردونه، اجرای ورکفلو رو متوقف میکنه.
راهحل یه خط بود: alwaysOutputData: true توی تنظیمات نود Get User.
ساعتها دیباگ. یه چکباکس.
مشکل کانتکست اشتباه
ربات توضیح کلمه تولید میکرد وقتی باید خبر تولید میکرد. یا برعکس.
جریان این بود: روتر تصمیم میگیره خبر تولید کنه، بعد پیام Loading بفرسته («⌛️ در حال تولید...»)، بعد AI محتوا تولید کنه.
باگ چی بود؟ نود AI Prompt داشت $json.ai_type رو میخوند — ولی $json اون لحظه پاسخ تلگرام از پیام Loading بود، نه خروجی روتر.
راهحل: $('Router').first().json.ai_type
این مقدار خجالتآوری از وقتم رو گرفت.
کابوسهای انکودینگ SQL
آلمانی حروف خاص داره: ü، ö، ä، ß. و وقتی سعی میکنی اینا رو با یه رشته کوئری ساختهشده پویا INSERT کنی توی MySQL، اوضاع... جالب میشه.
Invalid JSON text: "Invalid encoding in string." at position 23
راهحل یه تابع escape درست بود که هم بکاسلشها و هم تککوتیشنها رو مدیریت کنه.
سردردهای کش TTS
وقتی تلفظ صوتی اضافه کردم، نسخه اول فایلهای صوتی رو با خود متن کش میکرد. این برای کلمات کوتاه خوب کار میکرد، ولی مقالات خبری پاراگرافهای طولانی با کاراکترهای خاص دارن. اسم فایلهای MinIO به هم ریخت.
راهحل این بود که با شناسههای دیتابیس کش کنم: cards/123.mp3 برای فلشکارتها، vocab/456.mp3 برای جستجوی کلمات، و news/789_0.mp3 برای مقالات خبری (شناسه جلسه بهعلاوه اندکس). تمیز، قابل پیشبینی، و بدون مشکل انکودینگ.
قابلیتهایی که جواب دادن
تلفظ صوتی
هر فلشکارت، توضیح کلمه، و مقاله خبری یه دکمه 🔊 داره. روش بزن و کلمه یا جمله رو با آلمانی طبیعی میشنوی. صدا با موتور TTS اوپنایآی تولید میشه و توی MinIO کش میشه، پس همون کلمه هیچوقت دوباره نیاز به تولید نداره. عالی برای یادگیری تلفظ درست.
پیامهای دوزبانه
ربات با سطحت سازگار میشه. مبتدیها در سطح A1 و A2 پیامهای انگلیسی دوستانه میگیرن. یادگیرندههای متوسط و پیشرفته در سطح B1، B2، و C1 غوطهوری کامل آلمانی میگیرن. حتی پرامپتهای فلشکارت عوض میشه: «Do you know the meaning?» میشه «Weißt du die Bedeutung?»
کش کردن واژگان
وقتی هر کاربری درباره «Wanderlust» سوال میکنه، AI توضیحش میده. اون توضیح کش میشه. وقتی کاربر بعدی درباره «Wanderlust» سوال میکنه، کلاً از فراخوانی AI صرفنظر میکنیم و از کش سرو میکنیم. همون سطح، همون توضیح، صفر هزینه API. با گذشت زمان، یه پایگاه داده واژگان مشترک بین همه کاربرا ساخته میشه.
سیستم لایتنر
فلشکارتها از تکرار فاصلهدار استفاده میکنن. کارتهای جدید یا سخت توی جعبه ۱ فردا مرور میشن. کارتهای جعبه ۲ بعد از ۲ روز. جعبه ۳ بعد از ۴ روز. جعبه ۴ بعد از ۸ روز. جعبه ۵ بعد از ۱۶ روز.
درست جواب دادی؟ میره جعبه بالاتر. اشتباه جواب دادی؟ برمیگرده جعبه ۱. این از نظر علمی ثابت شده کارآمدترین روش برای حفظ کردن چیزها در بلندمدت.
یادآوریهای روزانه هوشمند
یادآوری فقط اسپم نمیکنه. چک میکنه که آیا جلسه یادگیری امروز رو انجام دادی و آیا فلشکارتی تمرین کردی. اگه هر دو انجام شده، یادآوری نمیاد. اگه یکی مونده، فقط همون کار رو یادآوری میکنه. اگه هر دو مونده، آروم برای هر دو تلنگر میزنه.
معماری نهایی

تعداد نودها: حدود ۳۶ تا (از بیش از ۴۰ کمتر شد)
خطوط منطق مسیریابی: حدود ۲۰۰ تا (همه متمرکز)
زمان اضافه کردن دستور جدید: تقریباً ۵ دقیقه
درسهایی که یاد گرفتم
- از n8n برای I/O استفاده کن، نه منطق. نودهای کد برای مسیریابی پیچیده دوستی.
- یه منبع حقیقت داشته باش. همه پیامها توی یه آبجکت. همه مسیریابی توی یه تابع.
- حواست به کانتکست باشه. متغیر
$jsonبعد از هر نود عوض میشه. وقتی به دادهشون نیاز داری به نودهای خاص رفرنس بده. - با کاربرای کاملاً جدید تست کن. ایج کیس نتیجه خالی قطعاً گازت میگیره.
- خروجیهای Switch رو بشمر. اتصالات بصری میتونن گمراهکننده باشن. اندکسهای آرایه حقیقت رو میگن.
- هوشمندانه کش کن. برای اسم فایلها از IDها استفاده کن، نه متن خام. خودت در آینده ازت تشکر میکنه.
- همه چیز رو escape کن. اگه دادههات کاراکترهای خاص داره، escape کن. بعد احتمالاً دوباره escape کن.
قدم بعدی چیه؟
ربات الان زندهست و هر روز ازش استفاده میکنم. بهبودهای آینده ممکنه شامل تشخیص گرامر (نه فقط واژگان)، استریکهای پیشرفت و گیمیفیکیشن، و پشتیبانی از زبانهای دیگه مثل فرانسه یا اسپانیایی باشه.
ولی فعلاً، دقیقاً همون کاری رو میکنه که نیاز داشتم: تمرین روزانه آلمانی با تلفظ صوتی و حداقل اصطکاک.
بهترین ابزار اونیه که واقعاً ازش استفاده کنی. این یکی رو هر روز استفاده میکنم.