نحتاج كثيرًا إلى استعمال جداول البيانات لتخزين بيانات دورية خلال يومنا لتجنب نسيانها فقد لا نكون دومًا خلف طاولة العمل حيث الدفاتر والحاسوب بل قد نكون في الشارع أو الحافلة ولا نملك سوى الهاتف في يدنا ولا نريد استعمال تطبيق الملاحظات الممتلئ بملاحظات مبعثرة هنا وهناك. على سبيل المثال إذا احتجنا لاستخدام جداول بيانات جوجل Google Sheets لتسهيل تسجيل بعض البيانات ومتابعتها دوريًا من مكان واحد سنواجه صعوبة في التعامل مع Google Sheets على الجوال لأن الشاشة صغيرة وغير مريحة بالعمل، لذا قد نضطر إلى تدوين الملاحظات سريعًا لإدخالها في جداول البيانات وتنظيمها لاحقًا عند توفر الحاسوب.
سنوضح في مقال اليوم طريقة سهلة وعملية لإدارة إدخال البيانات في جداول بيانات جوجل من خلال إنشاء بوت تلغرام Telegram وظيفته إدخال البيانات وتنظيمها مباشرةً في جداول البيانات، فلا يكاد يخلو أي هاتف جوال من تطبيق تلغرام الشهير وبهذا ندخل البيانات بسهولة من خلاله دون الحاجة لفتح تطبيق جداول البيانات، كل ما نحتاجه هو إرسال رسالة بصيغة معينة للبوت ليأخذها ويرسلها لجداول بيانات جوجل ويخزنها هناك.أي تتألف العملية من ثلاثة خطوات، ارسال الرسالة التي تحوي البيانات إلى البوت، ثم نقل البيانات، ثم أخيرًا تخزينها في وجهتها.
متطلبات مسبقة للعمل
نحتاج قبل إكمال هذا المقال إلى ما يلي:
- حساب على تطبيق تلغرام
- حساب جوجل مع إمكانية الوصول إلى تطبيق جداول بيانات جوجل Google Sheets
- معرفة مسبقة باستعمال Google Sheets، ويمكن الرجوع إلى مقال مقدمة إلى جداول بيانات جوجل Google Sheets
- معرفة أساسية بلغة جافاسكريبت، ويمكن الرجوع إلى مقال تعلم لغة جافا سكريبت JavaScript
التطبيق النموذجي لربط بوت تلغرام مع جداول بيانات جوجل
سنعتمد على نموذج بيانات بسيط بغرض التعلم حيث سنتعلم من خلاله آلية ربط بوت تلغرام مع جداول بيانات جوجل، والنموذج الذي سنستخدمه هنا هو بيانات الإيرادات والنفقات اليومية كما توضح الصورة التالية:
ملاحظة: استخدمنا هذا النموذج بغرض التعلم ونترك لكم تطبيق ما ستتعلمونه على نموذج البيانات المناسب لاحتياجاتكم.
الخطوة الأولى: إنشاء بوت تلغرام Telegram Bot
سنستعمل تطبيق تلغرام على الويب للسهولة، لذا سنفتح تطبيق تلغرام ويب ونسجل الدخول، ثم نبحث في حقل البحث عن BotFather وهو كبيرُ البوتات الذي سيساعدنا على إنشاء بوتات تلغرام وإدراتها. لنتبه إلى أن مُعرِّف كبير البوتات الرسمي هو @botfather إذ ستظهر لنا عدة نتائج في حقل البحث:
لنضغط بعدها على البدء Start وستظهر أمامنا رسالة فيها قائمة طويلة من الخيارات اللازمة لإنشاء بوتات تلغرام وإدارتها، بعدها سيسألنا كبيرُ البوتات عن اسم البوت الذي نريد إنشاءه فنجبه باسم البوت وسندخل هنا الاسم إيرادات ونفقات:
سيطلب بعدها كبيرُ البوتات إدخال اسم مُعرِّف أو اسم المستخدم للبوت الجديد، وننتبه لأن هذا الاسم يجب أن ينتهي بالكلمة bot سواءً بالشكل usernameBot أو username_bot، وسنستخدم هنا الاسم MyIncomeAndExpensesBot:
ملاحظة: قد لا يقبل البوت بعض اقتراحات اسم المستخدم مثلًا عندما جربنا إدخال اسم المستخدم IncomeExpensesBot وIncomeAndExpensesBot لم يقبلهما لكونهما محجوزان مسبقً، لذا قد نحتاج لتعديل الاسم حتى نجد اسمًا متاحًا.
سينُشئ بعدها كبيرُ البوتات البوت الجديد ويرسل لنا رسالة يخبرنا فيها عن رابط الوصول للبوت ورمز الوصول إليه access token عبر طلبات HTTP:
لنحفظ هذا الرمز لأننا سنستعمله لاحقًا في التواصل مع هذا البوت.
تجربة التواصل مع بوت تلغرام الجديد
سنلاحظ في آخر الرسالة الموضحة بالصورة السابقة أن كبير البوتات قد أشار إلى توثيق واجهة البوت البرمجية، سنحتاج دومًا لفتح هذا التوثيق ونرجع إليه دومًا في آلية ربط بوت تلغرام والتواصل معه، فقد تتغير الآلية وتتعدل الواجهة البرمجة مستقبلًا.
لنتأكد أن البوت قد أُنشئ وأصبح جاهزًا للتواصل، وذلك بزيارة الرابط التالي في متصفح الويب بعد تبديل {access_token}
برمز وصول البوت الذي حصلنا عليه مسبقًا:
https://api.telegram.org/bot{access_token}/getMe
إن سارت الأمور على ما يرام، فسنحصل على نتيجة مشابهة للنتيجة التالية تحوي تفاصيل البوت ورده علينا:
{ "ok": true, "result": { "id": 7666156735, "is_bot": true, "first_name": "إيرادات ونفقات", "username": "MyIncomeAndExpensesBot", "can_join_groups": true, "can_read_all_group_messages": false, "supports_inline_queries": false, "can_connect_to_business": false, "has_main_web_app": false } }
نلاحظ أننا حصلنا من الرسالة السابقة على رقم البوت id
، واسمه first_name
، واسم المستخدم username
، وبعض التفاصيل الأخرى التي لن تلزمنا في هذا المقال مثل إمكانية انضمام البوت للمجموعات وغيرها.
ضبط بوت تلغرام
قبل أن نكمل، نود أن نشير لإمكانية ضبط بوت تلغرام الجديد -وأي بوت آخر- عبر كبير البوتات BotFather مباشرةً، فمثلًا لنجرب كتابة /
أو اضغط على زر قائمة Menu بجانب حقل الكتابة في المحادثة وستظهر لنا خيارات كثيرة كان كبير البوتات قد ذكر أهمها في أول رسالة أرسلها لنا بعد الضغط على زر البدء Start:
سنذكر بعض هذه الأوامر:
-
/mybots
لعرض بوتات تلغرام الخاصة بنا -
/setname
لتغيير اسم بوت تلغرام -
/setdescription
لضبط أو تغيير وصف البوت -
/setuserpic
لضبط أو تغيير صورة البوت
يمكن تجربة هذه الأوامر مع بقية الأوامر لتتعلم أكثر عن ضبط وإدارة بوتات تلغرام.
فهم آلية الربط والتواصل بين بوت تلغرام وجداول بيانات جوجل
قبل أن ننتقل إلى الخطوة التالية من مقالنا، من الضروري فهم آلية ربط بوت تلغرام مع جداول بيانات جوجل -أو أي خدمة أخرى مثلًا- حتى نفهم كيف سنكمل عملية الضبط.
لنتذكر أن هدف المقال إرسال رسالة بصيغة معينة إلى بوت تلغرام، ليقوم بدوره بتنفيذ أوامر محددة وظيفتها أخذ رسالتنا وإرسالها إلى جداول بيانات جوجل لتخزينها هناك، وبذلك يمكن تقسيم العملية إلى ثلاثة خطوات، الأولى إرسال الرسالة التي تحوي البيانات إلى البوت، و الثانية نقل البيانات، والثالثة تخزينها في وجهتها.
يمكننا التواصل مع البوت عبر آليتين : الأولى هي التواصل عبر الواجهة البرمجية APIs مباشرةً وسؤاله عما نريد، مثلًا يمكن إرسال طلب للواجهة البرمجية /getMe
لطلب معلومات عن البوت، أو الواجهة البرمجية
/getUpdates
لطلب كافة الرسائل المرسلة إلى البوت وغيرها، ويمكنك الرجوع إلى توثيق واجهات بوت تلغرام البرمجية للاطلاع على القائمة الكاملة.
الآلية الثانية هي التواصل من خلال خطافات الويب Web Hooks أي أننا نطلب من البوت نفسه أن يتواصل معنا عند وقوع أحداث معينة، وسنجد في توثيق Update قائمة الأحداث المدعومة في بوت تلغرام، حيث نحدد ما هي الأحداث التي نحتاجها بالضبط.
هذا يشبه آلية تواصلنا نحن البشر، تخيل لو كنا نريد -ونحن في العمل- معرفة إن وصلت طلبية طلبناها إلى منزلنا أم لا، عندها إما أن أتصل بالمنزل ونسأل الأهل إن وصلت الطلبية، أو أن نطلب من الأهل أن يتصلوا بنا متى ما وصلت الطلبية.
سنعتمد في هذا المقال على آلية خطافات الويب web hooks وسنختار حدث استلام رسالة، فكلما استلم البوت رسالة سيقوم بأمر محدد وهو إرسالها إلى خادم جوجل ننشره على الإنترنت عبر عبر برمجة تطبيقات Google أو Apps Scripts والذي بدوره يستلم البيانات ويُخزِّنها في جدول بيانات جوجل وهذا ما سنعمل على ضبطه.
ملاحظة: ذكرنا أننا سنعتمد على خادم ننشره عبر برمجة تطبيقات جوجل وبذلك لن نحتاج إلى إنشاء خادم خاص للتواصل، ولكن الأمر هنا يعود لما يناسب احتياجنا ومتطلباتنا، فيمكن إنشاء خادم خاص والتواصل مع البوت ومعالجة البيانات وتخزينها بأي لغة برمجة نريدها، وإظهارها بالشكل الذي نريد مما يفتح لنا أفاقًا واسعةً لبناء وتطوير بوتات تلغرام خدمية.
الخطوة الثانية: إنشاء جدول بيانات جوجل والتحكم به برمجيًا
لننشئ جدول بيانات جديد في درايف لتخزين البيانات الواردة من بوت التلغرام برمجيًا بشكل مؤتمت، وذلك عبر إضافة توفرها جوجل وهي لغة برمجة تطبيقات جوجل Apps Scripts .
ما هي لغة Google Apps Scripts
تعد Google Apps Scripts لغة برمجة هدفها إدارة جميع تطبيقات جوجل كبريد Gmail وتخزين Drive وجداول بيانات جوجل Sheets وغيرها، وهي مبنية على لغة جافاسكربت ولاحقتها ملفاتها البرمجية هي .gs
وتوفر جوجل بيئة تطوير على السحابة Google Workspace تساعدنا على تنفيذ الأكواد التي نكتبها بهذه اللغة، أي لن نحتاج لبيئة تطوير لمعالجة وتنفيذ الملفات المكتوبة بهذه اللغة محليًا.
التحكم بجدول البيانات برمجيًا
بعد إنشاء جدول بيانات جديد وتسميته باسم الإيرادات والنفقات، يمكننا ربطه والتحكم به برمجيًا بسهولة عبر Apps Scripts من خيار الإضافات Extensions ثم برمجة تطبيقات Google كما في الصورة التالية:
سيُنشَأ ملف بلغة برمجة تطبيقات جوجل Apps Scripts ويُربط بملف جداول البيانات:
هنا يمكننا فعل أي شيء بملف جدول البيانات برمجيًا، وحتى الوصول إلى بقية خدمات جوجل وتطبيقاتها مثل البريد Gmail ومستندات جوجل، ويمكننا أيضًا التواصل مع خدمات خارجية وهو ما نريد فعله بربط جدول البيانات مع بوت التلغرام الذي أنشأناه.
نلاحظ في الجزء الأيمن في قسم الملفات وجود ملف باسم الرمز.gs، نلاحظ أيضًا أن هذه البيئة تشبه بيئة التطوير إذ توفر لنا مكانًا لكتابة الكود وتنفيذه من الخيارات في الشريط العلوي.
ملاحظة: يمكن تغيير اسم المشروع من الأعلى إلى اسم مناسب وهنا سنضعه بنفس اسم البوت.
لنجرب بدايةً عرض ناتج تنفيذ الواجهة البرمجية getMe
في هذه البيئة، لذا نغيّر اسم الدالة myFunction
إلى getMe
مثلًا ونكتب محتواها البرمجي التالي:
function getMe() { const response = UrlFetchApp.fetch('https://api.telegram.org/bot7666156735:AAFlJYPgOzT5cTf8CfRx_5vf0GNuVmcvAuk/getMe'); Logger.log(response.getContentText()); }
استعملنا الدالة UrlFetchApp.fetch
لجلب محتوى رابط أو واجهة برمجية، ومررنا لها رابط الواجهة البرمجية /getMe
كاملًا مع رمز الوصول access token، ثم استعملنا Logger.log
لطباعة الناتج.
نضغط على زر تنفيذ في الأعلى، وبما أننا ننفذ هذا السكربت لأول مرة فسيُطلَب منا إعطاءه صلاحيات محددة:
نضغط على مراجعة الأذونات، ثم نحدد حسابنا ونضغط بعدها على السماح بعد مراجعة الأذونات المطلوبة، وسنجد بعدها ناتج تنفيذ الدالة getMe
ضمن السكربت في قسم سجل التنفيذ في الأسفل:
المعلومات هنا هي نفس المعلومات التي حصلنا عليها من فتح الرابط في المتصفح تمامًا، وبذلك نكون قد نجحنا في اختبار بيئة التطوير وجاهزيتها.
ملاحظة: قد نضطر في كل تعديل إلى ضغط زر الحفظ وهو بجانب زر التنفيذ في الشريط العلوي أو ضغط الاختصار ctrl + s لحفظ المشروع.
سنعيد تنظيم الشيفرة قليلًا ليسهل علينا استدعاء بقية الواجهات البرمجية بالشكل التالي:
const token = '7666156735:AAFlJYPgOzT5cTf8CfRx_5vf0GNuVmcvAuk'; const url = 'https://api.telegram.org/bot' \+ token; function getMe() { const response = UrlFetchApp.fetch(url \+ '/getMe'); Logger.log(response.getContentText()); }
يمكن الآن استدعاء /getUpdates
بسهولة بكتابة دالة جديدة باسم getUpdates
بالشكل التالي:
const token = '7666156735:AAFlJYPgOzT5cTf8CfRx_5vf0GNuVmcvAuk'; const url = 'https://api.telegram.org/bot' \+ token; function getMe() {` const response = UrlFetchApp.fetch(url \+ '/getMe'); Logger.log(response.getContentText()); } function getUpdates() { const response = UrlFetchApp.fetch(url \+ '/getUpdates'); Logger.log(response.getContentText()); }
لتنفيذ الدالة، نختار من القائمة الموجودة في الشريط العلوي اسم الدالة، ثم نضغط على تنفيذ ونشاهد الناتج في قسم سجل التنفيذ.
ملاحظة: قد تظهر لنا رسالة تحذير بأن التطبيق غير آمن ولم يتحقق جوجل منه ولا بأس بالإكمال كما توضح الصورة التالية لأننا نحن من نطور السكربت ولكن يجب الحذر إن كان السكريبت من مصدر غير موثوق لأن قد يصل إلى معلومات حساسة.
نشر تطبيق ويب يصل بين بوت تلغرام وجداول بيانات جوجل
فهمنا سابقًا آلية التواصل التي سنطبقها والتي تتم بتواصل بوت التلغرام عبر خطاف ويب web hook مع خادم ويب كلما استلم البوت رسالة جديدة بعدها سيعمل خادم الويب على تخزين البيانات الواردة بعد معالجتها في جدول البيانات كما هو موضح بالصورة التالية:
السؤال هو كيف سنحصل على خادم ويب؟ يمكننا بناء خادم ويب بأي لغة نريد سواءً بلغة بايثون أو لغة جافاسكربت، أو يمكننا الاستفادة من بيئة تطوير تطبيقات جوجل Apps Script فهي تمكننا من نشر المشروع على أنه تطبيق ويب على الإنترنت مباشرة واستضافته على خوادم جوجل السحابية.
لنشر المشروع، نضغط على زر النشر في الأعلى ثم نختار تطبيق ويب:
نختار من القائمة التي تظهر من حقل المستخدم الذي لديه الإذن بالوصول إلى عملية النشر الخيار أي شخص لكي يعمل التطبيق ثم نضغط على زر نشر:
نحصل بعد تمام عملية النشر على رابط URL للتطبيق ويب جاهز ومنشور على الإنترنت، ويمكننا نسخ رابطه ولصقه في المتصفح للتأكد من عمل التطبيق، وإن جربنا في حالتنا فسنحصل على الخطأ التالي:
Script function not found: doGet
توضح هذه الرسالة أن السكربت لا يحوي دالة باسم doGet
لتنفيذها وهو طلب HTTP من النوع get لذا نضيف الدالة doGet
إلى السكربت:
function doGet(e) { return HtmlService.createHtmlOutput("Hello " \+ JSON.stringify(e)); }
تعمل الدالة السابقة على إعادة صفحة HTML فيها كلمة مرحبًا Hello وكائن بالمعلومات المرسلة بالطلبية، ولنعد إلى نافذة المتصفح حيث فتحنا رابط التطبيق وحدِّثنا الصفحة ولكن وجدنا أن الرسالة لم تتغير. يحصل ذلك لأننا لم ننشر نسخة جديدة من التطبيق بعد إضافة الدالة الجديدة إليه.
لنشر نسخة جديدة من التطبيق، نضغط على زر نشر في الأعلى ثم نختار إدارة عمليات النشر ثم نضغط على زر التعديل في قسم الإعداد ثم نختار من قائمة الإصدار خيار الإصدار الجديد لنشر إصدار جديد من التطبيق:
إن حدثنا الصفحة فسنرى ناتج تنفيذ الدالة doGet
التالي:
Hello {"queryString":"","contentLength":-1,"contextPath":"","parameters":{},"parameter":{}}
ملاحظة: قد يحتاج تطبيق الويب لبعض الوقت بعد نشره أو نشر إصدار جديد منه للعمل.
أصبحنا الآن جاهزين للانتقال إلى الخطوة التالية وهي ربط خطاف ويب web hook بوت التلغرام وبين تطبيق الويب المنشور الذي أنشأناه للتو.
الخطوة الثالثة: ضبط خطاف الويب بين تطبيق الويب وبوت التلغرام
يمكن لتطبيق الويب الذي أنشأناه عبر لغة برمجة تطبيقات جوجل أن يستقبل ويعالج طلبات HTTP، وقد رأينا كيف أنه استقبل طلب GET ويمكنه أيضًا أن يستلم طلب POST أيضًا، فعبر ربط خطاف الويب بين تطبيق الويب وبوت التلغرام، سيرسل البوت طلب POST كلما استلم رسالة جديدة.
ولضبط خطاف الويب في بوت التلغرام، سنستعين بالواجهة البرمجية setWebhook
التي تأخذ معاملًا باسم url
وهو رابط تطبيق أو خادم الويب الذي ستُرسل إليه التحديثات الواردة من بوت التلغرام.
نُعرِّف في السكربت دالة جديدةًبالاسم setWebhook
والتي سترسل طلبية إلى الواجهة البرمجية تلك مع رابط تطبيق الويب، ويمكن أن نحصل على رابط تطبيق الويب بالضغط على زر نشر في الأعلى ثم الضغط على خيار إدارة عمليات النشر ثم ننسخ عنوان URL للتطبيق ونضغه في السكريبت بالشكل التالي:
… const webAppUrl = "https://script.google.com/macros/s/AKfycbwkoaVQ6aIWp6R0Pz5Q1EkiIydaKMusCKI7hEUYtPWXau7SLiznaagiAHDqkfiLzhhyXw/exec"; function setWebhook() { const response = UrlFetchApp.fetch(url \+ '/setWebhook?url=' \+ webAppUrl); Logger.log(response.getContentText()); }
نحصل بعد تنفيذ الدالة setWebhook
وحفظ السكربت على الناتج التالي:
{"ok":true,"result":true,"description":"Webhook was set"}
تخبرنا الرسالة بأنه تم ضبط خطاف الويب بنجاح، لكن إن جربنا الآن أن نرسل رسالة إلى بوت التلغرام فلن يحصل أي شيء، لماذا؟ لأننا لم نضبط بعد عملية معالجة طلبات POST التي يرسلها البوت عبر خطاف الويب بعد، فعندما يتلقى الخادم طلبًا من البوت عبر POST مثلاً، عند وصول رسالة جديدة، يجب على الخادم أن يعالج هذا الطلب.
وللقيام بالمعالجة المطلوبة سنعرِّف دالة باسم doPost
التي تعالج طلبيات POST المستقبلة، وسنجرب حاليًا إرسال بريد إلكتروني إلى حسابنا كلما استلمنا رسالة جديدة بالشكل التالي:
function doPost(e) { GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), "رسالة جديدة من بوت التلغرام", JSON.stringify(e, null, 4)); }
لنحفظ السكريبت ثم ننشر إصدارًا جديدًا من التطبيق والذي سيطلب مرة أخرى قبول أذونات جديدة لأننا أضفنا تطبيقًا جديدًا إلى السكريبت وهو تطبيق البريد GmailApp
، لنقبلها بالضغط على رز تفويض الوصول ولنجرب مرة أخرى إرسال رسالة إلى البوت.
إن أرسلنا رسالة إلى البوت، فستصل رسالة إلى البريد تحوي معلومات الطلب كالتالي:
أصبحت الآن البيانات بحوزتنا وتصلنا إلى تطبيق الويب مباشرة من البوت عند استلامه أي رسالة جديدة، وخطوتنا التالية هي معالجة هذه البيانات وإدخالها ضمن جدول البيانات.
الخطوة الرابعة: إرسال رد على كل رسالة يرسلها بوت التلغرام
قبل أن نحفظ البيانات في جدول بيانات جوجل، سنعمل على كتابة دالة وظيفتها إرسال رد أو تأكيد على بوت التلغرام ونستفيد منها في التواصل مع البوت والرد عليه كما أرسل لنا تحديثًا.
كانت خطوة إرسال البريد خطوة تجريبية فقط لعرض المعلومات التي وصلتنا من البوت لنحاول استخلاص بعض المعلومات المهمة منها وهي بالشكل التالي:
{ "contextPath": "", "queryString": "", "contentLength": 295, "parameters": {}, "parameter": {}, "postData": { "contents": "{\"update_id\":412227169,\n\" message\":{\"message_id\":39,\"from\":{\"id\":957260622,\"is_bot\":false,\"first_name\":\"Jamil\",\"username\":\"jBailony\",\"language_code\":\"en\"},\"chat\":{\"id\":957260622,\"first_name\":\"Jamil\",\"username\":\"jBailony\",\"type\":\"private\"},\"date\":1735667656,\"text\":\"Hi, this is my first message\"}}", "length": 295, "name": "postData", "type": "application/json" } }
ما يهمنا هنا موجود ضمن postData
في خاصية اسمها contents
ولكنها بصيغة JSON لنفككها إلى كائن بالشكل التالية:
const contents = JSON.parse(e.postData.contents);
والناتج هو كالتالي:
{ "update_id": 412227169, "message": { "message_id": 39, "from": { "id": 957260622, "is_bot": false, "first_name": "Jamil", "username": "jBailony", "language_code": "en" }, "chat": { "id": 957260622, "first_name": "Jamil", "username": "jBailony", "type": "private" }, "date": 1735667656, "text": "Hi, this is my first message" } }
يلزمنا مُعرِّف المحادثة id
والرسالة نفسها التي وصلتنا:
const contents = JSON.parse(e.postData.contents); const id = contents.message.from.id; const name = contents.message.from.first_name;
نحتاج إلى المُعرِّفلأننا سنعتمد على الواجهة البرمجية /sendMessage
التي تحتاج إلى المعامل chat_id
الذي هو مُعرِّف المحادثة السابق id
. لنكتب الدالة sendMessage
التي تعتمد على تلك الواجهة البرمجية بالشكل التالي:
function sendMessage(chatId, message) { const response = UrlFetchApp.fetch(${url}/sendMessage?chat_id=${chatId}\&text=${message}); Logger.log(response.getContentText()); }
نلاحظ أن الواجهة البرمجية /sendMessage
تحتاج أيضًا إلى المعامل text
الذي يمثِّل نص الرسالة المراد إرسالها.
لنستعملها الآن ضمن الدالة doPost
بإرسال رد على الرسالة:
function doPost(e) { GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), "رسالة جديدة من بوت التلغرام", JSON.stringify(e, null, 4)); const contents = JSON.parse(e.postData.contents); const id = contents.message.from.id; const name = contents.message.from.first_name; const text = contents.message.text; sendMessage(id, `وصلتنا رسالتك، شكرًا لك يا ` + name); }
نحفظ المشروع وننشر إصدارًا جديدًا منه ثم نرسل رسالة إلى البوت وسنجد أنه يرد علينا كما توضح الصورة التالية:
ممتاز، كل شيء يعمل على ما يرام، وخطوتنا التالية هي إدخال المعلومات المستلمة ضمن جدول البيانات.
الخطوة الخامسة: حفظ البيانات المستلمة من بوت التلغرام في جدول البيانات
أصبح لدينا محتوى الرسالة المستلمة من البوت، ونحتاج الآن إلى إدخالها ضمن جدول البيانات ولكن قبل ذلك، لنتعلم استعمال بعض الدوال التي ستلزمنا في التعامل مع جداول بيانات جوجل Google Sheets.
التعامل مع جداول بيانات جوجل عبر لغة Apps Script
سنعتمد على الواجهة SpreadsheetApp
في التعامل مع جداول بيانات جوجل، ولنكتب بداية الدالة التالية التي وظيفتها إضافة سطر في جدول البيانات المرتبط بالشكل التالي:
function appendRow() {` SpreadsheetApp.getActiveSheet().appendRow([1, 2, 3, 4]); }
إن حفظنا السكريبت ثم نفذنا الدالة appendRow
فسنحصل على الناتج التالي:
ملاحظة: سيُطلب منا مرة أخرى قبول أذونات جديدة لأننا استعملنا واجهة تطبيق جديد في السكريبت، وهذا سيحصل كلما أضفنا واجهة جديدة لأحد تطبيقات جوجل.
عملت الطريقة السابقة بشكل صحيح، ولكنها ليست الطريقة الأفضل، فمن الأفضل استعمال رابط ملف جدول البيانات مباشرة في تحديد الملف بالشكل التالي:
const fileURL = 'https://docs.google.com/spreadsheets/d/1LZlRezLubnD_4mGZulQ7RUZ9-itRefmJNSsSizRh8iY/edit'; function appendRow() { SpreadsheetApp.openByUrl(fileURL).appendRow([1, 2, 3, 4]); }
أو يمكننا الاعتماد على مُعرِّف الملف الذي نستخلصه من رابط ملف جدول البيانات السابق بالشكل التالي فهذه الطريقة أفضل وأقصر:
const fileId = '1LZlRezLubnD_4mGZulQ7RUZ9-itRefmJNSsSizRh8iY'; function appendRow() { SpreadsheetApp.openById(fileId).appendRow([1, 2, 3, 4]); }
نلاحظ أن هذا الأمر سيضيف سطرًا في أول جدول بيانات أو ورقة sheet في الملف، ولكن ماذا لو أردنا إضافة بيانات في ورقة محددة؟ يمكننا استعمال الطريقة التالية:
const fileId = '1LZlRezLubnD_4mGZulQ7RUZ9-itRefmJNSsSizRh8iY'; const sheetName = "Sheet1"; function appendRow() { SpreadsheetApp.openById(fileId).getSheetByName("Sheet1").appendRow([1, 2, 3, 4]); }
استعملنا getSheetByName
مع تمرير اسم الورقة إليها من أجل تعديلها.
سنتعلم أخيرًا كيفية جلب قيمة محددة من السطر الأخير وإعادة إضافتها في السطر التالي مع زيادتها بقيمة 1 مثلًا وذلك بالشكل التالي:
const fileId = '1LZlRezLubnD_4mGZulQ7RUZ9-itRefmJNSsSizRh8iY'; const sheetName = "Sheet1"; function appendRow() { const sheet = SpreadsheetApp.openById(fileId).getSheetByName("incomes"); sheet.appendRow([5]); const lastValue = Number(sheet.getRange(sheet.getLastRow(), 1).getValue()); sheet.appendRow([lastValue + 1]); }
تعمل هذه الدالة على إضافة قيمة في آخر سطر ثم جلب هذه القيمة باستعمال الدالة getRange
التي تأخذ رقم السطر والعمود إذ رقم السطر هو رقم آخر سطر حصلنا عليه من الدالة getLastRow
والعمود مررناه يدويًا، وبعد تنفيذ الدالة حصلنا على الناتج المتوقع:
هذا يكفي لمقالنا الحالي، وسمكن استكشاف بقية الدوال والواجهات من التوثيق الرسمي.
إضافة البيانات المستلمة من بوت التلغرام إلى جدول البيانات
سنختار صيغة البيانات المرسلة من البوت إلينا حتى نعالجها ولتكن بالشكل التالي:
اقتباس@مصاريف 123 شراء خضار
تمثِّل @ وما بعدها اسم الورقة ثم يليها رقم ثم يليها الوصف مباشرةً، ولنعتمد أنه إذا لم نحدد اسم الورقة فلتكن افتراضيًا ورقة المصاريف، ونأخذ هذه المعلومات ونضيفها في الورقة المحددة، لذا سنُعدِّل الدالة doPost
والدالة appendRow
بالشكل التالي:
function doPost(e) { GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), "رسالة جديدة من بوت التلغرام", JSON.stringify(e, null, 4)); const contents = JSON.parse(e.postData.contents); const id = contents.message.from.id; const name = contents.message.from.first_name; const text = contents.message.text; let sheetName = "مصاريف"; let itemValue, itemName; if (/^@/.test(text)) { const textArray = text.slice(1).split(" "); sheetName = textArray[0]; itemValue = textArray[1]; itemName = textArray.splice(2).join(" "); } else { const textArray = text.split(" "); itemValue = textArray[0]; itemName = textArray.shift().join(" "); } appendRow(sheetName, [new Date().toLocaleString(), itemValue, itemName]); sendMessage(id, `سجلت المبلغ والقيمة في جدول ${sheetName}، شكرًا لك!`); } function appendRow(sheetName, data) { const spreadSheet = SpreadsheetApp.openById(fileId); const sheet = spreadSheet.insertSheet(sheetName); sheet.appendRow(data); }
السكربت المكتوب بسيط وهو يتحقق إن كانت اسم الورقة مضافة ضمن الرسالة فيأخذها ويحلل الرسالة وفقًا لذلك أو إن لم تكن مضافة يحلل الرسالة مباشرة ويستخلص منها البيانات، ثم يدخل البيانات مع التاريخ ولاحظ أننا استعملنا الدالة insertSheet
في الدالة appendRow
وذلك لإضافة الورقة ضمن الملف.
نحفظ السكربت ونعيد نشره ثم نرسل للبوت الرسالة التالية @نفقات 10 شراء فواكه ونجد أنه قد رد علينا بالرسالة سجلت المبلغ والقيمة في جدول النفقات، شكرًا لك! وقد أضاف ورقة جديدة بالاسم نفقات وأضاف فيها البيانات:
تعمل عملية الربط بصورة صحيحة إلى الآن ولكن ماذا لو جربنا أن نرسل الرسالة مرة أخرى نفسها؟
الخطوة السادسة: التقاط الأخطاء ومعرفتها وحلها
إن جربنا إرسال نفس الرسالة للبوت @نفقات 10 شراء فواكه، لن يحصل أي شيء ولن يرد علينا البوت بأي رد، وهذا دليل بأنه حصل خطأ ولكن كيف سنعرفه ونحله؟ سنلتقط الخطأ أولًا عبر كتلة try/catch
ونرسل داخل catch
رسالة إلى البوت بالخطأ الحاصل بالشكل التالي:
const chatId = 957260622; function doPost(e) { try { GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), "رسالة جديدة من بوت التلغرام", JSON.stringify(e, null, 4)); const contents = JSON.parse(e.postData.contents); const id = contents.message.from.id; const name = contents.message.from.first_name; const text = contents.message.text; let sheetName = "مصاريف"; let itemValue, itemName; if (/^@/.test(text)) { const textArray = text.slice(1).split(" "); sheetName = textArray[0]; itemValue = textArray[1]; itemName = textArray.splice(2).join(" "); } else { const textArray = text.split(" "); itemValue = textArray[0]; itemName = textArray.shift().join(" "); } appendRow(sheetName, [String(new Date().toLocaleString()), itemValue, itemName]); sendMessage(id, `سجلت المبلغ والقيمة في جدول ${sheetName}، شكرًا لك!`); } catch (e) { sendMessage(chatId, `حصل خطأ`); sendMessage(chatId, JSON.stringify(e?.message ?? e, null, 4)); } }
أخذنا رقم المحادثة وحفظناه في المتغير chatId
الذي حصلنا عليه من المعلومات المرسلة إلينا في البريد والسبب أن الخطأ قد يحصل في أي مكان، فربما يحصل في البداية ويضيع رقم المحادثة وبالتالي لن يصلنا البريد.
نلاحظ أيضًا أننا استعملنا التعبير e?.message ?? e
وذلك لاستخراج نص الرسالة مباشرة وسبب الخطأ فلا نريد أن تصلنا الكثير من البيانات في الرسالة.
إن جربنا حفظ السكريبت وإعادة نشر التطبيق ثم إرسال نفس الرسالة للبوت وهي @نفقات 10 شراء فواكه، فسنلاحظ أنه لم يتغير شيء ولم تصلنا رسالة بالخطأ الحاصل، وبعد التحقق من السكربت نجد أن الخطأ في الدالة sendMessage
إذ يجب ترميز نص الرسالة عبر encodeURIComponent
قبل إرسالها في حال إرسال كائن أو شيفرة برمجية وبعد التعديل على الدالة تصبح بالشكل التالي:
function sendMessage(chatId, message) { const response = UrlFetchApp.fetch(`${url}/sendMessage?chat_id=${chatId}&text=${encodeURIComponent(message)}`); Logger.log(response.getContentText()); }
نعيد نشر التطبيق ثم إرسال نفس الرسالة وحينها نحصل على الرد التالي من البوت:
تقول رسالة الخطأ بأن هنالك ورقة بنفس الاسم نفقات موجودة سابقًا ولا يمكن إنشاء ورقة جديدة بالاسم نفسه. عرفنا الآن سبب الخطأ ويمكننا حله بتعديل الدالة appendRow
بالشكل التالي:
function appendRow(sheetName, data) { const spreadSheet = SpreadsheetApp.openById(fileId); const sheet = spreadSheet.getSheetByName(sheetName) ? spreadSheet.getSheetByName(sheetName) : spreadSheet.insertSheet(sheetName); sheet.appendRow(data); }
تحققنا أن الورقة موجودة، فإن كانت موجودة نستعملها عبر getSheetByName
ونضيف فيها أو ننشئها من جديد عبر insertSheet
وبذلك تُحل المشكلة إن أعدنا إرسال الرسالة من جديدة وطبعًا بعد نشر إصدار جديد من تطبيق الويب.ن
الشيفرة الكاملة للبوت
فيما يلي الشيفرة الكاملة للبوت بعد الانتهاء منه وتنفيذ كافة الخطوات
const token = '7666156735:AAFlJYPgOzT5cTf8CfRx_5vf0GNuVmcvAuk'; const url = 'https://api.telegram.org/bot' + token; const webAppUrl = "https://script.google.com/macros/s/AKfycbwkoaVQ6aIWp6R0Pz5Q1EkiIydaKMusCKI7hEUYtPWXau7SLiznaagiAHDqkfiLzhhyXw/exec"; const fileId = '1LZlRezLubnD_4mGZulQ7RUZ9-itRefmJNSsSizRh8iY'; const chatId = 957260622; function getMe() { const response = UrlFetchApp.fetch(url + '/getMe'); Logger.log(response.getContentText()); } function getUpdates() { const response = UrlFetchApp.fetch(url + '/getUpdates'); Logger.log(response.getContentText()); } function doGet(e) { return HtmlService.createHtmlOutput("Hello " + JSON.stringify(e)); } function setWebhook() { const response = UrlFetchApp.fetch(url + '/setWebhook?url=' + webAppUrl); Logger.log(response.getContentText()); } function appendRow(sheetName, data) { const spreadSheet = SpreadsheetApp.openById(fileId); const sheet = spreadSheet.getSheetByName(sheetName) ? spreadSheet.getSheetByName(sheetName) : spreadSheet.insertSheet(sheetName); sheet.appendRow(data); } function sendMessage(chatId, message) { const response = UrlFetchApp.fetch(`${url}/sendMessage?chat_id=${chatId}&text=${encodeURIComponent(message)}`); Logger.log(response.getContentText()); } function doPost(e) { try { GmailApp.sendEmail(Session.getEffectiveUser().getEmail(), "رسالة جديدة من بوت التلغرام", JSON.stringify(e, null, 4)); const contents = JSON.parse(e.postData.contents); const id = contents.message.from.id; const name = contents.message.from.first_name; const text = contents.message.text; let sheetName = "مصاريف"; let itemValue, itemName; if (/^@/.test(text)) { const textArray = text.slice(1).split(" "); sheetName = textArray[0]; itemValue = textArray[1]; itemName = textArray.splice(2).join(" "); } else { const textArray = text.split(" "); itemValue = textArray[0]; itemName = textArray.shift().join(" "); } appendRow(sheetName, [String(new Date().toLocaleString()), itemValue, itemName]); sendMessage(id, `سجلت المبلغ والقيمة في جدول ${sheetName}، شكرًا لك!`); } catch (e) { sendMessage(chatId, `حصل خطأ`); sendMessage(chatId, JSON.stringify(e?.message ?? e, null, 4)); } }
أفكار لتطوير البوت
انتهينا من تطوير تطبيقنا وربطنا بوت تلغرام مع جداول البيانات بطريقة صحيحة ولكن هنالك الكثير من الأفكار والتطويرات التي يمكن إجراؤها على التطبيق وفيما يلي أبرز الأفكار التحسينية.
تحليل الرسالة المستقبلة من البوت
لم نعمل على تحليل الرسالة التي يستقبلها البوت، لذا يُفضَّل إضافة خطوة تحقق validation منها، فإن كانت لا تُطابق شروطًا معينة مثلًا لا تحوي على قيمة عددية تمثل المصروف أو النفقات سنرفض الرسالة ونعيد إرسال رسالة للبوت توضح أن هنالك شيء ناقص في الرسالة مع توضيح كيفية استعمال البوت وصيغة التواصل معه.
إضافة قائمة أوامر للبوت
بعد فترة قد ننسى كيفية استعمال البوت ولا نريد أن تفتح الشيفرة البرمجية لذا يمكن أن نضيف قائمة أوامر commands للبوت عبر /setcommands
فيها مثلًا كيفية الاستعمال ليرد علينا البوت بكيفية الاستعمال وغيرها من الأوامر السريعة التي توسِّع عمل البوت وتسهِّل استخدامه.
تحسين تنسيق الرسالة المرسلة للبوت
أرسلنا في تطبيقنا رسالة نصية عادية text ولكن يمكننا الاستفادة من المعامل parse_mode
في الواجهة البرمجية /sendMessage
لإرسال الرسالة بتنسيق مارك داون أو بتنسيق HTML، وللمزيد انظر التنسيقات المدعومة في التوثيق.
تحسين تنسيق جدول البيانات وتطويره
يمكنك إضافة جداول أو أوراق sheets مسبقة وتعديل تنسيقها بصيغة معينة، فنحن لم نركز في هذا المقال على تنسيق الجداول لأنه خارج نطاقنا- كما يمكنك أيضًا إضافة أعمدة أو صفوف جامعة sum أو متوسط حسابي average بحيث نربطها مع أمر في البوت يجلب لنا مجموع المصروف أو النفقات الكلية أو لشهر محدد مثلً،ا وهكذا نطور تطبيق المصاريف والنفقات أو نتوسع وتُطوِّر من التطبيق أكثر ليصبح تطبيق محاسبي يفيد المستخدم. كما يمكننا إضافة أوامر تجلب لنا صورة عن مخططات شهرية مثلًا ثم إرسال هذه الصورة إلى البوت عبر الواجهة البرمجية /sendPhoto
وغيرها من الأفكار العديدة الأخرى.
ربط البوت مع ChatGPT
هذه فكرة مميزة جدًا إذ يمكننا تحليل البيانات ومعالجتها عبر ربط البوت مع أحد نماذج الذكاء الاصطناعي مثل التفاعل مع الواجهة البرمجية API لروبوت الدردشة الشهير ChatGPT ، وبهذا يمكن تمرير الرسائل التي يرسلها المستخدم للبوت، وتحويلها إلى ChatGPT للحصول على رد وإعادة الناتج إلى البوت من جديد وبذلك نطوِّر من قدرات البوت وإمكانياته.
خاتمة
تعلمنا في هذا المقال كيفية إنشاء تطبيق متكامل يربط بين بوت تلغرام وجداول بيانات جوجل، وبذلك أصبح لدينا بوت يحفظ البيانات المرسلة إليه ويرد علينا برسالة مخصصة تؤكد حفظ البيانات أو رسالة بملخص الخطأ إن حصل، ولا يكتفي بذلك بل أيضًا يرسل لنا رسالة إلى بريدنا كلما وصلته رسالة من المستخدم. وختمنا المقال بأفكار لتحسين التطبيق والبناء عليه لتطوير بوتات تلغرام أكثر احترافية، وإذا كانت لديكم أي أفكار أخرى تخص تطوير بوتات تلغرام نرحب بمشاركتها معنا في قسم التعليقات أسفل المقال.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.