Статьи

Как создать недавний виджет твитов

В этом уроке мы рассмотрим, как создать виджет «последние твиты». В репозитории имеется довольно много плагинов, связанных с Twitter, однако я надеюсь, что в этом руководстве будут рассмотрены некоторые важные (и общеприменимые) методы, необходимые для создания такого плагина (на основе Twitter или нет). ,


Класс Widget, WP_Widget , является вспомогательным классом для создания виджетов. Чтобы создать виджет, вы просто создаете расширение этого класса четырьмя методами: __construct , widget , update и form .

01
02
03
04
05
06
07
08
09
10
11
12
class WP_Widget_Wptuts_Twitter_Widget extends WP_Widget {
 
    var $w_arg;// A class variable to store an array of our widget settings and their default values
 
    function __construct() {}
 
    function widget( $args, $instance ) {}
 
    function update( $new_instance, $old_instance ) {}
 
    function form( $instance ) {}
}
  • __construct инициализирует виджет. Здесь вы устанавливаете уникальный идентификатор виджета, заголовок, описание (для стороны администратора) и класс (ы), которые нужно передать контейнеру виджета на внешнем интерфейсе.
  • widget — метод, отвечающий за печать содержимого виджета на внешнем интерфейсе.
  • form — этот метод должен печатать настройки виджета в форме Appearance> Widget
  • update — эта функция отвечает за проверку параметров, отправленных из формы виджета (через form ). Массив $new_instance содержит параметры для проверки, а массив $old_instance содержит сохраненные в данный момент параметры. Ожидается, что проверенные параметры будут возвращены, или false, чтобы прервать обновление параметров.

Начнем с конструирования класса. Чтобы зарегистрировать ваш виджет (ы), вам нужно подключиться к действию widgets_init и вызвать register_widget , передавая имя класса виджета ( WP_Widget_Wptuts_Twitter в этом случае). Этот фрагмент находится сразу после нашего класса виджетов:

1
2
3
4
add_action( ‘widgets_init’, ‘wptuts_register_widget’);
function wptuts_register_widget() {
    register_widget(‘WP_Widget_Wptuts_Twitter_Widget’);
}

register_widget создает экземпляр нашего виджета и вызывает метод __construct . Это должно установить некоторые из констант класса, например, уникальный идентификатор ( id_base ) и параметры конфигурации виджета, в частности:

  • description — описание виджета (появится на странице администрирования виджета).
  • имя класса — класс (ы), который будет добавлен к атрибуту класса виджета.

Вы также можете указать массив ‘control options’ — это позволяет вам указать ширину формы администратора виджета (обычно 250px), если вам нужно больше места. Эстетически, однако, предпочтительно не изменять это, и не перегружать ваши виджеты чрезмерными опциями.

Для настройки этих конфигураций вы можете использовать метод WP_Widget::__construct (т.е. parent::__construct ). Это ожидает:

  • ID Base — уникальный идентификатор для класса Widget
  • Имя — имя вашего виджета, используемого на экране администратора.
  • Параметры виджета — задает имя класса и описание.
  • Параметры управления — (необязательно) массив высоты и ширины формы виджета.

Затем (опционально) мы можем установить настройки виджета и их значения по умолчанию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function __construct() {
    $widget_ops = array(
        ‘classname’ => ‘wptuts_widget_twitter’,
        ‘description’ => __(‘Displays a list of recent tweets’,’wptuts_twitter’)
    );
    parent::__construct(‘WP_Widget_Wptuts_Twitter’, __(‘Twitter’,’wptuts_twitter’), $widget_ops);
 
    // Sett widget’s settings and default values
    $this->w_arg = array(
        ‘title’=> »,
        ‘screen_name’=> »,
        ‘count’=> ‘5’,
        ‘published_when’=> ‘1’
    );
}

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


Прежде чем идти дальше, мы собираемся создать вспомогательный метод, который отправляет запрос в API Twitter. Этот простой метод принимает запрос (URL) и использует функцию wp_remote_get для получения ответа. wp_remote_get является частью HTTP API, который был wp_remote_get в этом руководстве .

Затем метод проверяет наличие ошибок: либо is_wp_error WordPress (с is_wp_error ), либо Twitter (проверяя код ответа). Оператор switch в методе позволяет вам действовать по-разному в соответствии с этим кодом — в приведенном ниже примере все ответы об ошибках преобразуются в объект ошибки WordPress или в противном случае возвращается успешный ответ (массив твитов).

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function retrieve_remote_tweets($request_url) {
 
    $raw_response = wp_remote_get( $request_url, array( ‘timeout’ => 1 ) );
 
    if ( is_wp_error( $raw_response ) )
        return $raw_response;
 
    $code = (int) wp_remote_retrieve_response_code($raw_response);
    $response = json_decode( wp_remote_retrieve_body($raw_response) );
 
    switch( $code ):
        case 200:
            return $response;
 
        case 304:
        case 400:
        case 401:
        case 403:
        case 404:
        case 406:
        case 420:
        case 500:
        case 502:
        case 503:
        case 504:
            return new WP_Error($code, $response->error);
 
        default:
            return new WP_Error($code, __(‘Invalid response’,’wptuts_twitter’));
    endswitch;
}

Твиттер имеет обширную документацию по своему API — который, помимо прочего, позволяет вам получать ваши последние твиты, твиты со своей «домашней» страницы или твиты, которые соответствуют поисковому запросу. Некоторые из более продвинутых функций API требуют аутентификации через OAuth.

В этом уроке мы рассмотрим твиты конкретного пользователя (или временную шкалу пользователя ). URL для получения этих твитов имеет очень простую структуру:

1
https://api.twitter.com/1/statuses/user_timeline.json?screen_name={screen-name}&arg1=val1&arg2=val2

где {screen-name} должно быть {screen-name} пользователя, а другие аргументы могут быть любыми из перечисленных здесь . Обычно для аргументов, таких как include_entities , устанавливается значение true, чтобы получить метаданные о твитах (например, любые URL-адреса или хештеги, которые они содержат).


Twitter накладывает ограничение на количество запросов, которые вы можете сделать: 150 в час для неаутентифицированных запросов и 350 в противном случае. Не только это, но получение твитов требует времени — увеличение времени загрузки вашего сайта. Получение твитов из Твиттера при каждой загрузке страницы неоправданно дорого, особенно если нет необходимости, чтобы твиты появлялись на вашем сайте сразу после их публикации.

Кэширование, в частности переходные процессы WordPress, может использоваться для локального хранения ваших твитов (или любых данных) в вашей базе данных. Извлечение их из базы данных намного быстрее, чем извлечение их удаленно, и это не влияет на ограничение API. Конечно, данные скоро устаревают, поэтому время хранения вместе с твитами истекает.

Идея состоит в том, что при отображении ваших твитов, если срок действия данных истек (или твиты отсутствуют в базе данных), вы извлекаете их из Twitter и обновляете кеш. Таким образом, вы можете ограничить количество запросов к Твиттеру раз в 20 минут, час или день в зависимости от того, как долго вы готовы ждать обновления ваших твитов. Кроме того, когда твиты не обновляются, вы будете наслаждаться более быстрым временем загрузки. Кэширование, при правильном использовании, является отличным способом повысить производительность вашего плагина или темы. Если вы не использовали его раньше, я бы порекомендовал посмотреть это видео и следовать этому уроку .

Однако — предположим, что срок действия вашего твита-кеша истек, а сервис Твиттера закрыт на техническое обслуживание Твиты удаляются из базы данных, но вы не можете получить какие-либо твиты из Twitter, чтобы заменить их, и поэтому вам нечего будет отображать. Решением этой проблемы является «мягкое кэширование».


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

Мягкое кэширование изначально не поддерживается WordPress, но Марк Джакит и Аарон Кэмпбелл создали отличную реализацию этого класса . Я буду использовать немного упрощенный метод, который, я надеюсь, проиллюстрирует, как мягкое кэширование может работать в WordPress.

Идея состоит в том, чтобы вручную обработать истечение переходного процесса. Вместо того, чтобы хранить твиты как переходные процессы со временем истечения, вы храните массив, содержащий твиты и время истечения, внутри переходного процесса, который не имеет времени истечения. Мы можем реализовать этот метод, используя следующую функцию вместо set_transient .

1
2
3
4
5
function set_twitter_transient($key, $data, $expiration) {
    // Time when transient expires
    $expire = time() + $expiration;
    set_transient( $key, array( $expire, $data ) );
}

Конечно, без истечения срока действия данные никогда (или фактически не гарантированно) удаляются и заменяются новыми данными. Теперь мы должны сами позаботиться об этом.

При извлечении этого не истекающего переходного процесса вы проверяете, прошло ли сохраненное время истечения. Если это так, вы пытаетесь получить новые твиты с помощью вышеуказанного метода retrieve_remote_tweets . Если это успешно, вы обновляете переходный процесс (с новыми данными и новым временем истечения) и используете эти новые данные. Если это не так, вы просто используете сохраненные в данный момент данные, пока не получите успешный ответ от Twitter.

Чтобы получить твиты (удаленно или локально) и обработать кеш, мы определяем метод get_tweets . Это принимает массив аргументов, с помощью которых генерируется запрос Twitter. Например:

  • screen_name — имя учетной записи Twitter для подписки
  • count — количество твитов для получения
  • include_rts — 1 | 0 — 1 для включения ретвитов, 0 для исключения ретвитов.

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

В этой функции жестко прописан срок действия 1 час.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function get_tweets($args) {
    // Build request URL
    $args[‘screen_name’] = ‘@’.$args[‘screen_name’];
    $request_url = ‘https://api.twitter.com/1/statuses/user_timeline.json’;
    $request_url = add_query_arg($args,$request_url);
 
    // Generate key
    $key = ‘wptt_’.md5($request_url);
 
    // expires every hour
    $expiration = 60*60;
 
    $transient = get_transient( $key );
    if ( false === $transient ) {
        // Hard expiration
        $data = $this->retrieve_remote_tweets( $request_url );
 
        if ( !is_wp_error($data) ) {
            // Update transient
            $this->set_twitter_transient($key, $data, $expiration);
        }
        return $data;
 
    }
    else {
        // Soft expiration.
        if ( $transient[0] !== 0 && $transient[0] <= time() ) {
 
            // Expiration time passed, attempt to get new data
            $new_data = $this->retrieve_remote_tweets( $request_url );
 
            if ( !is_wp_error($new_data) ) {
                // If successful return update transient and new data
                $this->set_twitter_transient($key, $new_data, $expiration);
                $transient[1] = $new_data;
            }
        }
        return $transient[1];
    }
}

При использовании переходного API WordPress вы должны проверить, находит ли get_transient ваши данные в кэше, а если нет, получить или сгенерировать данные (скажем, из Twitter), а затем обновить переходный процесс. Однако метод get_tweets описанный выше, будет обрабатывать все это — и будет возвращать ваши «старые данные», если он не сможет их обновить. Тем не менее возвращать данные по-прежнему не гарантируется: возможно, в базе данных нет твитов (например, запускаемых в первый раз), и он не получил успешного ответа от Twitter. В этих случаях WP_Error объект WP_Error — и мы должны убедиться, что ваш плагин справляется с этим и корректно ухудшается.


Задача метода формы заключается в отображении формы параметров виджета на странице «Внешний вид»> «Виджет». Он передает сохраненные в данный момент параметры в качестве аргумента (массив). Конечно, вы можете использовать wp_parse_args для замены любых «пропущенных» значений значениями по умолчанию.

Как правило, форма виджета должна быть довольно маленькой (и часто внутри узкой боковой панели) — ее можно расширить с помощью параметров управления, упомянутых ранее, но предпочтительнее придерживаться эстетики WordPress. По этой причине я буду следить за макетом стандартного виджета WordPress.

Я также буду использовать методы $this->get_field_id и $this->get_field_name для генерации значений имени и идентификатора для входных данных для каждого экземпляра виджета, поэтому WordPress может обрабатывать их обработку для нас.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function form( $instance=array() ) {
    // Merge $instance with defaults
    $instance = extract(wp_parse_args( (array) $instance, $this->w_arg ));
    ?>
    <p>
        <label for=»<?php echo $this->get_field_id(‘title’); ?>»><?php _e(‘Title’, ‘wptuts_twitter’);
        <input id=»<?php echo $this->get_field_id(‘title’); ?>» name=»<?php echo $this->get_field_name(‘title’); ?>» type=»text» value=»<?php echo esc_attr($title);?>» />
    </p>
 
    <p>
        <label for=»<?php echo $this->get_field_id(‘screen_name’); ?>»><?php _e(‘Twitter username’, ‘wptuts_twitter’);
        <input id=»<?php echo $this->get_field_id(‘screen_name’); ?>» name=»<?php echo $this->get_field_name(‘screen_name’); ?>» type=»text» value=»<?php echo esc_attr($screen_name);?>» />
    </p>
 
    <p>
        <label for=»<?php echo $this->get_field_id(‘count’); ?>»><?php _e(‘Number of Tweets’, ‘wptuts_twitter’);
        <input id=»<?php echo $this->get_field_id(‘count’); ?>» name=»<?php echo $this->get_field_name(‘count’); ?>» type=»text» value=»<?php echo intval($count);?>» />
    </p>
 
    <p>
        <label for=»<?php echo $this->get_field_id(‘published_when’); ?>»><?php _e(‘Show when tweet was published’, ‘wptuts_twitter’);
        <input id=»<?php echo $this->get_field_id(‘published_when’); ?>» name=»<?php echo $this->get_field_name(‘published_when’); ?>» <?php checked($published_when,1);
    </p>
 
    <?php
}

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

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

1
2
3
4
5
6
7
8
function update( $new_instance=array(), $old_instance=array() ) {
    $validated = array();
    $validated[‘title’] = sanitize_text_field( $new_instance[‘title’] );
    $validated[‘screen_name’]= preg_replace( ‘/[^A-Za-z0-9_]/’, »,$new_instance[‘screen_name’] );
    $validated[‘count’] = absint( $new_instance[‘count’] );
    $validated[‘published_when’] = ( isset( $new_instance[‘published_when’] ) ? 1 : 0 );
    return $validated;
}

Отображаемое имя проверяется путем удаления всего, кроме буквенно-цифровых символов и подчеркиваний.


Наконец мы подошли к способу отображения виджета. Все части на месте, все, что осталось, — это создать содержимое самого виджета. Ответственный за это метод widget передает два аргумента: первый — это массив аргументов, соответствующих настройкам виджета на боковой панели — аргументы отображения, включая before_title , after_title , before_widget и after_widget . Второе — это настройки для этого конкретного экземпляра виджета — это массив настроек, сохраненных из нашей формы.

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

01
02
03
04
05
06
07
08
09
10
11
12
function widget( $args, $instance ) {
    extract($args);
    $title = apply_filters( ‘widget_title’, $instance[‘title’] );
 
    echo $before_widget;
 
    echo $before_title.esc_html($title).$after_title;
 
    echo $this->generate_tweet_list($instance);
 
    echo $after_widget;
}

Метод generate_tweet_list просто извлекает твиты с помощью get_tweets — и, если ошибок не было, перебирает твиты и отображает их в списке. Для каждого твита применяется метод make_clickable — он сканирует твит и превращает любое отображаемое имя, хэштег или ссылку в фактическую ссылку (см. Ниже).

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function generate_tweet_list( $args=array() ) {
 
    $args = shortcode_atts(array(
        ‘include_entities’ => ‘true’,
        ‘include_rts’=>1,
        ‘screen_name’ => »,
        ‘count’=>5,
        ‘published_when’=>1
    ), $args);
 
    // Retrieve tweets
    $tweets = $this->get_tweets($args);
 
    $content = ‘<ul>’;
    if ( is_wp_error($tweets) || !is_array($tweets) || count($tweets) ==0 ) {
        $content .= ‘<li>’ .
    }
    else {
        $count = 0;
        foreach ( $tweets as $tweet ) {
 
            $content .= ‘<li>’;
            $content .= «<span class=’tweet-content’>».$this->make_clickable($tweet).»
 
            if ( $args[‘published_when’] ) {
                $content .= «<span class=’time-meta’>»;
                $href = esc_url(«http://twitter.com/{$tweet->user->screen_name}/statuses/{$tweet->id_str}»);
                $time_diff = human_time_diff( strtotime($tweet->created_at)).’
                $content .= «<a href={$href}>».$time_diff.»</a>»;
                $content .= ‘
            }
 
            $content .= ‘</li>’;
 
            if ( ++$count >= $args[‘count’] )
                break;
        }
    }
    $content .= ‘</ul>’;
 
    // wp_enqueue_script(‘wptuts_twitter_script’);
    // wp_enqueue_style(‘wptuts_twitter_style’);
 
    return $content;
}

Вы заметите, что я включил вызовы wp_enqueue_script и wp_enqueue_style . Они закомментированы, потому что мне не нужно было включать какие-либо стили или сценарии. Но начиная с версии 3.3 вы можете использовать эти функции во время генерации тела страницы (т.е. в обратных вызовах виджетов или шорткодов). Если мне нужно было поставить в очередь какие-либо сценарии для работы этого виджета, то здесь это гарантирует, что они загружаются только тогда, когда они действительно нужны. Вы должны убедиться, что вы не загружаете скрипты и стили без необходимости.


Здесь мы определяем метод make_clickable . Он принимает объект твита и возвращает его содержимое после замены любых URL-адресов, хэш-тегов, URL-адресов пользователей или мультимедиа соответствующей ссылкой. Так как мы установили для include_entities значение true, объект твита включает в себя « сущности » твита. Сущность — это любой URL, упоминание пользователя, хэштег или медиа, включенные в твит. Для этого мы используем регистронезависимую функцию str_ireplace .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function make_clickable( $tweet ) {
    $entities = $tweet->entities;
    $content = $tweet->text;
 
    // Make any links clickable
    if ( !empty($entities->urls) ) {
        foreach ( $entities->urls as $url ) {
            $content =str_ireplace($url->url, ‘<a href=»‘.esc_url($url->expanded_url).'»>’.$url->display_url.'</a>’, $content);
        }
    }
 
    // Make any hashtags clickable
    if ( !empty($entities->hashtags) ) {
        foreach ( $entities->hashtags as $hashtag ) {
            $url = ‘http://search.twitter.com/search?q=’ .
            $content =str_ireplace(‘#’.$hashtag->text, ‘<a href=»‘.esc_url($url).'»>#’.$hashtag->text.'</a>’, $content);
        }
    }
 
    // Make any users clickable
    if ( !empty($entities->user_mentions) ) {
        foreach ( $entities->user_mentions as $user ) {
            $url = ‘http://twitter.com/’.urlencode($user->screen_name);
            $content =str_ireplace(‘@’.$user->screen_name, ‘<a href=»‘.esc_url($url).'»>@’.$user->screen_name.'</a>’, $content);
        }
    }
 
    // Make any media urls clickable
    if ( !empty($entities->media) ) {
        foreach ( $entities->media as $media ) {
            $content =str_ireplace($media->url, ‘<a href=»‘.esc_url($media->expanded_url).'»>’.$media->display_url.'</a>’, $content);
        }
    }
 
    return $content;
}

Готовый продукт (в зависимости от стиля вашей темы) должен выглядеть примерно так:


В этом уроке я попытался придерживаться основного, но важного принципа: разделения интересов . Наш класс представляет собой набор методов, и идея заключается в том, что каждый метод должен отвечать за определенную цель. У меня есть обратный вызов для отображения структуры виджета, который вызывает generate_tweet_list для создания самого списка, который использует get_tweets для извлечения твитов — сам использует retrieve_remote_tweets для непосредственного взаимодействия с API Twitter.

На это есть несколько причин:

  • Удобочитаемость — разбивка кода на более мелкие фрагменты, упрощение чтения и отладки
  • Сокращение дублирования кода — без отделения моего кода, если бы у меня было два метода, которые оба пытаются получить твиты из Twitter, оба должны были бы включать все необходимые проверки и обработку кэша. Но, разделив мои функции, как показано, они оба могут использовать метод get_tweets .
  • Легко редактировать — если Twitter когда-либо изменяет свой API, у меня теперь есть только одна функция для редактирования, и я могу быть уверен, что остальная часть плагина продолжит работать нормально.
  • Расширяемость — макет этого кода больше похож на Lego, чем на пластилин. Лего намного лучше. Теперь у нас есть коллекция функций Lego brick, с помощью которых мы можем создавать больше вещей.

Для иллюстрации последнего пункта. Мы создали виджет, который отображает список последних твитов. Из-за разделения проблем, только с несколькими дополнительными строками (добавленными за пределы класса) мы можем создать шорткод, который делает то же самое:

01
02
03
04
05
06
07
08
09
10
11
12
function wptuts_twitter_shortcode_cb( $atts ) {
    $args = shortcode_atts( array(
        ‘screen_name’ => »,
        ‘count’ => 5,
        ‘published_when’ => 5,
        ‘include_rts’ => 1,
    ), $atts );
 
    $tw = new WP_Widget_Wptuts_Twitter_Widget();
    return $tw->generate_tweet_list( $args );
}
add_shortcode( ‘wptuts_twtter’, ‘wptuts_twitter_shortcode_cb’ );

Затем твиты могут отображаться с помощью шорткода: [wptuts_twtter screen_name="stephenharris88"] . Он также принимает атрибуты count , include_rts и include_rts .

У нас также есть очень полезный и универсальный метод retrieve_remote_tweets который может взаимодействовать с Twitter любым удобным для нас способом. Он вернет либо правильный ответ, либо объект ошибки WordPress с сообщением об ошибке.


Класс WP-TLC-Transients , упомянутый ранее, является более общей реализацией мягкого кэширования, позволяющей вам указывать функции, с помощью которых обновляется кеш. Он также реализует фоновое обновление: обновляет кэш только после загрузки страницы. Класс предназначен для использования в плагинах (после переименования для предотвращения конфликтов), что позволяет эффективно обрабатывать мягкое кэширование. Фактически это то, что делает плагин Twitter Widget Pro Аарона Кэмпбелла.

Примечание. Если вы используете виртуальный хостинг, другие веб-сайты могут предоставлять ваш IP Запросы Twitter API с этих сайтов будут влиять на ограничение вашей скорости. Если вы обнаружите, что вас постоянно ограничивают скорость, то это вероятная причина.

Плагин, созданный в этом руководстве, доступен на моем GitHub .