Groovy

RED

Если вы хотите, чтобы бот действительно настраивался, то RED должен быть на вашем радаре. Учтите, что для его размещения вам понадобится собственный сервер.

Модульный подход RED означает, что нет двух одинаковых серверов, работающих под управлением RED, но есть и некоторые ключевые особенности. Как и MEE6, модерация является центральной функцией, с командами страйков или запретов, а также фильтрацией сообщений.

Здесь также есть боты и игры, воспроизведение музыки, поиск подарков, автосерверные сообщения и многое другое. Как и в MEE6, вы также можете настраивать команды ботов, настраивать имя и аватар вашего бота в соответствии с индивидуальным стилем вашего сервера.

Если вы не нашли нужной функции, вы можете расширить RED с помощью плагинов, которые кодируются на питоне. Вы можете искать созданные сообществом плагины на сайте RED.

Стэк технологий

RabbitMQ

  • Сообщения, которые хранятся в очереди, персистентные. Т.е. если система какое-то время недоступна, то сообщения никуда не пропадут и будут обработаны после того, как система поднимется. Перезапуск самого RabbitMQ также не приводит к потере сообщений.
  • Очередь служит буфером, который система может обрабатывать в комфортном режиме, избегая пиковых нагрузок. Понятно, что для запросов, выполнения которых ждет в интерфейсе пользователь, это не подойдет. В этом случае нужен незамедлительный ответ. А вот для разного рода асинхронных взаимодействий подходит очень хорошо.
  • Асинхронный режим взаимодействия. При этом также поддерживаются и синхронные вызовы.

Apache ServiceMix

  1. Собственно сам Karaf контейнер, в котором работают бандлы и который позволяет ими управлять: устанавливать / удалять / останавливать / запускать, просматривать логи, видеть зависимости компонентов и т.д.
  2. Широкий набор бандлов, которые выполняют классические интеграционные функции: валидация, различные трансформации (например, из JSON в XML, трансформации при помощи XSLT и т.д.), обогащение, роутинг, split and join, мониторинг, исполнение интеграционных процессов и т.д.
  3. Широкий набор различных адаптеров: файловые адаптеры, адаптеры к web и rest сервисам, JMS, RabbitMQ, Kafka и т.д. Полный список адаптеров можно посмотреть на сайте Camel.

Configuring a Groovy Scripting Origin

  1. In the Properties panel, on the General tab, configure the
    following properties:

    General Property Description
    Name Stage name.
    Description Optional description.
    Generates event records when events occur. Use for
    .
    Error record handling for the stage:

    • Discard — Discards the record.
    • Send to Error — Sends the record to the pipeline for error handling.
    • Stop Pipeline — Stops the pipeline.
  2. On the Performance tab, configure the following
    properties:

    Performance Property Description
    Batch Size Number of records to generate in a single batch.

    The script
    accesses this value with the
    constant and implements batch processing.

    Default value is
    1000. Data Collector honors values up to the Data Collector maximum batch size. The Data Collector default is 1000.

    Number of Threads Number of threads that generate data concurrently in
    parallel.

    The script accesses this value with the
    constant and implements
    multithreaded processing.

  3. On the Script tab, configure the following property:

    Script Property Description
    User Script

    Script that runs during pipeline execution.

    Tip: To toggle full-screen editing, press either F11
    or Esc, depending on the operating system, when the cursor
    is in the editor.

  4. On the Advanced tab, configure the following
    properties:

    Advanced Property Description
    Record type to use during script
    execution:

    • Data Collector Records — Select when scripts use Data Collector Java API methods to access records.
    • Native Objects — Select when scripts use native types to
      access records.

    Default value is Native Objects.

    Parameters in Script Script parameters and their values.

    The script accesses the
    values with the
    dictionary.

Стадии сборки

Их делят на инициализацию, конфигурацию и выполнение.

Идея состоит в том, что gradle собирает ациклический граф зависимостей и вызывает только необходимый минимум их них. Если я правильно понял, стадия инициализации происходит в тот момент, когда исполняется код из build.gradle.

Например, такой:

Или такой:

Возможно, это неочевидно, но вышеуказанное будет тормозить инициализацию. Чтобы не заниматься копированием файлов при каждой инициализации, нужно в задаче использоваль блок или — тогда код завернётся в замыкание и его позовут в момент выполнения задачи.

или так

В старых примерах вместо можно встретить оператор , но от него потом отказались из-за неочевидности поведения.

Loading Groovy Code on the Fly

The Maven compilation let us include Groovy files in our project and reference their classes and methods from Java.

Although, this is not enough if we want to change the logic at runtime: the compilation runs outside the runtime stage, so we still have to restart our application in order to see our changes.

To take advantage of the dynamic power (and risks) of Groovy, we need to explore the techniques available to load our files when our application is already running.

6.1. GroovyClassLoader

To achieve this, we need the GroovyClassLoader, which can parse source code in text or file format and generate the resulting class objects.

When the source is a file, the compilation result is also cached, to avoid overhead when we ask the loader multiple instances of the same class.

Script coming directly from a String object, instead, won’t be cached, hence calling the same script multiple times could still cause memory leaks.

GroovyClassLoader is the foundation other integration systems are built on.

The implementation is relatively simple:

6.2. GroovyShell

The Shell Script Loader parse() method accepts sources in text or file format and generates an instance of the Script class.

This instance inherits the run() method from Script, which executes the entire file top to bottom and returns the result given by the last line executed.

If we want to, we can also extend Script in our code, and override the default implementation to call directly our internal logic.

The implementation to call Script.run() looks like this:

Please note that the run() doesn’t accept parameters, so we would need to add to our file some global variables initialize them through the Binding object.

As this object is passed in the GroovyShell initialization, the variables are shared with all the Script instances.

If we prefer a more granular control, we can use invokeMethod(), which can access our own methods through reflection and pass arguments directly.

Let’s look at this implementation:

Under the covers, GroovyShell relies on the GroovyClassLoader for compiling and caching the resulting classes, so the same rules explained earlier apply in the same way.

6.3. GroovyScriptEngine

The GroovyScriptEngine class is particularly for those applications which rely on the reloading of a script and its dependencies.

Although we have these additional features, the implementation has only a few small differences:

This time we have to configure source roots, and we refer to the script with just its name, which is a bit cleaner.

Looking inside the loadScriptByName method, we can see right away the check isSourceNewer where the engine checks if the source currently in cache is still valid.

Every time our file changes, GroovyScriptEngine will automatically reload that particular file and all the classes depending on it.

Although this is a handy and powerful feature, it could cause a very dangerous side effect: reloading many times a huge number of files will result in CPU overhead without warning.

If that happens, we may need to implement our own caching mechanism to deal with this issue.

6.4. GroovyScriptEngineFactory (JSR-223)

JSR-223 provides a standard API for calling scripting frameworks since Java 6.

The implementation looks similar, although we go back to loading via full file paths:

It’s great if we are integrating our app with several scripting languages, but its feature set is more restricted. For example, it doesn’t support class reloading. As such, if we are only integrating with Groovy, then it may be better to stick with earlier approaches.

Pitfalls of Running Groovy in a Java Project

8.1. Performance

We all know that when a system needs to be very performant, there are some golden rules to follow.

Two that may weigh more on our project are:

  • avoid reflection
  • minimize the number of bytecode instructions

Reflection, in particular, is a costly operation due to the process of checking the class, the fields, the methods, the method parameters, and so on.

If we analyze the method calls from Java to Groovy, for example, when running the example addWithCompiledClasses, the stack of operation between .calcSum and the first line of the actual Groovy method looks like:

Which is consistent with Java. The same happens when we cast the object returned by the loader and call its method.

However, this is what the invokeMethod call does:

In this case, we can appreciate what’s really behind Groovy’s power: the MetaClass.

A MetaClass defines the behavior of any given Groovy or Java class, so Groovy looks into it whenever there’s a dynamic operation to execute in order to find the target method or field. Once found, the standard reflection flow executes it.

Two golden rules broken with one invoke method!

If we need to work with hundreds of dynamic Groovy files, how we call our methods will then make a huge performance difference in our system.

8.2. Method or Property Not Found

As mentioned earlier, if we want to deploy new versions of Groovy files in a CD life cycle, we need to treat them like they were an API separate from our core system.

This means putting in place multiple fail-safe checks and code design restrictions so our newly joined developer doesn’t blow up the production system with a wrong push.

Examples of each are: having a CI pipeline and using method deprecation instead of deletion.

What happens if we don’t? We get dreadful exceptions due to missing methods and wrong argument counts and types.

And if we think that compilation would save us, let’s look at the method calcSum2() of our Groovy scripts:

By looking through the entire file, we immediately see two problems: the method calcSum3() and the variable z are not defined anywhere.

Even so, the script is compiled successfully, without even a single warning, both statically in Maven and dynamically in the GroovyClassLoader.

It’ll fail only when we try to invoke it.

Maven’s static compilation will show an error only if our Java code refers directly to calcSum3(), after casting the GroovyObject like we do in the addWithCompiledClasses() method, but it’s still ineffective if we use reflection instead.

Monitoring the JVM

One can use the standard classes available in java.lang.management for carrying out the monitoring of the JVM. The following code example shows how this can be done.

import java.lang.management.*

def os = ManagementFactory.operatingSystemMXBean 
println """OPERATING SYSTEM: 
\tOS architecture = $os.arch 
\tOS name = $os.name 
\tOS version = $os.version 
\tOS processors = $os.availableProcessors 
""" 
 
def rt = ManagementFactory.runtimeMXBean 
println """RUNTIME: 
   \tRuntime name = $rt.name 
   \tRuntime spec name = $rt.specName 
   \tRuntime vendor = $rt.specVendor 
   \tRuntime spec version = $rt.specVersion 
   \tRuntime management spec version = $rt.managementSpecVersion 
   """ 

def mem = ManagementFactory.memoryMXBean 
def heapUsage = mem.heapMemoryUsage 
def nonHeapUsage = mem.nonHeapMemoryUsage 

println """MEMORY: 
   HEAP STORAGE: 
      \tMemory committed = $heapUsage.committed 
      \tMemory init = $heapUsage.init 
      \tMemory max = $heapUsage.max 
      \tMemory used = $heapUsage.used NON-HEAP STORAGE: 
      \tNon-heap memory committed = $nonHeapUsage.committed 
      \tNon-heap memory init = $nonHeapUsage.init 
      \tNon-heap memory max = $nonHeapUsage.max 
      \tNon-heap memory used = $nonHeapUsage.used 
   """
  
println "GARBAGE COLLECTION:" 
ManagementFactory.garbageCollectorMXBeans.each { gc ->
   println "\tname = $gc.name"
   println "\t\tcollection count = $gc.collectionCount"
   println "\t\tcollection time = $gc.collectionTime"
   String[] mpoolNames =   gc.memoryPoolNames
	
   mpoolNames.each { 
      mpoolName -> println "\t\tmpool name = $mpoolName"
   } 
}

When the code is executed, the output will vary depending on the system on which the code is run. A sample of the output is given below.

OPERATING SYSTEM: 
   OS architecture = x86 
   OS name = Windows 7 
   OS version = 6.1 
   OS processors = 4
   
RUNTIME: 
   Runtime name = 5144@Babuli-PC 
   Runtime spec name = Java Virtual Machine Specification 
   Runtime vendor = Oracle Corporation 
   Runtime spec version = 1.7 
   Runtime management spec version = 1.2
   
MEMORY: 
   HEAP STORAGE: 
      Memory committed = 16252928 
      Memory init = 16777216 
      Memory max = 259522560 
      Memory used = 7355840
   
NON-HEAP STORAGE: 
   Non-heap memory committed = 37715968 
   Non-heap memory init = 35815424 
   Non-heap memory max = 123731968 
   Non-heap memory used = 18532232 
   
GARBAGE COLLECTION: 
   name = Copy 
   collection count = 15 
   collection time = 47 
   mpool name = Eden Space 
   mpool name = Survivor Space
		
   name = MarkSweepCompact 
      collection count = 0 
      collection time = 0 
		
      mpool name = Eden Space 
      mpool name = Survivor Space 
      mpool name = Tenured Gen 
      mpool name = Perm Gen 
      mpool name = Perm Gen  
      mpool name = Perm Gen 

Joint Compilation

Before going into the details of how to configure Maven, we need to understand what we are dealing with.

Our code will contain both Java and Groovy files. Groovy won’t have any problem at all finding the Java classes, but what if we want Java to find Groovy classes and methods?

Here comes joint compilation to the rescue!

Joint compilation is a process designed to compile both Java and Groovy files in the same project, in a single Maven command.

With joint compilation, the Groovy compiler will:

  • parse the source files
  • depending on the implementation, create stubs that are compatible with the Java compiler
  • invoke the Java compiler to compile the stubs along with Java sources – this way Java classes can find Groovy dependencies
  • compile the Groovy sources – now our Groovy sources can find their Java dependencies

Depending on the plugin implementing it, we may be required to separate the files into specific folders or to tell the compiler where to find them.

Without joint compilation, the Java source files would be compiled as if they were Groovy sources. Sometimes this might work since most of the Java 1.7 syntax is compatible with Groovy, but the semantics would be different.

История

Первым упоминанием о языке было сообщение в блоге Джеймса Стрэчена (англ. James Strachan) от августа 2003 года. Позднее было выпущено несколько версий между 2004 и 2006 годами. После того, как начался процесс стандартизации JCP, нумерация версий была изменена, и версию называют «1.0». Версия «1.0» была выпущена 2 января 2007 года. В декабре 2007 года вышел Groovy 1.1, эта версия вскоре была перенумерована как «1.5» вследствие значительных изменений в языке.

Стрэчен покинул проект за год до релиза Groovy 1.0 в 2007 году, а в июле 2009 года Стрэчен написал в своём блоге, что возможно не создал бы Groovy, если бы в 2003 году прочитал книгу Мартина Одерского с соавторами о программировании на языке Scala (вышедшую в 2007 году).

Проект разработки языка и комитет JSR-241 с 2007 года возглавляет Гийом Лафорж (Guillaume Laforge). Компанию G2One, занимавшуюся развитием и коммерциализацией языка и фреймворка Grails, осенью 2008 года приобрела , в итоге через цепочку слияний и поглощений (VMware, Pivotal, EMC) актив перешёл в 2017 году в корпорацию Dell. С 2015 года язык является проектом верхнего уровня фонда Apache.

Hello world

  1. Качаем дистрибутив ServiceMix, распаковываем и запускаем servicemix.sh (bat).
  2. Создаем и настраиваем новый maven проект.
    В src/main/resources нужно создать каталог META-INF, в котором создать подкаталог spring.
    Т.к. нам нужно собрать бандл — правим pom.xml (добавляем инструкции packaging и build):

    Каких-либо maven зависимостей добавлять не нужно.

  3. Настраиваем Camel контекст и роут.
    В каталоге spring нужно создать файл camel-context.xml (загрузчик автоматически ищет файл с описанием Camel контекста в META-INF/spring и запускает роуты). В файл camel-context.xml поместим следующее содержимое (по тексту даны комментарии):

    Для того чтобы наш роут выполнил свою задачу (записал в лог текст “Hello world”), нужно передать ему на вход сообщение. В нашем случае эту задачу решает таймер &ltfrom uri=«timer://startTimer?repeatCount=1»/&gt, который, следуя инструкции repeatCount=1, один раз отправит на вход роута сообщение. Т.к. таймер посылает на вход роута пустое сообщение, нам нужно его чем-то заполнить – помещаем в тело сообщения текст “Hello world” &ltsetBody&gt. В конце роута выводим содержимое тела сообщения в лог &ltlog message=»${body}»&gt.

  4. Собираем наш проект: mvn package
  5. Деплоим собранный бандл.
    Одним из способов задеплоить бандл в ServiceMix является копирование jar файла в каталог deploy. Копируем собранный jar в каталог ServiceMix/deploy.

ServiceMix/data/log/servicemix.logHelloWorldRoute | 43 — org.apache.camel.camel-core — 2.16.3 | Hello world

Method Parameters

A method is more generally useful if its behavior is determined by the value of one or more parameters. We can transfer values to the called method using method parameters. Note that the parameter names must differ from each other.

The simplest type of a method with parameters as the one shown below −

def methodName(parameter1, parameter2, parameter3) { 
   // Method code goes here 
}

Following is an example of simple method with parameters

class Example {
   static void sum(int a,int b) {
      int c = a+b;
      println(c);
   }  
	
   static void main(String[] args) {
      sum(10,5);
   } 
}

In this example, we are creating a sum method with 2 parameters, a and b. Both parameters are of type int. We are then calling the sum method from our main method and passing the values to the variables a and b.

The output of the above method would be the value 15.

Method Return Values

Methods can also return values back to the calling program. This is required in modern-day programming language wherein a method does some sort of computation and then returns the desired value to the calling method.

Following is an example of simple method with a return value.

class Example {
   static int sum(int a,int b = 5) {
      int c = a+b;
      return c;
   } 
	
   static void main(String[] args) {
      println(sum(6));
   } 
}

In our above example, note that this time we are specifying a return type for our method sum which is of the type int. In the method we are using the return statement to send the sum value to the calling main program. Since the value of the method is now available to the main method, we are using the println function to display the value in the console.

The output of the above method would be the value 11.

Tatsumaki

Fun — это модное слово, которое лучше всего ассоциируется с ботом Tatsumaki. Он яркий, с множеством интересных функций, которыми могут воспользоваться пользователи вашего сервера, чтобы повысить активность пользователей.

Это бот, который охватывает все основные функции для улучшения модерации и более эффективного использования сервера, а также для того, чтобы дать пользователям веселые и удобные командные игры.

Функции модерации Tatsumaki не требуют настройки. Они готовы к использованию, с командами для управления пользователями (запрет, отключение звука и т.д.), обрезки сообщений, настройки приветственных сообщений и других.

Вы также можете осуществлять поиск на YouTube, искать факты о кошках, играть в различные игры, а также проводить опросы по всему серверу. Существует обширная система уровней с XP, доступная для пользователей в зависимости от их активности.

Default Parameters

There is also a provision in Groovy to specify default values for parameters within methods. If no values are passed to the method for the parameters, the default ones are used. If both nondefault and default parameters are used, then it has to be noted that the default parameters should be defined at the end of the parameter list.

Following is an example of simple method with parameters −

def someMethod(parameter1, parameter2 = 0, parameter3 = 0) { 
   // Method code goes here 
} 

Let’s look at the same example we looked at before for the addition of two numbers and create a method which has one default and another non-default parameter −

class Example { 
   static void sum(int a,int b = 5) { 
      int c = a+b; 
      println(c); 
   } 
	
   static void main(String[] args) {
      sum(6); 
   } 
}

In this example, we are creating a sum method with two parameters, a and b. Both parameters are of type int. The difference between this example and the previous example is that in this case we are specifying a default value for b as 5. So when we call the sum method from our main method, we have the option of just passing one value which is 6 and this will be assigned to the parameter a within the sum method.

The output of the above method would be the value 11.

class Example {
   static void sum(int a,int b = 5) {
      int c = a+b;
      println(c);
   } 
	
   static void main(String[] args) {
      sum(6,6);
   } 
}

We can also call the sum method by passing 2 values, in our example above we are passing 2 values of 6. The second value of 6 will actually replace the default value which is assigned to the parameter b.

The output of the above method would be the value 12.

Relational operators

Relational operators allow of the comparison of objects. Following are the relational operators available in Groovy −

Operator Description Example
== Tests the equality between two objects 2 == 2 will give true
!= Tests the difference between two objects 3 != 2 will give true
< Checks to see if the left objects is less than the right operand. 2 < 3 will give true
<= Checks to see if the left objects is less than or equal to the right operand. 2 <= 3 will give true
> Checks to see if the left objects is greater than the right operand. 3 > 2 will give true
>= Checks to see if the left objects is greater than or equal to the right operand. 3 >= 2 will give true

Особенности работы

В отличие от Java, в Groovy исходный код может быть выполнен как обычный скрипт, если содержит код вне определения класса или класс с методом main или Runnable или GroovyTestCase:

#!/usr/bin/env groovy
println "I can execute this script now!"

Строки в Groovy: Java Strings с одинарными кавычками и GStrings с двойными кавычками.

def javaStyleString = 'java String style'
def GStringsStyleString = "${javaStyleString}"
def j = '${javaStyleString}' 
def bigGroovyString = """
    ${javaStyleString}
    ${GStringsStyleString}
"""
println bigGroovyString

Groovy неявно генерирует методы для доступа к переменным (setColor(String color) и getColor()):

class AGroovyBean {
  String color
}

def myGroovyBean = new AGroovyBean()

myGroovyBean.setColor('blue')
assert myGroovyBean.getColor() == 'blue'

myGroovyBean.color = 'green'
assert myGroovyBean.color == 'green'

Groovy предлагает простой и последовательный доступ к спискам, отображениям и массивам:

def myList = 'One', 'Two', 'Three'  //выглядит как массив, но это список
assert myList2 == 'Three'
myList3 = 'Four'  //добавляем элемент в список
assert myList.size() == 4

def monthMap =  'January'  31, 'February'  28, 'March'  31   //определяем ассоциативный массив
assert monthMap'March' == 31  
monthMap'April' = 30  //добавляем элемент в ассоциативный массив
assert monthMap.size() == 4

Closure (замыкание) — это анонимная функция и объект в одном виде:

def closureFunction = {a, b ->
    println a
    println b
}

closureFunction(1, 2)

return в функции указывать не обязательно — по умолчанию будет возвращено значение последней упомянутой переменной.

Неизменяемые классы маркируются с помощью аннотации Immutable:

@Immutable
class ImmutableClass {
    String stringVariable
    Integer integerVariable
}
def newVariable = new ImmutableClass(stringVariable  "some string", integerVariable  23)

MEE6

Есть причина, по которой MEE6 в настоящее время — один из лучших ботов Discord. Он обладает обширными возможностями и поддержкой. Вы также можете обновиться до премиум пакета подписки для еще большего количества функций.

Множество ботов Disord предназначены для модерации сервера, и MEE6 не вызовет разочарования. Вы можете настроить автоматические правила для защиты от таких проблем, как спам на сервере. Администраторы MEE6 также могут настроить систему «страйков» для автоматизации наказаний, если пользователи регулярно нарушают правила.

Бот настраиваемый, так что вы можете создавать свои собственные команды для пользователей, а также персонализированные приветственные сообщения. Если вы хотите, чтобы пользователи могли устанавливать собственные роли, вы можете настроить команды для этого.

Существует также система регулировки уровней для пользователей. Обычные пользователи могут «подняться» на более высокий уровень в зависимости от активности их работы. Вы можете установить награды, такие как дополнительный доступ в комнату или новые роли.

Если вы уже знаете, как добавлять боты Discord на ваш сервер, вам не составит труда столкнуться с проблемой, используя MEE6. Просто пригласите бота на свой сервер, чтобы начать работу.

Event Generation

You can use the Groovy Scripting origin
to generate event records
for an event stream. Enable event generation when you want the stage to generate an
event record based on scripting logic.

As with any record, you can pass event records downstream to a
destination for event storage or to any executor that can be configured to use the
event. For more information about events and the event framework, see .

To generate events:

  1. On the General tab, select the Produce
    Events property.

    This enables the event output stream for use.

  2. Include both of the following methods in the script:
    • — Creates an event record with the specified event type and version
      number. You can create a new event type or use an existing event type.
      Existing event types are documented in other event-generating stages.

      The event record contains no record fields. Generate record
      fields as needed.

    • — Use to append an event
      record to a batch and pass events to the event output stream.

Свой тип задачи

Задача наследуется от , в которой есть много-много полей, методов и прочего. Код AbstractTask, от которой унаследована DefaultTask.

Полезные моменты:

  • вместо ручного добавления и можно использовать поля и аннотации к ним: и т.п.
  • метод, который будут запускать при выполнении задачи: .
  • удобные методы типа всё ещё можно вызвать, но придётся их явно вызывать для проекта:

Когда для нашей задачи кто-то в пишет

у задачи вызывается метод .

Я не уверен, что это правильных подход, но если у задачи есть несколько полей, взаимное состояние которых сложно контролировать геттерами-сеттерами, то кажется вполне удобным переопределить метод следующим образом:

Причём даже если написать

то метод всё равно будет вызван.

Assignment operators

The Groovy language also provides assignment operators. Following are the assignment operators available in Groovy −

Operator Description Example
+= This adds right operand to the left operand and assigns the result to left operand.

def A = 5

A+=3

Output will be 8

-= This subtracts right operand from the left operand and assigns the result to left operand

def A = 5

A-=3

Output will be 2

*= This multiplies right operand with the left operand and assigns the result to left operand

def A = 5

A*=3

Output will be 15

/= This divides left operand with the right operand and assigns the result to left operand

def A = 6

A/=3

Output will be 2

%= This takes modulus using two operands and assigns the result to left operand

def A = 5

A%=3

Output will be 2

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *