JavaRush /مدونة جافا /Random-AR /نحن نضيف القدرة على الاشتراك في مجموعة من المقالات. (الجز...
Roman Beekeeper
مستوى

نحن نضيف القدرة على الاشتراك في مجموعة من المقالات. (الجزء الأول) - "مشروع جافا من الألف إلى الياء"

نشرت في المجموعة
مرحبًا! اليوم سوف نقوم بإضافة اشتراك لمجموعة مقالات في JavaRush. وهذا يتوافق مع إصدار JRTB-5 على GitHub. اسمحوا لي أن أشرح: يحتوي JavaRush على قسم يسمى المقالات ، وفيه مجموعات من المقالات. تتمثل الفكرة في تلقي إشعارات حول مقال جديد من مجموعة واحدة أو أكثر من خلال روبوت التلغرام."مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 1

أضف JRTB-5

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

ما هو التباهي؟ دعونا معرفة ذلك الآن

لم نتحدث عن التباهي بعد. بالنسبة لأولئك الذين لا يعرفون، سأشرح ذلك باختصار: هذا هو المكان الذي يمكنك من خلاله إلقاء نظرة علنية على واجهة برمجة التطبيقات (API) الخاصة بالخادم ومحاولة تقديم بعض الطلبات إليها. عادةً ما يقوم التباهي بتجميع الطلبات المحتملة. في حالتنا، هناك ثلاث مجموعات: سؤال المنتدى ، المجموعة ، المنشور . سيكون في كل مجموعة طلب واحد أو أكثر يشير إلى جميع البيانات اللازمة لبناء هذا الطلب (أي ما هي المعلمات الإضافية التي يمكن تمريرها، وماذا تفعل بها، وما هي طريقة http، وما إلى ذلك). أنصحك بقراءة ومشاهدة المزيد حول هذا الموضوع، لأن هذا هو الجزء من التطوير الذي سيواجهه كل واحد منكم تقريبًا. لمعرفة ذلك، دعونا نتعرف على عدد المجموعات الموجودة في JavaRush. للقيام بذلك، قم بتوسيع مجموعة التحكم في المجموعة وحدد Get request /api/1.0/rest/groups/count . سيعيد لنا عدد المجموعات في JavaRush. لننظر: "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 2الصورة توضح أن هذا الاستعلام يدعم عدة معلمات (الاستعلام، النوع، المرشح). لتجربة هذا الطلب، تحتاج إلى العثور على زر جربه ، وبعد ذلك يمكن تكوين هذه المعلمات: "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 3يمكنك أيضًا تكوين النوع والتصفية والاستعلام هناك (إنه أمر مثير للاهتمام هنا: سيكون هذا بحثًا نصيًا في مجموعة). لكن في الوقت الحالي، لنقم بتشغيله دون أي قيود ونرى عدد المجموعات الموجودة في JavaRush. للقيام بذلك، انقر على تنفيذ. أدناه سيكون هناك استجابة (في قسم استجابة الخادم) لهذا الطلب: "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 4نرى أن هناك 30 مجموعة في المجموع ، وقد تم إكمال هذا الطلب في 95 مللي ثانية وهناك مجموعة من بعض الرؤوس في الاستجابة. بعد ذلك، دعونا نحاول تكوين بعض المعلمات. دعنا نختار معلمة النوع المساوية لقيمة الشركة ونرى كيف تتغير النتيجة: "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 5هناك 4 منها، كيف يمكن التحقق من ذلك؟ الأمر سهل: يمكنك الانتقال إلى موقع الويب والعثور على قسم المقالات وتحديد جميع المجموعات وإضافة الفلتر المناسب هناك ( https://javarush.com/groups/all?type=COMPANY ). ونعم، في الواقع، هناك 4 منهم فقط، على الرغم من وجود ثلاثة في الواقع:D "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 6حتى الآن يناسبهم ذلك. بالمناسبة، إذا تحققنا من الجامعات، فلا يوجد أي منها حتى الآن. للمتعة فقط، انظر ماذا يحدث إذا قمت بتعيين عامل التصفية = MY في متصفح قمت بتسجيل الدخول فيه إلى Javarush ولم تقم بتسجيل الدخول. المزيد عن التباهي - في هذه المقالة عن حبري .

كتابة عميل لـ Javarush API للمجموعات

الآن، استنادًا إلى واجهة برمجة التطبيقات المفتوحة، سنكتب عميل Java يمكنه تقديم الطلبات وتلقي الاستجابات ومعرفة الكائنات التي ستصل بالضبط. سنأخذ أيضًا كائنات من التباهي، من قسم النماذج (في أسفل الصفحة). لنقم بإنشاء حزمة جديدة ونطلق عليها اسم javarushclient بجوار الخدمة أو المستودع. في المستقبل، سننقل هذا إلى مكتبة منفصلة داخل منظمة مجتمع Javarush وسنستخدمه حصريًا باعتباره تابعًا.
لقد كتبت بالفعل عن إنشاء عملاء Java في المقالة "دليل إنشاء عميل لـ Skyscanner API ونشره في jCenter وMaven Central" .
أولاً، تحتاج إلى إضافة Unitrest، وهي مكتبة لإنشاء طلبات http إلى JavaRush API:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
ووضع النسخة في كتلة الخصائص:
<unirest.version>3.11.01</unirest.version>
بمجرد أن يكون لدينا تبعية، يمكننا البدء في إضافة التعليمات البرمجية. لنقم بإنشاء عميل لمجموعات JavaRushGroupClient وتنفيذه في فئة JavaRushGroupClientImpl. لكن عليك أولاً إنشاء DTOs (كائنات نقل البيانات) - أي الفئات التي ستحمل كائناتها جميع البيانات اللازمة للعميل. يمكن مشاهدة جميع النماذج بطريقة التباهي، ويوجد في الأسفل قسم النماذج ، حيث يمكنك عدها. هذا ما يبدو عليه GroupDiscussionInfo في التباهي: في حزمة javarushclient، سنقوم بإنشاء "مشروع جافا من الألف إلى الياء": إضافة إمكانية الاشتراك في مجموعة من المقالات.  الجزء 1 - 7حزمة dto ، والتي سنضيف إليها، بناءً على بيانات من التباهي، الفئات التالية:
  • حالة معلومات المجموعة :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Member group status.
    */
    public enum MeGroupInfoStatus {
       UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED
    }

  • معلومات المجموعة :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * Group information related to authorized user. If there is no user - will be null.
    */
    @Data
    public class MeGroupInfo {
       private MeGroupInfoStatus status;
       private Integer userGroupId;
    }

  • نوع معلومات المجموعة :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Info type;
    */
    public enum GroupInfoType {
       UNKNOWN, CITY, COMPANY, COLLEGE, TECH, SPECIAL, COUNTRY
    }

  • معلومات مناقشة المستخدم :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * DTO for User discussion info.
    */
    @Data
    public class UserDiscussionInfo {
    
       private Boolean isBookmarked;
       private Integer lastTime;
       private Integer newCommentsCount;
    }

  • حالة رؤية المجموعة :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Visibility status.
    */
    public enum GroupVisibilityStatus {
       UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED
    }

  • ثم - معلومات المجموعة :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    import lombok.ToString;
    
    /**
    * Group Info DTO class.
    */
    @Data
    @ToString
    public class GroupInfo {
    
       private Integer id;
       private String avatarUrl;
       private String createTime;
       private String description;
       private String key;
       private Integer levelToEditor;
       private MeGroupInfo meGroupInfo;
       private String pictureUrl;
       private String title;
       private GroupInfoType type;
       private Integer userCount;
       private GroupVisibilityStatus visibilityStatus;
    }

نظرًا لأن GroupInfo و GroupDiscussionInfo متماثلان تمامًا تقريبًا، فلنربطهما بالميراث - GroupDiscusionInfo :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
* Group discussion info class.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class GroupDiscussionInfo extends GroupInfo {

   private UserDiscussionInfo userDiscussionInfo;
   private Integer commentsCount;
}
سنحتاج أيضًا إلى عامل تصفية لطلب GroupFilter :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* Filters for group requests.
*/
public enum GroupFilter {

   UNKNOWN, MY, ALL
}
في طلب الحصول على المعرف، يتم إرجاع GroupDiscussionInfo، وفي طلب مجموعة من المجموعات، يمكنك الحصول على كل من GroupInfo وGroupDiscussionInfo. نظرًا لأن الطلبات يمكن أن تحتوي على نوع واستعلام وتصفية وإزاحة وحدود، فلنقم بإنشاء فئة GroupRequestArgs منفصلة ونجعلها فئة منشئة (اقرأ ما هو نمط المنشئ):
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group requests.
*/
@Builder
@Getter
public class GroupRequestArgs {

   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   /**
    * specified where to start getting groups
    */
   private final Integer offset;
   /**
    * Limited number of groups.
    */
   private final Integer limit;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if(nonNull(query)) {
           queries.put("query", query);
       }
       if(nonNull(type)) {
           queries.put("type", type);
       }
       if(nonNull(filter)) {
           queries.put("filter", filter);
       }
       if(nonNull(offset)) {
           queries.put("offset", offset);
       }
       if(nonNull(limit)) {
           queries.put("limit", limit);
       }
       return queries;
   }
}
للبحث عن عدد المجموعات، يختلف الأمر قليلاً. لديها فقط الاستعلام والنوع والتصفية. ويبدو أنك لا تريد تكرار الكود. في الوقت نفسه، إذا بدأت في الجمع بينهما، فسيظهر الأمر قبيحًا عند العمل مع شركات البناء. لذلك قررت فصلهما وتكرار الكود. هذا ما يبدو عليه GroupCountRequestArgs :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Builder;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group count requests.
*/
@Builder
@Getter
public class GroupsCountRequestArgs {
   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if (nonNull(query)) {
           queries.put("query", query);
       }
       if (nonNull(type)) {
           queries.put("type", type);
       }
       if (nonNull(filter)) {
           queries.put("filter", filter);
       }
       return queries;
   }
}
نعم، لم أذكر أن الفئتين الأخيرتين لهما طريقة populateQueries، والتي ستقوم بإعداد الخريطة لإنشاء استعلام (ستراه لاحقًا). بناءً على الفئات الموضحة أعلاه، فلنقم بإنشاء واجهة لـ JavaRushGroupClient :
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;

import java.util.List;

/**
 * Client for Javarush Open API corresponds to Groups.
 */
public interface JavaRushGroupClient {

    /**
     * Get all the {@link GroupInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}.
     * @return the collection of the {@link GroupInfo} objects.
     */
    List<GroupInfo> getGroupList(GroupRequestArgs requestArgs);

    /**
     * Get all the {@link GroupDiscussionInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}
     * @return the collection of the {@link GroupDiscussionInfo} objects.
     */
    List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs);

    /**
     * Get count of groups filtered by provided {@link GroupRequestArgs}.
     *
     * @param countRequestArgs provided {@link GroupsCountRequestArgs}.
     * @return the count of the groups.
     */
    Integer getGroupCount(GroupsCountRequestArgs countRequestArgs);

    /**
     * Get {@link GroupDiscussionInfo} by provided ID.
     *
     * @param id provided ID.
     * @return {@link GroupDiscussionInfo} object.
     */
    GroupDiscussionInfo getGroupById(Integer id);
}
طلبان مختلفان للحالة التي نريد فيها إضافة معلومات GroupInfo أو GroupDiscussionInfo. بخلاف ذلك، تكون هذه الطلبات متطابقة، والفرق الوحيد هو أن علامة includeDiscussion في أحدهما ستكون صحيحة، وفي الأخرى ستكون خاطئة. ولذلك كانت هناك 4 طرق، وليس ثلاثة. والآن لنبدأ بالتنفيذ:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* Implementation of the {@link JavaRushGroupClient} interface.
*/
@Component
public class JavaRushGroupClientImpl implements JavaRushGroupClient {

   private final String javarushApiGroupPath;

   public JavaRushGroupClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiGroupPath = javarushApi + "/groups";
   }

   @Override
   public List<GroupInfo> getGroupList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupInfo>>() {
               })
               .getBody();
   }

   @Override
   public List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupDiscussionInfo>>() {
               })
               .getBody();
   }

   @Override
   public Integer getGroupCount(GroupsCountRequestArgs countRequestArgs) {
       return Integer.valueOf(
               Unirest.get(String.format("%s/count", javarushApiGroupPath))
                       .queryString(countRequestArgs.populateQueries())
                       .asString()
                       .getBody()
       );
   }

   @Override
   public GroupDiscussionInfo getGroupById(Integer id) {
       return Unirest.get(String.format("%s/group%s", javarushApiGroupPath, id.toString()))
               .asObject(GroupDiscussionInfo.class)
               .getBody();
   }


}
أقوم بإضافة المسار إلى واجهة برمجة التطبيقات في المُنشئ باستخدام التعليق التوضيحي للقيمة المألوف بالفعل. وهذا يعني أن القيمة الموجودة داخل التعليق التوضيحي تتوافق مع حقل في ملف الخصائص. لذلك، دعونا نضيف سطرًا جديدًا إلى application.properties:
javarush.api.path=https://javarush.com/api/1.0/rest
ستكون هذه القيمة الآن في مكان واحد لجميع عملاء واجهة برمجة التطبيقات، وإذا تغير مسار واجهة برمجة التطبيقات، فسنقوم بتحديثه بسرعة. في السابق، قمت بدق المسامير بالمجهر، وتلقيت استجابة من طلب http عبر Unirest، وقمت بترجمتها إلى سلسلة ثم قمت بتحليل هذه السلسلة من خلال Jackson... لقد كان الأمر مخيفًا ومملًا ويتطلب العديد من الأشياء الإضافية. في هذه المكتبة يمكنك أن ترى كيف تبدو. بمجرد أن أضع يدي عليه، سأعيد صياغة كل شيء.
أي شخص يرغب في محاولة تحديث هذه المكتبة - إضافة كائنات الاستقبال فقط باستخدام أدوات مكتبة Unirest - يكتب في رسالة شخصية أو كإصدار جديد في المكتبة نفسها. ستكون هذه تجربة عمل حقيقية بالنسبة لك، لكنني لا أمانع. سأجري مراجعة كاملة للكود وسأساعد إذا لزم الأمر.
والسؤال الآن هو: هل يعمل الكود الخاص بنا كما نتوقع؟ الجواب سهل: ما عليك سوى كتابة الاختبارات لهم. كما قلت أكثر من مرة، يجب أن يكون المطورون قادرين على كتابة الاختبارات. ولذلك، باستخدام واجهة مستخدم Swagger الخاصة بنا، سنرسل الطلبات وننظر إلى الردود ونستبدلها في الاختبارات كالنتيجة المتوقعة. ربما لاحظت على الفور أن عدد المجموعات ليس ثابتًا ويمكن أن يتغير. وأنت على حق. والسؤال الوحيد هو كم مرة يتغير هذا الرقم؟ نادرًا جدًا، لذلك يمكننا القول على مدار عدة أشهر أن هذه القيمة ستكون ثابتة. وإذا تغير شيء ما، فسوف نقوم بتحديث الاختبارات. لقاء - JavaRushGroupClientTest:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfoType.TECH;

@DisplayName("Integration-level testing for JavaRushGroupClientImplTest")
class JavaRushGroupClientTest {

   private final JavaRushGroupClient groupClient = new JavaRushGroupClientImpl("https://javarush.com/api/1.0/rest");

   @Test
   public void shouldProperlyGetGroupsWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupsDiscWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetGroupDiscWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder().build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(30, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupTECHCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder()
               .type(TECH)
               .build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(7, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupById() {
       //given
       Integer androidGroupId = 16;

       //when
       GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId);

       //then
       Assertions.assertNotNull(groupById);
       Assertions.assertEquals(16, groupById.getId());
       Assertions.assertEquals(TECH, groupById.getType());
       Assertions.assertEquals("android", groupById.getKey());
   }
}
تتم كتابة الاختبارات بنفس الأسلوب كما كان من قبل. هناك عدة اختبارات لكل طلب. لا فائدة من اختبار كل شيء، لأنني أعتقد أن واجهة برمجة التطبيقات هذه قد تم اختبارها بالفعل بأفضل طريقة.

خاتمة

كجزء من هذه المقالة، أضفنا عميل Java للمجموعات إلى JavaRush API. كما يقولون، عش وتعلم. أثناء كتابتي لهذا العميل، استفدت من وثائقهم واستخدمت العمل بشكل ملائم مع العناصر التي يقدمونها. ألفت انتباهكم إلى المهمة التي اقترحتها. إذا كان أي شخص مهتمًا، فاكتب لي رسالة خاصة، وأنا متأكد من أنها ستكون تجربة ممتعة للغاية. وكان هذا الجزء الأول. في الخطوة الثانية، سنقوم بتنفيذ أمر الإضافة مباشرة و(إذا قمنا بتضمينه في مقالة واحدة) سنضيف الحصول على قائمة بالمجموعات التي اشترك فيها المستخدم. بعد ذلك، أي شخص لديه الرغبة والموهبة في كتابة النصوص للبوت، يرجى الكتابة لي في رسالة خاصة. أنا لست خبيرًا في هذا الأمر وأي مساعدة ستكون مفيدة جدًا. دعونا نضفي الطابع الرسمي على كل هذا كتطوير مفتوح المصدر، سيكون مثيرًا للاهتمام! حسنًا، كالعادة - قم بالإعجاب والاشتراك والجرس ومنح مشروعنا نجمة وكتابة التعليقات وتقييم المقالة!
روابط مفيدة

توجد قائمة بجميع المواد الموجودة في السلسلة في بداية هذه المقالة.

تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION