...

вторник, 25 февраля 2014 г.

[Из песочницы] VkInviter – приглашатель-помощник администраторам групп ВКонтакте

Всем доброго дня!



Я являюсь администратором одной музыкальной группы ВКонтакте (далее – ВК). Музыканты часто ездят с гастролями по разным городам России и странам СНГ. Один из способов оповестить фанатов группы о предстоящем концерте в их городе – разослать приглашения на соответствующую встречу ВК.

В статье хочу показать одно из возможных решений этой задачи.

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


Прежде всего, вы должны являться администратором группы ВК и данная группа должна быть организатором встречи в ВК.


Решение «Все сделать вручную»



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

«Пригласить друзей» – «Пригласить участников группы»:


Минусы этого решения очевидны:



  • Долго, т.к. в некоторых городах количество участников больше нескольких тысяч

  • Невозможно выполнить приглашение участников, проживающих в определенном городе


Решение «Автоматизировать процесс»



Здесь есть два подхода: написать приложение с помощью ВК API или standalone через post-get запросы.

После анализа я понял, что ВК API не подходит. В описании нет метода приглашения пользователя на встречу, да и не очень-то хочется связываться с регистрацией приложения и прочими внутренними правилами ВК.


Следовательно, придется анализировать post-get запросы, и написать «симулятор» пользователя.


Рассмотрим основные этапы работы:



  • Авторизация пользователя

  • Получение участников группы в определенном городе

  • Рассылка приглашений


Авторизация



С логином все достаточно просто – POST запрос по адресу login.vk.com:

act=login&q=1&al_frame=1&expire=1&captcha_sid=&captcha_key=&from_host=vk.com&from_protocol=http&email=USERNAME&pass=PASSWORD

В ответ получаем параметр location с адресом, на который будет выполнен редирект:

http://ift.tt/OwReby

После редиректа в куки записывается параметр remixsid – идентификатор сессии, который является подтверждением успешной авторизации


Поклонники из города N



Для получения списка участников группы из определенного города воспользуемся стандартным поиском vk.com/search, нам особенно важен этот набор фильтров:


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

al_search.php?al=1&c[group]=6206&c[section]=people

где group — id группы.


После добавления фильтрации по стране и городу запрос примет вид:

al_search.php?al=1&c[city]=1&c[country]=1&c[group]=6206&c[section]=people

где, соответственно, city — id города, country — id страны


Ответом на запрос является список пользователей.

Заголовок ответа несет в себе два важных значения:

"has_more":true,"offset":200

has_more — определяет, будут ли еще пользователи в выдаче

offset — «отступ» или сдвиг от первого пользователя


Блок с информацией об одном пользователе имеет вид:



<div class="people_row three_col_row clear_fix">
<div class="img search_bigph_wrap fl_l" onmouseover="Searcher.bigphOver(this, 1746355)">
<a href="/romasladky" onclick="return nav.go(this, event);"><img class="search_item_img" src="http://ift.tt/OwRfMW" /></a>
</div>
<div class="info fl_l">
<div class="labeled name"><a href="/romasladky" onclick="return nav.go(this, event);">Рома Сладкин</a></div><div class="labeled ">Климатпрофф</div><div class="online">Online</div>
</div>


В этом блоке интересны данные:



  • id

  • имя

  • href на страницу


Парсить ответ достаточно удобно с помощью регулярных выражений.

Для получения id использую такое выражение:

"<div[^>]*onmouseover=\"Searcher.bigphOver\\(this, (\\d+)\\)\">"


Для получения имени и href:

"<div class=\"labeled name\"><a href=\"/([^\"]+)\" onclick=\"return nav\\.go\\(this, event\\);\">([^<]+)<";


В данном подходе есть одно синтетическое ограничение — контакт не выдает более 1000 результатов поиска. Это критично, т.к., например, в Москве у группы 3000+ участников. Что бы обойти это ограничение придется добавить дополнительную фильтрацию на пользователей, а затем объединить результаты работы всех фильтров.


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

Для этой задачи подойдут:

Пол — [sex], значения: 0-2

Порядок сортировки – [sortId], значения 0-1

Семейное положение – [statusId], значения 0-7.


На ruby этот перебор выглядит так:



offset=0
for sort_id in 0..1 do
for status_id in 0..7 do
for sex_id in 0..2 do
offset=0
begin
get_str = "/al_search.php?al=1"
get_str += "&c[city]=#{city_id}" if city_id.to_i>0
get_str += "&c[country]=#{country_id}" if country_id.to_i>0
get_str += "&c[group]=#{group_id}"
get_str += "&c[name]=1&c[section]=people"
get_str += "&c[sex]=#{sex_id}" if sex_id>0
get_str += "&c[sort]=#{sort_id}" if sort_id>0
get_str += "&c[status]=#{status_id}" if status_id>0
get_str += "&offset=#{offset}" if offset>0

...


Таким образом будет получен список всех пользователей в городе.


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

act=a_invite&al=1&gid=65898108&hash=99247d766b77d7a584&mid=22935

где gid – id встречи, mid – id пользователя, hash – некий хэш, несущий информацию о приглашающем. Вот этот-то хэш теперь надо получить для всех пользователей из нашего списка.


Получение hash



Для получения хэша придется парсить список «друзей», которых я могу пригласить на встречу. Друзья указаны в кавычках, т.к. здесь под этим понятием выступают все участники группы-организатора.

GET-запрос получения этого списка выглядит так:

http://ift.tt/OwRg3a

, где gid – id встречи

Ответом будет является json-строка с набором блоков вида:

['1298','http://ift.tt/1dskOV6','/id1298','2','0','Сергей Суворов','0','1','61','','0','2d9d4211c2297c3a06']

где 1-й параметр – id пользователя, а последний – нужный нам хэш.

Обработка этих данных выполнена с помощью регулярного выражения:

@"\['(\d*)','[^']*','\/([\w\.]+)','[^']*','[^']*','([^']+)','[^']*','(\d+)','[^']*','[^']*','[^']*','(\w+)'\]"


Формат данного ответа меняется достаточно часто, поэтому в программе был сделан трюк: дополнительно происходит вычисление количества ссылок на аватарки (2-параметр) и проверка совпадения количества аватарок и пользователей после обработки.


На С# эта проверка выглядит так:



string patternNorm = @"'http:\/\/cs\d+\.vk\.me";
string patternDeactiveOrDeleted = @"'\/images\/\w+\.gif'";

MatchCollection mcNorm = Regex.Matches(responseString, patternNorm);
MatchCollection mcDeactiveOrDeleted = Regex.Matches(responseString, patternDeactiveOrDeleted);

int httpCount = mcNorm.Count + mcDeactiveOrDeleted.Count;
if (listVkUser.Count != httpCount)
{
throw new Exception("http total count != user count");
}


После обработки списка «друзей» и слияния его со списком пользователей из города все готово к рассылке приглашений.


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

captcha.php?sid={1}&s=1 , где sid – уникальный id данной капчи.

При каждом запросе по данному url будет выдаваться новая картинка с капчей.

Здесь – единственное место, где в процессе требуется участие пользователя и ручной ввод.


Программа VkInviter



Для автоматизации перечисленных действий написана программа VkInviter.

Главное окно программы представлено на скриншоте:

image

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

Исходный код выложен на github, также выложен скрипт на ruby, который может быть полезен для понимания общей логики.


Заключение



В заключении хочу сказать, пару слов об эффективности.

Заметил, что приблизительно 60% всех пользователей запретили приглашать себя на встречи.

Из принявших приглашение на встречу приходит где-то 10%.

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

Исходным кодом может пользоваться любой желающий по своему усмотрению.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий