لقد قمت ببناء أداة مساعدة صغيرة تقوم بمزامنة الأحداث باستمرار من موجز iCalendar (ICS) إلى فئة Discourse عبر واجهة برمجة تطبيقات REST.
هذا ليس مكونًا إضافيًا كاملاً لـ Discourse - فهو يعمل بجانب تثبيت Discourse الخاص بك - لذا فهو ينتمي هنا في #extras. إذا كنت ترغب في عرض أحداث التقويم من مصدر خارجي (مثل تقويم Google، وجداول أوقات الجامعات، وما إلى ذلك) داخل مواضيع Discourse، فسيكون هذا مفيدًا.
المستودع
كيف يعمل
يقرأ الأحداث من موجز ICS معين
يطابقها مع المواضيع الموجودة (حسب UID أو بالرجوع إلى الوقت/الموقع)
ينشئ أو يحدث المواضيع في فئتك المختارة
يمكن تشغيله باستمرار كخدمة systemd (آمن ضد التنفيذ المكرر عبر flock)
المتطلبات
Ubuntu 24.04 LTS (تم اختباره)
Python 3 (موجود بالفعل في Ubuntu 24.04 LTS)
مفتاح واجهة برمجة تطبيقات Discourse
معرف فئة لاستهداف مواضيع الأحداث
مثال على المخرجات
إليك كيف يبدو الأمر عند مزامنة موجز جدول أوقات الجامعة لـ ICS في Discourse:
لو كان لدي أي وقت، ربما كنت سأحاول تحويل هذا إلى إضافة (plugin) مناسبة. أعتقد أنه لا ينبغي أن يكون من الصعب جدًا إنشاء بعض الإعدادات وتحويل بايثون إلى روبي ووضعه في مهمة (job).
فكرة أخرى، يمكن أن تكون مفيدة للأشخاص الذين يتم استضافتهم ويرغبون في استخدام هذا، وهي تحويل المهمة إلى إجراء في GitHub (github action) وجعلها تقوم بتشغيل المهمة يوميًا. لقد فعلت هذا لبعض النصوص البرمجية التي احتاج عميل مستضاف إلى تشغيلها يوميًا منذ فترة وهي تعمل بشكل جيد جدًا. إنها في آن واحد أصعب (تتطلب تعلم سير عمل GitHub وكيفية التعامل مع الأسرار بدلاً من وظيفة cron قديمة) وأسهل (لا يتعين عليك تعلم كيفية التعامل مع تثبيت الأشياء على جهاز عبر واجهة سطر الأوامر).
إذا كان old_clean == fresh_clean: لا يوجد تحديث (يتجنب التغيير).
إذا اختلفا: تحقق مما إذا كان التغيير “ذا مغزى”:
meaningful = (
_norm_time(old_attrs.get("start")) != _norm_time(new_attrs.get("start"))
or _norm_time(old_attrs.get("end")) != _norm_time(new_attrs.get("end"))
or _norm_loc(old_attrs.get("location")) != _norm_loc(new_attrs.get("location"))
)
إذا كان meaningful = True → تحديث مع دفعة (يرتفع الموضوع في الأحدث).
إذا كان meaningful = False → تحديث بهدوء (bypass_bump=True → مراجعة فقط، لا دفعة).
يتم دمج العلامات (يضمن وجود العلامات الثابتة/الافتراضية، ولا يزيل أبدًا العلامات اليدوية/الخاصة بالمشرف).
لا يتم تغيير العنوان والفئة أبدًا عند التحديث.
تدفق التحديث بدون مطابقة UID
يحاول البرنامج النصي التبني:
• يبني ثلاثيات مرشحة للبداية/النهاية/الموقع (أو البداية/النهاية فقط مع --time-only-dedupe).
• يبحث في /search.json و /latest.json عن حدث موجود بسمات متطابقة.
• إذا تم العثور عليه → تبني هذا الموضوع، وتزويده بعلامة UID والعلامات (يُترك الجسم دون تغيير في هذه المرحلة).
• إذا لم يتم العثور عليه → إنشاء موضوع جديد تمامًا بالعلامة والعلامات.
بمجرد تبنيه أو إنشائه، ستحل جميع المزامنات المستقبلية مباشرة عن طريق UID.
⸻
العواقب العملية
• تغييرات الوقت
• افتراضي: يفشل التبني (الأوقات مختلفة) → يتم إنشاء موضوع جديد.
• مع --time-only-dedupe: يفشل التبني بنفس الطريقة؛ يتم إنشاء موضوع جديد.
• تغييرات الموقع
• افتراضي: يفشل التبني (الموقع مختلف) → يتم إنشاء موضوع جديد.
• مع --time-only-dedupe: ينجح التبني (الأوقات متطابقة)، ولكن يتم تمييز اختلاف الموقع على أنه “ذو مغزى” → تحديث مع دفعة.
• تغييرات الوصف
• إذا تغير نص الوصف ولكن لم تتغير البداية/النهاية/الموقع:
• يتم تحديث الجسم بهدوء (bypass_bump=True).
• يتم إنشاء مراجعة للموضوع، ولكن لا توجد دفعة في الأحدث.
• إذا لم يتغير الوصف (أو كان مجرد ضوضاء مثل Last Updated: التي يتم تطبيعها بعيدًا)، فلن يحدث أي تحديث على الإطلاق.
• علامة UID
• يضمن المطابقة الموثوقة في المزامنات المستقبلية.
• يعني أن حقول الوصف الصاخبة لا تؤثر على ما إذا كان الموضوع الصحيح سيتم العثور عليه.
⸻
لماذا يبقى الوصف “كما هو” أحيانًا
يقارن البرنامج النصي الجسم بأكمله (باستثناء علامة UID).
إذا كان سطر متقلب فقط مثل Last Updated: مختلفًا، ولكنه يتم تطبيعه بعيدًا (مثل المسافات البيضاء، نهايات الأسطر، Unicode)، فإن old_clean و fresh_clean يبدوان متطابقين → لا يتم إجراء أي تحديث.
هذا عن قصد، لمنع التغيير من ضوضاء التغذية.
⸻
ملخص
• الوقت يحدد التفرد (ينشئ دائمًا موضوعًا جديدًا عند تغيير الأوقات).
• تغييرات الموقع → دفعة مرئية (حتى يلاحظ المستخدمون تحديثات المكان).
• تغييرات الوصف → تحديث هادئ (مراجعة ولكن لا دفعة).
• علامة UID = مفتاح هوية موثوق، يضمن العثور دائمًا على الموضوع الصحيح، حتى لو كان الوصف قديمًا أو صاخبًا.
هذا يحقق توازنًا جيدًا: التغييرات المهمة تظهر في الأحدث، والتغييرات غير المهمة تبقى غير مرئية.
بالنظر إلى الوراء، من المضحك نوعًا ما كيف تطورت هذه الملحمة بأكملها.
البرنامج النصي للمستورد نفسه قوي الآن: علامات UID، منطق إزالة التكرار، التحديثات الهادفة مقابل الصامتة، مساحات أسماء العلامات… كل الأشياء التي تريدها بالفعل في بيئة الإنتاج. تتوافق السلوكيات تمامًا مع الملاحظات التي نشرتها - تحدد الأوقات التفرد، وتشغل المواقع زيادة، وتحدث الأوصاف بصمت، وتحافظ علامات UID على كل شيء مثبتًا. إنه أنيق، إنه متوقع، لقد تم الانتهاء منه.
في غضون ذلك، كان موضوع Meta المسكين الذي استضاف كل شيء… حسنًا، محكومًا عليه بالفشل.
بدأ حياته بالرد كحساب وهمي (بداية قوية )، وتضخم إلى سلسلة من أكوام التعليمات البرمجية ولقطات الشاشة، ثم تطور إلى سجل تغييرات زائف به عدد من الالتزامات أكثر من المستودع نفسه. وفقط عندما أصبح البرنامج النصي مستقرًا أخيرًا؟ تم جدولته للحذف.
بصراحة، إنه أمر شاعري. الغرض الكامل للبرنامج النصي هو منع الأحداث المكررة من تلويث منتدىك. الموضوع نفسه؟ يُنظر إليه على أنه مكرر، ويتم تمييزه بصمت لجمع القمامة. المصير نفسه الذي بُني لمنعه أصبح قدره.
لذا، إليكم هذا الموضوع المحكوم عليه بالفشل:
لم تقم بزيادة “الأحدث”، لكنك زدت قلوبنا.
أنا متردد في الخوض في الإعداد والصيانة المطلوبة لتشغيل البرنامج النصي الرائع الخاص بك كما هو (وأشتبه في أن العديد من المستضيفين الذاتيين سيكونون في نفس القارب).
ملخص سريع: أقوم حاليًا بتشغيل ثلاث نسخ من برنامج استيراد Python الخاص بي من ICS إلى Discourse (جدول مواعيد الجامعة، حجوزات مركز الرياضة، وتقويم Outlook). بدأت في تغليفه كمكون إضافي لـ Discourse، لكن إصدار المكون الإضافي لم يصل إلى مجموعة ميزات البرنامج النصي - ويرجع ذلك أساسًا إلى أن كل تغذية تحتاج إلى معالجة مخصصة (غرائب معرفات فريدة، تحديثات جزئية، إلغاءات، مراجعات مزعجة، إلخ). المكون الإضافي الخاص بـ Angus رائع للعديد من الحالات؛ حالات الاستخدام الخاصة بي تبدو أكثر “خاصة بالتغذية”.
لدي أيضًا طلب سحب مفتوح ضد النواة يهدف إلى تقليل ضوضاء زر “الأحدث” الأزرق أثناء تحديثات ICS الكبيرة/المتقطعة. مع التغذيات المزدحمة (مثل جداول مواعيد الجامعات)، يمكن لمجموعة من التعديلات ذات القيمة المنخفضة أن تبقي “الأحدث” في حالة اهتزاز؛ يقوم طلب السحب بتنفيذ زر “المواضيع الجديدة” بشكل فعال عندما يكون “الأحدث” مفتوحًا أثناء تشغيل دفعة آلية. يسعدني ربط طلب السحب هذا هنا إذا كان مفيدًا.
على المدى الطويل: أنا حاليًا على IONOS المستضاف ذاتيًا. إذا انتقلت إلى الاستضافة الرسمية لاحقًا، فلا يزال بإمكاني الاستمتاع بطريقة للحفاظ على تدفق Python (أو ما يعادله) دون الحاجة إلى ميزات Enterprise، إذا كان ICS الوارد موجودًا هناك. أشك في أن حلاً عامًا للنواة/المكون الإضافي يمكن أن يعمل إذا سمح بـ “محولات” قابلة للتوصيل لكل تغذية مع الحفاظ على التماثل القوي (معرف فريد لـ ICS)، ومعالجة الإلغاء، ودلالات التعديل بدون رفع.
إذا كان هناك اهتمام، يمكنني رسم واجهة محول بسيطة ومسار ترحيل من برنامج Python النصي الخاص بي إلى مهمة Ruby، أو المساهمة بقطع مستقلة عن التغذية (تعيين معرف فريد، إلغاء الضوضاء/التحديثات بدون رفع، منطق الإلغاء) في المكون الإضافي للتقويم/الأحداث.
هذا سؤال جيد يا ناثان - وأعتقد أن هناك بالتأكيد مجالًا لنهج بسيط وغير مرتبط بالخلاصة يمكن أن يعيش إما كتوسيع صغير لمكون الإضافي للتقويم/الحدث أو كوظيفة أساسية خفيفة الوزن.
لكي يكون طلب السحب مفيدًا بشكل عام، يبدو أن المفتاح هو جعل المستورد قائمًا على المحول بدلاً من كونه خاصًا بالخلاصة. شيء مثل:
تحدد كل خلاصة محولًا صغيرًا (يمكن أن يكون بايثون أو YAML أو روبي) يقوم بتعيين حقول ICS → حقول موضوع Discourse (title، body، tags، start، end، location، إلخ).
تتعامل الوظيفة الأساسية مع التكرار (تعيين UID ↔ معرف الموضوع)، والإلغاء (STATUS:CANCELLED)، والتعديلات الهادئة (التحديث دون تخطي الأحدث).
يمكن للمكونات الإضافية أو إعدادات الموقع تكوين فترة الاستطلاع، وتعيينات العلامات، وسياسة التخطي (always، never، on major change).
بهذه الطريقة، يمكن للمؤسسات ذات الخلاصات الصاخبة أو المعقدة (جداول الجامعات، حجوزات الغرف، تقويمات Outlook، إلخ) توفير محول مناسب لبياناتها دون ترميز أي شيء بشكل ثابت في الوظيفة الأساسية.
إذا كان هناك اهتمام، فسأكون سعيدًا بتحديد واجهة المحول هذه أو إنشاء نموذج أولي لمساعد “ICS upsert” الأساسي كوظيفة روبي يمكن للآخرين البناء عليها - حتى يتطور هذا تدريجيًا من نصوص بايثون مستقلة إلى شيء قابل للصيانة وعام ضمن نظام Discourse البيئي.