RizVN Login



Автоматическая публикация анонсов и статей на subscribe.ru (sjPoster)

Оглавление

  1. Автоматическая публикация анонсов и статей на subscribe.ru (sjPoster)Проблема массовой публикацией статей и анонсов в группы на subscribe.ru
  2. Существующие решения для автоматизации публикации анонсов
  3. Реализуем механизм автоматической публикации на jQuery
  4. Пример автоматической публикации из sjPoster
  5. Заключительные слова о постинге в сервисе subscribe

 

1. Проблема массовой публикации статей и анонсов в группы на subscribe.ru

Сервис Subscribe.ru предоставляет достаточно много возможностей как для авторов, так и для читателей. И обо всех его достоинствах написано не мало статей. Однако, у сервиса есть и свои ограничения, которые ощутимо сказываются на затрачиваемом времени. Речь идет о массовой публикации статей и анонсов в группы, которая немало волнует владельцев сайтов, периодически публикующих свои статьи. Безусловно, проблема возникает только тогда, когда количество групп начинает исчисляться в десятках. Если брать среднюю отправку в одну группу порядка одной минуты, с учетом возможных задержек сервиса, переходам по страницам и прочих проблем, то отправка в 30-40 групп уже не кажется чем-то быстрым. А если у вас не один такой сайт, и с каждого периодически нужно публиковать статьи, то затраченное время становится неплохо ощутимым. К примеру, отправка по одной статье с трех сайтов разных тематик в более 100 групп будет занимать порядка двух часов, и это в только том случае, если вы действительно умеете быстро выполнять рутинные дела без ошибок. 

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

Примечание: Коммерческая сторона сервиса в данной статье не рассматривается.

Можно продолжать тратить время на публикацию каждой статьи, а можно что-то с этим делать. 

 

2. Существующие решения для автоматизации публикации анонсов

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

Первый. Это уже разработанная программа для постинга Subscribe Poster, которую предлагал один из авторов блогов для тех, кто отошлет ему письмо. Однако, среди описываемых характеристик была пара проблем, которые несколько ограничивали ее применение. Это ввод своих логина и пароля для отправки, что оправдано, но несколько смущает с точки зрения безопасности. И, пожалуй, самое главное ограничение это жесткая привязка к группе одного раздела. Последнее означает, что вам каждый раз придется мучится с идентификаторами разделов, чтобы ваша статья попала именно в те разделы, которые вам нужно. Безусловно, можно было бы сохранять разные настройки под разные публикации, но таких настроек так же может стать огромное количество. К примеру, сайт ida-freewares.ru, который вы читаете, содержит материалы разной направленности: безопасность, оптимизация, разработка, сеть, инструкции, хаки и прочее. Составление под каждый отдельный случай таких файлов конфигурации займет уйму времени, а в некоторых случаях и вовсе будет сравнимо по времени с ручной отправкой. Тем не менее, решение может пригодиться тем людям, которые пишут статьи одной или нескольких направленностей.

Второй. Способ базируется на записи и настройки макросов для повторяющихся операций. Суть его проста. Вы используете интерфейс "Написать в группы" и для тех операций, которые повторяются постоянно, записываете последовательность действий мыши и клавиатуры, а затем назначаете выполнение этих действий на горячие клавиши. К примеру, открытие интерфейса "Написать в группы"  и автоматическая вставка из буфера обмена скопированного текста. Однако, у метода так же имеются свои минусы. Во-первых, несмотря на присутствие автоматизации, сильно от рутины вы не избавитесь. Во-вторых, это не решает главную проблему - затраченное время. Безусловно, времени будет тратиться меньше, но если вы умеете быстро выполнять однотипные операции, то особой разницы не почувствуете. 

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

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

 

3. Реализуем механизм автоматической публикации на jQuery

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

  • Автоматизация публикации статей и анонсов. Это означает, что, кроме первичной настройки и ввода данных, от вас ничего не должно требоваться.
  • Выбор групп и разделов. Каждая статья или анонс не обязательно соответствуют только одному разделу в каждой группе. Поэтому у вас всегда должна быть возможность настроить нужные группы и разделы перед отправкой.
  • Относительно простой способ сбора данных о группах и разделах. Сбор информация обо всех группах, разделах и их идентификаторах должен быть настолько простым, что бы с ним мог справиться практически любой.
  • Конфигурация должна относительно легко меняться. Сегодня у вас, к примеру, 10 групп. А завтра их уже 50-60. Каждый раз, при добавлении или удалении группы, заново проходить весь процесс настройки - несколько затратно по времени.
  • Инструменты для категоризации групп. Группы могут быть разной направленности. К примеру, одни о создании сайтов, вторые о дизайне, третьи о ... Поэтому, необходим способ их разбиения по категориям.
  • Небольшая требовательность инструмента для запуска. Чем меньше инструменту будет требоваться для запуска, тем лучше.
  • Легкость корректировки. Сегодня сервис использует одни способы для отправки данных, завтра другие. Должна оставаться возможность вручную "подкрутить" инструмент.
  • Время на разработку инструмента должно быть адекватным. Месяц для разработки - это несколько много. Безусловно, за месяц инструмент получится отличным, но тогда остро встанет вопрос о рациональности.

Примечание: Пункт, связанный с отсутствием методов взлома и прочих ухищрений, подразумевается сам собой на портале ida-freewares.ru, поэтому все решение будет построено только на честных способах и приемах.

После того, как определились с требованиями и возможностями, можно переходить к определению способа реализации. Какие есть варианты? Как такового, у сервиса api для групп не существует (api - по сути, набор различных команд для более быстрого обмена данными). Есть api для автоматизированной рассылки писем, но оно не подходит. Поэтому, инструменту придется использовать только обычный способ - взаимодействие со страницами (как браузер). Отсюда следует, что, по сути, остается два варианта. Первый, можно писать полноценный инструмент, который будет сам соединяться с сайтом, используя ваши данные, а затем сам будет собирать и вводить данные. Второй, это использовать каким-либо образом возможности браузера и уже имеющиеся данные.

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

Существует такой тег "iframe". Он позволяет загружать другие страницы и его так же можно использовать для отправки данных со всеми необходимыми данными авторизации (считайте отправлять данные под вашим логином, без необходимости отдельного входа на сервис). Однако, доступ к содержимому внутри тега получить нельзя (из-за ограничений безопасности браузеров). Другими словами, видеть то, что происходит внутри него, может только пользователь в браузере. Тем не менее, этих возможностей вполне достаточно для автоматизации. Поэтому, инструмент базировался на теге iframe и был сделан полностью на jQuery, так как он позволяет достаточно легко манипулировать элементами DOM. 

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

  • css - каталог
    • template.css - файл стилей окошек
  • images - каталог
    • ajax-loader.gif - единственная картинка - прокрутка для загрузчика
  • js - каталог
    • jquery-msg.js - скрипт для создания всплывающих окон в автоматическом режиме
  • index.html - файл для тестового запуска

Остается только добавить два файла в каталог js:

  • jquery.json.min.js - скрипт для составления JSON. Будет использоваться для сохранения данных. Скачать его можно отсюда.
  • jquery-subscribe-js-poster.js - скрипт инструмента

Теперь, можно приступать к реализации.

Примечание: Так как, код получился достаточно массивным, то в последующем будут рассмотрены только основные необходимые части механизмов и правки. Так, например, стили в файле template.css не будут рассматриваться, так как их легче взять из файла в исходнике и в них не применяются какие-либо особые приемы.

 

Изменим index.html для инструмента автоматизации публикаций

Нужно убрать все лишнее и добавить нужные ссылки в index.html. Итоговая страница для инструмента автоматической публикации получилась следующая:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <link href="https://code.jquery.com/ui/1.11.0/themes/ui-lightness/jquery-ui.css" rel="stylesheet" type="text/css"/>
        <link href="/css/template.css" rel="stylesheet" type="text/css"/>
 
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
        <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.min.js"></script>
 
        <script src="/js/jquery.json.min.js"></script>
 
        <script src="/js/jquery-msg.js"></script>
        <script src="/js/jquery-subscribe-js-poster.js"></script>
        
    </head>
 
    <body>
        <div class="sj-poster-container">
        </div>
        <br/>
        <center>
            <a href="http://ida-freewares.ru" target="_blank" >Разработано сайтом Мир бесплатного программного обеспечения</a>
        </center>
    </body>
</html>

Как видно, в основном добавилось пара ссылок на js скрипты и изменилось тело тега body, а так же убраны текстовые элементы информеров. Теперь, можно переходить к скриптам. 

Примечание: Ссылку на ресурс советуем оставить по нескольким причинам. Во-первых, инструмент вряд ли будет доступен для поисковиков (из-за характера реализации), так что можете не волноваться об открытости ссылок и прочих премудростях. Во-вторых, возможно, что в будущем инструмент будет дополняться и подправляться для корректности взаимодействия с сервисом. Во-третьих, со временем, на сайте будут так же появляться другие не менее полезные инструменты. Ну и, в крайнем случае, вы уважаете время, потраченное автором.

 

Создаем основу для хранения групп и категорий в sjPoster 

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

    ////////////////////////////////////////////////
    // Создаем общий интерфейс для постера
    ////////////////////////////////////////////////
    pObj.sjPoster = {
    
        _settings: {
            groups: {},
            cats: {}
        },
    
        clearGroups: function () {
            this._settings.groups = {};
        },
    
        deleteGroup: function (id) {
            if (this._settings.groups[id + ''])
                delete this._settings.groups[id + ''];
        },
    
        updateGroup: function (id, name, interests) {
            var group = (this._settings.groups[id + ''] = this._settings.groups[id + ''] || {});
            
            if (name) group.name = name;
            if (interests instanceof Array) group.interests = interests;
            
            if (!group.interests)
                group.interests = [];
        },
    
        clearCats: function () {
            this._settings.cats = {};
        },
    
        deleteCat: function (id) {
            if (this._settings.cats[id + ''])
                delete this._settings.cats[id + ''];
        },
    
        updateCats: function (id, name, groups) {
            var cat = (this._settings.cats[id + ''] = this._settings.cats[id + ''] || {});
            
            if (name) cat.name = name;
            if (groups instanceof Array) cat.groups = groups;
            
            if(!cat.groups)
                cat.groups = [];
        },
    
        addCatGroup: function (id, groupid) {
            var cat = this._settings.cats[id + ''];
 
            if (cat) {
                for (var i = 0; i < cat.groups.length; i++) {
                    if(cat.groups[i] + '' == groupid)
                        return;
                }
                cat.groups.push(groupid);
            }
        },
 
        deleteCatGroup: function (id, groupid) {
            var cat = this._settings.cats[id + ''];
 
            if (cat) {
                for (var i = 0; i < cat.groups.length; i++) {
                    if(cat.groups[i] + '' == groupid) {
                        cat.groups.splice(i,1);
                        return;
                    }
                }
            }
        },
        
        loadSettings: function (handler) {
            var self = this;
            $.ajax('data.txt', {    
                contentType: "text/plain; charset=utf-8",
                dataType: 'json',
                async: false,
                success: function (result) {
                    if (result)
                        self._settings = result; //evalJSON
                    if (typeof(handler) === 'function') {
                        handler(result);
                    }
                }
            });
        },

Интерфейс для хранения и управления группами, разделами и категориями групп получился достаточно простым. Единственно нужно внести несколько пояснений. Во-первых, конструкция вида [id + ''] используется для того, чтобы все идентификаторы превращать в строковые имена. Это позволяет избавиться от проблемы с идентификатором в виде строки или числа (с точки зрения js - это разные вещи). Во-вторых, несмотря на то, что для хранения используется json, сам файл с настройками имеет расширение txt. Делается это по той причине, что данный инструмент можно располагать на любых веб-серверах. Так например, IIS не всегда воспринимает расширение json, как существующее, поэтому могут возникать проблемы. В-третьих, асинхронность при загрузке отключена для того, чтобы не решать проблемы синхронизации (ради одной загрузки основных данных не имеет смысла использовать такие инструменты, как из обзора поэтапная загрузка данных, или же разбивать построение интерфейса).

 

Создаем общий интерфейс инструмента sjPoster

Как вы, наверное, уже заметили в коде index.html есть только один div для инструмента. Это означает, что весь интерфейс будет строиться jQuery (все равно многие части будут строиться скриптами). Итоговый код получается примерно следующим:

        createInterface: function (container) {
            var header = $('<h1>Автоматическая публикация анонсов в Subscribe (sjPoster)</h1>'),
                tabsContainer = $('<div class="js-poster-tabs">'),
                ulTabs = $('<ul class="js-poster-tabs-ul">'),
                self = this;
                
            $(container).append(header).append(tabsContainer);
            tabsContainer.append(ulTabs);
            // Создаем вкладку с текстом и заголовком
            ulTabs.append('<li><a href="#tabs-text">Текст анонса и заголовок</a></li>');
            tabsContainer.append(this.createTabText());
            
            // Создаем вкладку для парсинга данных
            ulTabs.append('<li><a href="#tabs-parse">Парсинг данных</a></li>');
            tabsContainer.append(this.createTabParse());
 
            // Создаем вкладку с настройками групп
            ulTabs.append('<li><a href="#tabs-settings">Настройки групп</a></li>');
            tabsContainer.append(this.createTabSettings());
 
            // Создаем вкладку для запуск процесса
            ulTabs.append('<li><a href="#tabs-vibor">Выбор групп для постинга</a></li>');
            tabsContainer.append(this.createTabVibor());
 
            // Создаем вкладку для запуск процесса
            ulTabs.append('<li><a href="#tabs-start">Запуск постинга</a></li>');
            tabsContainer.append(this.createTabStart());
            
            // Создаем вкладки
            $(tabsContainer).tabs({
                beforeActivate: function( event, ui ) {
                    if(ui.newPanel.is('#tabs-settings')) {
                        self.formTabSettings();
                    }
                }
            });
        },
        
        init: function (container) {
            window.dialogs.wait.show();
 
            // Загружаем настройки
            this.loadSettings();
            
            // Формируем инструмент
            this.createInterface(container);
            
            window.dialogs.wait.hide();
        }

Итого на общем интерфейсе будет 5 вкладок:

  1. "Текст анонса и заголовок" - для ввода данных публикации (заголовок и текст)
  2. "Парсинг данных" - для парсинга групп и их разделов
  3. "Настройки групп" - для создания и наполнения категории групп, а так же для формирования JSON для сохранения в data.txt
  4. "Выбор групп для постинга" - для выбора групп и разделов для постинга вашей статьи или анонса
  5. "Запуск постинга" - вкладка для запуска и отслеживания процесса автоматической публикации

 

Создаем вкладку "Текст анонса и заголовок" в sjPoster

Вкладка с текстом анонса и заголовком будет содержать поле для ввода заголовка, область для ввода html-кода и область для просмотра html-кода. Итоговый код получился достаточно простым:

        // Создаем таб с текстом
        createTabText: function () {
            var tab = $('<div id="tabs-text" class="js-poster-tab">');
            tab.append('<span>Введите заголовок</span>').append('<br>').append('<br>')
            .append('<input type="text" class="js-poster-tab-input" >').append('<br>').append('<br>')
            .append('<span>Разместите html внутри следующей области</span>').append('<br>').append('<br>')
            .append('<textarea class="js-poster-tab-text-textarea" >').append('<br>').append('<br>')
            .append('<span>Как будет выглядеть html</span>').append('<br>').append('<br>')
            .append('<div class="js-poster-tab-text-result"></div>').append('<br>');
            
            $(tab).find('.js-poster-tab-text-textarea').change(function () {
                $(tab).find('.js-poster-tab-text-result').html($(this).val());
            });
            
            return tab;
        }

Как видите, ничего сложного нет. Можно переходить к следующей вкладке 

 

Создаем вкладку "Парсинг данных" в  sjPoster

Данные о группах и их разделах можно получить из кода двух страниц. С интерфейса "написать в группы", при переключении в выпадающем списке групп. И с конкретного интерфейса создания темы в каждой группе. Несмотря на поддержку обоих способов, советуем использовать последний интерфейс, так как в случае с "написать в группы" код необходимо копировать из вкладки разработчика, предварительно выполнив следующий js: 

  • $("#id_groups option:selected").attr("selected", "selected")

Итоговый код получился следующий:

        // Создаем таб для парсинга
        createTabParse: function () {
            var tab = $('<div id="tabs-parse" class="js-poster-tab">'),
                self = this;
            tab
            .append('<span>Разместите html страницы внутри следующей области</span>').append('<br>').append('<br>')
            .append('<span class="js-poster-tab-comment">Если вы парсите с общего интерфейса "Написать в группы",' + 
                    ' а не c интерфейса группы, то перед копированием html необходимо выполнить магическую Js строку (вообще, html должен обновляться браузером, но...):</span>').append('<br>')
            .append('<span class="js-poster-tab-comment">$("#id_groups option:selected").attr("selected", "selected")</span>').append('<br>').append('<br>')
            .append('<textarea type="text" class="js-poster-tab-parse-textarea" >').append('<br>').append('<br>')
            .append('<span>А затем нажмите кнопку (как только парсинг закончится, текст из области исчезнет)</span>').append('<br>').append('<br>')
            .append('<button class="js-poster-tab-parse-button">Парсить</button>').append('<br>');
            
            tab.find('.js-poster-tab-parse-button').click(function () {
                var interests = [],
                    htmlText = tab.find('.js-poster-tab-parse-textarea').val(),
                    name = $(htmlText).find('#group_link').text(),
                    id = $(htmlText).find('.subscriberu_fbbox').next().text()
                    ;
                $(htmlText).find('.write-place input').each(function () {
                    interests.push({
                        value: $(this).val(),
                        name: $.trim($(this).parent().text().replace(/[\n\r]/g, ''))
                    });
                });
                
                if ($(htmlText).find('#id_groups').length > 0) {
                    id = $(htmlText).find('#id_groups').val();
                    name = $(htmlText).find('#id_groups option:selected').text();
                    
                    $(htmlText).find('#id_interests option').each(function () {
                        if($(this).val()) {
                            interests.push({
                                value: $(this).val(),
                                name: $(this).text()
                            });
                        }
                    });
                }
                
                if (!!id) self.updateGroup(id, name, interests);
                
                tab.find('.js-poster-tab-parse-textarea').val('');
            });
            
            return tab;
        },

Механизм достаточно прост. Вы просто копируете html содержимое каждой страницы и вставляете в поле для ввода. Затем нажимаете кнопку "Парсить". После чего, все данные о группе сохраняются внутри sjPoster, а само поле очищается для следующего ввода. Как вы заметили, для тех групп, которые уже присутствуют в списке, просто обновляются имена и список их разделов. Так что не будет никакой необходимости в чистке от дубликатов.

 

Создаем вкладку "Настройки групп" в sjPoster

Данная вкладка строится на основе всех спарсенных данных sjPoster, поэтому каждый раз перестраивается при переходе на нее. Итоговый код для создания вкладки состоит из 2-х функций. Одна функция для создания пустой вкладки, другая для построения ее содержимого, при переходе. Сам код:

        formTabSettings: function () {
            window.dialogs.wait.show();
            
            var tab = $('#tabs-settings'),
                self = this;
            
            tab.html('');
            
            tab
            .append('<span>Добавить категорию</span>').append('<br>').append('<br>')
            .append('<input type="text" class="js-poster-tab-input" >').append('<span>&nbsp;</span>')
            .append('<button class="js-poster-tab-settings-add-cat">Добавить</button>').append('<br>').append('<br>')
            .append('<span>Текущий набор и соответствие группам</span>').append('<br>').append('<br>')
            .append('<div class="js-poster-tab-settings-list"></div>').append('<br>').append('<br>')
            .append('<span>Сформировать JSON для сохранения</span>').append('<br>').append('<br>')
            .append('<button class="js-poster-tab-settings-form-json">Получить JSON для сохранения</button>').append('<br>').append('<br>')
            .append('<textarea class="js-poster-tab-settings-textarea" >').append('<br>').append('<br>')
            ;    
 
            // Собираем и отображаем группы из списка по категориям
            var copyGroups = $.extend(true, {}, self._settings.groups),
                cats = self._settings.cats,
                divList = tab.find('.js-poster-tab-settings-list'),
                selectCats = $('<select class="js-poster-tab-settings-cats" >');
            
            // Формируем первый элемент без группы
            selectCats.append('<option value="">Без категории</option>');
            
            // Проходимся по всем категориям и наполняем список для переноса
            for(var counter in cats) {
                if (cats.hasOwnProperty(counter)) {
                    selectCats.append('<option value="' + counter + '">' + cats[counter].name + '</option>');
                }
            }
            
            // Проходимся по всем категориям
            for(var counter in cats) {
                if (cats.hasOwnProperty(counter)) {
                    divList.append('<hr/>')
                    .append('<span class="js-poster-tab-settings-list-cat" data="' + counter + '">' + cats[counter].name + ' (' + cats[counter].groups.length + ')' + '</span>')
                    .append('<button class="js-poster-tab-settings-list-cat-btn-del">Удалить</button>')
                    .append('<br/>').append('<br/>');
                    
                    for(var gCnt = 0; gCnt < cats[counter].groups.length; gCnt++) {
                        if (copyGroups[cats[counter].groups[gCnt] + '']) {
                            var group = copyGroups[cats[counter].groups[gCnt] + ''],
                                groupContainer = $('<div class="js-poster-tab-vibor-list-cat-group-container" >');
                            
                            divList.append(groupContainer);
                            
                            groupContainer.append('<span>&nbsp;</span>')
                            .append('<span class="js-poster-tab-settings-list-cat-group" data="' + cats[counter].groups[gCnt] + '" currCat="' + counter + '">' + group.name + ' (' + cats[counter].groups[gCnt] + ')' + '</span>')
                            .append('<button class="js-poster-tab-settings-list-btn-del">Удалить</button>')
                            .append(selectCats.clone())
                            .append('<button class="js-poster-tab-settings-list-btn">Перенести в </button>')
                            .append('<br/>').append('<br/>');
                        
                            delete copyGroups[cats[counter].groups[gCnt] + ''];
                        }
                    }
                }
            }
            
            var nonCatCount = 0;
            
            // Отображаем группы без категории
            divList.append('<hr/>')
            .append('<span class="js-poster-tab-settings-list-cat" data="0">Без категории</span>').append('<br/>').append('<br/>');
            
            for(var cCounter in copyGroups) {
                var groupContainer = $('<div class="js-poster-tab-vibor-list-cat-group-container" >');
                            
                divList.append(groupContainer);
                
                groupContainer.append('<span>&nbsp;</span>')
                .append('<span class="js-poster-tab-settings-list-cat-group" data="' + cCounter + '" currCat="0">' + copyGroups[cCounter + ''].name + ' (' + cCounter + ')' + '</span>')
                .append('<button class="js-poster-tab-settings-list-btn-del">Удалить</button>')
                .append(selectCats.clone())
                .append('<button class="js-poster-tab-settings-list-btn">Перенести в </button>')
                .append('<br/>').append('<br/>');
                nonCatCount++;
            }
            
            var lastCat = divList.find('.js-poster-tab-settings-list-cat:last');
            lastCat.text(lastCat.text() + ' (' + nonCatCount + ')');
            
            // Добавление категории
            tab.find('.js-poster-tab-settings-add-cat').click(function () {
                if(tab.find('.js-poster-tab-input').val()) {
                    var catName = tab.find('.js-poster-tab-input').val(),
                        date = new Date(),
                        catId = date.getUTCFullYear() + '' + date.getMonth() + '' 
                            + date.getDate() + '' + date.getHours() + '' 
                            + date.getMinutes() + '' + date.getSeconds() + ''
                            + date.getMilliseconds();
                            
                    self.updateCats(catId, catName);
                    self.formTabSettings();
                }
                    
            });
            
            // Перенос группы
            tab.find('.js-poster-tab-settings-list-btn').click(function () {
                var catId = $(this).prev().val() + '',
                    groupId = $(this).prev().prev().prev().attr('data') + '',
                    currCat = $(this).prev().prev().prev().attr('currCat') + '';
                
                if(currCat != '0') 
                    self.deleteCatGroup(currCat, groupId);
                self.addCatGroup(catId, groupId);
                self.formTabSettings();
            });
 
            // Удаление группы
            tab.find('.js-poster-tab-settings-list-btn-del').click(function () {
                var groupId = $(this).prev().attr('data') + '',
                    currCat = $(this).prev().attr('currCat') + '';
                
                if(currCat != '0') 
                    self.deleteCatGroup(currCat, groupId);
                self.deleteGroup(groupId);
                self.formTabSettings();
            });
 
            // Удаление категории
            tab.find('.js-poster-tab-settings-list-cat-btn-del').click(function () {
                var catId = $(this).prev().attr('data') + '';
                self.deleteCat(catId);
                self.formTabSettings();
            });
            
            // Формируем JSON для сохранения
            tab.find('.js-poster-tab-settings-form-json').click(function () {
                tab.find('.js-poster-tab-settings-textarea').val($.toJSON(self._settings));
            });
            
            window.dialogs.wait.hide();
        },
        
        createTabSettings: function () {
            var tab = $('<div id="tabs-settings" class="js-poster-tab">');
            return tab;        
        },

Как видно, код получился немного громоздким. Но, это вполне естественно для такого набора функций. Теперь, на этой вкладке вы можете формировать категории и наполнять их группами, а так же получать JSON со всеми текущими настройками для последующего сохранения в файле. Важно отметить, что все изменения сохраняются внутри sjPoster, так что при переходе с одной вкладки на другую, перестроение интерфейса не сбросит текущих настроек. Однако, не забывайте после изменения настроек, сохранять JSON.

Теперь, после того, как были наполнены категории и группы, можно переходить к интерфейсу настройки парсинга. 

 

Создаем вкладку "Выбор групп для постинга" в sjPoster

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

 
        // Создаем для группы список разделов
        createGroupInterestList: function (groupId) {
            var select = $('<select class="js-poster-tab-group-interests-list">'),
                group = this._settings.groups[groupId + ''];
            
            if (group) {
                for (var counter = 0; counter < group.interests.length; counter++) {
                    select.append('<option value="' + group.interests[counter].value + '">' + group.interests[counter].name + '</option>');
                }
            }
            
            if (select.find('option').length == 0) {
                select.attr('disabled', 'disabled');
                select.addClass('disabled');
            }
            
            return select;
        },
        
        formTabVibor: function (tab) {
            var self = this;
            tab.html('');
            
            tab
            .append('<br>').append('<hr/>')
            .append('<button class="js-poster-tab-vibor-btn-refresh">Перезагрузить группы</button>').append('<br>')
            .append('<div class="js-poster-tab-vibor-list"></div>').append('<br>')
            ;
            
            // Собираем и отображаем группы из списка по категориям
            var copyGroups = $.extend(true, {}, self._settings.groups),
                cats = self._settings.cats,
                divList = tab.find('.js-poster-tab-vibor-list'),
                catGroupsCounter;
            
            // Проходимся по всем категориям
            for(var counter in cats) {
                if (cats.hasOwnProperty(counter)) {
                    var container = $('<div class="js-poster-tab-vibor-list-container">');
                    divList.append(container);
                    container.append('<hr/>')
                    .append('<span class="js-poster-tab-vibor-list-cat" data="' + counter + '">' + cats[counter].name + ' (' + cats[counter].groups.length + ')' + '</span>')
                    .append('<button class="js-poster-tab-vibor-list-cat-btn-del">Удалить</button>')
                    .append('<br/>').append('<br/>');
                    
                    for(var gCnt = 0; gCnt < cats[counter].groups.length; gCnt++) {
                        if (copyGroups[cats[counter].groups[gCnt] + '']) {
                            var group = copyGroups[cats[counter].groups[gCnt] + ''],
                                groupContainer = $('<div class="js-poster-tab-vibor-list-cat-group-container" >');
                            
                            container.append(groupContainer);
                            
                            groupContainer.append('<span>&nbsp;</span>')
                            .append('<span class="js-poster-tab-vibor-list-cat-group" data="' + cats[counter].groups[gCnt] + '" currCat="' + counter + '">' + group.name + ' (' + cats[counter].groups[gCnt] + ')' + '</span>')
                            .append('<button class="js-poster-tab-vibor-list-btn-del">Удалить</button>')
                            .append(self.createGroupInterestList(cats[counter].groups[gCnt]))
                            .append('<br/>').append('<br/>');
                        
                            delete copyGroups[cats[counter].groups[gCnt] + ''];
                        }
                    }
                }
            }
            
            var nonCatCount = 0;
            
            // Отображаем группы без категории
            var container = $('<div class="js-poster-tab-vibor-list-container">');
            divList.append(container);
 
            container.append('<hr/>')
            .append('<span class="js-poster-tab-vibor-list-cat" data="0">Без категории</span>')
            .append('<button class="js-poster-tab-vibor-list-cat-btn-del">Удалить</button>')
            .append('<br/>').append('<br/>');
            
            for(var cCounter in copyGroups) {
                var groupContainer = $('<div class="js-poster-tab-vibor-list-cat-group-container" >');
                container.append(groupContainer);
                
                groupContainer.append('<span>&nbsp;</span>')
                .append('<span class="js-poster-tab-vibor-list-cat-group" data="' + cCounter + '" currCat="0">' + copyGroups[cCounter + ''].name + ' (' + cCounter + ')' + '</span>')
                .append('<button class="js-poster-tab-vibor-list-btn-del">Удалить</button>')
                .append(self.createGroupInterestList(cCounter))
                .append('<br/>').append('<br/>');
                nonCatCount++;
            }
            
            var lastCat = divList.find('.js-poster-tab-vibor-list-cat:last');
            lastCat.text(lastCat.text() + ' (' + nonCatCount + ')');
            
            tab.find('.js-poster-tab-vibor-btn-refresh').click(function () {
                self.formTabVibor(tab);
            });
            
            // Удаление группы
            tab.find('.js-poster-tab-vibor-list-btn-del').click(function () {
                var countGroups = $(this).parent().parent().children('.js-poster-tab-vibor-list-cat-group-container').length - 1,
                    catText = $(this).parent().parent().find('.js-poster-tab-vibor-list-cat').text();
                $(this).parent().parent().find('.js-poster-tab-vibor-list-cat').text(catText.replace(/\(\d+\)/g, '(' + countGroups + ')'));
                $(this).parent().remove();
            });
 
            // Удаление категории
            tab.find('.js-poster-tab-vibor-list-cat-btn-del').click(function () {
                $(this).parent().remove();
            });
            
        },
        
        createTabVibor: function () {
            var tab = $('<div id="tabs-vibor" class="js-poster-tab">');
            this.formTabVibor(tab);
            return tab;        
        },

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

Примечание: Так как список будет использоваться во время публикации статей, то его нельзя менять, при запущенном постинге. 

Теперь, можно приступать к интерфейсу автоматической публикации.

 

Создаем вкладку "Запуск постинга" в sjPoster

Вкладка запуск постинга использует те настройки, которые были выбраны на вкладке "Выбор групп для постинга", поэтому, как уже говорилось, после запуска лучше перейти к другим делам и не изменять настройки на других вкладках. Сам механизм постинга использует для автоматической публикации хитрость с тегом iframe, о которой уже говорилось. Все результаты постинга отображаются в iframe, так что пользователю остается только проверить, все ли анонсы были корректно опубликованы. Подробнее о том, как воспринимать результаты, читайте в следующей главе (4-я).

Сам код:

        getRandomInt: function(min, max)
        {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },
 
        createFormAndSubmit: function (container, groupForSend, counterSend, onload) {
            var formContainer = $('<div class="js-poster-tab-start-list-container">'),
                groupId = groupForSend.find('.js-poster-tab-vibor-list-cat-group').attr('data'),
                iframeForSend = $('<iframe class="js-poster-tab-start-list-container-iframe" width="450" height="80" name="' + 'iframe_' + groupId + '" >'),
                formForSend = $('<form class="js-poster-tab-start-list-container-form" target="' + 'iframe_' + groupId + '" >'),
                btn = $('<button class="js-poster-tab-start-list-container-btn" >Результат нужный - удалить форму</button>'),
                interestId = groupForSend.find('.js-poster-tab-group-interests-list').val(),
                header = $.trim($('#tabs-text').find('input.js-poster-tab-input').val()),
                body = $.trim($('#tabs-text').find('.js-poster-tab-text-textarea').val()),
                info = $('<div class="js-poster-tab-start-list-container-info">'),
                self = this;
            container.append(formContainer);
            formContainer
            .append('<hr/>')
            .append(formForSend)
            .append(info)
            .append(btn)
            .append('<br/>').append('<br/>').append('<br/>').append('<br/>')
            .append(iframeForSend)
            ;
            
            // Заполним info
            info
            .append('<span>Техническая информация запуска #(' + (counterSend + 1) + '): </span>')
            .append('<br/>')
            .append('<span>Группа: </span>')
            .append('<span>' + groupForSend.find('.js-poster-tab-vibor-list-cat-group').text() + '</span>')
            .append('<br/>')
            .append('<span>Раздел: </span>')
            .append('<span>' + groupForSend.find('.js-poster-tab-group-interests-list option:selected').text() + '</span>')
            .append('<br/>')
            .append('<span>Раздел (ID): </span>')
            .append('<span>' + groupForSend.find('.js-poster-tab-group-interests-list option:selected').val() + '</span>')
            ;
            
            // Заполняем iframe
            iframeForSend[0].onload = function () { iframeForSend[0].onload = null; onload(); };
            
            // Заполняем форму и отправляем
            formForSend[0].method = 'post';
            formForSend[0].action = 'http://subscribe.ru/member/group/write/?random=' + (new Date()).getUTCMilliseconds() + '' + self.getRandomInt(1, 60000);
            formForSend.append('<input name="groups" type="hidden" value="' + groupId + '" />');
            formForSend.append('<input name="interests" type="hidden" value="' + interestId + '" />');
            formForSend.append('<input name="subject" type="hidden" value="" />');
            formForSend.find('input[name="subject"]').val(header);
            formForSend.append('<input name="body" type="hidden" value="" />');
            formForSend.find('input[name="body"]').val(body);
            formForSend.append('<input type="submit" value="Отправить еще раз" />');
            
            btn.click(function () {
                btn.parent().remove();
            });
            
            formForSend.submit();
        },
        
        createTabStart: function () {
            var tab = $('<div id="tabs-start" class="js-poster-tab">'),
                self = this;
 
            tab
            .append('<br>')
            .append('<hr/>')
            .append('<button class="js-poster-tab-start-btn">Запустить отправку анонсов и статей на Subscribe</button>')
            .append('<hr/>')
            .append('<br>')
            .append('<div class="js-poster-tab-start-list"></div>')
            .append('<br>')
            ;
            
            tab.find('.js-poster-tab-start-btn').click(function () {
                var errorMsg = '';
                    
                if ($('#tabs-vibor').find('.js-poster-tab-vibor-list-cat-group-container').length == 0) {
                    errorMsg += '<font color=red>* Не выбраны группы для анонсирования</font>'
                        + '<br/>'
                        + 'Перейдите на вкладку "Выбор групп для постинга" <br/> и выберите группы для постинга'
                        + '<br/>';
                }
                if ($.trim($('#tabs-text').find('input.js-poster-tab-input').val()) == '') {
                    errorMsg += '<font color=red>* Не указан заголовок</font>'
                        + '<br/>'
                        + 'Перейдите на вкладку "Текст анонса и заголовок" <br/> и укажите заголовок'
                        + '<br/>';
                }
                if ($.trim($('#tabs-text').find('.js-poster-tab-text-textarea').val()) == '') {
                    errorMsg += '<font color=red>* Не указан анонс</font>'
                        + '<br/>'
                        + 'Перейдите на вкладку "Текст анонса и заголовок" <br/> и укажите анонс'
                        + '<br/>';
                }
                
                if (errorMsg !== '') {
                    window.dialogs.error.show('Исправьте ошибки', errorMsg);
                    return;
                }
                
                window.dialogs.wait.show();
                
                var groupForSend = $('#tabs-vibor').find('.js-poster-tab-vibor-list-cat-group-container'),
                    groupForSendLength = groupForSend.length,
                    container = tab.find(".js-poster-tab-start-list"),
                    recursionFunction = function (counter, handler) {
                        // Защита от запуска с конца
                        if (counter >= groupForSendLength) {
                            if (typeof(handler) === 'function')
                                handler();
                            return;
                        }
 
                        // Если тукущий элемент не последний, то запускаем следующую итерацию
                        if (counter < groupForSendLength - 1) {
                            self.createFormAndSubmit(container, $(groupForSend[counter]), counter, function () {
                                setTimeout(function () {
                                        recursionFunction(counter + 1, handler);
                                    }, 
                                self.getRandomInt(15000, 45000));
                            });
                        }
                        else {
                            self.createFormAndSubmit(container, $(groupForSend[counter]), counter);
                            if (typeof(handler) === 'function')
                                handler();
                        }
                    };
                
                recursionFunction(0, function () { window.dialogs.wait.hide(); window.dialogs.info.show('Выполнение операции', 'Постинг завершен!'); });
            });
            
            return tab;        
        },

Как видно, во время автоматического постинга, для каждой группы создается отдельный iframe и form. Форма с нужными данными будет отправлять post-ом данные в iframe, таким образом, отправка данных будет происходить с текущим входом пользователя. Так же для автоматической отправки статей и анонсов используется рекурсивный механизм, который запускает следующую отправку статьи спустя время из промежутка 15-45 секунд. Сделано это по нескольких причинам. Во-первых, лишняя нагрузка на сервер будет приводить к отказам сервиса, что сделает инструмент бессмысленным. Во-вторых, одновременное массовое обращение с большой вероятностью будет расцениваться как DDOS-атака со всеми вытекающими обстоятельствами, что в общем-то было бы правдой. В-третьих, сервис бесплатно предоставляет вам возможности, которыми вы пользуетесь. 

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

И последняя проверка. Смотрим соответствие требованиям:

  • Автоматизация публикации статей и анонсов. Инструмент сам отсылает статьи и анонсы - Есть!
  • Выбор групп и разделов. Есть вкладка для настройки публикации - Есть! 
  • Относительно простой способ сбора данных о группах и разделах. Данные собираются из исходного html-страницы простым для пользователя способом - Есть!
  • Конфигурация должна относительно легко меняться. Все данные легко добавляются на вкладке "Парсинга" и легко редактируются в "Настройки групп" - Есть!
  • Инструменты для категоризации групп. Вкладка "Настройки групп" позволяет создавать категории и управлять группами - Есть!
  • Небольшая требовательность инструмента для запуска. Нужен только веб-сервер - Есть!
  • Легкость корректировки. Весь код в открытом виде, его всегда можно дополнить или подкорректировать - Есть!
  • Время на разработку инструмента должно быть адекватным. Инструмент написан за относительно небольшой срок, который с лихвой компенсируется сохраненным временем последующих публикаций - Есть!

Теперь, собираем все воедино и располагаем на любом веб-сервере. Можно даже на localhost. Основная причина в необходимости веб-сервера - это файл data.txt. 

Примечание: Необходимость с веб-сервером из-за сохранения данных в data.txt, безусловно, можно обойти. Но, инструмент разрабатывался и тестировался только на веб-сервере. Кроме того, если у вас нет локального веб-сервера, то он есть на вашем хостинге, куда достаточно просто закинуть проект. В крайнем случае, вы всегда можете использовать бесплатные хостинги, коих масса. Все коды находятся в открытом виде, так что вы всегда можете проверить инструмент на наличие скрытых возможностей.

 

4. Пример автоматической публикации из sjPoster 

После того, как вы расположили проект на любом веб-сервере, включая localhost, можно приступать к его использованию.

1. Войдите в сервис subscribe и в том же браузере откройте проект sjPoster.

Примечание: Если вы умеет составлять html, то вам не обязательно выполнять пункты 2 и 3. 

2. Откройте любой из интерфейсов для написания статьи или анонса, а затем оформите текст для публикации, например, как показано ниже.

Пишем анонс в Subscribe

Совет: Делайте анонс уникальным. Копирование начала статьи будет уменьшать уникальность вашего текста. А так же выставляйте для ссылок "открываться в другом окне", во многих группах это условие является обязательным.

3. Нажмите на кнопку "html" и скопируйте текст из всплывающего окна

Копируем html из анонса

4. Откройте проект sjPoster в том же браузере, где вы залогинены, и на вкладке "Текст анонса и заголовок" введите текст анонса или статьи, а так же заголовок

Вставляем текст и заголовок в sjPoster

5. Затем перейдите в браузере на сервис subscribe и откройте интерфейс "Создать тему" в первой группе.

Открываем создать тему в группе

6. Нажмите в браузере комбинацию клавиш "Ctrl + U", чтобы открыть исходный код страницы. И скопируйте его.

Копируем html код страницы

7. Откройте sjPoster на вкладке "Парсинг данных" и вставьте скопированный html-код страницы в поле для ввода. Нажмите кнопку "Парсить". По окончанию парсинга, текст внутри поля должен исчезнуть.

Парсим данные в sjPoster

8. Повторяйте с шаги 5-7 до тех пор, пока не пройдетесь по всем группам.

9. Откройте вкладку "Настройки групп". Создайте нужные категории и распределите группы по категориям. Нажмите кнопку "Получить JSON для сохранения" и сохраните полученный JSON в файле data,txt

Примечание: Кнопка удаления напротив категорий - удаляет только категории, все группы переходят в группу "Без категории". 

ВАЖНО: Кнопка удаления напротив групп - удаляет их из инструмента, и для их восстановления потребуется заново пройти шаги 5-7. Поэтому будьте внимательны.

Совет: Прежде, чем вносить изменения, нажмите кнопку "Получить JSON для сохранения" и сохраните полученный JSON. Желательно, это сделать сразу после парсинга.

Сохраняем настройки групп в sjPoster

10. Не обязательно, но желательно. Чтобы убедиться, что все настройки в data.txt были успешно сохранены вами. Сохраните текст анонса и заголовок. Перезагрузите инструмент, заново заполните текст анонса и заголовок, а затем перейдите на вкладку "Настройки групп". Все данные должны быть такими же, как вы настроили.

11. Перейдите на вкладку "Выбор групп для постинга". И настройте разделы для групп, а так же оставьте только те группы, в которые вы хотите разместить анонс.

Выбираем группы для автоматической публикации в sjPoster

12. Перейдите на вкладку "Запуск постинга" и нажмите кнопку "Запустить отправку анонсов и статей на Subscribe"

Запускаем автоматический постинг в sjPoster

13. Запустится постинг, во время которого вы будете наблюдать страницу с всплывающим окном ожидания

Ожидание конца постинга в sjPoster

14. По окончанию постинга, на странице должно появиться всплывающее окно с текстом "Постинг завершен"

Сообщение о конце постинга в sjPoster

15. Для каждого постинга будет отображаться область с технической информацией (группа и раздел), а так же форма iframe с результатом и кнопка "Результат нужный - удалить форму", которая по нажатию удалит текущую форму из списка. 

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

  • {"id":"Текст ссылки", "result": 1}

Текст "result:1" и наличие ссылки в "id" - это обязательное условие. Если в поле result будет что-то отличное от 1, то это так же считается ошибкой и необходимо зайти на сервис, а затем вручную повторить отправку анонса.

Возможны и другие варианты ответов. Например, на следующем рисунке, первая отправка прошла корректно, а вторая нет из-за нагруженности сервиса.

Пример результата постинга в sjPoster

Если текст будет не читабелен, как в примере, то можно воспользоваться любым декодером текста. На рисунке ниже показан как раз пример использования такого декодера для второго ответа:

Декодируем текст ошибки

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

 

5. Заключительные слова о постинге в сервисе subscribe

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

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

Совет. Не стоит идти на поводу собственных чувств и пытаться уменьшить интервалы отправки анонсов или же пытаться отправлять сразу с нескольких инструментов. Помните, что несмотря на то, что инструмент будет находится на веб-сервере, все запросы будут происходить с вашего IP адреса и от ваших регистрационных данных. Используйте данный инструмент со здравым смыслом, и вы сэкономите себе много времени.

Ссылка для скачивания инструмента:

sjPoster 

1 1 1 1 1 1 1 1 1 1 Рейтинг 5.00 (2 Голосов)

Комментарии / отзывы   

+2 # Сергей 16.09.2016 08:39
Спасибо за титанический труд. Посмотрел и облизнулся. Прога парсит, но только по одной группе. Ни о каком списке речи быть не может. Уже сохранил JSON для каждой группы в data.txt и все равно после запуска она не видит группы.Или это уже не актуальная информация?
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # Игорь (Администратор) 16.09.2016 11:26
Пожалуйста. sjPoster делал под старый интерфейс. Тем не менее, даже после того как сменили интерфейс, нормально постил (сам api остался тот же). Хотя, я уже давно не использовал, могло что-то измениться.

По поводу data.txt, sjPoster нужно располагать на реальном серевере или хотя бы localhost, иначе data.txt не будет подгружаться. Т.е. если вы скачали архив и просто открыли index.html в браузере, то файлик не подгрузится (начинаться будет с file///C://...). Кидаете sjPoster куда-нибудь к себе на сайт в суб-суб-каталог и пользуетесь. Или если у вас есть любой локальный сервер с доступом в интернет, то туда.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # человек 06.01.2017 17:48
Программа парсит группы только со старого дизайна Subcribe, с нового нет.

Плюс надо менять кодировку в файлах index и jquery-subscribe-js-poster c utf-8 на windows-1251, чтобы вместо символов в интерфейс и публикуемом тексте был русский язык.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # Игорь (Администратор) 08.01.2017 14:00
Спасибо за ценную информацию. Честно говоря, просто к моменту смены старого дизайна перестал постить, поэтому сам не тестировал. Но, штука действительно полезная оказалась. Времени экономила массу + ссылочная масса + небольшой прирост посещаемости.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+1 # человек 10.01.2017 01:40
Да, кстати, еще важная информация. Если делать сайт на localhost (делал с помощью Denwer) файл data.txt с группами не подгружается. Программа отлично работает только на хостинге в интернете.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # Игорь (Администратор) 10.01.2017 02:26
А вот тут у вас что-то странное. У меня с localhost все отлично запускалось, правда я и не на денвере запускал.

Если уж о Denwer говорить и у вас проблемы с localhost, то никто вам не мешает создать любой домен вида "sjposter.sjposter" и запускать из под него у себя на машине.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+1 # человек 10.01.2017 02:34
У меня сайт на localhost был назван mysite.com, там ни чего подгружается. Думаю дай сделаю сайт sjposter.sjposter.ru, все заработало :-)
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # Игорь (Администратор) 10.01.2017 17:56
Вообще такого быть не должно, но раз у вас запустилось.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+1 # человек 10.01.2017 02:38
Спасибо за такую полезную программу :-)
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
+2 # Игорь (Администратор) 10.01.2017 17:56
Пожалуйста
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
0 # Виктор 17.04.2017 01:12
8) Спасибо за прекрасный скрипт!

правда пришлось переделать, и адаптировать под новый дизайн.
Вариантов конечно кучу сделал..все тестировал по разному... Но потом все таки обьединил часть из вашей реализации и своей...создал мультиаккаунтность...сделал скрипт +php+curl на локальном денвере...сейчас работает просто отменно. Спасибо вам, с вашей статьи пошли идеи для других реализаций. :-)
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору
0 # Игорь (Администратор) 20.04.2017 00:24
Насчет дизайна. Вообще, раньше была возможность воспользоваться старым дизайном, не знаю насколько так сейчас. Плюс API оставался прежним.

По поводу php+curl. У меня практически не менялся список групп, так что этот вариант мне был самым оптимальным. Плюс его можно запустить вообще везде.

А так, пожалуйста.
Ответить | Ответить с цитатой | Цитировать | Сообщить модератору

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

Комментарий - это вежливое и наполненное смыслом сообщение (правила).


Введите защитный код

Обновить
Защитный код

Каталог программ