اذهب إلى المحتوى

نحتاج كثيرًا إلى استعمال جداول البيانات لتخزين بيانات دورية خلال يومنا لتجنب نسيانها فقد لا نكون دومًا خلف طاولة العمل حيث الدفاتر والحاسوب بل قد نكون في الشارع أو الحافلة ولا نملك سوى الهاتف في يدنا ولا نريد استعمال تطبيق الملاحظات الممتلئ بملاحظات مبعثرة هنا وهناك. على سبيل المثال إذا احتجنا لاستخدام جداول بيانات جوجل Google Sheets لتسهيل تسجيل بعض البيانات ومتابعتها دوريًا من مكان واحد سنواجه صعوبة في التعامل مع Google Sheets على الجوال لأن الشاشة صغيرة وغير مريحة بالعمل، لذا قد نضطر إلى تدوين الملاحظات سريعًا لإدخالها في جداول البيانات وتنظيمها لاحقًا عند توفر الحاسوب.
سنوضح في مقال اليوم طريقة سهلة وعملية لإدارة إدخال البيانات في جداول بيانات جوجل من خلال إنشاء بوت تلغرام Telegram وظيفته إدخال البيانات وتنظيمها مباشرةً في جداول البيانات، فلا يكاد يخلو أي هاتف جوال من تطبيق تلغرام الشهير وبهذا ندخل البيانات بسهولة من خلاله دون الحاجة  لفتح تطبيق جداول البيانات، كل ما نحتاجه هو إرسال رسالة بصيغة معينة للبوت ليأخذها ويرسلها لجداول بيانات جوجل ويخزنها هناك.أي تتألف العملية من ثلاثة خطوات، ارسال الرسالة التي تحوي البيانات إلى البوت، ثم نقل البيانات، ثم أخيرًا تخزينها في وجهتها.

متطلبات مسبقة للعمل

نحتاج قبل إكمال هذا المقال إلى ما يلي:

التطبيق النموذجي لربط بوت تلغرام مع جداول بيانات جوجل

سنعتمد على نموذج بيانات بسيط بغرض التعلم حيث سنتعلم من خلاله آلية ربط بوت تلغرام مع جداول بيانات جوجل، والنموذج الذي سنستخدمه هنا هو بيانات الإيرادات والنفقات اليومية كما توضح الصورة التالية:

001 sheet example

ملاحظة: استخدمنا هذا النموذج بغرض التعلم ونترك لكم تطبيق ما ستتعلمونه على نموذج البيانات المناسب لاحتياجاتكم.

الخطوة الأولى: إنشاء بوت تلغرام Telegram Bot

سنستعمل تطبيق تلغرام على الويب للسهولة، لذا سنفتح تطبيق تلغرام ويب ونسجل الدخول، ثم نبحث في حقل البحث عن BotFather وهو كبيرُ البوتات الذي سيساعدنا على إنشاء بوتات تلغرام وإدراتها. لنتبه إلى أن مُعرِّف كبير البوتات الرسمي هو ‎@botfather إذ ستظهر لنا عدة نتائج في حقل البحث:

002 botfather telegram

لنضغط بعدها على البدء Start وستظهر أمامنا رسالة فيها قائمة طويلة من الخيارات اللازمة لإنشاء بوتات تلغرام وإدارتها، بعدها سيسألنا كبيرُ البوتات عن اسم البوت الذي نريد إنشاءه فنجبه باسم البوت وسندخل هنا الاسم إيرادات ونفقات:

003 telegram bot name

سيطلب بعدها كبيرُ البوتات إدخال اسم مُعرِّف أو اسم المستخدم للبوت الجديد، وننتبه لأن هذا الاسم يجب أن ينتهي بالكلمة bot سواءً بالشكل usernameBot أو username_bot، وسنستخدم هنا الاسم MyIncomeAndExpensesBot:

004 telegram bot username

ملاحظة: قد لا يقبل البوت بعض اقتراحات اسم المستخدم مثلًا عندما جربنا إدخال اسم المستخدم IncomeExpensesBot وIncomeAndExpensesBot لم يقبلهما لكونهما محجوزان مسبقً، لذا قد نحتاج لتعديل الاسم حتى نجد اسمًا متاحًا.
سينُشئ بعدها كبيرُ البوتات البوت الجديد ويرسل لنا رسالة يخبرنا فيها عن رابط الوصول للبوت ورمز الوصول إليه access token عبر طلبات HTTP:

005 telegram bot access token

لنحفظ هذا الرمز لأننا سنستعمله لاحقًا في التواصل مع هذا البوت.

تجربة التواصل مع بوت تلغرام الجديد

سنلاحظ في آخر الرسالة الموضحة بالصورة السابقة أن كبير البوتات قد أشار إلى توثيق واجهة البوت البرمجية، سنحتاج دومًا لفتح هذا التوثيق ونرجع إليه دومًا في آلية ربط بوت تلغرام والتواصل معه، فقد تتغير الآلية وتتعدل الواجهة البرمجة مستقبلًا.
لنتأكد أن البوت قد أُنشئ وأصبح جاهزًا للتواصل، وذلك بزيارة الرابط التالي في متصفح الويب بعد تبديل {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:

006 telegram bot menu

سنذكر بعض هذه الأوامر:

  •  /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 كما في الصورة التالية:

007 create app scripts file

سيُنشَأ ملف بلغة برمجة تطبيقات جوجل Apps Scripts ويُربط بملف جداول البيانات:

008 apps scripts ui

هنا يمكننا فعل أي شيء بملف جدول البيانات برمجيًا، وحتى الوصول إلى بقية خدمات جوجل وتطبيقاتها مثل البريد 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 لطباعة الناتج.
نضغط على زر تنفيذ في الأعلى، وبما أننا ننفذ هذا السكربت لأول مرة فسيُطلَب منا إعطاءه صلاحيات محددة:

009 apps scripts permissions

نضغط على مراجعة الأذونات، ثم نحدد حسابنا ونضغط بعدها على السماح بعد مراجعة الأذونات المطلوبة، وسنجد بعدها ناتج تنفيذ الدالة getMe ضمن السكربت في قسم سجل التنفيذ في الأسفل:

009 getme output

المعلومات هنا هي نفس المعلومات التي حصلنا عليها من فتح الرابط في المتصفح تمامًا، وبذلك نكون قد نجحنا في اختبار بيئة التطوير وجاهزيتها.
ملاحظة: قد نضطر في كل تعديل إلى ضغط زر الحفظ وهو بجانب زر التنفيذ في الشريط العلوي أو ضغط الاختصار 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());  
}    

لتنفيذ الدالة، نختار من القائمة الموجودة في الشريط العلوي اسم الدالة، ثم نضغط على تنفيذ ونشاهد الناتج في قسم سجل التنفيذ.
ملاحظة: قد تظهر لنا رسالة تحذير بأن التطبيق غير آمن ولم يتحقق جوجل منه ولا بأس بالإكمال كما توضح الصورة التالية لأننا نحن من نطور السكربت ولكن يجب الحذر إن كان السكريبت من مصدر غير موثوق لأن قد يصل إلى معلومات حساسة.

014 google hasn t verify app message

نشر تطبيق ويب يصل بين بوت تلغرام وجداول بيانات جوجل

فهمنا سابقًا آلية التواصل التي سنطبقها والتي تتم بتواصل بوت التلغرام عبر خطاف ويب web hook مع خادم ويب كلما استلم البوت رسالة جديدة بعدها سيعمل خادم الويب على تخزين البيانات الواردة بعد معالجتها في جدول البيانات كما هو موضح بالصورة التالية:

10 telegram bot apps scripts google sheets

السؤال هو كيف سنحصل على خادم ويب؟ يمكننا بناء خادم ويب بأي لغة نريد سواءً بلغة بايثون أو لغة جافاسكربت، أو يمكننا الاستفادة من بيئة تطوير تطبيقات جوجل Apps Script فهي تمكننا من نشر المشروع على أنه تطبيق ويب على الإنترنت مباشرة واستضافته على خوادم جوجل السحابية.

لنشر المشروع، نضغط على زر النشر في الأعلى ثم نختار تطبيق ويب:

011 publish app scriptes wep app

نختار من القائمة التي تظهر من حقل المستخدم الذي لديه الإذن بالوصول إلى عملية النشر الخيار أي شخص لكي يعمل التطبيق ثم نضغط على زر نشر:

12 publish app script as web app

نحصل بعد تمام عملية النشر على رابط URL للتطبيق ويب جاهز ومنشور على الإنترنت، ويمكننا نسخ رابطه ولصقه في المتصفح للتأكد من عمل التطبيق، وإن جربنا في حالتنا فسنحصل على الخطأ التالي:

Script function not found: doGet    

توضح هذه الرسالة أن السكربت لا يحوي دالة باسم doGet لتنفيذها وهو طلب HTTP من النوع get لذا نضيف الدالة doGet إلى السكربت:

function doGet(e) {
 return HtmlService.createHtmlOutput("Hello " \+ JSON.stringify(e));   
} 

تعمل الدالة السابقة على إعادة صفحة HTML فيها كلمة مرحبًا Hello وكائن بالمعلومات المرسلة بالطلبية، ولنعد إلى نافذة المتصفح حيث فتحنا رابط التطبيق وحدِّثنا الصفحة ولكن وجدنا أن الرسالة لم تتغير. يحصل ذلك لأننا لم ننشر نسخة جديدة من التطبيق بعد إضافة الدالة الجديدة إليه.
لنشر نسخة جديدة من التطبيق، نضغط على زر نشر في الأعلى ثم نختار إدارة عمليات النشر ثم نضغط على زر التعديل في قسم الإعداد ثم نختار من قائمة الإصدار خيار الإصدار الجديد لنشر إصدار جديد من التطبيق:

014 maneging apps scripts republising

إن حدثنا الصفحة فسنرى ناتج تنفيذ الدالة 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، لنقبلها بالضغط على رز تفويض الوصول ولنجرب مرة أخرى إرسال رسالة إلى البوت.
إن أرسلنا رسالة إلى البوت، فستصل رسالة إلى البريد تحوي معلومات الطلب كالتالي:

016 first telegram gmail message

أصبحت الآن البيانات بحوزتنا وتصلنا إلى تطبيق الويب مباشرة من البوت عند استلامه أي رسالة جديدة، وخطوتنا التالية هي معالجة هذه البيانات وإدخالها ضمن جدول البيانات.

الخطوة الرابعة: إرسال رد على كل رسالة يرسلها بوت التلغرام

قبل أن نحفظ البيانات في جدول بيانات جوجل، سنعمل على كتابة دالة وظيفتها إرسال رد أو تأكيد على بوت التلغرام ونستفيد منها في التواصل مع البوت والرد عليه كما أرسل لنا تحديثًا.
كانت خطوة إرسال البريد خطوة تجريبية فقط لعرض المعلومات التي وصلتنا من البوت لنحاول استخلاص بعض المعلومات المهمة منها وهي بالشكل التالي:

{
    "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);
}

نحفظ المشروع وننشر إصدارًا جديدًا منه ثم نرسل رسالة إلى البوت وسنجد أنه يرد علينا كما توضح الصورة التالية:

017 using sendmessage endpoint

ممتاز، كل شيء يعمل على ما يرام، وخطوتنا التالية هي إدخال المعلومات المستلمة ضمن جدول البيانات.

الخطوة الخامسة: حفظ البيانات المستلمة من بوت التلغرام في جدول البيانات

أصبح لدينا محتوى الرسالة المستلمة من البوت، ونحتاج الآن إلى إدخالها ضمن جدول البيانات ولكن قبل ذلك، لنتعلم استعمال بعض الدوال التي ستلزمنا في التعامل مع جداول بيانات جوجل Google Sheets.

التعامل مع جداول بيانات جوجل عبر لغة Apps Script

سنعتمد على الواجهة SpreadsheetApp في التعامل مع جداول بيانات جوجل، ولنكتب بداية الدالة التالية التي وظيفتها إضافة سطر في جدول البيانات المرتبط بالشكل التالي:

function appendRow() {`    
 SpreadsheetApp.getActiveSheet().appendRow([1, 2, 3, 4]);  
}

إن حفظنا السكريبت ثم نفذنا الدالة appendRow فسنحصل على الناتج التالي:

018 append row to sheet

ملاحظة: سيُطلب منا مرة أخرى قبول أذونات جديدة لأننا استعملنا واجهة تطبيق جديد في السكريبت، وهذا سيحصل كلما أضفنا واجهة جديدة لأحد تطبيقات جوجل.
عملت الطريقة السابقة بشكل صحيح، ولكنها ليست الطريقة الأفضل، فمن الأفضل استعمال رابط ملف جدول البيانات مباشرة في تحديد الملف بالشكل التالي:

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 والعمود مررناه يدويًا، وبعد تنفيذ الدالة حصلنا على الناتج المتوقع:

018 append value in sheet

هذا يكفي لمقالنا الحالي، وسمكن استكشاف بقية الدوال والواجهات من التوثيق الرسمي.

إضافة البيانات المستلمة من بوت التلغرام إلى جدول البيانات

سنختار صيغة البيانات المرسلة من البوت إلينا حتى نعالجها ولتكن بالشكل التالي:

اقتباس

@مصاريف 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 شراء فواكه ونجد أنه قد رد علينا بالرسالة سجلت المبلغ والقيمة في جدول النفقات، شكرًا لك! وقد أضاف ورقة جديدة بالاسم نفقات وأضاف فيها البيانات:

020 save message in sheet

تعمل عملية الربط بصورة صحيحة إلى الآن ولكن ماذا لو جربنا أن نرسل الرسالة مرة أخرى نفسها؟

الخطوة السادسة: التقاط الأخطاء ومعرفتها وحلها

إن جربنا إرسال نفس الرسالة للبوت @نفقات 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());
}

نعيد نشر التطبيق ثم إرسال نفس الرسالة وحينها نحصل على الرد التالي من البوت:

021 debug error code and message

تقول رسالة الخطأ بأن هنالك ورقة بنفس الاسم نفقات موجودة سابقًا ولا يمكن إنشاء ورقة جديدة بالاسم نفسه. عرفنا الآن سبب الخطأ ويمكننا حله بتعديل الدالة 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 للحصول على رد وإعادة الناتج إلى البوت من جديد وبذلك نطوِّر من قدرات البوت وإمكانياته.

خاتمة

تعلمنا في هذا المقال كيفية إنشاء تطبيق متكامل يربط بين بوت تلغرام وجداول بيانات جوجل، وبذلك أصبح لدينا بوت يحفظ البيانات المرسلة إليه ويرد علينا برسالة مخصصة تؤكد حفظ البيانات أو رسالة بملخص الخطأ إن حصل، ولا يكتفي بذلك بل أيضًا يرسل لنا رسالة إلى بريدنا كلما وصلته رسالة من المستخدم. وختمنا المقال بأفكار لتحسين التطبيق والبناء عليه لتطوير بوتات تلغرام أكثر احترافية، وإذا كانت لديكم أي أفكار أخرى تخص تطوير بوتات تلغرام نرحب بمشاركتها معنا في قسم التعليقات أسفل المقال.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...