إنشاء أتمتة مخصصة

:information_source: هذه مسودة، وقد تحتاج إلى بعض العمل الإضافي.

المفردات

  • trigger: يمثل اسم المشغل، على سبيل المثال: user_added_to_group
  • triggerable: يمثل منطق الكود المرتبط بالمشغل، على سبيل المثال: triggers/user_added_to_group_.rb
  • script: يمثل اسم النص البرمجي، على سبيل المثال: send_pms
  • scriptable: يمثل منطق الكود المرتبط بالنص البرمجي، على سبيل المثال: scripts/send_pms.rb

واجهة برمجة تطبيقات المكون الإضافي (Plugin API)

add_automation_scriptable(name, &block)
add_automation_triggerable(name, &block)

واجهة برمجة تطبيقات النص البرمجي (Scriptable API)

حقل (field)

field :name, component: يتيح لك إضافة قيمة قابلة للتخصيص في واجهة المستخدم الخاصة بالأتمتة الخاصة بك.

قائمة المكونات الصالحة:

# foo يجب أن يكون فريدًا ويمثل اسم الحقل الخاص بك.

field :foo, component: :text # ينشئ حقل إدخال نصي
field :foo, component: :list # ينشئ حقل إدخال نصي متعدد التحديدات حيث يمكن للمستخدمين إدخال القيم
field :foo, component: :choices, extra: { content: [ {id: 1, name: 'your.own.i18n.key.path' } ] } # ينشئ مربع تحكم مع محتوى مخصص
field :foo, component: :boolean # ينشئ مربع اختيار
field :foo, component: :category # ينشئ منتقي فئات
field :foo, component: :group # ينشئ منتقي مجموعات
field :foo, component: :date_time # ينشئ منتقي تاريخ ووقت
field :foo, component: :tags # ينشئ منتقي علامات
field :foo, component: :user  # ينشئ منتقي مستخدمين
field :foo, component: :pms  # يسمح بإنشاء قالب رسالة خاصة واحدة أو أكثر
field :foo, component: :categories  # يسمح بتحديد صفر أو أكثر من الفئات
field :foo, component: :key-value  # يسمح بإنشاء أزواج مفتاح-قيمة
field :foo, component: :message  # يسمح بإنشاء رسالة خاصة مع متغيرات قابلة للاستبدال
field :foo, component: :trustlevel  # يسمح بتحديد مستوى ثقة واحد أو أكثر
المشغلات (triggerables) والمشغل! (triggerable!)
# يتيح لك تحديد قائمة المشغلات المسموح بها لنص برمجي
triggerables %i[recurring]

# يتيح لك فرض مشغل لنصك البرمجي ويتيح لك أيضًا فرض بعض الحالات على الحقول
field :recurring, component: :boolean
triggerable! :recurring, state: { foo: false }
العناصر النائبة (placeholders)
# يتيح لك تمييز مفتاح كقابل للاستبدال في النصوص باستخدام صيغة العنصر النائب `%%sender%%`
placeholder :sender

لاحظ أن مسؤولية النص البرمجي هي توفير قيم للعناصر النائبة وتطبيق الاستبدال باستخدام input = utils.apply_placeholders(input, { sender: 'bob' })

النص البرمجي (script)

هذا هو جوهر الأتمتة وحيث تحدث كل المنطق.

# يتم إرسال السياق عند تشغيل الأتمتة، ويمكن أن يختلف كثيرًا بين المشغلات
script do |context, fields, automation|
end

التوطين (Localization)

كل حقل ستستخدمه سيعتمد على مفاتيح i18n وسيتم تقسيمه حسب اسم المشغل/النص البرمجي.

على سبيل المثال، نص برمجي بهذه المحتويات:

field :post_created_edited, component: :category

سيتطلب المفاتيح التالية في client.en.yml:

en:
  js:
    discourse_automation:
      scriptables:
        post_created_edited:
          fields:
            restricted_category:
              label: Category
              description: Optional, allows to limit trigger execution to this category

لاحظ أن الوصف اختياري هنا.


هذه الوثيقة تخضع للتحكم في الإصدار - اقترح تغييرات على github.

7 إعجابات

عندما رأيت أن هذا موضوع جديد، شعرت بالحماس: اعتقدت أنه تم مشاركة المزيد من التفاصيل! :ضحك:

بصفتي شخصًا لا يبرمج بلغة روبي، ولكنه مهتم جدًا بأتمتة سير العمل، كنت آمل أن أتمكن من فهم المزيد قليلاً من خلال الأمثلة…

:تفكير:

أعتقد أنني سأحتاج إلى البدء من Developing Discourse Plugins - Part 1 - Create a basic plugin… :ابتسامة_عرق:

8 إعجابات

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

أتفق على أنه سيكون من الرائع لو كانت هذه الخطوات أكثر تفصيلاً. لقد أرسلت نداءً للمساعدة المجتمعية هنا لمعرفة ما إذا كان لدى أي شخص خبرة في مثل هذه الأمور: :crossed_fingers:

5 إعجابات

سيكون مثال “أهلاً بالعالم” رائعًا.
أين يجب تخزين البرامج النصية؟ أود تجربة ذلك قليلاً.

4 إعجابات

أعتقد أنه يمكنك كتابة نصوص برمجية مخصصة باستخدام Chat GPT مع هذا المكون الإضافي.

ربما يكون أفضل مكان للبدء في البحث هو في البرنامج النصي للأتمتة الذي تمت إضافته إلى المكون الإضافي لاستكشاف البيانات: discourse-data-explorer/plugin.rb at main · discourse/discourse-data-explorer · GitHub. يجدر أيضًا إلقاء نظرة على البرامج النصية والمشغلات الحالية للمكون الإضافي للأتمتة: https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation

نظرًا لعدم وجود الكثير من المعلومات حول Meta حول إضافة أتمتة مخصصة، إليك مثال لملف plugin.rb يضيف برنامجًا نصيًا لتحديث تفضيل البريد الإلكتروني لملخص نشاط المستخدم. يمكن تشغيل البرنامج النصي بواسطة مشغلات المكون الإضافي للأتمتة ‘user_added_to_group’ أو ‘user_removed_from_group’.

# frozen_string_literal: true

# name: automation-script-example
# about: An example of how to add a script to an automation
# version: 0.0.1
# authors: scossar

enabled_site_setting :automation_script_example_enabled

after_initialize do
  reloadable_patch do
    if defined?(DiscourseAutomation)
      DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS =
        "user_update_summary_email_options"
      add_automation_scriptable(
        DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS
      ) do

        field :email_digests, component: :boolean

        version 1
        triggerables [:user_added_to_group, :user_removed_from_group]

        script do |context, fields, automation|
          if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")
            user_id = context["user"].id
            digest_option = fields.dig("email_digests", "value")
            user_option = UserOption.find_by(user_id: user_id)

            if (user_option)
              user_option.update(email_digests: digest_option)
            end
          end
        end
      end
    end
  end
end

الكود الكامل للمكون الإضافي هنا: GitHub - scossar/automation-script-example: An example of how to add a custom script to the Discourse Automation plugin..

:warning: يرجى عدم استخدام هذا الكود كما هو في موقع إنتاجي. لم ألقِ نظرة على كود الأتمتة قبل هذا المساء. إذا تلقيت أي ملاحظات حول المشكلات المحتملة في الكود، فسوف أقوم بتحديث هذا المنشور ومستودع GitHub.

تعديل: كان قلقي هو كيفية التعامل على أفضل وجه مع حالة تشغيل العديد من نصوص الأتمتة بواسطة مشغلات ‘user_added_to_group’ أو ‘user_removed_from_group’. كان الإصدار الأولي للمكون الإضافي يتحقق من:

fields.has_key?("email_digests")

لكن هذا بدا غير موثوق. ماذا لو تمت إضافة نص برمجي آخر يحتوي أيضًا على مفتاح email_digests؟

يقوم الكود المحدث بتمرير المعلمة automation إلى كتلة الكود ويتحقق من:

automation.script == "user_update_summary_email_options"

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

7 إعجابات

أود أن أعرف هذا أيضًا - بمجرد إنشاء مستودع مثل مستودع @simon ، كيف يمكن للمكون الإضافي الوصول إليه؟

هل يتعين علينا عمل نسخة متفرعة من المكون الإضافي بأكمله وإسقاطه مع النسخ الموجودة في https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation/scripts؟ أم أن هناك طريقة أكثر أناقة؟

إعجاب واحد (1)

تحتاج إلى تثبيته مثل أي مكون إضافي آخر لـ Discourse: تثبيت المكونات الإضافية في Discourse. لذا ستقوم بتثبيت مكون الأتمتة الإضافي وتثبيت المكون الإضافي الخاص بك الذي يضيف البرامج النصية المخصصة. السبب في أنه يعمل هو بسبب الطرق المحددة هنا: https://github.com/discourse/discourse-automation/blob/main/lib/plugin/instance.rb. في الكود المثال الذي نشرته أعلاه، سترى أنه تتم إضافة البرنامج النصي المخصص باستدعاء لـ add_automation_scriptable.

ملاحظة: لا تقم بتثبيت الأتمتة المثال من مستودع github الخاص بي، بل خذه كمثال لكيفية توسيع مكون الأتمتة الإضافي. (لقد نسيت أنني قمت بالربط به هنا وقمت بتحديثه بحيث يعمل فقط مع النسخة المعدلة من مكون الأتمتة الإضافي لـ Discourse. الكود الذي ربطته هنا لا يزال صالحًا: Create custom Automations - #6 by simon. سأقوم بتحديث المكون الإضافي automation-script-example في أقرب وقت ممكن حتى يعمل بدون التغييرات التي أجريتها على نسختي المعدلة من مكون الأتمتة الإضافي.)

لقد كان قلقي لا أساس له. هذا الشرط ليس ضروريًا:

if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")

سأقوم بتحديث المثال قريبًا.

4 إعجابات

هل فهمي صحيح بأن الأتمتة المخصصة تتطلب تثبيتًا مستضافًا ذاتيًا (أو وصولاً مباشرًا إلى الواجهة الخلفية لنظام الملفات حيث تم تثبيت Discourse)؟

إعجابَين (2)

نعم، ولكننا منفتحون جدًا على دمج نصوص برمجية جديدة للأتمتة في البناء المجتمعي، ماذا تفكر في بنائه؟

6 إعجابات

Specifically, we’re looking for a way to replace some specific string in posts (we don’t care strongly about the exact syntax, but something like the plaintext @ref `Random.rand!` ) with a formatted link like Random.rand!. Looking up the exact URL is a complicated process with tens of thousands of possible targets that is completely infeasible with regexes (like Auto linkify words/watched words does) and much easier with a Turing-complete plugin-like environment… so I was curious if automations could do this.

So I was looking for a post-edit action, somewhat akin to what the @system user does when you quote the entire previous post (see here). It’d be an “Edit post” script that would trigger on “Post created” (or perhaps after-post-cooked)… but I suppose that the automations framework wouldn’t allow such a general “post-edit” action. I think it’d need to be a pretty specific link-to-Julia-docs custom automation, which certainly doesn’t make sense in a community build.

I could be barking up the wrong tree here with custom automations; I was just exploring what’s possible. Of course, as a forum for programming language enthusiasts, intrepid users are already thinking about programming bots that could use the discourse API to do this.

على وجه التحديد، نحن نبحث عن طريقة لاستبدال سلسلة نصية معينة في المشاركات (لا نهتم كثيرًا بالصيغة الدقيقة، ولكن شيئًا مثل النص العادي @ref `Random.rand!` ) برابط منسق مثل Random.rand!. إن البحث عن عنوان URL الدقيق هو عملية معقدة مع عشرات الآلاف من الأهداف المحتملة وهو أمر غير ممكن تمامًا باستخدام التعبيرات العادية (مثلما تفعل الكلمات التي يتم ربطها تلقائيًا/الكلمات التي تتم مراقبتها) وأسهل بكثير باستخدام بيئة شبيهة بالإضافات كاملة تورينج… لذلك كنت أتساءل عما إذا كانت الأتمتة يمكنها القيام بذلك.

لذلك كنت أبحث عن إجراء ما بعد التحرير، يشبه إلى حد ما ما يفعله المستخدم @system عندما تقتبس المشاركة السابقة بأكملها (انظر هنا). سيكون نصًا برمجيًا “لتحرير المشاركة” يتم تشغيله عند “إنشاء المشاركة” (أو ربما بعد معالجة المشاركة)… ولكن أفترض أن إطار عمل الأتمتة لن يسمح بمثل هذا الإجراء العام “ما بعد التحرير”. أعتقد أنه سيحتاج إلى أتمتة مخصصة محددة جدًا للربط بوثائق Julia، وهو بالتأكيد لا معنى له في بناء مجتمعي.

قد أكون مخطئًا هنا مع الأتمتة المخصصة؛ كنت أستكشف فقط ما هو ممكن. بالطبع، كمنتدى لعشاق لغات البرمجة، يفكر المستخدمون الشجعان بالفعل في برمجة روبوتات يمكنها استخدام واجهة برمجة تطبيقات Discourse للقيام بذلك.

إعجاب واحد (1)

لست متأكدًا من أن الأتمتة هي ما تبحث عنه هنا لأن شيئًا يبدو حاسمًا هنا هو تجربة “المستخدم النهائي”. مع الأتمتة، سيتم استبدال هذا بعد فوات الأوان.

بالتفكير في هذا النوع من المشاكل، ربما أوصي بالذهاب مع مكون إضافي مخصص أو مكون سمة.

يمكن أن يعمل مكون السمة على النحو التالي:

  1. يكتب المستخدم: ^Rand
  2. يتم إجراء مكالمة HTTP إلى خدمة خلفية تستضيفها والتي تسرد جميع الخيارات مع عناوين URL
  3. يختار المستخدم الخيار الذي يريده ويضغط على Enter
  4. يتم استبدال Markdown بـ [Random.rand!](https://docs.julialang.org/en/v1/stdlib/Random/#Random.rand!)

يمكن أن يعمل المكون الإضافي الذي يعدل خط أنابيب Markdown بشكل مشابه لـ onebox ويربط تلقائيًا أثناء الكتابة تاركًا بناء الجملة الأصلي. على سبيل المثال: ^Random.rand

أسمعك بشأن linkify ليس مثاليًا، الاكتشاف صعب، بالإضافة إلى أنه قد يتعين عليك استضافة موقع ويب حتى تقوم بتطبيعه إلى lookup.docs.julialang.org?q=Random.rand!

بالتأكيد مشكلة مثيرة للاهتمام للغاية. أعتقد أن تجربة المستخدم لمكون السمة يمكن أن تكون معقولة هنا.

إعجابَين (2)

هذا رائع جدًا، شكرًا لك على الأفكار والإرشادات هنا! سأطرح هذا في موضوع منفصل إذا وعندما تكون لدي المزيد من الأسئلة (أو الإجابات :).

إعجابَين (2)

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

إعجاب واحد (1)

@McUles و @nathank هل وجدت المعلومات التي كنت تبحث عنها؟

لجعل الأتمتة المخصصة تعمل، كان عليّ تعديل الملفات التالية:

تم إنشاء برنامج الأتمتة المخصص

تم التحديث: server.en.yml

تمت إضافة اسم الأتمتة المخصصة؛ والعنوان؛ والوصف في قسم scriptables في ملف yml.

تم التحديث: client.en.yml

تمت إضافة اسم الأتمتة المخصصة في scriptables؛ إضافة الكلمة المفتاحية ‘field’؛ داخل الكلمة المفتاحية field إضافة ‘field_name’ متبوعة بـ ‘label’ و ‘description’.

تم التحديث: scripts.rb

تمت إضافة اسم الأتمتة المخصصة في قائمة البرامج النصية. مثال: FILE_NAME = “file_name”

تم التحديث: plugin.rb

داخل ‘after_initialize do’، تمت إضافة المسار إلى برنامج الأتمتة المخصص. مثال: ‘lib/discourse_automation/scripts/file_name’

لا أفهم تمامًا ما وضعته هناك - هل هذه تعديلات على إضافة الأتمتة، أم مكونات مهمة لإضافة شقيقة تحتوي على الأتمتة المخصصة؟

سيكون من الرائع دمج هذا في الموضوع الأصلي

إعجابَين (2)

هذا هو. لم أكن أعرف حقًا إجابة سؤالك، لذا إليك كيف وجدت إجابة وأيضًا الإجابة على “هل هناك مثال، في مكان ما، يمكنني رؤيته؟”

أولاً، احصل على هذا: GitHub - discourse/all-the-plugins

ثم استخدم grep لشيء ما، مثل “add_automation_scriptable”، وبعد ذلك يمكنك معرفة ما الذي يستخدمه.

 (main) pfaffman@noreno:~/src/discourse-repos/all-the-plugins/official$ grep -r add_automation_scriptable
discourse-assign/plugin.rb:    add_automation_scriptable("random_assign") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_slack_message") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_chat_integration_message") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_pm") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_topic") do

لذا ربما انظر إلى discourse-assign أو data-explorer

إعجابَين (2)