ساخت ربات تلگرامی برای یادگیری زبان آلمانی: از ۴۰ نود به ۲۰۰ خط کد

ساخت ربات تلگرامی برای یادگیری زبان آلمانی: از ۴۰ نود به ۲۰۰ خط کد

چطور یک ربات یادگیری زبان ساختم که درس‌های تولیدشده با هوش مصنوعی، فلش‌کارت با تکرار فاصله‌دار، و تلفظ صوتی دارد — و چند درس سخت هم درباره معماری 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 کن.

قدم بعدی چیه؟

ربات الان زنده‌ست و هر روز ازش استفاده می‌کنم. بهبودهای آینده ممکنه شامل تشخیص گرامر (نه فقط واژگان)، استریک‌های پیشرفت و گیمیفیکیشن، و پشتیبانی از زبان‌های دیگه مثل فرانسه یا اسپانیایی باشه.

ولی فعلاً، دقیقاً همون کاری رو می‌کنه که نیاز داشتم: تمرین روزانه آلمانی با تلفظ صوتی و حداقل اصطکاک.

بهترین ابزار اونیه که واقعاً ازش استفاده کنی. این یکی رو هر روز استفاده می‌کنم.

GermanPracticeWithbot

مطالب بیشتر

توجه و روابط: چالش‌ها و درس‌ها

توجه و روابط: چالش‌ها و درس‌ها

نظاره‌گرها می‌گفت کسی نفهمید که تغییر کردی؟ گفتم فهمیدن، توجه می‌خواد، توجه چشم و حواس می‌خواد و اهمیت دادن! سال‌هاست من با این مساله توجه درگیرم، حتی حال روزمره‌ام رو هم تحت تاثیر شدید می‌ذاره، ریشه‌هاش هم یافتم، بهبود هم دادم ولی بخشی از منه پذیرفتمش. یادم میاد قبل از مهاجرت بحث جدی با دوست دخترم داشتم که آقا حرف زدن، خوندن و نوشتن بخشی مهمی از رابطه‌است. اون موقع‌ها اینستاگرام هم می‌نوشتم این کانال هم بود، طرف مثل بز نگاه می‌کرد و رد می‌شد، مثلاً اگر نوشته بودی مردم می‌گفت خب اگر مرده بود خ

نویسنده: چَپَری نویس
چشم‌هایش: سفر به دنیای درون

چشم‌هایش: سفر به دنیای درون

هر درختی ثمری دارد و هر کس هنری. منِ بی‌مایه‌ی بدبخت، تهی‌دست چون بید. لیکن از مشرقِ الطافِ الهی نه عجیب است که چون شب روز شود، بر همه تابید خورشید. ما، کیانیم که در معرضِ یاران آییم؟ ماکیان را چه محل در نظرِ بازِ سپید؟ #سعدی چشم‌ها، همان که سال‌هاست برای من دریچه دیدن دنیای آدم‌ها شده‌اند. آدم‌هایی با طعم‌ها و رنگ‌های متفاوت که فقط و فقط چشم‌هایشان عامل این تفاوت است. هر کدام از گوشه‌ای می‌آیند و از رنجی می‌گذرد و می‌روند و تو می‌مانی و چشم‌ها که قابش برای تو مانده و دیگر هیچ. هر ورقی از

نویسنده: چَپَری نویس
یک روز سرد و بارانی دریاچه

یک روز سرد و بارانی دریاچه

با خودم گفته بودم امروز باید برم تمرین کامل کنم یک و نیم کیلومتر شنا ۴٠ کیلومتر دوچرخه ده کیلومتر دویدن با همه دعواهای درونی بلند شدن، دوچرخه مرتب کردم، آماده شدم. بارون شدیدی شروع شد و هوا سرد. ١۴ درجه. لباس مخصوص هم مثلاً قرار سبک باشه، نفس کشیدن داشته باشه و زیاد خیس نشه به خاطر همین کاری برای گرما نمیکنه. تا برسم به دریاچه خیس شده بودم و به لرز افتادم. نمیدونم چه فکری کردم. وسایل و دوچرخه رو از پله‌ها بردم پایین کنار آب گذاشتم. آماده شدم و رفتم توی آب. دماسنج کنار پله‌ها میگفت ١١ درجه ا

نویسنده: چَپَری نویس
ساحل و دریا: یک تجربه نزدیک به رهایی

ساحل و دریا: یک تجربه نزدیک به رهایی

از دور به نظر دریا ناآرومی بود ولی اونقدر هم خشمگین نبود. کفش درآوردم و از جای اشتباهی وارد آب شدم. با هر قدم پام لای سنگ‌ها و مرجان‌ها و تیغ‌ها گیر می‌کرد و زخمی می‌شد، موج‌ها از نزدیک بزرگ و بزرگ‌تر می‌شد و برای تعادل داشتن باید پام رو محکم‌تر فشار می‌دادم که در نتیجه عمیق‌تر زخم می‌شد و رنج بیشتر. ی جایی دیگه نکشیدم، شیرجه زدم و شنا رو شروع کنم. فاصله سه موج تا ساحل رو رفتم و فکر می‌کردم می‌تونم دورتر برم که به آرامش پشتش برسم. ولی هرچی جلوتر رفتم حس کردم دارم وارد چیزی می‌شم که شاید نشدنی ب

نویسنده: چَپَری نویس